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 ChatPromptValue37from 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[str, Any] = 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 | dict[str, Any]391392393class _ImageTemplateParam(TypedDict, total=False):394 image_url: str | dict[str, Any]395396397class _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[str, Any] = 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 | Sequence[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 prompt: (438 StringPromptTemplate439 | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]440 )441 if isinstance(template, str):442 prompt = PromptTemplate.from_template(443 template,444 template_format=template_format,445 partial_variables=partial_variables,446 )447 return cls(prompt=prompt, **kwargs)448 if isinstance(template, Sequence):449 if (partial_variables is not None) and len(partial_variables) > 0:450 msg = "Partial variables are not supported for list of templates."451 raise ValueError(msg)452 prompt = []453 for tmpl in template:454 if isinstance(tmpl, str) or (455 isinstance(tmpl, dict)456 and "text" in tmpl457 and set(tmpl.keys()) <= {"type", "text"}458 ):459 if isinstance(tmpl, str):460 text: str = tmpl461 else:462 text = cast("_TextTemplateParam", tmpl)["text"] # type: ignore[assignment]463 prompt.append(464 PromptTemplate.from_template(465 text, template_format=template_format466 )467 )468 elif (469 isinstance(tmpl, dict)470 and "image_url" in tmpl471 and set(tmpl.keys())472 <= {473 "type",474 "image_url",475 }476 ):477 img_template = cast("_ImageTemplateParam", tmpl)["image_url"]478 input_variables = []479 if isinstance(img_template, str):480 variables = get_template_variables(481 img_template, template_format482 )483 if variables:484 if len(variables) > 1:485 msg = (486 "Only one format variable allowed per image"487 f" template.\nGot: {variables}"488 f"\nFrom: {tmpl}"489 )490 raise ValueError(msg)491 input_variables = [variables[0]]492 img_template = {"url": img_template}493 img_template_obj = ImagePromptTemplate(494 input_variables=input_variables,495 template=img_template,496 template_format=template_format,497 )498 elif isinstance(img_template, dict):499 img_template = dict(img_template)500 for key in ["url", "path", "detail"]:501 if key in img_template:502 input_variables.extend(503 get_template_variables(504 img_template[key], template_format505 )506 )507 img_template_obj = ImagePromptTemplate(508 input_variables=input_variables,509 template=img_template,510 template_format=template_format,511 )512 else:513 msg = f"Invalid image template: {tmpl}" # type: ignore[unreachable]514 raise ValueError(msg)515 prompt.append(img_template_obj)516 elif isinstance(tmpl, dict):517 if template_format == "jinja2":518 msg = (519 "jinja2 is unsafe and is not supported for templates "520 "expressed as dicts. Please use 'f-string' or 'mustache' "521 "format."522 )523 raise ValueError(msg)524 data_template_obj = DictPromptTemplate(525 template=cast("dict[str, Any]", tmpl),526 template_format=template_format,527 )528 prompt.append(data_template_obj)529 else:530 msg = f"Invalid template: {tmpl}" # type: ignore[unreachable]531 raise ValueError(msg)532 return cls(prompt=prompt, **kwargs)533 msg = f"Invalid template: {template}" # type: ignore[unreachable]534 raise ValueError(msg)535536 @classmethod537 def from_template_file(538 cls: type[Self],539 template_file: str | Path,540 input_variables: list[str],541 **kwargs: Any,542 ) -> Self:543 """Create a class from a template file.544545 Args:546 template_file: path to a template file.547 input_variables: list of input variables.548 **kwargs: Keyword arguments to pass to the constructor.549550 Returns:551 A new instance of this class.552 """553 template = Path(template_file).read_text(encoding="utf-8")554 return cls.from_template(template, input_variables=input_variables, **kwargs)555556 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:557 """Format messages from kwargs.558559 Args:560 **kwargs: Keyword arguments to use for formatting.561562 Returns:563 List of `BaseMessage` objects.564 """565 return [self.format(**kwargs)]566567 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:568 """Async format messages from kwargs.569570 Args:571 **kwargs: Keyword arguments to use for formatting.572573 Returns:574 List of `BaseMessage` objects.575 """576 return [await self.aformat(**kwargs)]577578 @property579 def input_variables(self) -> list[str]:580 """Input variables for this prompt template.581582 Returns:583 List of input variable names.584 """585 prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]586 return [iv for prompt in prompts for iv in prompt.input_variables]587588 def format(self, **kwargs: Any) -> BaseMessage:589 """Format the prompt template.590591 Args:592 **kwargs: Keyword arguments to use for formatting.593594 Returns:595 Formatted message.596 """597 if isinstance(self.prompt, StringPromptTemplate):598 text = self.prompt.format(**kwargs)599 return self._msg_class(600 content=text, additional_kwargs=self.additional_kwargs601 )602 content: list[str | dict[str, Any]] = []603 for prompt in self.prompt:604 inputs = {var: kwargs[var] for var in prompt.input_variables}605 if isinstance(prompt, StringPromptTemplate):606 formatted_text = prompt.format(**inputs)607 if formatted_text != "":608 content.append({"type": "text", "text": formatted_text})609 elif isinstance(prompt, ImagePromptTemplate):610 formatted_image = prompt.format(**inputs)611 content.append({"type": "image_url", "image_url": formatted_image})612 elif isinstance(prompt, DictPromptTemplate):613 formatted_dict = prompt.format(**inputs)614 content.append(formatted_dict)615 return self._msg_class(616 content=content, additional_kwargs=self.additional_kwargs617 )618619 async def aformat(self, **kwargs: Any) -> BaseMessage:620 """Async format the prompt template.621622 Args:623 **kwargs: Keyword arguments to use for formatting.624625 Returns:626 Formatted message.627 """628 if isinstance(self.prompt, StringPromptTemplate):629 text = await self.prompt.aformat(**kwargs)630 return self._msg_class(631 content=text, additional_kwargs=self.additional_kwargs632 )633 content: list[str | dict[str, Any]] = []634 for prompt in self.prompt:635 inputs = {var: kwargs[var] for var in prompt.input_variables}636 if isinstance(prompt, StringPromptTemplate):637 formatted_text = await prompt.aformat(**inputs)638 if formatted_text != "":639 content.append({"type": "text", "text": formatted_text})640 elif isinstance(prompt, ImagePromptTemplate):641 formatted_image = await prompt.aformat(**inputs)642 content.append({"type": "image_url", "image_url": formatted_image})643 elif isinstance(prompt, DictPromptTemplate):644 formatted_dict = prompt.format(**inputs)645 content.append(formatted_dict)646 return self._msg_class(647 content=content, additional_kwargs=self.additional_kwargs648 )649650 @override651 def pretty_repr(self, html: bool = False) -> str:652 """Human-readable representation.653654 Args:655 html: Whether to format as HTML.656657 Returns:658 Human-readable representation.659 """660 # TODO: Handle partials661 title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")662 title = get_msg_title_repr(title, bold=html)663 prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]664 prompt_reprs = "\n\n".join(prompt.pretty_repr(html=html) for prompt in prompts)665 return f"{title}\n\n{prompt_reprs}"666667668class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):669 """Human message prompt template.670671 This is a message sent from the user.672 """673674 _msg_class: type[BaseMessage] = HumanMessage675676677class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):678 """AI message prompt template.679680 This is a message sent from the AI.681 """682683 _msg_class: type[BaseMessage] = AIMessage684685686class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):687 """System message prompt template.688689 This is a message that is not sent to the user.690 """691692 _msg_class: type[BaseMessage] = SystemMessage693694695class BaseChatPromptTemplate(BasePromptTemplate[str], ABC):696 """Base class for chat prompt templates."""697698 @property699 @override700 def lc_attributes(self) -> dict[str, Any]:701 return {"input_variables": self.input_variables}702703 def format(self, **kwargs: Any) -> str:704 """Format the chat template into a string.705706 Args:707 **kwargs: Keyword arguments to use for filling in template variables in all708 the template messages in this chat template.709710 Returns:711 Formatted string.712 """713 return self.format_prompt(**kwargs).to_string()714715 async def aformat(self, **kwargs: Any) -> str:716 """Async format the chat template into a string.717718 Args:719 **kwargs: Keyword arguments to use for filling in template variables in all720 the template messages in this chat template.721722 Returns:723 Formatted string.724 """725 return (await self.aformat_prompt(**kwargs)).to_string()726727 def format_prompt(self, **kwargs: Any) -> ChatPromptValue:728 """Format prompt.729730 Should return a `ChatPromptValue`.731732 Args:733 **kwargs: Keyword arguments to use for formatting.734 """735 messages = self.format_messages(**kwargs)736 return ChatPromptValue(messages=messages)737738 async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:739 """Async format prompt.740741 Should return a `ChatPromptValue`.742743 Args:744 **kwargs: Keyword arguments to use for formatting.745 """746 messages = await self.aformat_messages(**kwargs)747 return ChatPromptValue(messages=messages)748749 @abstractmethod750 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:751 """Format kwargs into a list of messages.752753 Returns:754 List of `BaseMessage` objects.755 """756757 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:758 """Async format kwargs into a list of messages.759760 Returns:761 List of `BaseMessage` objects.762 """763 return self.format_messages(**kwargs)764765 def pretty_repr(766 self,767 html: bool = False, # noqa: FBT001,FBT002768 ) -> str:769 """Human-readable representation.770771 Args:772 html: Whether to format as HTML.773774 Returns:775 Human-readable representation.776 """777 raise NotImplementedError778779 def pretty_print(self) -> None:780 """Print a human-readable representation."""781 print(self.pretty_repr(html=is_interactive_env())) # noqa: T201782783784MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate785786MessageLikeRepresentation = (787 MessageLike788 | tuple[str | type, str | Sequence[dict[str, Any]] | Sequence[object]]789 | str790 | dict[str, Any]791)792793794class ChatPromptTemplate(BaseChatPromptTemplate):795 """Prompt template for chat models.796797 Use to create flexible templated prompts for chat models.798799 !!! example800801 ```python802 from langchain_core.prompts import ChatPromptTemplate803804 template = ChatPromptTemplate(805 [806 ("system", "You are a helpful AI bot. Your name is {name}."),807 ("human", "Hello, how are you doing?"),808 ("ai", "I'm doing well, thanks!"),809 ("human", "{user_input}"),810 ]811 )812813 prompt_value = template.invoke(814 {815 "name": "Bob",816 "user_input": "What is your name?",817 }818 )819 # Output:820 # ChatPromptValue(821 # messages=[822 # SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),823 # HumanMessage(content='Hello, how are you doing?'),824 # AIMessage(content="I'm doing well, thanks!"),825 # HumanMessage(content='What is your name?')826 # ]827 # )828 ```829830 !!! note "Messages Placeholder"831832 ```python833 # In addition to Human/AI/Tool/Function messages,834 # you can initialize the template with a MessagesPlaceholder835 # either using the class directly or with the shorthand tuple syntax:836837 template = ChatPromptTemplate(838 [839 ("system", "You are a helpful AI bot."),840 # Means the template will receive an optional list of messages under841 # the "conversation" key842 ("placeholder", "{conversation}"),843 # Equivalently:844 # MessagesPlaceholder(variable_name="conversation", optional=True)845 ]846 )847848 prompt_value = template.invoke(849 {850 "conversation": [851 ("human", "Hi!"),852 ("ai", "How can I assist you today?"),853 ("human", "Can you make me an ice cream sundae?"),854 ("ai", "No."),855 ]856 }857 )858859 # Output:860 # ChatPromptValue(861 # messages=[862 # SystemMessage(content='You are a helpful AI bot.'),863 # HumanMessage(content='Hi!'),864 # AIMessage(content='How can I assist you today?'),865 # HumanMessage(content='Can you make me an ice cream sundae?'),866 # AIMessage(content='No.'),867 # ]868 # )869 ```870871 !!! note "Single-variable template"872873 If your prompt has only a single input variable (i.e., one instance of874 `'{variable_nams}'`), and you invoke the template with a non-dict object, the875 prompt template will inject the provided argument into that variable location.876877 ```python878 from langchain_core.prompts import ChatPromptTemplate879880 template = ChatPromptTemplate(881 [882 ("system", "You are a helpful AI bot. Your name is Carl."),883 ("human", "{user_input}"),884 ]885 )886887 prompt_value = template.invoke("Hello, there!")888 # Equivalent to889 # prompt_value = template.invoke({"user_input": "Hello, there!"})890891 # Output:892 # ChatPromptValue(893 # messages=[894 # SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),895 # HumanMessage(content='Hello, there!'),896 # ]897 # )898 ```899 """900901 messages: Annotated[list[MessageLike], SkipValidation()]902 """List of messages consisting of either message prompt templates or messages."""903904 validate_template: bool = False905 """Whether or not to try validating the template."""906907 def __init__(908 self,909 messages: Sequence[MessageLikeRepresentation],910 *,911 template_format: PromptTemplateFormat = "f-string",912 **kwargs: Any,913 ) -> None:914 """Create a chat prompt template from a variety of message formats.915916 Args:917 messages: Sequence of message representations.918919 A message can be represented using the following formats:920921 1. `BaseMessagePromptTemplate`922 2. `BaseMessage`923 3. 2-tuple of `(message type, template)`; e.g.,924 `('human', '{user_input}')`925 4. 2-tuple of `(message class, template)`926 5. A string which is shorthand for `('human', template)`; e.g.,927 `'{user_input}'`928 template_format: Format of the template.929 **kwargs: Additional keyword arguments passed to `BasePromptTemplate`,930 including (but not limited to):931932 - `input_variables`: A list of the names of the variables whose values933 are required as inputs to the prompt.934 - `optional_variables`: A list of the names of the variables for935 placeholder or `MessagePlaceholder` that are optional.936937 These variables are auto inferred from the prompt and user need not938 provide them.939940 - `partial_variables`: A dictionary of the partial variables the prompt941 template carries.942943 Partial variables populate the template so that you don't need to944 pass them in every time you call the prompt.945946 - `validate_template`: Whether to validate the template.947 - `input_types`: A dictionary of the types of the variables the prompt948 template expects.949950 If not provided, all variables are assumed to be strings.951952 Examples:953 Instantiation from a list of message templates:954955 ```python956 template = ChatPromptTemplate(957 [958 ("human", "Hello, how are you?"),959 ("ai", "I'm doing well, thanks!"),960 ("human", "That's good to hear."),961 ]962 )963 ```964965 Instantiation from mixed message formats:966967 ```python968 template = ChatPromptTemplate(969 [970 SystemMessage(content="hello"),971 ("human", "Hello, how are you?"),972 ]973 )974 ```975 """976 messages_ = [977 _convert_to_message_template(message, template_format)978 for message in messages979 ]980981 # Automatically infer input variables from messages982 input_vars: set[str] = set()983 optional_variables: set[str] = set()984 partial_vars: dict[str, Any] = {}985 for message in messages_:986 if isinstance(message, MessagesPlaceholder) and message.optional:987 partial_vars[message.variable_name] = []988 optional_variables.add(message.variable_name)989 elif isinstance(990 message, (BaseChatPromptTemplate, BaseMessagePromptTemplate)991 ):992 input_vars.update(message.input_variables)993994 kwargs = {995 "input_variables": sorted(input_vars),996 "optional_variables": sorted(optional_variables),997 "partial_variables": partial_vars,998 **kwargs,999 }1000 cast("type[ChatPromptTemplate]", super()).__init__(messages=messages_, **kwargs)10011002 @classmethod1003 def get_lc_namespace(cls) -> list[str]:1004 """Get the namespace of the LangChain object.10051006 Returns:1007 `["langchain", "prompts", "chat"]`1008 """1009 return ["langchain", "prompts", "chat"]10101011 def __add__(self, other: Any) -> ChatPromptTemplate:1012 """Combine two prompt templates.10131014 Args:1015 other: Another prompt template.10161017 Returns:1018 Combined prompt template.1019 """1020 partials = {**self.partial_variables}10211022 # Need to check that other has partial variables since it may not be1023 # a ChatPromptTemplate.1024 if hasattr(other, "partial_variables") and other.partial_variables:1025 partials.update(other.partial_variables)10261027 # Allow for easy combining1028 if isinstance(other, ChatPromptTemplate):1029 return ChatPromptTemplate(messages=self.messages + other.messages).partial(1030 **partials1031 )1032 if isinstance(1033 other, (BaseMessagePromptTemplate, BaseMessage, BaseChatPromptTemplate)1034 ):1035 return ChatPromptTemplate(messages=[*self.messages, other]).partial(1036 **partials1037 )1038 if isinstance(other, (list, tuple)):1039 other_ = ChatPromptTemplate.from_messages(other)1040 return ChatPromptTemplate(messages=self.messages + other_.messages).partial(1041 **partials1042 )1043 if isinstance(other, str):1044 prompt = HumanMessagePromptTemplate.from_template(other)1045 return ChatPromptTemplate(messages=[*self.messages, prompt]).partial(1046 **partials1047 )1048 msg = f"Unsupported operand type for +: {type(other)}"1049 raise NotImplementedError(msg)10501051 @model_validator(mode="before")1052 @classmethod1053 def validate_input_variables(cls, values: dict[str, Any]) -> Any:1054 """Validate input variables.10551056 If `input_variables` is not set, it will be set to the union of all input1057 variables in the messages.10581059 Args:1060 values: values to validate.10611062 Returns:1063 Validated values.10641065 Raises:1066 ValueError: If input variables do not match.1067 """1068 messages = values["messages"]1069 input_vars: set[str] = set()1070 optional_variables = set()1071 input_types: dict[str, Any] = values.get("input_types", {})1072 for message in messages:1073 if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1074 input_vars.update(message.input_variables)1075 if isinstance(message, MessagesPlaceholder):1076 if "partial_variables" not in values:1077 values["partial_variables"] = {}1078 if (1079 message.optional1080 and message.variable_name not in values["partial_variables"]1081 ):1082 values["partial_variables"][message.variable_name] = []1083 optional_variables.add(message.variable_name)1084 if message.variable_name not in input_types:1085 input_types[message.variable_name] = list[AnyMessage]1086 if "partial_variables" in values:1087 input_vars -= set(values["partial_variables"])1088 if optional_variables:1089 input_vars -= optional_variables1090 if "input_variables" in values and values.get("validate_template"):1091 if input_vars != set(values["input_variables"]):1092 msg = (1093 "Got mismatched input_variables. "1094 f"Expected: {input_vars}. "1095 f"Got: {values['input_variables']}"1096 )1097 raise ValueError(msg)1098 else:1099 values["input_variables"] = sorted(input_vars)1100 if optional_variables:1101 values["optional_variables"] = sorted(optional_variables)1102 values["input_types"] = input_types1103 return values11041105 @classmethod1106 def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:1107 """Create a chat prompt template from a template string.11081109 Creates a chat template consisting of a single message assumed to be from the1110 human.11111112 Args:1113 template: Template string1114 **kwargs: Keyword arguments to pass to the constructor.11151116 Returns:1117 A new instance of this class.1118 """1119 prompt_template = PromptTemplate.from_template(template, **kwargs)1120 message = HumanMessagePromptTemplate(prompt=prompt_template)1121 return cls.from_messages([message])11221123 @classmethod1124 def from_messages(1125 cls,1126 messages: Sequence[MessageLikeRepresentation],1127 template_format: PromptTemplateFormat = "f-string",1128 ) -> ChatPromptTemplate:1129 """Create a chat prompt template from a variety of message formats.11301131 Examples:1132 Instantiation from a list of message templates:11331134 ```python1135 template = ChatPromptTemplate.from_messages(1136 [1137 ("human", "Hello, how are you?"),1138 ("ai", "I'm doing well, thanks!"),1139 ("human", "That's good to hear."),1140 ]1141 )1142 ```11431144 Instantiation from mixed message formats:11451146 ```python1147 template = ChatPromptTemplate.from_messages(1148 [1149 SystemMessage(content="hello"),1150 ("human", "Hello, how are you?"),1151 ]1152 )1153 ```1154 Args:1155 messages: Sequence of message representations.11561157 A message can be represented using the following formats:11581159 1. `BaseMessagePromptTemplate`1160 2. `BaseMessage`1161 3. 2-tuple of `(message type, template)`; e.g.,1162 `('human', '{user_input}')`1163 4. 2-tuple of `(message class, template)`1164 5. A string which is shorthand for `('human', template)`; e.g.,1165 `'{user_input}'`1166 template_format: Format of the template.11671168 Returns:1169 A chat prompt template.11701171 """1172 return cls(messages, template_format=template_format)11731174 def format_messages(self, **kwargs: Any) -> list[BaseMessage]:1175 """Format the chat template into a list of finalized messages.11761177 Args:1178 **kwargs: Keyword arguments to use for filling in template variables1179 in all the template messages in this chat template.11801181 Raises:1182 ValueError: If messages are of unexpected types.11831184 Returns:1185 List of formatted messages.1186 """1187 kwargs = self._merge_partial_and_user_variables(**kwargs)1188 result = []1189 for message_template in self.messages:1190 if isinstance(message_template, BaseMessage):1191 result.extend([message_template])1192 elif isinstance(1193 message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1194 ):1195 message = message_template.format_messages(**kwargs)1196 result.extend(message)1197 else:1198 msg = f"Unexpected input: {message_template}" # type: ignore[unreachable]1199 raise ValueError(msg) # noqa: TRY0041200 return result12011202 async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:1203 """Async format the chat template into a list of finalized messages.12041205 Args:1206 **kwargs: Keyword arguments to use for filling in template variables1207 in all the template messages in this chat template.12081209 Returns:1210 List of formatted messages.12111212 Raises:1213 ValueError: If unexpected input.1214 """1215 kwargs = self._merge_partial_and_user_variables(**kwargs)1216 result = []1217 for message_template in self.messages:1218 if isinstance(message_template, BaseMessage):1219 result.extend([message_template])1220 elif isinstance(1221 message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1222 ):1223 message = await message_template.aformat_messages(**kwargs)1224 result.extend(message)1225 else:1226 msg = f"Unexpected input: {message_template}" # type: ignore[unreachable]1227 raise ValueError(msg) # noqa:TRY0041228 return result12291230 def partial(self, **kwargs: Any) -> ChatPromptTemplate:1231 """Get a new `ChatPromptTemplate` with some input variables already filled in.12321233 Args:1234 **kwargs: Keyword arguments to use for filling in template variables.12351236 Ought to be a subset of the input variables.12371238 Returns:1239 A new `ChatPromptTemplate`.12401241 Example:1242 ```python1243 from langchain_core.prompts import ChatPromptTemplate12441245 template = ChatPromptTemplate.from_messages(1246 [1247 ("system", "You are an AI assistant named {name}."),1248 ("human", "Hi I'm {user}"),1249 ("ai", "Hi there, {user}, I'm {name}."),1250 ("human", "{input}"),1251 ]1252 )1253 template2 = template.partial(user="Lucy", name="R2D2")12541255 template2.format_messages(input="hello")1256 ```1257 """1258 prompt_dict = self.__dict__.copy()1259 prompt_dict["input_variables"] = list(1260 set(self.input_variables).difference(kwargs)1261 )1262 prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}1263 return type(self)(**prompt_dict)12641265 def append(self, message: MessageLikeRepresentation) -> None:1266 """Append a message to the end of the chat template.12671268 Args:1269 message: representation of a message to append.1270 """1271 self.messages.append(_convert_to_message_template(message))12721273 def extend(self, messages: Sequence[MessageLikeRepresentation]) -> None:1274 """Extend the chat template with a sequence of messages.12751276 Args:1277 messages: Sequence of message representations to append.1278 """1279 self.messages.extend(1280 [_convert_to_message_template(message) for message in messages]1281 )12821283 @overload1284 def __getitem__(self, index: int) -> MessageLike: ...12851286 @overload1287 def __getitem__(self, index: slice) -> ChatPromptTemplate: ...12881289 def __getitem__(self, index: int | slice) -> MessageLike | ChatPromptTemplate:1290 """Use to index into the chat template.12911292 Returns:1293 If index is an int, returns the message at that index.12941295 If index is a slice, returns a new `ChatPromptTemplate` containing the1296 messages in that slice.1297 """1298 if isinstance(index, slice):1299 start, stop, step = index.indices(len(self.messages))1300 messages = self.messages[start:stop:step]1301 return ChatPromptTemplate.from_messages(messages)1302 return self.messages[index]13031304 def __len__(self) -> int:1305 """Return the length of the chat template."""1306 return len(self.messages)13071308 @property1309 def _prompt_type(self) -> str:1310 """Name of prompt type. Used for serialization."""1311 return "chat"13121313 @deprecated(1314 since="1.2.21",1315 removal="2.0.0",1316 alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "1317 "prompts and `load`/`loads` to deserialize them.",1318 )1319 def save(self, file_path: Path | str) -> None:1320 """Save prompt to file.13211322 Args:1323 file_path: path to file.1324 """1325 raise NotImplementedError13261327 @override1328 def pretty_repr(self, html: bool = False) -> str:1329 """Human-readable representation.13301331 Args:1332 html: Whether to format as HTML.13331334 Returns:1335 Human-readable representation.1336 """1337 # TODO: handle partials1338 return "\n\n".join(msg.pretty_repr(html=html) for msg in self.messages)133913401341def _create_template_from_message_type(1342 message_type: str,1343 template: str | list[str | dict[str, Any] | bool],1344 template_format: PromptTemplateFormat = "f-string",1345) -> BaseMessagePromptTemplate:1346 """Create a message prompt template from a message type and template string.13471348 Args:1349 message_type: The type of the message template (e.g., `'human'`, `'ai'`, etc.)1350 template: The template string.1351 template_format: Format of the template.13521353 Returns:1354 A message prompt template of the appropriate type.13551356 Raises:1357 ValueError: If unexpected message type.1358 """1359 if message_type in {"human", "user"}:1360 message: BaseMessagePromptTemplate = HumanMessagePromptTemplate.from_template(1361 cast("str", template), template_format=template_format1362 )1363 elif message_type in {"ai", "assistant"}:1364 message = AIMessagePromptTemplate.from_template(1365 cast("str", template), template_format=template_format1366 )1367 elif message_type == "system":1368 message = SystemMessagePromptTemplate.from_template(1369 cast("str", template), template_format=template_format1370 )1371 elif message_type == "placeholder":1372 if isinstance(template, str):1373 if template[0] != "{" or template[-1] != "}":1374 msg = (1375 f"Invalid placeholder template: {template}."1376 " Expected a variable name surrounded by curly braces."1377 )1378 raise ValueError(msg)1379 var_name = template[1:-1]1380 message = MessagesPlaceholder(variable_name=var_name, optional=True)1381 else:1382 try:1383 var_name_wrapped, is_optional = template1384 except ValueError as e:1385 msg = (1386 "Unexpected arguments for placeholder message type."1387 " Expected either a single string variable name"1388 " or a list of [variable_name: str, is_optional: bool]."1389 f" Got: {template}"1390 )1391 raise ValueError(msg) from e13921393 if not isinstance(is_optional, bool):1394 msg = f"Expected is_optional to be a boolean. Got: {is_optional}"1395 raise ValueError(msg) # noqa: TRY00413961397 if not isinstance(var_name_wrapped, str):1398 msg = f"Expected variable name to be a string. Got: {var_name_wrapped}"1399 raise ValueError(msg) # noqa: TRY0041400 if var_name_wrapped[0] != "{" or var_name_wrapped[-1] != "}":1401 msg = (1402 f"Invalid placeholder template: {var_name_wrapped}."1403 " Expected a variable name surrounded by curly braces."1404 )1405 raise ValueError(msg)1406 var_name = var_name_wrapped[1:-1]14071408 message = MessagesPlaceholder(variable_name=var_name, optional=is_optional)1409 else:1410 msg = (1411 f"Unexpected message type: {message_type}. Use one of 'human',"1412 f" 'user', 'ai', 'assistant', or 'system'."1413 )1414 raise ValueError(msg)1415 return message141614171418def _convert_to_message_template(1419 message: MessageLikeRepresentation,1420 template_format: PromptTemplateFormat = "f-string",1421) -> BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate:1422 """Instantiate a message from a variety of message formats.14231424 A message can be represented using the following formats:14251426 1. `BaseMessagePromptTemplate`1427 2. `BaseMessage`1428 3. 2-tuple of `(message type, template)`; e.g., `('human', '{user_input}')`1429 4. 2-tuple of `(message class, template)`1430 5. A string which is shorthand for `('human', template)`; e.g., `'{user_input}'`14311432 Args:1433 message: A representation of a message in one of the supported formats.1434 template_format: Format of the template.14351436 Returns:1437 An instance of a message or a message template.14381439 Raises:1440 ValueError: If unexpected message type.1441 ValueError: If 2-tuple does not have 2 elements.1442 """1443 if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1444 message_: BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate = (1445 message1446 )1447 elif isinstance(message, BaseMessage):1448 message_ = message1449 elif isinstance(message, str):1450 message_ = _create_template_from_message_type(1451 "human", message, template_format=template_format1452 )1453 elif isinstance(message, (tuple, dict)):1454 if isinstance(message, dict):1455 if set(message.keys()) != {"content", "role"}:1456 msg = (1457 "Expected dict to have exact keys 'role' and 'content'."1458 f" Got: {message}"1459 )1460 raise ValueError(msg)1461 message_type_str = message["role"]1462 template = message["content"]1463 else:1464 if len(message) != 2: # noqa: PLR20041465 msg = f"Expected 2-tuple of (role, template), got {message}" # type: ignore[unreachable]1466 raise ValueError(msg)1467 message_type_str, template = message14681469 if isinstance(message_type_str, str):1470 message_ = _create_template_from_message_type(1471 message_type_str, template, template_format=template_format1472 )1473 elif (1474 hasattr(message_type_str, "model_fields")1475 and "type" in message_type_str.model_fields1476 ):1477 message_type = message_type_str.model_fields["type"].default1478 message_ = _create_template_from_message_type(1479 message_type, template, template_format=template_format1480 )1481 else:1482 message_ = message_type_str(1483 prompt=PromptTemplate.from_template(1484 cast("str", template), template_format=template_format1485 )1486 )1487 else:1488 msg = f"Unsupported message type: {type(message)}" # type: ignore[unreachable]1489 raise NotImplementedError(msg)14901491 return message_149214931494# For backwards compat:1495_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.