libs/langchain/langchain_classic/chains/base.py PYTHON 807 lines View on github.com → Search inside
1"""Base interface that all chains should implement."""23import builtins4import contextlib5import inspect6import json7import logging8import warnings9from abc import ABC, abstractmethod10from pathlib import Path11from typing import Any, cast1213import yaml14from langchain_core._api import deprecated15from langchain_core.callbacks import (16    AsyncCallbackManager,17    AsyncCallbackManagerForChainRun,18    BaseCallbackManager,19    CallbackManager,20    CallbackManagerForChainRun,21    Callbacks,22)23from langchain_core.outputs import RunInfo24from langchain_core.runnables import (25    RunnableConfig,26    RunnableSerializable,27    ensure_config,28    run_in_executor,29)30from langchain_core.utils.pydantic import create_model31from pydantic import (32    BaseModel,33    ConfigDict,34    Field,35    field_validator,36    model_validator,37)38from typing_extensions import override3940from langchain_classic.base_memory import BaseMemory41from langchain_classic.schema import RUN_KEY4243logger = logging.getLogger(__name__)444546def _get_verbosity() -> bool:47    from langchain_classic.globals import get_verbose4849    return get_verbose()505152class Chain(RunnableSerializable[dict[str, Any], dict[str, Any]], ABC):53    """Abstract base class for creating structured sequences of calls to components.5455    Chains should be used to encode a sequence of calls to components like56    models, document retrievers, other chains, etc., and provide a simple interface57    to this sequence.5859    The Chain interface makes it easy to create apps that are:60        - Stateful: add Memory to any Chain to give it state,61        - Observable: pass Callbacks to a Chain to execute additional functionality,62            like logging, outside the main sequence of component calls,63        - Composable: the Chain API is flexible enough that it is easy to combine64            Chains with other components, including other Chains.6566    The main methods exposed by chains are:67        - `__call__`: Chains are callable. The `__call__` method is the primary way to68            execute a Chain. This takes inputs as a dictionary and returns a69            dictionary output.70        - `run`: A convenience method that takes inputs as args/kwargs and returns the71            output as a string or object. This method can only be used for a subset of72            chains and cannot return as rich of an output as `__call__`.73    """7475    memory: BaseMemory | None = None76    """Optional memory object.77    Memory is a class that gets called at the start78    and at the end of every chain. At the start, memory loads variables and passes79    them along in the chain. At the end, it saves any returned variables.80    There are many different types of memory - please see memory docs81    for the full catalog."""82    callbacks: Callbacks = Field(default=None, exclude=True)83    """Optional list of callback handlers (or callback manager).84    Callback handlers are called throughout the lifecycle of a call to a chain,85    starting with on_chain_start, ending with on_chain_end or on_chain_error.86    Each custom chain can optionally call additional callback methods, see Callback docs87    for full details."""88    verbose: bool = Field(default_factory=_get_verbosity)89    """Whether or not run in verbose mode. In verbose mode, some intermediate logs90    will be printed to the console. Defaults to the global `verbose` value,91    accessible via `langchain.globals.get_verbose()`."""92    tags: list[str] | None = None93    """Optional list of tags associated with the chain.94    These tags will be associated with each call to this chain,95    and passed as arguments to the handlers defined in `callbacks`.96    You can use these to eg identify a specific instance of a chain with its use case.97    """98    metadata: builtins.dict[str, Any] | None = None99    """Optional metadata associated with the chain.100    This metadata will be associated with each call to this chain,101    and passed as arguments to the handlers defined in `callbacks`.102    You can use these to eg identify a specific instance of a chain with its use case.103    """104    callback_manager: BaseCallbackManager | None = Field(default=None, exclude=True)105    """[DEPRECATED] Use `callbacks` instead."""106107    model_config = ConfigDict(108        arbitrary_types_allowed=True,109    )110111    @override112    def get_input_schema(113        self,114        config: RunnableConfig | None = None,115    ) -> type[BaseModel]:116        # This is correct, but pydantic typings/mypy don't think so.117        return create_model("ChainInput", **dict.fromkeys(self.input_keys, (Any, None)))118119    @override120    def get_output_schema(121        self,122        config: RunnableConfig | None = None,123    ) -> type[BaseModel]:124        # This is correct, but pydantic typings/mypy don't think so.125        return create_model(126            "ChainOutput",127            **dict.fromkeys(self.output_keys, (Any, None)),128        )129130    @override131    def invoke(132        self,133        input: dict[str, Any],134        config: RunnableConfig | None = None,135        **kwargs: Any,136    ) -> dict[str, Any]:137        config = ensure_config(config)138        callbacks = config.get("callbacks")139        tags = config.get("tags")140        metadata = config.get("metadata")141        run_name = config.get("run_name") or self.get_name()142        run_id = config.get("run_id")143        include_run_info = kwargs.get("include_run_info", False)144        return_only_outputs = kwargs.get("return_only_outputs", False)145146        inputs = self.prep_inputs(input)147        callback_manager = CallbackManager.configure(148            callbacks,149            self.callbacks,150            self.verbose,151            tags,152            self.tags,153            metadata,154            self.metadata,155        )156        new_arg_supported = inspect.signature(self._call).parameters.get("run_manager")157158        run_manager = callback_manager.on_chain_start(159            None,160            inputs,161            run_id,162            name=run_name,163        )164        try:165            self._validate_inputs(inputs)166            outputs = (167                self._call(inputs, run_manager=run_manager)168                if new_arg_supported169                else self._call(inputs)170            )171172            final_outputs: dict[str, Any] = self.prep_outputs(173                inputs,174                outputs,175                return_only_outputs,176            )177        except BaseException as e:178            run_manager.on_chain_error(e)179            raise180        run_manager.on_chain_end(outputs)181182        if include_run_info:183            final_outputs[RUN_KEY] = RunInfo(run_id=run_manager.run_id)184        return final_outputs185186    @override187    async def ainvoke(188        self,189        input: dict[str, Any],190        config: RunnableConfig | None = None,191        **kwargs: Any,192    ) -> dict[str, Any]:193        config = ensure_config(config)194        callbacks = config.get("callbacks")195        tags = config.get("tags")196        metadata = config.get("metadata")197        run_name = config.get("run_name") or self.get_name()198        run_id = config.get("run_id")199        include_run_info = kwargs.get("include_run_info", False)200        return_only_outputs = kwargs.get("return_only_outputs", False)201202        inputs = await self.aprep_inputs(input)203        callback_manager = AsyncCallbackManager.configure(204            callbacks,205            self.callbacks,206            self.verbose,207            tags,208            self.tags,209            metadata,210            self.metadata,211        )212        new_arg_supported = inspect.signature(self._acall).parameters.get("run_manager")213        run_manager = await callback_manager.on_chain_start(214            None,215            inputs,216            run_id,217            name=run_name,218        )219        try:220            self._validate_inputs(inputs)221            outputs = (222                await self._acall(inputs, run_manager=run_manager)223                if new_arg_supported224                else await self._acall(inputs)225            )226            final_outputs: dict[str, Any] = await self.aprep_outputs(227                inputs,228                outputs,229                return_only_outputs,230            )231        except BaseException as e:232            await run_manager.on_chain_error(e)233            raise234        await run_manager.on_chain_end(outputs)235236        if include_run_info:237            final_outputs[RUN_KEY] = RunInfo(run_id=run_manager.run_id)238        return final_outputs239240    @property241    def _chain_type(self) -> str:242        msg = "Saving not supported for this chain type."243        raise NotImplementedError(msg)244245    @model_validator(mode="before")246    @classmethod247    def raise_callback_manager_deprecation(cls, values: dict) -> Any:248        """Raise deprecation warning if callback_manager is used."""249        if values.get("callback_manager") is not None:250            if values.get("callbacks") is not None:251                msg = (252                    "Cannot specify both callback_manager and callbacks. "253                    "callback_manager is deprecated, callbacks is the preferred "254                    "parameter to pass in."255                )256                raise ValueError(msg)257            warnings.warn(258                "callback_manager is deprecated. Please use callbacks instead.",259                DeprecationWarning,260                stacklevel=4,261            )262            values["callbacks"] = values.pop("callback_manager", None)263        return values264265    @field_validator("verbose", mode="before")266    @classmethod267    def set_verbose(268        cls,269        verbose: bool | None,  # noqa: FBT001270    ) -> bool:271        """Set the chain verbosity.272273        Defaults to the global setting if not specified by the user.274        """275        if verbose is None:276            return _get_verbosity()277        return verbose278279    @property280    @abstractmethod281    def input_keys(self) -> list[str]:282        """Keys expected to be in the chain input."""283284    @property285    @abstractmethod286    def output_keys(self) -> list[str]:287        """Keys expected to be in the chain output."""288289    def _validate_inputs(self, inputs: Any) -> None:290        """Check that all inputs are present."""291        if not isinstance(inputs, dict):292            _input_keys = set(self.input_keys)293            if self.memory is not None:294                # If there are multiple input keys, but some get set by memory so that295                # only one is not set, we can still figure out which key it is.296                _input_keys = _input_keys.difference(self.memory.memory_variables)297            if len(_input_keys) != 1:298                msg = (299                    f"A single string input was passed in, but this chain expects "300                    f"multiple inputs ({_input_keys}). When a chain expects "301                    f"multiple inputs, please call it by passing in a dictionary, "302                    "eg `chain({'foo': 1, 'bar': 2})`"303                )304                raise ValueError(msg)305306        missing_keys = set(self.input_keys).difference(inputs)307        if missing_keys:308            msg = f"Missing some input keys: {missing_keys}"309            raise ValueError(msg)310311    def _validate_outputs(self, outputs: dict[str, Any]) -> None:312        missing_keys = set(self.output_keys).difference(outputs)313        if missing_keys:314            msg = f"Missing some output keys: {missing_keys}"315            raise ValueError(msg)316317    @abstractmethod318    def _call(319        self,320        inputs: builtins.dict[str, Any],321        run_manager: CallbackManagerForChainRun | None = None,322    ) -> builtins.dict[str, Any]:323        """Execute the chain.324325        This is a private method that is not user-facing. It is only called within326            `Chain.__call__`, which is the user-facing wrapper method that handles327            callbacks configuration and some input/output processing.328329        Args:330            inputs: A dict of named inputs to the chain. Assumed to contain all inputs331                specified in `Chain.input_keys`, including any inputs added by memory.332            run_manager: The callbacks manager that contains the callback handlers for333                this run of the chain.334335        Returns:336            A dict of named outputs. Should contain all outputs specified in337                `Chain.output_keys`.338        """339340    async def _acall(341        self,342        inputs: builtins.dict[str, Any],343        run_manager: AsyncCallbackManagerForChainRun | None = None,344    ) -> builtins.dict[str, Any]:345        """Asynchronously execute the chain.346347        This is a private method that is not user-facing. It is only called within348            `Chain.acall`, which is the user-facing wrapper method that handles349            callbacks configuration and some input/output processing.350351        Args:352            inputs: A dict of named inputs to the chain. Assumed to contain all inputs353                specified in `Chain.input_keys`, including any inputs added by memory.354            run_manager: The callbacks manager that contains the callback handlers for355                this run of the chain.356357        Returns:358            A dict of named outputs. Should contain all outputs specified in359                `Chain.output_keys`.360        """361        return await run_in_executor(362            None,363            self._call,364            inputs,365            run_manager.get_sync() if run_manager else None,366        )367368    @deprecated("0.1.0", alternative="invoke", removal="2.0.0")369    def __call__(370        self,371        inputs: dict[str, Any] | Any,372        return_only_outputs: bool = False,  # noqa: FBT001,FBT002373        callbacks: Callbacks = None,374        *,375        tags: list[str] | None = None,376        metadata: dict[str, Any] | None = None,377        run_name: str | None = None,378        include_run_info: bool = False,379    ) -> dict[str, Any]:380        """Execute the chain.381382        Args:383            inputs: Dictionary of inputs, or single input if chain expects384                only one param. Should contain all inputs specified in385                `Chain.input_keys` except for inputs that will be set by the chain's386                memory.387            return_only_outputs: Whether to return only outputs in the388                response. If `True`, only new keys generated by this chain will be389                returned. If `False`, both input keys and new keys generated by this390                chain will be returned.391            callbacks: Callbacks to use for this chain run. These will be called in392                addition to callbacks passed to the chain during construction, but only393                these runtime callbacks will propagate to calls to other objects.394            tags: List of string tags to pass to all callbacks. These will be passed in395                addition to tags passed to the chain during construction, but only396                these runtime tags will propagate to calls to other objects.397            metadata: Optional metadata associated with the chain.398            run_name: Optional name for this run of the chain.399            include_run_info: Whether to include run info in the response. Defaults400                to False.401402        Returns:403            A dict of named outputs. Should contain all outputs specified in404                `Chain.output_keys`.405        """406        config = {407            "callbacks": callbacks,408            "tags": tags,409            "metadata": metadata,410            "run_name": run_name,411        }412413        return self.invoke(414            inputs,415            cast("RunnableConfig", {k: v for k, v in config.items() if v is not None}),416            return_only_outputs=return_only_outputs,417            include_run_info=include_run_info,418        )419420    @deprecated("0.1.0", alternative="ainvoke", removal="2.0.0")421    async def acall(422        self,423        inputs: dict[str, Any] | Any,424        return_only_outputs: bool = False,  # noqa: FBT001,FBT002425        callbacks: Callbacks = None,426        *,427        tags: list[str] | None = None,428        metadata: dict[str, Any] | None = None,429        run_name: str | None = None,430        include_run_info: bool = False,431    ) -> dict[str, Any]:432        """Asynchronously execute the chain.433434        Args:435            inputs: Dictionary of inputs, or single input if chain expects436                only one param. Should contain all inputs specified in437                `Chain.input_keys` except for inputs that will be set by the chain's438                memory.439            return_only_outputs: Whether to return only outputs in the440                response. If `True`, only new keys generated by this chain will be441                returned. If `False`, both input keys and new keys generated by this442                chain will be returned.443            callbacks: Callbacks to use for this chain run. These will be called in444                addition to callbacks passed to the chain during construction, but only445                these runtime callbacks will propagate to calls to other objects.446            tags: List of string tags to pass to all callbacks. These will be passed in447                addition to tags passed to the chain during construction, but only448                these runtime tags will propagate to calls to other objects.449            metadata: Optional metadata associated with the chain.450            run_name: Optional name for this run of the chain.451            include_run_info: Whether to include run info in the response. Defaults452                to False.453454        Returns:455            A dict of named outputs. Should contain all outputs specified in456                `Chain.output_keys`.457        """458        config = {459            "callbacks": callbacks,460            "tags": tags,461            "metadata": metadata,462            "run_name": run_name,463        }464        return await self.ainvoke(465            inputs,466            cast("RunnableConfig", {k: v for k, v in config.items() if k is not None}),467            return_only_outputs=return_only_outputs,468            include_run_info=include_run_info,469        )470471    def prep_outputs(472        self,473        inputs: dict[str, str],474        outputs: dict[str, str],475        return_only_outputs: bool = False,  # noqa: FBT001,FBT002476    ) -> dict[str, str]:477        """Validate and prepare chain outputs, and save info about this run to memory.478479        Args:480            inputs: Dictionary of chain inputs, including any inputs added by chain481                memory.482            outputs: Dictionary of initial chain outputs.483            return_only_outputs: Whether to only return the chain outputs. If `False`,484                inputs are also added to the final outputs.485486        Returns:487            A dict of the final chain outputs.488        """489        self._validate_outputs(outputs)490        if self.memory is not None:491            self.memory.save_context(inputs, outputs)492        if return_only_outputs:493            return outputs494        return {**inputs, **outputs}495496    async def aprep_outputs(497        self,498        inputs: dict[str, str],499        outputs: dict[str, str],500        return_only_outputs: bool = False,  # noqa: FBT001,FBT002501    ) -> dict[str, str]:502        """Validate and prepare chain outputs, and save info about this run to memory.503504        Args:505            inputs: Dictionary of chain inputs, including any inputs added by chain506                memory.507            outputs: Dictionary of initial chain outputs.508            return_only_outputs: Whether to only return the chain outputs. If `False`,509                inputs are also added to the final outputs.510511        Returns:512            A dict of the final chain outputs.513        """514        self._validate_outputs(outputs)515        if self.memory is not None:516            await self.memory.asave_context(inputs, outputs)517        if return_only_outputs:518            return outputs519        return {**inputs, **outputs}520521    def prep_inputs(self, inputs: dict[str, Any] | Any) -> dict[str, str]:522        """Prepare chain inputs, including adding inputs from memory.523524        Args:525            inputs: Dictionary of raw inputs, or single input if chain expects526                only one param. Should contain all inputs specified in527                `Chain.input_keys` except for inputs that will be set by the chain's528                memory.529530        Returns:531            A dictionary of all inputs, including those added by the chain's memory.532        """533        if not isinstance(inputs, dict):534            _input_keys = set(self.input_keys)535            if self.memory is not None:536                # If there are multiple input keys, but some get set by memory so that537                # only one is not set, we can still figure out which key it is.538                _input_keys = _input_keys.difference(self.memory.memory_variables)539            inputs = {next(iter(_input_keys)): inputs}540        if self.memory is not None:541            external_context = self.memory.load_memory_variables(inputs)542            inputs = dict(inputs, **external_context)543        return inputs544545    async def aprep_inputs(self, inputs: dict[str, Any] | Any) -> dict[str, str]:546        """Prepare chain inputs, including adding inputs from memory.547548        Args:549            inputs: Dictionary of raw inputs, or single input if chain expects550                only one param. Should contain all inputs specified in551                `Chain.input_keys` except for inputs that will be set by the chain's552                memory.553554        Returns:555            A dictionary of all inputs, including those added by the chain's memory.556        """557        if not isinstance(inputs, dict):558            _input_keys = set(self.input_keys)559            if self.memory is not None:560                # If there are multiple input keys, but some get set by memory so that561                # only one is not set, we can still figure out which key it is.562                _input_keys = _input_keys.difference(self.memory.memory_variables)563            inputs = {next(iter(_input_keys)): inputs}564        if self.memory is not None:565            external_context = await self.memory.aload_memory_variables(inputs)566            inputs = dict(inputs, **external_context)567        return inputs568569    @property570    def _run_output_key(self) -> str:571        if len(self.output_keys) != 1:572            msg = (573                f"`run` not supported when there is not exactly "574                f"one output key. Got {self.output_keys}."575            )576            raise ValueError(msg)577        return self.output_keys[0]578579    @deprecated("0.1.0", alternative="invoke", removal="2.0.0")580    def run(581        self,582        *args: Any,583        callbacks: Callbacks = None,584        tags: list[str] | None = None,585        metadata: dict[str, Any] | None = None,586        **kwargs: Any,587    ) -> Any:588        """Convenience method for executing chain.589590        The main difference between this method and `Chain.__call__` is that this591        method expects inputs to be passed directly in as positional arguments or592        keyword arguments, whereas `Chain.__call__` expects a single input dictionary593        with all the inputs594595        Args:596            *args: If the chain expects a single input, it can be passed in as the597                sole positional argument.598            callbacks: Callbacks to use for this chain run. These will be called in599                addition to callbacks passed to the chain during construction, but only600                these runtime callbacks will propagate to calls to other objects.601            tags: List of string tags to pass to all callbacks. These will be passed in602                addition to tags passed to the chain during construction, but only603                these runtime tags will propagate to calls to other objects.604            metadata: Optional metadata associated with the chain.605            **kwargs: If the chain expects multiple inputs, they can be passed in606                directly as keyword arguments.607608        Returns:609            The chain output.610611        Example:612            ```python613            # Suppose we have a single-input chain that takes a 'question' string:614            chain.run("What's the temperature in Boise, Idaho?")615            # -> "The temperature in Boise is..."616617            # Suppose we have a multi-input chain that takes a 'question' string618            # and 'context' string:619            question = "What's the temperature in Boise, Idaho?"620            context = "Weather report for Boise, Idaho on 07/03/23..."621            chain.run(question=question, context=context)622            # -> "The temperature in Boise is..."623            ```624        """625        # Run at start to make sure this is possible/defined626        _output_key = self._run_output_key627628        if args and not kwargs:629            if len(args) != 1:630                msg = "`run` supports only one positional argument."631                raise ValueError(msg)632            return self(args[0], callbacks=callbacks, tags=tags, metadata=metadata)[633                _output_key634            ]635636        if kwargs and not args:637            return self(kwargs, callbacks=callbacks, tags=tags, metadata=metadata)[638                _output_key639            ]640641        if not kwargs and not args:642            msg = (643                "`run` supported with either positional arguments or keyword arguments,"644                " but none were provided."645            )646            raise ValueError(msg)647        msg = (648            f"`run` supported with either positional arguments or keyword arguments"649            f" but not both. Got args: {args} and kwargs: {kwargs}."650        )651        raise ValueError(msg)652653    @deprecated("0.1.0", alternative="ainvoke", removal="2.0.0")654    async def arun(655        self,656        *args: Any,657        callbacks: Callbacks = None,658        tags: list[str] | None = None,659        metadata: dict[str, Any] | None = None,660        **kwargs: Any,661    ) -> Any:662        """Convenience method for executing chain.663664        The main difference between this method and `Chain.__call__` is that this665        method expects inputs to be passed directly in as positional arguments or666        keyword arguments, whereas `Chain.__call__` expects a single input dictionary667        with all the inputs668669670        Args:671            *args: If the chain expects a single input, it can be passed in as the672                sole positional argument.673            callbacks: Callbacks to use for this chain run. These will be called in674                addition to callbacks passed to the chain during construction, but only675                these runtime callbacks will propagate to calls to other objects.676            tags: List of string tags to pass to all callbacks. These will be passed in677                addition to tags passed to the chain during construction, but only678                these runtime tags will propagate to calls to other objects.679            metadata: Optional metadata associated with the chain.680            **kwargs: If the chain expects multiple inputs, they can be passed in681                directly as keyword arguments.682683        Returns:684            The chain output.685686        Example:687            ```python688            # Suppose we have a single-input chain that takes a 'question' string:689            await chain.arun("What's the temperature in Boise, Idaho?")690            # -> "The temperature in Boise is..."691692            # Suppose we have a multi-input chain that takes a 'question' string693            # and 'context' string:694            question = "What's the temperature in Boise, Idaho?"695            context = "Weather report for Boise, Idaho on 07/03/23..."696            await chain.arun(question=question, context=context)697            # -> "The temperature in Boise is..."698            ```699        """700        if len(self.output_keys) != 1:701            msg = (702                f"`run` not supported when there is not exactly "703                f"one output key. Got {self.output_keys}."704            )705            raise ValueError(msg)706        if args and not kwargs:707            if len(args) != 1:708                msg = "`run` supports only one positional argument."709                raise ValueError(msg)710            return (711                await self.acall(712                    args[0],713                    callbacks=callbacks,714                    tags=tags,715                    metadata=metadata,716                )717            )[self.output_keys[0]]718719        if kwargs and not args:720            return (721                await self.acall(722                    kwargs,723                    callbacks=callbacks,724                    tags=tags,725                    metadata=metadata,726                )727            )[self.output_keys[0]]728729        msg = (730            f"`run` supported with either positional arguments or keyword arguments"731            f" but not both. Got args: {args} and kwargs: {kwargs}."732        )733        raise ValueError(msg)734735    def model_dump(self, **kwargs: Any) -> dict:736        """Dictionary representation of chain.737738        Expects `Chain._chain_type` property to be implemented and for memory to be739            null.740741        Args:742            **kwargs: Keyword arguments passed to default743                `pydantic.BaseModel.model_dump` method.744745        Returns:746            A dictionary representation of the chain.747748        Example:749            ```python750            chain.model_dump(exclude_unset=True)751            # -> {"_type": "foo", "verbose": False, ...}752            ```753        """754        _dict = super().model_dump(**kwargs)755        with contextlib.suppress(NotImplementedError):756            _dict["_type"] = self._chain_type757        return _dict758759    def save(self, file_path: Path | str) -> None:760        """Save the chain.761762        Expects `Chain._chain_type` property to be implemented and for memory to be763            null.764765        Args:766            file_path: Path to file to save the chain to.767768        Example:769            ```python770            chain.save(file_path="path/chain.yaml")771            ```772        """773        if self.memory is not None:774            msg = "Saving of memory is not yet supported."775            raise ValueError(msg)776777        # Fetch dictionary to save778        chain_dict = self.model_dump()779        if "_type" not in chain_dict:780            msg = f"Chain {self} does not support saving."781            raise NotImplementedError(msg)782783        # Convert file to Path object.784        save_path = Path(file_path) if isinstance(file_path, str) else file_path785786        directory_path = save_path.parent787        directory_path.mkdir(parents=True, exist_ok=True)788789        if save_path.suffix == ".json":790            with save_path.open("w") as f:791                json.dump(chain_dict, f, indent=4)792        elif save_path.suffix.endswith((".yaml", ".yml")):793            with save_path.open("w") as f:794                yaml.dump(chain_dict, f, default_flow_style=False)795        else:796            msg = f"{save_path} must be json or yaml"797            raise ValueError(msg)798799    @deprecated("0.1.0", alternative="batch", removal="2.0.0")800    def apply(801        self,802        input_list: list[builtins.dict[str, Any]],803        callbacks: Callbacks = None,804    ) -> list[builtins.dict[str, str]]:805        """Call the chain on all inputs in the list."""806        return [self(inputs, callbacks=callbacks) for inputs in input_list]

