libs/core/langchain_core/output_parsers/base.py PYTHON 360 lines View on github.com → Search inside
1"""Base parser for language model outputs."""23from __future__ import annotations45import builtins  # noqa: TC0036import contextlib7from abc import ABC, abstractmethod8from typing import (9    TYPE_CHECKING,10    Any,11    Generic,12    TypeVar,13    cast,14)1516from typing_extensions import override1718from langchain_core._api import deprecated19from langchain_core.language_models import LanguageModelOutput20from langchain_core.messages import AnyMessage, BaseMessage21from langchain_core.outputs import ChatGeneration, Generation22from langchain_core.runnables import Runnable, RunnableConfig, RunnableSerializable23from langchain_core.runnables.config import run_in_executor2425if TYPE_CHECKING:26    from langchain_core.prompt_values import PromptValue2728T = TypeVar("T")29OutputParserLike = Runnable[LanguageModelOutput, T]303132class BaseLLMOutputParser(ABC, Generic[T]):33    """Abstract base class for parsing the outputs of a model."""3435    @abstractmethod36    def parse_result(self, result: list[Generation], *, partial: bool = False) -> T:37        """Parse a list of candidate model `Generation` objects into a specific format.3839        Args:40            result: A list of `Generation` to be parsed.4142                The `Generation` objects are assumed to be different candidate outputs43                for a single model input.44            partial: Whether to parse the output as a partial result.4546                This is useful for parsers that can parse partial results.4748        Returns:49            Structured output.50        """5152    async def aparse_result(53        self, result: list[Generation], *, partial: bool = False54    ) -> T:55        """Parse a list of candidate model `Generation` objects into a specific format.5657        Args:58            result: A list of `Generation` to be parsed.5960                The Generations are assumed to be different candidate outputs for a61                single model input.62            partial: Whether to parse the output as a partial result.6364                This is useful for parsers that can parse partial results.6566        Returns:67            Structured output.68        """69        return await run_in_executor(None, self.parse_result, result, partial=partial)707172class BaseGenerationOutputParser(73    BaseLLMOutputParser, RunnableSerializable[LanguageModelOutput, T]74):75    """Base class to parse the output of an LLM call."""7677    @property78    @override79    def InputType(self) -> Any:80        """Return the input type for the parser."""81        return str | AnyMessage8283    @property84    @override85    def OutputType(self) -> type[T]:86        """Return the output type for the parser."""87        # even though mypy complains this isn't valid,88        # it is good enough for pydantic to build the schema from89        return cast("type[T]", T)  # type: ignore[misc]9091    @override92    def invoke(93        self,94        input: str | BaseMessage,95        config: RunnableConfig | None = None,96        **kwargs: Any,97    ) -> T:98        if isinstance(input, BaseMessage):99            return self._call_with_config(100                lambda inner_input: self.parse_result(101                    [ChatGeneration(message=inner_input)]102                ),103                input,104                config,105                run_type="parser",106            )107        return self._call_with_config(108            lambda inner_input: self.parse_result([Generation(text=inner_input)]),109            input,110            config,111            run_type="parser",112        )113114    @override115    async def ainvoke(116        self,117        input: str | BaseMessage,118        config: RunnableConfig | None = None,119        **kwargs: Any | None,120    ) -> T:121        if isinstance(input, BaseMessage):122            return await self._acall_with_config(123                lambda inner_input: self.aparse_result(124                    [ChatGeneration(message=inner_input)]125                ),126                input,127                config,128                run_type="parser",129            )130        return await self._acall_with_config(131            lambda inner_input: self.aparse_result([Generation(text=inner_input)]),132            input,133            config,134            run_type="parser",135        )136137138class BaseOutputParser(139    BaseLLMOutputParser, RunnableSerializable[LanguageModelOutput, T]140):141    """Base class to parse the output of an LLM call.142143    Output parsers help structure language model responses.144145    Example:146        ```python147        # Implement a simple boolean output parser148149150        class BooleanOutputParser(BaseOutputParser[bool]):151            true_val: str = "YES"152            false_val: str = "NO"153154            def parse(self, text: str) -> bool:155                cleaned_text = text.strip().upper()156                if cleaned_text not in (157                    self.true_val.upper(),158                    self.false_val.upper(),159                ):160                    raise OutputParserException(161                        f"BooleanOutputParser expected output value to either be "162                        f"{self.true_val} or {self.false_val} (case-insensitive). "163                        f"Received {cleaned_text}."164                    )165                return cleaned_text == self.true_val.upper()166167            @property168            def _type(self) -> str:169                return "boolean_output_parser"170        ```171    """172173    @property174    @override175    def InputType(self) -> Any:176        """Return the input type for the parser."""177        return str | AnyMessage178179    @property180    @override181    def OutputType(self) -> type[T]:182        """Return the output type for the parser.183184        This property is inferred from the first type argument of the class.185186        Raises:187            TypeError: If the class doesn't have an inferable `OutputType`.188        """189        for base in self.__class__.mro():190            if hasattr(base, "__pydantic_generic_metadata__"):191                metadata = base.__pydantic_generic_metadata__192                if "args" in metadata and len(metadata["args"]) > 0:193                    return cast("type[T]", metadata["args"][0])194195        msg = (196            f"Runnable {self.__class__.__name__} doesn't have an inferable OutputType. "197            "Override the OutputType property to specify the output type."198        )199        raise TypeError(msg)200201    @override202    def invoke(203        self,204        input: str | BaseMessage,205        config: RunnableConfig | None = None,206        **kwargs: Any,207    ) -> T:208        if isinstance(input, BaseMessage):209            return self._call_with_config(210                lambda inner_input: self.parse_result(211                    [ChatGeneration(message=inner_input)]212                ),213                input,214                config,215                run_type="parser",216            )217        return self._call_with_config(218            lambda inner_input: self.parse_result([Generation(text=inner_input)]),219            input,220            config,221            run_type="parser",222        )223224    @override225    async def ainvoke(226        self,227        input: str | BaseMessage,228        config: RunnableConfig | None = None,229        **kwargs: Any | None,230    ) -> T:231        if isinstance(input, BaseMessage):232            return await self._acall_with_config(233                lambda inner_input: self.aparse_result(234                    [ChatGeneration(message=inner_input)]235                ),236                input,237                config,238                run_type="parser",239            )240        return await self._acall_with_config(241            lambda inner_input: self.aparse_result([Generation(text=inner_input)]),242            input,243            config,244            run_type="parser",245        )246247    @override248    def parse_result(self, result: list[Generation], *, partial: bool = False) -> T:249        """Parse a list of candidate model `Generation` objects into a specific format.250251        The return value is parsed from only the first `Generation` in the result, which252        is assumed to be the highest-likelihood `Generation`.253254        Args:255            result: A list of `Generation` to be parsed.256257                The `Generation` objects are assumed to be different candidate outputs258                for a single model input.259            partial: Whether to parse the output as a partial result.260261                This is useful for parsers that can parse partial results.262263        Returns:264            Structured output.265        """266        return self.parse(result[0].text)267268    @abstractmethod269    def parse(self, text: str) -> T:270        """Parse a single string model output into some structure.271272        Args:273            text: String output of a language model.274275        Returns:276            Structured output.277        """278279    async def aparse_result(280        self, result: list[Generation], *, partial: bool = False281    ) -> T:282        """Parse a list of candidate model `Generation` objects into a specific format.283284        The return value is parsed from only the first `Generation` in the result, which285        is assumed to be the highest-likelihood `Generation`.286287        Args:288            result: A list of `Generation` to be parsed.289290                The `Generation` objects are assumed to be different candidate outputs291                for a single model input.292            partial: Whether to parse the output as a partial result.293294                This is useful for parsers that can parse partial results.295296        Returns:297            Structured output.298        """299        return await run_in_executor(None, self.parse_result, result, partial=partial)300301    async def aparse(self, text: str) -> T:302        """Async parse a single string model output into some structure.303304        Args:305            text: String output of a language model.306307        Returns:308            Structured output.309        """310        return await run_in_executor(None, self.parse, text)311312    # TODO: rename 'completion' -> 'text'.313    def parse_with_prompt(314        self,315        completion: str,316        prompt: PromptValue,  # noqa: ARG002317    ) -> Any:318        """Parse the output of an LLM call with the input prompt for context.319320        The prompt is largely provided in the event the `OutputParser` wants to retry or321        fix the output in some way, and needs information from the prompt to do so.322323        Args:324            completion: String output of a language model.325            prompt: Input `PromptValue`.326327        Returns:328            Structured output.329        """330        return self.parse(completion)331332    def get_format_instructions(self) -> str:333        """Instructions on how the LLM output should be formatted."""334        raise NotImplementedError335336    @property337    def _type(self) -> str:338        """Return the output parser type for serialization."""339        msg = (340            f"_type property is not implemented in class {self.__class__.__name__}."341            " This is required for serialization."342        )343        raise NotImplementedError(msg)344345    @deprecated("1.4.2", alternative="asdict", removal="2.0.0")346    @override347    def dict(self, **kwargs: Any) -> builtins.dict[str, Any]:348        """DEPRECATED - use `asdict()` instead.349350        Return a dictionary representation of the output parser.351        """352        return self.asdict(**kwargs)353354    def asdict(self, **kwargs: Any) -> builtins.dict[str, Any]:355        """Return a dictionary representation of the output parser."""356        output_parser_dict = super().model_dump(**kwargs)357        with contextlib.suppress(NotImplementedError):358            output_parser_dict["_type"] = self._type359        return output_parser_dict

