libs/core/langchain_core/prompts/chat.py PYTHON 1,492 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 ChatPromptValue, ImageURL37from langchain_core.prompts.base import BasePromptTemplate38from langchain_core.prompts.dict import DictPromptTemplate39from langchain_core.prompts.image import ImagePromptTemplate40from langchain_core.prompts.message import (41    BaseMessagePromptTemplate,42)43from langchain_core.prompts.prompt import PromptTemplate44from langchain_core.prompts.string import (45    PromptTemplateFormat,46    StringPromptTemplate,47    get_template_variables,48)49from langchain_core.utils import get_colored_text50from langchain_core.utils.interactive_env import is_interactive_env515253class MessagesPlaceholder(BaseMessagePromptTemplate):54    """Prompt template that assumes variable is already list of messages.5556    A placeholder which can be used to pass in a list of messages.5758    !!! example "Direct usage"5960        ```python61        from langchain_core.prompts import MessagesPlaceholder6263        prompt = MessagesPlaceholder("history")64        prompt.format_messages()  # raises KeyError6566        prompt = MessagesPlaceholder("history", optional=True)67        prompt.format_messages()  # returns empty list []6869        prompt.format_messages(70            history=[71                ("system", "You are an AI assistant."),72                ("human", "Hello!"),73            ]74        )75        # -> [76        #     SystemMessage(content="You are an AI assistant."),77        #     HumanMessage(content="Hello!"),78        # ]79        ```8081    !!! example "Building a prompt with chat history"8283        ```python84        from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder8586        prompt = ChatPromptTemplate.from_messages(87            [88                ("system", "You are a helpful assistant."),89                MessagesPlaceholder("history"),90                ("human", "{question}"),91            ]92        )93        prompt.invoke(94            {95                "history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],96                "question": "now multiply that by 4",97            }98        )99        # -> ChatPromptValue(messages=[100        #     SystemMessage(content="You are a helpful assistant."),101        #     HumanMessage(content="what's 5 + 2"),102        #     AIMessage(content="5 + 2 is 7"),103        #     HumanMessage(content="now multiply that by 4"),104        # ])105        ```106107    !!! example "Limiting the number of messages"108109        ```python110        from langchain_core.prompts import MessagesPlaceholder111112        prompt = MessagesPlaceholder("history", n_messages=1)113114        prompt.format_messages(115            history=[116                ("system", "You are an AI assistant."),117                ("human", "Hello!"),118            ]119        )120        # -> [121        #     HumanMessage(content="Hello!"),122        # ]123        ```124    """125126    variable_name: str127    """Name of variable to use as messages."""128129    optional: bool = False130    """Whether `format_messages` must be provided.131132    If `True` `format_messages` can be called with no arguments and will return an empty133    list.134135    If `False` then a named argument with name `variable_name` must be passed in, even136    if the value is an empty list.137    """138139    n_messages: PositiveInt | None = None140    """Maximum number of messages to include.141142    If `None`, then will include all.143    """144145    def __init__(146        self, variable_name: str, *, optional: bool = False, **kwargs: Any147    ) -> None:148        """Create a messages placeholder.149150        Args:151            variable_name: Name of variable to use as messages.152            optional: Whether `format_messages` must be provided.153154                If `True` format_messages can be called with no arguments and will155                return an empty list.156157                If `False` then a named argument with name `variable_name` must be158                passed in, even if the value is an empty list.159        """160        # mypy can't detect the init which is defined in the parent class161        # b/c these are BaseModel classes.162        super().__init__(variable_name=variable_name, optional=optional, **kwargs)  # type: ignore[call-arg,unused-ignore]163164    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:165        """Format messages from kwargs.166167        Args:168            **kwargs: Keyword arguments to use for formatting.169170        Returns:171            List of `BaseMessage` objects.172173        Raises:174            ValueError: If variable is not a list of messages.175        """176        value = (177            kwargs.get(self.variable_name, [])178            if self.optional179            else kwargs[self.variable_name]180        )181        if not isinstance(value, list):182            msg = (183                f"variable {self.variable_name} should be a list of base messages, "184                f"got {value} of type {type(value)}"185            )186            raise ValueError(msg)  # noqa: TRY004187        value = convert_to_messages(value)188        if self.n_messages:189            value = value[-self.n_messages :]190        return value191192    @property193    def input_variables(self) -> list[str]:194        """Input variables for this prompt template.195196        Returns:197            List of input variable names.198        """199        return [self.variable_name] if not self.optional else []200201    @override202    def pretty_repr(self, html: bool = False) -> str:203        """Human-readable representation.204205        Args:206            html: Whether to format as HTML.207208        Returns:209            Human-readable representation.210        """211        var = "{" + self.variable_name + "}"212        if html:213            title = get_msg_title_repr("Messages Placeholder", bold=True)214            var = get_colored_text(var, "yellow")215        else:216            title = get_msg_title_repr("Messages Placeholder")217        return f"{title}\n\n{var}"218219220MessagePromptTemplateT = TypeVar(221    "MessagePromptTemplateT", bound="BaseStringMessagePromptTemplate"222)223"""Type variable for message prompt templates."""224225226class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC):227    """Base class for message prompt templates that use a string prompt template."""228229    prompt: StringPromptTemplate230    """String prompt template."""231232    additional_kwargs: dict = Field(default_factory=dict)233    """Additional keyword arguments to pass to the prompt template."""234235    @classmethod236    def from_template(237        cls,238        template: str,239        template_format: PromptTemplateFormat = "f-string",240        partial_variables: dict[str, Any] | None = None,241        **kwargs: Any,242    ) -> Self:243        """Create a class from a string template.244245        Args:246            template: a template.247            template_format: format of the template.248            partial_variables: A dictionary of variables that can be used to partially249                fill in the template.250251                For example, if the template is `"{variable1} {variable2}"`, and252                `partial_variables` is `{"variable1": "foo"}`, then the final prompt253                will be `"foo {variable2}"`.254255            **kwargs: Keyword arguments to pass to the constructor.256257        Returns:258            A new instance of this class.259        """260        prompt = PromptTemplate.from_template(261            template,262            template_format=template_format,263            partial_variables=partial_variables,264        )265        return cls(prompt=prompt, **kwargs)266267    @classmethod268    def from_template_file(269        cls,270        template_file: str | Path,271        **kwargs: Any,272    ) -> Self:273        """Create a class from a template file.274275        Args:276            template_file: path to a template file.277            **kwargs: Keyword arguments to pass to the constructor.278279        Returns:280            A new instance of this class.281        """282        prompt = PromptTemplate.from_file(template_file)283        return cls(prompt=prompt, **kwargs)284285    @abstractmethod286    def format(self, **kwargs: Any) -> BaseMessage:287        """Format the prompt template.288289        Args:290            **kwargs: Keyword arguments to use for formatting.291292        Returns:293            Formatted message.294        """295296    async def aformat(self, **kwargs: Any) -> BaseMessage:297        """Async format the prompt template.298299        Args:300            **kwargs: Keyword arguments to use for formatting.301302        Returns:303            Formatted message.304        """305        return self.format(**kwargs)306307    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:308        """Format messages from kwargs.309310        Args:311            **kwargs: Keyword arguments to use for formatting.312313        Returns:314            List of `BaseMessage` objects.315        """316        return [self.format(**kwargs)]317318    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:319        """Async format messages from kwargs.320321        Args:322            **kwargs: Keyword arguments to use for formatting.323324        Returns:325            List of `BaseMessage` objects.326        """327        return [await self.aformat(**kwargs)]328329    @property330    def input_variables(self) -> list[str]:331        """Input variables for this prompt template.332333        Returns:334            List of input variable names.335        """336        return self.prompt.input_variables337338    @override339    def pretty_repr(self, html: bool = False) -> str:340        """Human-readable representation.341342        Args:343            html: Whether to format as HTML.344345        Returns:346            Human-readable representation.347        """348        # TODO: Handle partials349        title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")350        title = get_msg_title_repr(title, bold=html)351        return f"{title}\n\n{self.prompt.pretty_repr(html=html)}"352353354class ChatMessagePromptTemplate(BaseStringMessagePromptTemplate):355    """Chat message prompt template."""356357    role: str358    """Role of the message."""359360    def format(self, **kwargs: Any) -> BaseMessage:361        """Format the prompt template.362363        Args:364            **kwargs: Keyword arguments to use for formatting.365366        Returns:367            Formatted message.368        """369        text = self.prompt.format(**kwargs)370        return ChatMessage(371            content=text, role=self.role, additional_kwargs=self.additional_kwargs372        )373374    async def aformat(self, **kwargs: Any) -> BaseMessage:375        """Async format the prompt template.376377        Args:378            **kwargs: Keyword arguments to use for formatting.379380        Returns:381            Formatted message.382        """383        text = await self.prompt.aformat(**kwargs)384        return ChatMessage(385            content=text, role=self.role, additional_kwargs=self.additional_kwargs386        )387388389class _TextTemplateParam(TypedDict, total=False):390    text: str | dict391392393class _ImageTemplateParam(TypedDict, total=False):394    image_url: str | dict395396397class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate):398    """Human message prompt template. This is a message sent from the user."""399400    prompt: (401        StringPromptTemplate402        | list[StringPromptTemplate | ImagePromptTemplate | DictPromptTemplate]403    )404    """Prompt template."""405    additional_kwargs: dict = Field(default_factory=dict)406    """Additional keyword arguments to pass to the prompt template."""407408    _msg_class: type[BaseMessage]409410    @classmethod411    def from_template(412        cls: type[Self],413        template: str414        | list[str | _TextTemplateParam | _ImageTemplateParam | dict[str, Any]],415        template_format: PromptTemplateFormat = "f-string",416        *,417        partial_variables: dict[str, Any] | None = None,418        **kwargs: Any,419    ) -> Self:420        """Create a class from a string template.421422        Args:423            template: a template.424            template_format: format of the template.425426                Options are: `'f-string'`, `'mustache'`, `'jinja2'`.427            partial_variables: A dictionary of variables that can be used too partially.428429            **kwargs: Keyword arguments to pass to the constructor.430431        Returns:432            A new instance of this class.433434        Raises:435            ValueError: If the template is not a string or list of strings.436        """437        if isinstance(template, str):438            prompt: StringPromptTemplate | list = PromptTemplate.from_template(439                template,440                template_format=template_format,441                partial_variables=partial_variables,442            )443            return cls(prompt=prompt, **kwargs)444        if isinstance(template, list):445            if (partial_variables is not None) and len(partial_variables) > 0:446                msg = "Partial variables are not supported for list of templates."447                raise ValueError(msg)448            prompt = []449            for tmpl in template:450                if isinstance(tmpl, str) or (451                    isinstance(tmpl, dict)452                    and "text" in tmpl453                    and set(tmpl.keys()) <= {"type", "text"}454                ):455                    if isinstance(tmpl, str):456                        text: str = tmpl457                    else:458                        text = cast("_TextTemplateParam", tmpl)["text"]  # type: ignore[assignment]459                    prompt.append(460                        PromptTemplate.from_template(461                            text, template_format=template_format462                        )463                    )464                elif (465                    isinstance(tmpl, dict)466                    and "image_url" in tmpl467                    and set(tmpl.keys())468                    <= {469                        "type",470                        "image_url",471                    }472                ):473                    img_template = cast("_ImageTemplateParam", tmpl)["image_url"]474                    input_variables = []475                    if isinstance(img_template, str):476                        variables = get_template_variables(477                            img_template, template_format478                        )479                        if variables:480                            if len(variables) > 1:481                                msg = (482                                    "Only one format variable allowed per image"483                                    f" template.\nGot: {variables}"484                                    f"\nFrom: {tmpl}"485                                )486                                raise ValueError(msg)487                            input_variables = [variables[0]]488                        img_template = {"url": img_template}489                        img_template_obj = ImagePromptTemplate(490                            input_variables=input_variables,491                            template=img_template,492                            template_format=template_format,493                        )494                    elif isinstance(img_template, dict):495                        img_template = dict(img_template)496                        for key in ["url", "path", "detail"]:497                            if key in img_template:498                                input_variables.extend(499                                    get_template_variables(500                                        img_template[key], template_format501                                    )502                                )503                        img_template_obj = ImagePromptTemplate(504                            input_variables=input_variables,505                            template=img_template,506                            template_format=template_format,507                        )508                    else:509                        msg = f"Invalid image template: {tmpl}"510                        raise ValueError(msg)511                    prompt.append(img_template_obj)512                elif isinstance(tmpl, dict):513                    if template_format == "jinja2":514                        msg = (515                            "jinja2 is unsafe and is not supported for templates "516                            "expressed as dicts. Please use 'f-string' or 'mustache' "517                            "format."518                        )519                        raise ValueError(msg)520                    data_template_obj = DictPromptTemplate(521                        template=cast("dict[str, Any]", tmpl),522                        template_format=template_format,523                    )524                    prompt.append(data_template_obj)525                else:526                    msg = f"Invalid template: {tmpl}"527                    raise ValueError(msg)528            return cls(prompt=prompt, **kwargs)529        msg = f"Invalid template: {template}"530        raise ValueError(msg)531532    @classmethod533    def from_template_file(534        cls: type[Self],535        template_file: str | Path,536        input_variables: list[str],537        **kwargs: Any,538    ) -> Self:539        """Create a class from a template file.540541        Args:542            template_file: path to a template file.543            input_variables: list of input variables.544            **kwargs: Keyword arguments to pass to the constructor.545546        Returns:547            A new instance of this class.548        """549        template = Path(template_file).read_text(encoding="utf-8")550        return cls.from_template(template, input_variables=input_variables, **kwargs)551552    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:553        """Format messages from kwargs.554555        Args:556            **kwargs: Keyword arguments to use for formatting.557558        Returns:559            List of `BaseMessage` objects.560        """561        return [self.format(**kwargs)]562563    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:564        """Async format messages from kwargs.565566        Args:567            **kwargs: Keyword arguments to use for formatting.568569        Returns:570            List of `BaseMessage` objects.571        """572        return [await self.aformat(**kwargs)]573574    @property575    def input_variables(self) -> list[str]:576        """Input variables for this prompt template.577578        Returns:579            List of input variable names.580        """581        prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]582        return [iv for prompt in prompts for iv in prompt.input_variables]583584    def format(self, **kwargs: Any) -> BaseMessage:585        """Format the prompt template.586587        Args:588            **kwargs: Keyword arguments to use for formatting.589590        Returns:591            Formatted message.592        """593        if isinstance(self.prompt, StringPromptTemplate):594            text = self.prompt.format(**kwargs)595            return self._msg_class(596                content=text, additional_kwargs=self.additional_kwargs597            )598        content: list = []599        for prompt in self.prompt:600            inputs = {var: kwargs[var] for var in prompt.input_variables}601            if isinstance(prompt, StringPromptTemplate):602                formatted_text: str = prompt.format(**inputs)603                if formatted_text != "":604                    content.append({"type": "text", "text": formatted_text})605            elif isinstance(prompt, ImagePromptTemplate):606                formatted_image: ImageURL = prompt.format(**inputs)607                content.append({"type": "image_url", "image_url": formatted_image})608            elif isinstance(prompt, DictPromptTemplate):609                formatted_dict: dict[str, Any] = prompt.format(**inputs)610                content.append(formatted_dict)611        return self._msg_class(612            content=content, additional_kwargs=self.additional_kwargs613        )614615    async def aformat(self, **kwargs: Any) -> BaseMessage:616        """Async format the prompt template.617618        Args:619            **kwargs: Keyword arguments to use for formatting.620621        Returns:622            Formatted message.623        """624        if isinstance(self.prompt, StringPromptTemplate):625            text = await self.prompt.aformat(**kwargs)626            return self._msg_class(627                content=text, additional_kwargs=self.additional_kwargs628            )629        content: list = []630        for prompt in self.prompt:631            inputs = {var: kwargs[var] for var in prompt.input_variables}632            if isinstance(prompt, StringPromptTemplate):633                formatted_text: str = await prompt.aformat(**inputs)634                if formatted_text != "":635                    content.append({"type": "text", "text": formatted_text})636            elif isinstance(prompt, ImagePromptTemplate):637                formatted_image: ImageURL = await prompt.aformat(**inputs)638                content.append({"type": "image_url", "image_url": formatted_image})639            elif isinstance(prompt, DictPromptTemplate):640                formatted_dict: dict[str, Any] = prompt.format(**inputs)641                content.append(formatted_dict)642        return self._msg_class(643            content=content, additional_kwargs=self.additional_kwargs644        )645646    @override647    def pretty_repr(self, html: bool = False) -> str:648        """Human-readable representation.649650        Args:651            html: Whether to format as HTML.652653        Returns:654            Human-readable representation.655        """656        # TODO: Handle partials657        title = self.__class__.__name__.replace("MessagePromptTemplate", " Message")658        title = get_msg_title_repr(title, bold=html)659        prompts = self.prompt if isinstance(self.prompt, list) else [self.prompt]660        prompt_reprs = "\n\n".join(prompt.pretty_repr(html=html) for prompt in prompts)661        return f"{title}\n\n{prompt_reprs}"662663664class HumanMessagePromptTemplate(_StringImageMessagePromptTemplate):665    """Human message prompt template.666667    This is a message sent from the user.668    """669670    _msg_class: type[BaseMessage] = HumanMessage671672673class AIMessagePromptTemplate(_StringImageMessagePromptTemplate):674    """AI message prompt template.675676    This is a message sent from the AI.677    """678679    _msg_class: type[BaseMessage] = AIMessage680681682class SystemMessagePromptTemplate(_StringImageMessagePromptTemplate):683    """System message prompt template.684685    This is a message that is not sent to the user.686    """687688    _msg_class: type[BaseMessage] = SystemMessage689690691class BaseChatPromptTemplate(BasePromptTemplate, ABC):692    """Base class for chat prompt templates."""693694    @property695    @override696    def lc_attributes(self) -> dict:697        return {"input_variables": self.input_variables}698699    def format(self, **kwargs: Any) -> str:700        """Format the chat template into a string.701702        Args:703            **kwargs: Keyword arguments to use for filling in template variables in all704                the template messages in this chat template.705706        Returns:707            Formatted string.708        """709        return self.format_prompt(**kwargs).to_string()710711    async def aformat(self, **kwargs: Any) -> str:712        """Async format the chat template into a string.713714        Args:715            **kwargs: Keyword arguments to use for filling in template variables in all716                the template messages in this chat template.717718        Returns:719            Formatted string.720        """721        return (await self.aformat_prompt(**kwargs)).to_string()722723    def format_prompt(self, **kwargs: Any) -> ChatPromptValue:724        """Format prompt.725726        Should return a `ChatPromptValue`.727728        Args:729            **kwargs: Keyword arguments to use for formatting.730        """731        messages = self.format_messages(**kwargs)732        return ChatPromptValue(messages=messages)733734    async def aformat_prompt(self, **kwargs: Any) -> ChatPromptValue:735        """Async format prompt.736737        Should return a `ChatPromptValue`.738739        Args:740            **kwargs: Keyword arguments to use for formatting.741        """742        messages = await self.aformat_messages(**kwargs)743        return ChatPromptValue(messages=messages)744745    @abstractmethod746    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:747        """Format kwargs into a list of messages.748749        Returns:750            List of `BaseMessage` objects.751        """752753    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:754        """Async format kwargs into a list of messages.755756        Returns:757            List of `BaseMessage` objects.758        """759        return self.format_messages(**kwargs)760761    def pretty_repr(762        self,763        html: bool = False,  # noqa: FBT001,FBT002764    ) -> str:765        """Human-readable representation.766767        Args:768            html: Whether to format as HTML.769770        Returns:771            Human-readable representation.772        """773        raise NotImplementedError774775    def pretty_print(self) -> None:776        """Print a human-readable representation."""777        print(self.pretty_repr(html=is_interactive_env()))  # noqa: T201778779780MessageLike = BaseMessagePromptTemplate | BaseMessage | BaseChatPromptTemplate781782MessageLikeRepresentation = (783    MessageLike784    | tuple[str | type, str | Sequence[dict] | Sequence[object]]785    | str786    | dict[str, Any]787)788789790class ChatPromptTemplate(BaseChatPromptTemplate):791    """Prompt template for chat models.792793    Use to create flexible templated prompts for chat models.794795    !!! example796797        ```python798        from langchain_core.prompts import ChatPromptTemplate799800        template = ChatPromptTemplate(801            [802                ("system", "You are a helpful AI bot. Your name is {name}."),803                ("human", "Hello, how are you doing?"),804                ("ai", "I'm doing well, thanks!"),805                ("human", "{user_input}"),806            ]807        )808809        prompt_value = template.invoke(810            {811                "name": "Bob",812                "user_input": "What is your name?",813            }814        )815        # Output:816        # ChatPromptValue(817        #    messages=[818        #        SystemMessage(content='You are a helpful AI bot. Your name is Bob.'),819        #        HumanMessage(content='Hello, how are you doing?'),820        #        AIMessage(content="I'm doing well, thanks!"),821        #        HumanMessage(content='What is your name?')822        #    ]823        # )824        ```825826    !!! note "Messages Placeholder"827828        ```python829        # In addition to Human/AI/Tool/Function messages,830        # you can initialize the template with a MessagesPlaceholder831        # either using the class directly or with the shorthand tuple syntax:832833        template = ChatPromptTemplate(834            [835                ("system", "You are a helpful AI bot."),836                # Means the template will receive an optional list of messages under837                # the "conversation" key838                ("placeholder", "{conversation}"),839                # Equivalently:840                # MessagesPlaceholder(variable_name="conversation", optional=True)841            ]842        )843844        prompt_value = template.invoke(845            {846                "conversation": [847                    ("human", "Hi!"),848                    ("ai", "How can I assist you today?"),849                    ("human", "Can you make me an ice cream sundae?"),850                    ("ai", "No."),851                ]852            }853        )854855        # Output:856        # ChatPromptValue(857        #    messages=[858        #        SystemMessage(content='You are a helpful AI bot.'),859        #        HumanMessage(content='Hi!'),860        #        AIMessage(content='How can I assist you today?'),861        #        HumanMessage(content='Can you make me an ice cream sundae?'),862        #        AIMessage(content='No.'),863        #    ]864        # )865        ```866867    !!! note "Single-variable template"868869        If your prompt has only a single input variable (i.e., one instance of870        `'{variable_nams}'`), and you invoke the template with a non-dict object, the871        prompt template will inject the provided argument into that variable location.872873        ```python874        from langchain_core.prompts import ChatPromptTemplate875876        template = ChatPromptTemplate(877            [878                ("system", "You are a helpful AI bot. Your name is Carl."),879                ("human", "{user_input}"),880            ]881        )882883        prompt_value = template.invoke("Hello, there!")884        # Equivalent to885        # prompt_value = template.invoke({"user_input": "Hello, there!"})886887        # Output:888        #  ChatPromptValue(889        #     messages=[890        #         SystemMessage(content='You are a helpful AI bot. Your name is Carl.'),891        #         HumanMessage(content='Hello, there!'),892        #     ]893        # )894        ```895    """896897    messages: Annotated[list[MessageLike], SkipValidation()]898    """List of messages consisting of either message prompt templates or messages."""899900    validate_template: bool = False901    """Whether or not to try validating the template."""902903    def __init__(904        self,905        messages: Sequence[MessageLikeRepresentation],906        *,907        template_format: PromptTemplateFormat = "f-string",908        **kwargs: Any,909    ) -> None:910        """Create a chat prompt template from a variety of message formats.911912        Args:913            messages: Sequence of message representations.914915                A message can be represented using the following formats:916917                1. `BaseMessagePromptTemplate`918                2. `BaseMessage`919                3. 2-tuple of `(message type, template)`; e.g.,920                    `('human', '{user_input}')`921                4. 2-tuple of `(message class, template)`922                5. A string which is shorthand for `('human', template)`; e.g.,923                    `'{user_input}'`924            template_format: Format of the template.925            **kwargs: Additional keyword arguments passed to `BasePromptTemplate`,926                including (but not limited to):927928                - `input_variables`: A list of the names of the variables whose values929                    are required as inputs to the prompt.930                - `optional_variables`: A list of the names of the variables for931                    placeholder or `MessagePlaceholder` that are optional.932933                    These variables are auto inferred from the prompt and user need not934                    provide them.935936                - `partial_variables`: A dictionary of the partial variables the prompt937                    template carries.938939                    Partial variables populate the template so that you don't need to940                    pass them in every time you call the prompt.941942                - `validate_template`: Whether to validate the template.943                - `input_types`: A dictionary of the types of the variables the prompt944                    template expects.945946                    If not provided, all variables are assumed to be strings.947948        Examples:949            Instantiation from a list of message templates:950951            ```python952            template = ChatPromptTemplate(953                [954                    ("human", "Hello, how are you?"),955                    ("ai", "I'm doing well, thanks!"),956                    ("human", "That's good to hear."),957                ]958            )959            ```960961            Instantiation from mixed message formats:962963            ```python964            template = ChatPromptTemplate(965                [966                    SystemMessage(content="hello"),967                    ("human", "Hello, how are you?"),968                ]969            )970            ```971        """972        messages_ = [973            _convert_to_message_template(message, template_format)974            for message in messages975        ]976977        # Automatically infer input variables from messages978        input_vars: set[str] = set()979        optional_variables: set[str] = set()980        partial_vars: dict[str, Any] = {}981        for message in messages_:982            if isinstance(message, MessagesPlaceholder) and message.optional:983                partial_vars[message.variable_name] = []984                optional_variables.add(message.variable_name)985            elif isinstance(986                message, (BaseChatPromptTemplate, BaseMessagePromptTemplate)987            ):988                input_vars.update(message.input_variables)989990        kwargs = {991            "input_variables": sorted(input_vars),992            "optional_variables": sorted(optional_variables),993            "partial_variables": partial_vars,994            **kwargs,995        }996        cast("type[ChatPromptTemplate]", super()).__init__(messages=messages_, **kwargs)997998    @classmethod999    def get_lc_namespace(cls) -> list[str]:1000        """Get the namespace of the LangChain object.10011002        Returns:1003            `["langchain", "prompts", "chat"]`1004        """1005        return ["langchain", "prompts", "chat"]10061007    def __add__(self, other: Any) -> ChatPromptTemplate:1008        """Combine two prompt templates.10091010        Args:1011            other: Another prompt template.10121013        Returns:1014            Combined prompt template.1015        """1016        partials = {**self.partial_variables}10171018        # Need to check that other has partial variables since it may not be1019        # a ChatPromptTemplate.1020        if hasattr(other, "partial_variables") and other.partial_variables:1021            partials.update(other.partial_variables)10221023        # Allow for easy combining1024        if isinstance(other, ChatPromptTemplate):1025            return ChatPromptTemplate(messages=self.messages + other.messages).partial(1026                **partials1027            )1028        if isinstance(1029            other, (BaseMessagePromptTemplate, BaseMessage, BaseChatPromptTemplate)1030        ):1031            return ChatPromptTemplate(messages=[*self.messages, other]).partial(1032                **partials1033            )1034        if isinstance(other, (list, tuple)):1035            other_ = ChatPromptTemplate.from_messages(other)1036            return ChatPromptTemplate(messages=self.messages + other_.messages).partial(1037                **partials1038            )1039        if isinstance(other, str):1040            prompt = HumanMessagePromptTemplate.from_template(other)1041            return ChatPromptTemplate(messages=[*self.messages, prompt]).partial(1042                **partials1043            )1044        msg = f"Unsupported operand type for +: {type(other)}"1045        raise NotImplementedError(msg)10461047    @model_validator(mode="before")1048    @classmethod1049    def validate_input_variables(cls, values: dict) -> Any:1050        """Validate input variables.10511052        If `input_variables` is not set, it will be set to the union of all input1053        variables in the messages.10541055        Args:1056            values: values to validate.10571058        Returns:1059            Validated values.10601061        Raises:1062            ValueError: If input variables do not match.1063        """1064        messages = values["messages"]1065        input_vars: set = set()1066        optional_variables = set()1067        input_types: dict[str, Any] = values.get("input_types", {})1068        for message in messages:1069            if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1070                input_vars.update(message.input_variables)1071            if isinstance(message, MessagesPlaceholder):1072                if "partial_variables" not in values:1073                    values["partial_variables"] = {}1074                if (1075                    message.optional1076                    and message.variable_name not in values["partial_variables"]1077                ):1078                    values["partial_variables"][message.variable_name] = []1079                    optional_variables.add(message.variable_name)1080                if message.variable_name not in input_types:1081                    input_types[message.variable_name] = list[AnyMessage]1082        if "partial_variables" in values:1083            input_vars -= set(values["partial_variables"])1084        if optional_variables:1085            input_vars -= optional_variables1086        if "input_variables" in values and values.get("validate_template"):1087            if input_vars != set(values["input_variables"]):1088                msg = (1089                    "Got mismatched input_variables. "1090                    f"Expected: {input_vars}. "1091                    f"Got: {values['input_variables']}"1092                )1093                raise ValueError(msg)1094        else:1095            values["input_variables"] = sorted(input_vars)1096        if optional_variables:1097            values["optional_variables"] = sorted(optional_variables)1098        values["input_types"] = input_types1099        return values11001101    @classmethod1102    def from_template(cls, template: str, **kwargs: Any) -> ChatPromptTemplate:1103        """Create a chat prompt template from a template string.11041105        Creates a chat template consisting of a single message assumed to be from the1106        human.11071108        Args:1109            template: Template string1110            **kwargs: Keyword arguments to pass to the constructor.11111112        Returns:1113            A new instance of this class.1114        """1115        prompt_template = PromptTemplate.from_template(template, **kwargs)1116        message = HumanMessagePromptTemplate(prompt=prompt_template)1117        return cls.from_messages([message])11181119    @classmethod1120    def from_messages(1121        cls,1122        messages: Sequence[MessageLikeRepresentation],1123        template_format: PromptTemplateFormat = "f-string",1124    ) -> ChatPromptTemplate:1125        """Create a chat prompt template from a variety of message formats.11261127        Examples:1128            Instantiation from a list of message templates:11291130            ```python1131            template = ChatPromptTemplate.from_messages(1132                [1133                    ("human", "Hello, how are you?"),1134                    ("ai", "I'm doing well, thanks!"),1135                    ("human", "That's good to hear."),1136                ]1137            )1138            ```11391140            Instantiation from mixed message formats:11411142            ```python1143            template = ChatPromptTemplate.from_messages(1144                [1145                    SystemMessage(content="hello"),1146                    ("human", "Hello, how are you?"),1147                ]1148            )1149            ```1150        Args:1151            messages: Sequence of message representations.11521153                A message can be represented using the following formats:11541155                1. `BaseMessagePromptTemplate`1156                2. `BaseMessage`1157                3. 2-tuple of `(message type, template)`; e.g.,1158                    `('human', '{user_input}')`1159                4. 2-tuple of `(message class, template)`1160                5. A string which is shorthand for `('human', template)`; e.g.,1161                    `'{user_input}'`1162            template_format: Format of the template.11631164        Returns:1165            A chat prompt template.11661167        """1168        return cls(messages, template_format=template_format)11691170    def format_messages(self, **kwargs: Any) -> list[BaseMessage]:1171        """Format the chat template into a list of finalized messages.11721173        Args:1174            **kwargs: Keyword arguments to use for filling in template variables1175                in all the template messages in this chat template.11761177        Raises:1178            ValueError: If messages are of unexpected types.11791180        Returns:1181            List of formatted messages.1182        """1183        kwargs = self._merge_partial_and_user_variables(**kwargs)1184        result = []1185        for message_template in self.messages:1186            if isinstance(message_template, BaseMessage):1187                result.extend([message_template])1188            elif isinstance(1189                message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1190            ):1191                message = message_template.format_messages(**kwargs)1192                result.extend(message)1193            else:1194                msg = f"Unexpected input: {message_template}"1195                raise ValueError(msg)  # noqa: TRY0041196        return result11971198    async def aformat_messages(self, **kwargs: Any) -> list[BaseMessage]:1199        """Async format the chat template into a list of finalized messages.12001201        Args:1202            **kwargs: Keyword arguments to use for filling in template variables1203                in all the template messages in this chat template.12041205        Returns:1206            List of formatted messages.12071208        Raises:1209            ValueError: If unexpected input.1210        """1211        kwargs = self._merge_partial_and_user_variables(**kwargs)1212        result = []1213        for message_template in self.messages:1214            if isinstance(message_template, BaseMessage):1215                result.extend([message_template])1216            elif isinstance(1217                message_template, (BaseMessagePromptTemplate, BaseChatPromptTemplate)1218            ):1219                message = await message_template.aformat_messages(**kwargs)1220                result.extend(message)1221            else:1222                msg = f"Unexpected input: {message_template}"1223                raise ValueError(msg)  # noqa:TRY0041224        return result12251226    def partial(self, **kwargs: Any) -> ChatPromptTemplate:1227        """Get a new `ChatPromptTemplate` with some input variables already filled in.12281229        Args:1230            **kwargs: Keyword arguments to use for filling in template variables.12311232                Ought to be a subset of the input variables.12331234        Returns:1235            A new `ChatPromptTemplate`.12361237        Example:1238            ```python1239            from langchain_core.prompts import ChatPromptTemplate12401241            template = ChatPromptTemplate.from_messages(1242                [1243                    ("system", "You are an AI assistant named {name}."),1244                    ("human", "Hi I'm {user}"),1245                    ("ai", "Hi there, {user}, I'm {name}."),1246                    ("human", "{input}"),1247                ]1248            )1249            template2 = template.partial(user="Lucy", name="R2D2")12501251            template2.format_messages(input="hello")1252            ```1253        """1254        prompt_dict = self.__dict__.copy()1255        prompt_dict["input_variables"] = list(1256            set(self.input_variables).difference(kwargs)1257        )1258        prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}1259        return type(self)(**prompt_dict)12601261    def append(self, message: MessageLikeRepresentation) -> None:1262        """Append a message to the end of the chat template.12631264        Args:1265            message: representation of a message to append.1266        """1267        self.messages.append(_convert_to_message_template(message))12681269    def extend(self, messages: Sequence[MessageLikeRepresentation]) -> None:1270        """Extend the chat template with a sequence of messages.12711272        Args:1273            messages: Sequence of message representations to append.1274        """1275        self.messages.extend(1276            [_convert_to_message_template(message) for message in messages]1277        )12781279    @overload1280    def __getitem__(self, index: int) -> MessageLike: ...12811282    @overload1283    def __getitem__(self, index: slice) -> ChatPromptTemplate: ...12841285    def __getitem__(self, index: int | slice) -> MessageLike | ChatPromptTemplate:1286        """Use to index into the chat template.12871288        Returns:1289            If index is an int, returns the message at that index.12901291            If index is a slice, returns a new `ChatPromptTemplate` containing the1292                messages in that slice.1293        """1294        if isinstance(index, slice):1295            start, stop, step = index.indices(len(self.messages))1296            messages = self.messages[start:stop:step]1297            return ChatPromptTemplate.from_messages(messages)1298        return self.messages[index]12991300    def __len__(self) -> int:1301        """Return the length of the chat template."""1302        return len(self.messages)13031304    @property1305    def _prompt_type(self) -> str:1306        """Name of prompt type. Used for serialization."""1307        return "chat"13081309    @deprecated(1310        since="1.2.21",1311        removal="2.0.0",1312        alternative="Use `dumpd`/`dumps` from `langchain_core.load` to serialize "1313        "prompts and `load`/`loads` to deserialize them.",1314    )1315    def save(self, file_path: Path | str) -> None:1316        """Save prompt to file.13171318        Args:1319            file_path: path to file.1320        """1321        raise NotImplementedError13221323    @override1324    def pretty_repr(self, html: bool = False) -> str:1325        """Human-readable representation.13261327        Args:1328            html: Whether to format as HTML.13291330        Returns:1331            Human-readable representation.1332        """1333        # TODO: handle partials1334        return "\n\n".join(msg.pretty_repr(html=html) for msg in self.messages)133513361337def _create_template_from_message_type(1338    message_type: str,1339    template: str | list,1340    template_format: PromptTemplateFormat = "f-string",1341) -> BaseMessagePromptTemplate:1342    """Create a message prompt template from a message type and template string.13431344    Args:1345        message_type: The type of the message template (e.g., `'human'`, `'ai'`, etc.)1346        template: The template string.1347        template_format: Format of the template.13481349    Returns:1350        A message prompt template of the appropriate type.13511352    Raises:1353        ValueError: If unexpected message type.1354    """1355    if message_type in {"human", "user"}:1356        message: BaseMessagePromptTemplate = HumanMessagePromptTemplate.from_template(1357            template, template_format=template_format1358        )1359    elif message_type in {"ai", "assistant"}:1360        message = AIMessagePromptTemplate.from_template(1361            cast("str", template), template_format=template_format1362        )1363    elif message_type == "system":1364        message = SystemMessagePromptTemplate.from_template(1365            cast("str", template), template_format=template_format1366        )1367    elif message_type == "placeholder":1368        if isinstance(template, str):1369            if template[0] != "{" or template[-1] != "}":1370                msg = (1371                    f"Invalid placeholder template: {template}."1372                    " Expected a variable name surrounded by curly braces."1373                )1374                raise ValueError(msg)1375            var_name = template[1:-1]1376            message = MessagesPlaceholder(variable_name=var_name, optional=True)1377        else:1378            try:1379                var_name_wrapped, is_optional = template1380            except ValueError as e:1381                msg = (1382                    "Unexpected arguments for placeholder message type."1383                    " Expected either a single string variable name"1384                    " or a list of [variable_name: str, is_optional: bool]."1385                    f" Got: {template}"1386                )1387                raise ValueError(msg) from e13881389            if not isinstance(is_optional, bool):1390                msg = f"Expected is_optional to be a boolean. Got: {is_optional}"1391                raise ValueError(msg)  # noqa: TRY00413921393            if not isinstance(var_name_wrapped, str):1394                msg = f"Expected variable name to be a string. Got: {var_name_wrapped}"1395                raise ValueError(msg)  # noqa: TRY0041396            if var_name_wrapped[0] != "{" or var_name_wrapped[-1] != "}":1397                msg = (1398                    f"Invalid placeholder template: {var_name_wrapped}."1399                    " Expected a variable name surrounded by curly braces."1400                )1401                raise ValueError(msg)1402            var_name = var_name_wrapped[1:-1]14031404            message = MessagesPlaceholder(variable_name=var_name, optional=is_optional)1405    else:1406        msg = (1407            f"Unexpected message type: {message_type}. Use one of 'human',"1408            f" 'user', 'ai', 'assistant', or 'system'."1409        )1410        raise ValueError(msg)1411    return message141214131414def _convert_to_message_template(1415    message: MessageLikeRepresentation,1416    template_format: PromptTemplateFormat = "f-string",1417) -> BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate:1418    """Instantiate a message from a variety of message formats.14191420    A message can be represented using the following formats:14211422    1. `BaseMessagePromptTemplate`1423    2. `BaseMessage`1424    3. 2-tuple of `(message type, template)`; e.g., `('human', '{user_input}')`1425    4. 2-tuple of `(message class, template)`1426    5. A string which is shorthand for `('human', template)`; e.g., `'{user_input}'`14271428    Args:1429        message: A representation of a message in one of the supported formats.1430        template_format: Format of the template.14311432    Returns:1433        An instance of a message or a message template.14341435    Raises:1436        ValueError: If unexpected message type.1437        ValueError: If 2-tuple does not have 2 elements.1438    """1439    if isinstance(message, (BaseMessagePromptTemplate, BaseChatPromptTemplate)):1440        message_: BaseMessage | BaseMessagePromptTemplate | BaseChatPromptTemplate = (1441            message1442        )1443    elif isinstance(message, BaseMessage):1444        message_ = message1445    elif isinstance(message, str):1446        message_ = _create_template_from_message_type(1447            "human", message, template_format=template_format1448        )1449    elif isinstance(message, (tuple, dict)):1450        if isinstance(message, dict):1451            if set(message.keys()) != {"content", "role"}:1452                msg = (1453                    "Expected dict to have exact keys 'role' and 'content'."1454                    f" Got: {message}"1455                )1456                raise ValueError(msg)1457            message_type_str = message["role"]1458            template = message["content"]1459        else:1460            if len(message) != 2:  # noqa: PLR20041461                msg = f"Expected 2-tuple of (role, template), got {message}"1462                raise ValueError(msg)1463            message_type_str, template = message14641465        if isinstance(message_type_str, str):1466            message_ = _create_template_from_message_type(1467                message_type_str, template, template_format=template_format1468            )1469        elif (1470            hasattr(message_type_str, "model_fields")1471            and "type" in message_type_str.model_fields1472        ):1473            message_type = message_type_str.model_fields["type"].default1474            message_ = _create_template_from_message_type(1475                message_type, template, template_format=template_format1476            )1477        else:1478            message_ = message_type_str(1479                prompt=PromptTemplate.from_template(1480                    cast("str", template), template_format=template_format1481                )1482            )1483    else:1484        msg = f"Unsupported message type: {type(message)}"1485        raise NotImplementedError(msg)14861487    return message_148814891490# For backwards compat:1491_convert_to_message = _convert_to_message_template

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, list):
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:
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.