Code quality findings 19

Avoid global variables; use function parameters or class attributes for better scope management
global-variable
will be printed to the console. Defaults to the global `verbose` value,
Ensure functions have docstrings for documentation
missing-docstring
def get_input_schema(
Ensure functions have docstrings for documentation
missing-docstring
def get_output_schema(
Ensure functions have docstrings for documentation
missing-docstring
def invoke(
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Ensure functions have docstrings for documentation
missing-docstring
async def ainvoke(
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Ensure functions have docstrings for documentation
missing-docstring
def set_verbose(
Avoid global variables; use function parameters or class attributes for better scope management
global-variable
Defaults to the global setting if not specified by the user.
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(inputs, dict):
Ensure functions have docstrings for documentation
missing-docstring
async def acall(
Ensure functions have docstrings for documentation
missing-docstring
def prep_outputs(
Ensure functions have docstrings for documentation
missing-docstring
async def aprep_outputs(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(inputs, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if not isinstance(inputs, dict):
Ensure functions have docstrings for documentation
missing-docstring
def run(
Ensure functions have docstrings for documentation
missing-docstring
async def arun(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
save_path = Path(file_path) if isinstance(file_path, str) else file_path
Ensure functions have docstrings for documentation
missing-docstring
def apply(

Get this view in your editor

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