libs/core/langchain_core/prompts/chat.py PYTHON 1,496 lines View on github.com → Search inside
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

Code quality findings 53

Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(value, list):
Ensure functions have docstrings for documentation
missing-docstring
def from_template(
Ensure functions have docstrings for documentation
missing-docstring
def from_template_file(
Ensure functions have docstrings for documentation
missing-docstring
def from_template(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(template, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(template, Sequence):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(tmpl, str) or (
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(tmpl, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(tmpl, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(tmpl, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(img_template, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(img_template, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(tmpl, dict):
Ensure functions have docstrings for documentation
missing-docstring
def from_template_file(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.prompt, StringPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, StringPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(prompt, ImagePromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(prompt, DictPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.prompt, StringPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, StringPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(prompt, ImagePromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(prompt, DictPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]
Ensure functions have docstrings for documentation
missing-docstring
def lc_attributes(self) -> dict[str, Any]:
Ensure functions have docstrings for documentation
missing-docstring
def pretty_repr(
Use logging module for better control and configurability
print-statement
def pretty_print(self) -> None:
Use logging module for better control and configurability
print-statement
print(self.pretty_repr(html=is_interactive_env())) # noqa: T201
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, MessagesPlaceholder) and message.optional:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(other, ChatPromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(other, (list, tuple)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(other, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, MessagesPlaceholder):
Ensure functions have docstrings for documentation
missing-docstring
def from_messages(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message_template, BaseMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message_template, BaseMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
prompt_dict["input_variables"] = list(
Use isinstance() for type checking instead of type()
type-check
return type(self)(**prompt_dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(index, slice):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(template, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(is_optional, bool):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(var_name_wrapped, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message, BaseMessage):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(message, (tuple, dict)):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(message_type_str, str):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.