Code quality findings 16

Ensure functions have docstrings for documentation
missing-docstring
async def aparse_result(
Ensure functions have docstrings for documentation
missing-docstring
def invoke(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(input, BaseMessage):
Ensure functions have docstrings for documentation
missing-docstring
async def ainvoke(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(input, BaseMessage):
Ensure functions have docstrings for documentation
missing-docstring
def parse(self, text: str) -> bool:
Ensure functions have docstrings for documentation
missing-docstring
def invoke(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(input, BaseMessage):
Ensure functions have docstrings for documentation
missing-docstring
async def ainvoke(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(input, BaseMessage):
Ensure functions have docstrings for documentation
missing-docstring
async def aparse_result(
Ensure functions have docstrings for documentation
missing-docstring
def parse_with_prompt(
Avoid complex 'lambda' functions; prefer named functions for clarity and debugging
info maintainability complex-lambda
lambda inner_input: self.parse_result([Generation(text=inner_input)]),
Avoid complex 'lambda' functions; prefer named functions for clarity and debugging
info maintainability complex-lambda
lambda inner_input: self.aparse_result([Generation(text=inner_input)]),
Avoid complex 'lambda' functions; prefer named functions for clarity and debugging
info maintainability complex-lambda
lambda inner_input: self.parse_result([Generation(text=inner_input)]),
Avoid complex 'lambda' functions; prefer named functions for clarity and debugging
info maintainability complex-lambda
lambda inner_input: self.aparse_result([Generation(text=inner_input)]),

Get this view in your editor

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