libs/langchain/langchain_classic/agents/agent.py PYTHON 1,793 lines View on github.com → Search inside
1"""Chain that takes in an input and produces an action and action input."""23from __future__ import annotations45import asyncio6import builtins7import contextlib8import json9import logging10import time11from abc import abstractmethod12from collections.abc import AsyncIterator, Callable, Iterator, Sequence13from pathlib import Path14from typing import (15    Any,16    cast,17)1819import yaml20from langchain_core._api import deprecated21from langchain_core.agents import AgentAction, AgentFinish, AgentStep22from langchain_core.callbacks import (23    AsyncCallbackManagerForChainRun,24    AsyncCallbackManagerForToolRun,25    BaseCallbackManager,26    CallbackManagerForChainRun,27    CallbackManagerForToolRun,28    Callbacks,29)30from langchain_core.exceptions import OutputParserException31from langchain_core.language_models import BaseLanguageModel32from langchain_core.messages import BaseMessage33from langchain_core.output_parsers import BaseOutputParser34from langchain_core.prompts import BasePromptTemplate35from langchain_core.prompts.few_shot import FewShotPromptTemplate36from langchain_core.prompts.prompt import PromptTemplate37from langchain_core.runnables import Runnable, RunnableConfig, ensure_config38from langchain_core.runnables.utils import AddableDict39from langchain_core.tools import BaseTool40from langchain_core.utils.input import get_color_mapping41from pydantic import BaseModel, ConfigDict, model_validator42from typing_extensions import Self, override4344from langchain_classic._api.deprecation import AGENT_DEPRECATION_WARNING45from langchain_classic.agents.agent_iterator import AgentExecutorIterator46from langchain_classic.agents.agent_types import AgentType47from langchain_classic.agents.tools import InvalidTool48from langchain_classic.chains.base import Chain49from langchain_classic.chains.llm import LLMChain50from langchain_classic.utilities.asyncio import asyncio_timeout5152logger = logging.getLogger(__name__)535455class BaseSingleActionAgent(BaseModel):56    """Base Single Action Agent class."""5758    @property59    def return_values(self) -> list[str]:60        """Return values of the agent."""61        return ["output"]6263    def get_allowed_tools(self) -> list[str] | None:64        """Get allowed tools."""65        return None6667    @abstractmethod68    def plan(69        self,70        intermediate_steps: list[tuple[AgentAction, str]],71        callbacks: Callbacks = None,72        **kwargs: Any,73    ) -> AgentAction | AgentFinish:74        """Given input, decided what to do.7576        Args:77            intermediate_steps: Steps the LLM has taken to date,78                along with observations.79            callbacks: Callbacks to run.80            **kwargs: User inputs.8182        Returns:83            Action specifying what tool to use.84        """8586    @abstractmethod87    async def aplan(88        self,89        intermediate_steps: list[tuple[AgentAction, str]],90        callbacks: Callbacks = None,91        **kwargs: Any,92    ) -> AgentAction | AgentFinish:93        """Async given input, decided what to do.9495        Args:96            intermediate_steps: Steps the LLM has taken to date,97                along with observations.98            callbacks: Callbacks to run.99            **kwargs: User inputs.100101        Returns:102            Action specifying what tool to use.103        """104105    @property106    @abstractmethod107    def input_keys(self) -> list[str]:108        """Return the input keys."""109110    def return_stopped_response(111        self,112        early_stopping_method: str,113        intermediate_steps: list[tuple[AgentAction, str]],  # noqa: ARG002114        **_: Any,115    ) -> AgentFinish:116        """Return response when agent has been stopped due to max iterations.117118        Args:119            early_stopping_method: Method to use for early stopping.120            intermediate_steps: Steps the LLM has taken to date,121                along with observations.122123        Returns:124            Agent finish object.125126        Raises:127            ValueError: If `early_stopping_method` is not supported.128        """129        if early_stopping_method == "force":130            # `force` just returns a constant string131            return AgentFinish(132                {"output": "Agent stopped due to iteration limit or time limit."},133                "",134            )135        msg = f"Got unsupported early_stopping_method `{early_stopping_method}`"136        raise ValueError(msg)137138    @classmethod139    def from_llm_and_tools(140        cls,141        llm: BaseLanguageModel,142        tools: Sequence[BaseTool],143        callback_manager: BaseCallbackManager | None = None,144        **kwargs: Any,145    ) -> BaseSingleActionAgent:146        """Construct an agent from an LLM and tools.147148        Args:149            llm: Language model to use.150            tools: Tools to use.151            callback_manager: Callback manager to use.152            kwargs: Additional arguments.153154        Returns:155            Agent object.156        """157        raise NotImplementedError158159    @property160    def _agent_type(self) -> str:161        """Return Identifier of an agent type."""162        raise NotImplementedError163164    @override165    def dict(self, **kwargs: Any) -> builtins.dict:166        """Return dictionary representation of agent.167168        Returns:169            Dictionary representation of agent.170        """171        _dict = super().model_dump()172        try:173            _type = self._agent_type174        except NotImplementedError:175            _type = None176        if isinstance(_type, AgentType):177            _dict["_type"] = str(_type.value)178        elif _type is not None:179            _dict["_type"] = _type180        return _dict181182    def save(self, file_path: Path | str) -> None:183        """Save the agent.184185        Args:186            file_path: Path to file to save the agent to.187188        Example:189        ```python190        # If working with agent executor191        agent.agent.save(file_path="path/agent.yaml")192        ```193        """194        # Convert file to Path object.195        save_path = Path(file_path) if isinstance(file_path, str) else file_path196197        directory_path = save_path.parent198        directory_path.mkdir(parents=True, exist_ok=True)199200        # Fetch dictionary to save201        agent_dict = self.dict()202        if "_type" not in agent_dict:203            msg = f"Agent {self} does not support saving"204            raise NotImplementedError(msg)205206        if save_path.suffix == ".json":207            with save_path.open("w") as f:208                json.dump(agent_dict, f, indent=4)209        elif save_path.suffix.endswith((".yaml", ".yml")):210            with save_path.open("w") as f:211                yaml.dump(agent_dict, f, default_flow_style=False)212        else:213            msg = f"{save_path} must be json or yaml"214            raise ValueError(msg)215216    def tool_run_logging_kwargs(self) -> builtins.dict:217        """Return logging kwargs for tool run."""218        return {}219220221class BaseMultiActionAgent(BaseModel):222    """Base Multi Action Agent class."""223224    @property225    def return_values(self) -> list[str]:226        """Return values of the agent."""227        return ["output"]228229    def get_allowed_tools(self) -> list[str] | None:230        """Get allowed tools.231232        Returns:233            Allowed tools.234        """235        return None236237    @abstractmethod238    def plan(239        self,240        intermediate_steps: list[tuple[AgentAction, str]],241        callbacks: Callbacks = None,242        **kwargs: Any,243    ) -> list[AgentAction] | AgentFinish:244        """Given input, decided what to do.245246        Args:247            intermediate_steps: Steps the LLM has taken to date,248                along with the observations.249            callbacks: Callbacks to run.250            **kwargs: User inputs.251252        Returns:253            Actions specifying what tool to use.254        """255256    @abstractmethod257    async def aplan(258        self,259        intermediate_steps: list[tuple[AgentAction, str]],260        callbacks: Callbacks = None,261        **kwargs: Any,262    ) -> list[AgentAction] | AgentFinish:263        """Async given input, decided what to do.264265        Args:266            intermediate_steps: Steps the LLM has taken to date,267                along with the observations.268            callbacks: Callbacks to run.269            **kwargs: User inputs.270271        Returns:272            Actions specifying what tool to use.273        """274275    @property276    @abstractmethod277    def input_keys(self) -> list[str]:278        """Return the input keys."""279280    def return_stopped_response(281        self,282        early_stopping_method: str,283        intermediate_steps: list[tuple[AgentAction, str]],  # noqa: ARG002284        **_: Any,285    ) -> AgentFinish:286        """Return response when agent has been stopped due to max iterations.287288        Args:289            early_stopping_method: Method to use for early stopping.290            intermediate_steps: Steps the LLM has taken to date,291                along with observations.292293        Returns:294            Agent finish object.295296        Raises:297            ValueError: If `early_stopping_method` is not supported.298        """299        if early_stopping_method == "force":300            # `force` just returns a constant string301            return AgentFinish({"output": "Agent stopped due to max iterations."}, "")302        msg = f"Got unsupported early_stopping_method `{early_stopping_method}`"303        raise ValueError(msg)304305    @property306    def _agent_type(self) -> str:307        """Return Identifier of an agent type."""308        raise NotImplementedError309310    @override311    def dict(self, **kwargs: Any) -> builtins.dict:312        """Return dictionary representation of agent."""313        _dict = super().model_dump()314        with contextlib.suppress(NotImplementedError):315            _dict["_type"] = str(self._agent_type)316        return _dict317318    def save(self, file_path: Path | str) -> None:319        """Save the agent.320321        Args:322            file_path: Path to file to save the agent to.323324        Raises:325            NotImplementedError: If agent does not support saving.326            ValueError: If `file_path` is not json or yaml.327328        Example:329        ```python330        # If working with agent executor331        agent.agent.save(file_path="path/agent.yaml")332        ```333        """334        # Convert file to Path object.335        save_path = Path(file_path) if isinstance(file_path, str) else file_path336337        # Fetch dictionary to save338        agent_dict = self.dict()339        if "_type" not in agent_dict:340            msg = f"Agent {self} does not support saving."341            raise NotImplementedError(msg)342343        directory_path = save_path.parent344        directory_path.mkdir(parents=True, exist_ok=True)345346        if save_path.suffix == ".json":347            with save_path.open("w") as f:348                json.dump(agent_dict, f, indent=4)349        elif save_path.suffix.endswith((".yaml", ".yml")):350            with save_path.open("w") as f:351                yaml.dump(agent_dict, f, default_flow_style=False)352        else:353            msg = f"{save_path} must be json or yaml"354            raise ValueError(msg)355356    def tool_run_logging_kwargs(self) -> builtins.dict:357        """Return logging kwargs for tool run."""358        return {}359360361class AgentOutputParser(BaseOutputParser[AgentAction | AgentFinish]):362    """Base class for parsing agent output into agent action/finish."""363364    @abstractmethod365    def parse(self, text: str) -> AgentAction | AgentFinish:366        """Parse text into agent action/finish."""367368369class MultiActionAgentOutputParser(370    BaseOutputParser[list[AgentAction] | AgentFinish],371):372    """Base class for parsing agent output into agent actions/finish.373374    This is used for agents that can return multiple actions.375    """376377    @abstractmethod378    def parse(self, text: str) -> list[AgentAction] | AgentFinish:379        """Parse text into agent actions/finish.380381        Args:382            text: Text to parse.383384        Returns:385            List of agent actions or agent finish.386        """387388389class RunnableAgent(BaseSingleActionAgent):390    """Agent powered by Runnables."""391392    runnable: Runnable[dict, AgentAction | AgentFinish]393    """Runnable to call to get agent action."""394    input_keys_arg: list[str] = []395    return_keys_arg: list[str] = []396    stream_runnable: bool = True397    """Whether to stream from the runnable or not.398399    If `True` then underlying LLM is invoked in a streaming fashion to make it possible400        to get access to the individual LLM tokens when using stream_log with the401        `AgentExecutor`. If `False` then LLM is invoked in a non-streaming fashion and402        individual LLM tokens will not be available in stream_log.403    """404405    model_config = ConfigDict(406        arbitrary_types_allowed=True,407    )408409    @property410    def return_values(self) -> list[str]:411        """Return values of the agent."""412        return self.return_keys_arg413414    @property415    def input_keys(self) -> list[str]:416        """Return the input keys."""417        return self.input_keys_arg418419    def plan(420        self,421        intermediate_steps: list[tuple[AgentAction, str]],422        callbacks: Callbacks = None,423        **kwargs: Any,424    ) -> AgentAction | AgentFinish:425        """Based on past history and current inputs, decide what to do.426427        Args:428            intermediate_steps: Steps the LLM has taken to date,429                along with the observations.430            callbacks: Callbacks to run.431            **kwargs: User inputs.432433        Returns:434            Action specifying what tool to use.435        """436        inputs = {**kwargs, "intermediate_steps": intermediate_steps}437        final_output: Any = None438        if self.stream_runnable:439            # Use streaming to make sure that the underlying LLM is invoked in a440            # streaming441            # fashion to make it possible to get access to the individual LLM tokens442            # when using stream_log with the AgentExecutor.443            # Because the response from the plan is not a generator, we need to444            # accumulate the output into final output and return that.445            for chunk in self.runnable.stream(inputs, config={"callbacks": callbacks}):446                if final_output is None:447                    final_output = chunk448                else:449                    final_output += chunk450        else:451            final_output = self.runnable.invoke(inputs, config={"callbacks": callbacks})452453        return final_output454455    async def aplan(456        self,457        intermediate_steps: list[tuple[AgentAction, str]],458        callbacks: Callbacks = None,459        **kwargs: Any,460    ) -> AgentAction | AgentFinish:461        """Async based on past history and current inputs, decide what to do.462463        Args:464            intermediate_steps: Steps the LLM has taken to date,465                along with observations.466            callbacks: Callbacks to run.467            **kwargs: User inputs.468469        Returns:470            Action specifying what tool to use.471        """472        inputs = {**kwargs, "intermediate_steps": intermediate_steps}473        final_output: Any = None474        if self.stream_runnable:475            # Use streaming to make sure that the underlying LLM is invoked in a476            # streaming477            # fashion to make it possible to get access to the individual LLM tokens478            # when using stream_log with the AgentExecutor.479            # Because the response from the plan is not a generator, we need to480            # accumulate the output into final output and return that.481            async for chunk in self.runnable.astream(482                inputs,483                config={"callbacks": callbacks},484            ):485                if final_output is None:486                    final_output = chunk487                else:488                    final_output += chunk489        else:490            final_output = await self.runnable.ainvoke(491                inputs,492                config={"callbacks": callbacks},493            )494        return final_output495496497class RunnableMultiActionAgent(BaseMultiActionAgent):498    """Agent powered by Runnables."""499500    runnable: Runnable[dict, list[AgentAction] | AgentFinish]501    """Runnable to call to get agent actions."""502    input_keys_arg: list[str] = []503    return_keys_arg: list[str] = []504    stream_runnable: bool = True505    """Whether to stream from the runnable or not.506507    If `True` then underlying LLM is invoked in a streaming fashion to make it possible508        to get access to the individual LLM tokens when using stream_log with the509        `AgentExecutor`. If `False` then LLM is invoked in a non-streaming fashion and510        individual LLM tokens will not be available in stream_log.511    """512513    model_config = ConfigDict(514        arbitrary_types_allowed=True,515    )516517    @property518    def return_values(self) -> list[str]:519        """Return values of the agent."""520        return self.return_keys_arg521522    @property523    def input_keys(self) -> list[str]:524        """Return the input keys.525526        Returns:527            List of input keys.528        """529        return self.input_keys_arg530531    def plan(532        self,533        intermediate_steps: list[tuple[AgentAction, str]],534        callbacks: Callbacks = None,535        **kwargs: Any,536    ) -> list[AgentAction] | AgentFinish:537        """Based on past history and current inputs, decide what to do.538539        Args:540            intermediate_steps: Steps the LLM has taken to date,541                along with the observations.542            callbacks: Callbacks to run.543            **kwargs: User inputs.544545        Returns:546            Action specifying what tool to use.547        """548        inputs = {**kwargs, "intermediate_steps": intermediate_steps}549        final_output: Any = None550        if self.stream_runnable:551            # Use streaming to make sure that the underlying LLM is invoked in a552            # streaming553            # fashion to make it possible to get access to the individual LLM tokens554            # when using stream_log with the AgentExecutor.555            # Because the response from the plan is not a generator, we need to556            # accumulate the output into final output and return that.557            for chunk in self.runnable.stream(inputs, config={"callbacks": callbacks}):558                if final_output is None:559                    final_output = chunk560                else:561                    final_output += chunk562        else:563            final_output = self.runnable.invoke(inputs, config={"callbacks": callbacks})564565        return final_output566567    async def aplan(568        self,569        intermediate_steps: list[tuple[AgentAction, str]],570        callbacks: Callbacks = None,571        **kwargs: Any,572    ) -> list[AgentAction] | AgentFinish:573        """Async based on past history and current inputs, decide what to do.574575        Args:576            intermediate_steps: Steps the LLM has taken to date,577                along with observations.578            callbacks: Callbacks to run.579            **kwargs: User inputs.580581        Returns:582            Action specifying what tool to use.583        """584        inputs = {**kwargs, "intermediate_steps": intermediate_steps}585        final_output: Any = None586        if self.stream_runnable:587            # Use streaming to make sure that the underlying LLM is invoked in a588            # streaming589            # fashion to make it possible to get access to the individual LLM tokens590            # when using stream_log with the AgentExecutor.591            # Because the response from the plan is not a generator, we need to592            # accumulate the output into final output and return that.593            async for chunk in self.runnable.astream(594                inputs,595                config={"callbacks": callbacks},596            ):597                if final_output is None:598                    final_output = chunk599                else:600                    final_output += chunk601        else:602            final_output = await self.runnable.ainvoke(603                inputs,604                config={"callbacks": callbacks},605            )606607        return final_output608609610@deprecated(611    "0.1.0",612    message=AGENT_DEPRECATION_WARNING,613    removal="2.0.0",614)615class LLMSingleActionAgent(BaseSingleActionAgent):616    """Base class for single action agents."""617618    llm_chain: LLMChain619    """LLMChain to use for agent."""620    output_parser: AgentOutputParser621    """Output parser to use for agent."""622    stop: list[str]623    """List of strings to stop on."""624625    @property626    def input_keys(self) -> list[str]:627        """Return the input keys.628629        Returns:630            List of input keys.631        """632        return list(set(self.llm_chain.input_keys) - {"intermediate_steps"})633634    @override635    def dict(self, **kwargs: Any) -> builtins.dict:636        """Return dictionary representation of agent."""637        _dict = super().dict()638        del _dict["output_parser"]639        return _dict640641    def plan(642        self,643        intermediate_steps: list[tuple[AgentAction, str]],644        callbacks: Callbacks = None,645        **kwargs: Any,646    ) -> AgentAction | AgentFinish:647        """Given input, decided what to do.648649        Args:650            intermediate_steps: Steps the LLM has taken to date,651                along with the observations.652            callbacks: Callbacks to run.653            **kwargs: User inputs.654655        Returns:656            Action specifying what tool to use.657        """658        output = self.llm_chain.run(659            intermediate_steps=intermediate_steps,660            stop=self.stop,661            callbacks=callbacks,662            **kwargs,663        )664        return self.output_parser.parse(output)665666    async def aplan(667        self,668        intermediate_steps: list[tuple[AgentAction, str]],669        callbacks: Callbacks = None,670        **kwargs: Any,671    ) -> AgentAction | AgentFinish:672        """Async given input, decided what to do.673674        Args:675            intermediate_steps: Steps the LLM has taken to date,676                along with observations.677            callbacks: Callbacks to run.678            **kwargs: User inputs.679680        Returns:681            Action specifying what tool to use.682        """683        output = await self.llm_chain.arun(684            intermediate_steps=intermediate_steps,685            stop=self.stop,686            callbacks=callbacks,687            **kwargs,688        )689        return self.output_parser.parse(output)690691    def tool_run_logging_kwargs(self) -> builtins.dict:692        """Return logging kwargs for tool run."""693        return {694            "llm_prefix": "",695            "observation_prefix": "" if len(self.stop) == 0 else self.stop[0],696        }697698699@deprecated(700    "0.1.0",701    message=AGENT_DEPRECATION_WARNING,702    removal="2.0.0",703)704class Agent(BaseSingleActionAgent):705    """Agent that calls the language model and deciding the action.706707    This is driven by a LLMChain. The prompt in the LLMChain MUST include708    a variable called "agent_scratchpad" where the agent can put its709    intermediary work.710    """711712    llm_chain: LLMChain713    """LLMChain to use for agent."""714    output_parser: AgentOutputParser715    """Output parser to use for agent."""716    allowed_tools: list[str] | None = None717    """Allowed tools for the agent. If `None`, all tools are allowed."""718719    @override720    def dict(self, **kwargs: Any) -> builtins.dict:721        """Return dictionary representation of agent."""722        _dict = super().dict()723        del _dict["output_parser"]724        return _dict725726    def get_allowed_tools(self) -> list[str] | None:727        """Get allowed tools."""728        return self.allowed_tools729730    @property731    def return_values(self) -> list[str]:732        """Return values of the agent."""733        return ["output"]734735    @property736    def _stop(self) -> list[str]:737        return [738            f"\n{self.observation_prefix.rstrip()}",739            f"\n\t{self.observation_prefix.rstrip()}",740        ]741742    def _construct_scratchpad(743        self,744        intermediate_steps: list[tuple[AgentAction, str]],745    ) -> str | list[BaseMessage]:746        """Construct the scratchpad that lets the agent continue its thought process."""747        thoughts = ""748        for action, observation in intermediate_steps:749            thoughts += action.log750            thoughts += f"\n{self.observation_prefix}{observation}\n{self.llm_prefix}"751        return thoughts752753    def plan(754        self,755        intermediate_steps: list[tuple[AgentAction, str]],756        callbacks: Callbacks = None,757        **kwargs: Any,758    ) -> AgentAction | AgentFinish:759        """Given input, decided what to do.760761        Args:762            intermediate_steps: Steps the LLM has taken to date,763                along with observations.764            callbacks: Callbacks to run.765            **kwargs: User inputs.766767        Returns:768            Action specifying what tool to use.769        """770        full_inputs = self.get_full_inputs(intermediate_steps, **kwargs)771        full_output = self.llm_chain.predict(callbacks=callbacks, **full_inputs)772        return self.output_parser.parse(full_output)773774    async def aplan(775        self,776        intermediate_steps: list[tuple[AgentAction, str]],777        callbacks: Callbacks = None,778        **kwargs: Any,779    ) -> AgentAction | AgentFinish:780        """Async given input, decided what to do.781782        Args:783            intermediate_steps: Steps the LLM has taken to date,784                along with observations.785            callbacks: Callbacks to run.786            **kwargs: User inputs.787788        Returns:789            Action specifying what tool to use.790        """791        full_inputs = self.get_full_inputs(intermediate_steps, **kwargs)792        full_output = await self.llm_chain.apredict(callbacks=callbacks, **full_inputs)793        return await self.output_parser.aparse(full_output)794795    def get_full_inputs(796        self,797        intermediate_steps: list[tuple[AgentAction, str]],798        **kwargs: Any,799    ) -> builtins.dict[str, Any]:800        """Create the full inputs for the LLMChain from intermediate steps.801802        Args:803            intermediate_steps: Steps the LLM has taken to date,804                along with observations.805            **kwargs: User inputs.806807        Returns:808            Full inputs for the LLMChain.809        """810        thoughts = self._construct_scratchpad(intermediate_steps)811        new_inputs = {"agent_scratchpad": thoughts, "stop": self._stop}812        return {**kwargs, **new_inputs}813814    @property815    def input_keys(self) -> list[str]:816        """Return the input keys."""817        return list(set(self.llm_chain.input_keys) - {"agent_scratchpad"})818819    @model_validator(mode="after")820    def validate_prompt(self) -> Self:821        """Validate that prompt matches format.822823        Args:824            values: Values to validate.825826        Returns:827            Validated values.828829        Raises:830            ValueError: If `agent_scratchpad` is not in prompt.input_variables831                and prompt is not a FewShotPromptTemplate or a PromptTemplate.832        """833        prompt = self.llm_chain.prompt834        if "agent_scratchpad" not in prompt.input_variables:835            logger.warning(836                "`agent_scratchpad` should be a variable in prompt.input_variables."837                " Did not find it, so adding it at the end.",838            )839            prompt.input_variables.append("agent_scratchpad")840            if isinstance(prompt, PromptTemplate):841                prompt.template += "\n{agent_scratchpad}"842            elif isinstance(prompt, FewShotPromptTemplate):843                prompt.suffix += "\n{agent_scratchpad}"844            else:845                msg = f"Got unexpected prompt type {type(prompt)}"846                raise ValueError(msg)847        return self848849    @property850    @abstractmethod851    def observation_prefix(self) -> str:852        """Prefix to append the observation with."""853854    @property855    @abstractmethod856    def llm_prefix(self) -> str:857        """Prefix to append the LLM call with."""858859    @classmethod860    @abstractmethod861    def create_prompt(cls, tools: Sequence[BaseTool]) -> BasePromptTemplate:862        """Create a prompt for this class.863864        Args:865            tools: Tools to use.866867        Returns:868            Prompt template.869        """870871    @classmethod872    def _validate_tools(cls, tools: Sequence[BaseTool]) -> None:873        """Validate that appropriate tools are passed in.874875        Args:876            tools: Tools to use.877        """878879    @classmethod880    @abstractmethod881    def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser:882        """Get default output parser for this class."""883884    @classmethod885    def from_llm_and_tools(886        cls,887        llm: BaseLanguageModel,888        tools: Sequence[BaseTool],889        callback_manager: BaseCallbackManager | None = None,890        output_parser: AgentOutputParser | None = None,891        **kwargs: Any,892    ) -> Agent:893        """Construct an agent from an LLM and tools.894895        Args:896            llm: Language model to use.897            tools: Tools to use.898            callback_manager: Callback manager to use.899            output_parser: Output parser to use.900            kwargs: Additional arguments.901902        Returns:903            Agent object.904        """905        cls._validate_tools(tools)906        llm_chain = LLMChain(907            llm=llm,908            prompt=cls.create_prompt(tools),909            callback_manager=callback_manager,910        )911        tool_names = [tool.name for tool in tools]912        _output_parser = output_parser or cls._get_default_output_parser()913        return cls(914            llm_chain=llm_chain,915            allowed_tools=tool_names,916            output_parser=_output_parser,917            **kwargs,918        )919920    def return_stopped_response(921        self,922        early_stopping_method: str,923        intermediate_steps: list[tuple[AgentAction, str]],924        **kwargs: Any,925    ) -> AgentFinish:926        """Return response when agent has been stopped due to max iterations.927928        Args:929            early_stopping_method: Method to use for early stopping.930            intermediate_steps: Steps the LLM has taken to date,931                along with observations.932            **kwargs: User inputs.933934        Returns:935            Agent finish object.936937        Raises:938            ValueError: If `early_stopping_method` is not in ['force', 'generate'].939        """940        if early_stopping_method == "force":941            # `force` just returns a constant string942            return AgentFinish(943                {"output": "Agent stopped due to iteration limit or time limit."},944                "",945            )946        if early_stopping_method == "generate":947            # Generate does one final forward pass948            thoughts = ""949            for action, observation in intermediate_steps:950                thoughts += action.log951                thoughts += (952                    f"\n{self.observation_prefix}{observation}\n{self.llm_prefix}"953                )954            # Adding to the previous steps, we now tell the LLM to make a final pred955            thoughts += (956                "\n\nI now need to return a final answer based on the previous steps:"957            )958            new_inputs = {"agent_scratchpad": thoughts, "stop": self._stop}959            full_inputs = {**kwargs, **new_inputs}960            full_output = self.llm_chain.predict(**full_inputs)961            # We try to extract a final answer962            parsed_output = self.output_parser.parse(full_output)963            if isinstance(parsed_output, AgentFinish):964                # If we can extract, we send the correct stuff965                return parsed_output966            # If we can extract, but the tool is not the final tool,967            # we just return the full output968            return AgentFinish({"output": full_output}, full_output)969        msg = (970            "early_stopping_method should be one of `force` or `generate`, "971            f"got {early_stopping_method}"972        )973        raise ValueError(msg)974975    def tool_run_logging_kwargs(self) -> builtins.dict:976        """Return logging kwargs for tool run."""977        return {978            "llm_prefix": self.llm_prefix,979            "observation_prefix": self.observation_prefix,980        }981982983class ExceptionTool(BaseTool):984    """Tool that just returns the query."""985986    name: str = "_Exception"987    """Name of the tool."""988    description: str = "Exception tool"989    """Description of the tool."""990991    @override992    def _run(993        self,994        query: str,995        run_manager: CallbackManagerForToolRun | None = None,996    ) -> str:997        return query998999    @override1000    async def _arun(1001        self,1002        query: str,1003        run_manager: AsyncCallbackManagerForToolRun | None = None,1004    ) -> str:1005        return query100610071008NextStepOutput = list[AgentFinish | AgentAction | AgentStep]1009RunnableAgentType = RunnableAgent | RunnableMultiActionAgent101010111012class AgentExecutor(Chain):1013    """Agent that is using tools."""10141015    agent: BaseSingleActionAgent | BaseMultiActionAgent | Runnable1016    """The agent to run for creating a plan and determining actions1017    to take at each step of the execution loop."""1018    tools: Sequence[BaseTool]1019    """The valid tools the agent can call."""1020    return_intermediate_steps: bool = False1021    """Whether to return the agent's trajectory of intermediate steps1022    at the end in addition to the final output."""1023    max_iterations: int | None = 151024    """The maximum number of steps to take before ending the execution1025    loop.10261027    Setting to 'None' could lead to an infinite loop."""1028    max_execution_time: float | None = None1029    """The maximum amount of wall clock time to spend in the execution1030    loop.1031    """1032    early_stopping_method: str = "force"1033    """The method to use for early stopping if the agent never1034    returns `AgentFinish`. Either 'force' or 'generate'.10351036    `"force"` returns a string saying that it stopped because it met a1037        time or iteration limit.10381039    `"generate"` calls the agent's LLM Chain one final time to generate1040        a final answer based on the previous steps.1041    """1042    handle_parsing_errors: bool | str | Callable[[OutputParserException], str] = False1043    """How to handle errors raised by the agent's output parser.1044    Defaults to `False`, which raises the error.1045    If `true`, the error will be sent back to the LLM as an observation.1046    If a string, the string itself will be sent to the LLM as an observation.1047    If a callable function, the function will be called with the exception as an1048    argument, and the result of that function will be passed to the agent as an1049    observation.1050    """1051    trim_intermediate_steps: (1052        int | Callable[[list[tuple[AgentAction, str]]], list[tuple[AgentAction, str]]]1053    ) = -11054    """How to trim the intermediate steps before returning them.1055    Defaults to -1, which means no trimming.1056    """10571058    @classmethod1059    def from_agent_and_tools(1060        cls,1061        agent: BaseSingleActionAgent | BaseMultiActionAgent | Runnable,1062        tools: Sequence[BaseTool],1063        callbacks: Callbacks = None,1064        **kwargs: Any,1065    ) -> AgentExecutor:1066        """Create from agent and tools.10671068        Args:1069            agent: Agent to use.1070            tools: Tools to use.1071            callbacks: Callbacks to use.1072            kwargs: Additional arguments.10731074        Returns:1075            Agent executor object.1076        """1077        return cls(1078            agent=agent,1079            tools=tools,1080            callbacks=callbacks,1081            **kwargs,1082        )10831084    @model_validator(mode="after")1085    def validate_tools(self) -> Self:1086        """Validate that tools are compatible with agent.10871088        Args:1089            values: Values to validate.10901091        Returns:1092            Validated values.10931094        Raises:1095            ValueError: If allowed tools are different than provided tools.1096        """1097        agent = self.agent1098        tools = self.tools1099        allowed_tools = agent.get_allowed_tools()  # type: ignore[union-attr]1100        if allowed_tools is not None and set(allowed_tools) != {1101            tool.name for tool in tools1102        }:1103            msg = (1104                f"Allowed tools ({allowed_tools}) different than "1105                f"provided tools ({[tool.name for tool in tools]})"1106            )1107            raise ValueError(msg)1108        return self11091110    @model_validator(mode="before")1111    @classmethod1112    def validate_runnable_agent(cls, values: dict) -> Any:1113        """Convert runnable to agent if passed in.11141115        Args:1116            values: Values to validate.11171118        Returns:1119            Validated values.1120        """1121        agent = values.get("agent")1122        if agent and isinstance(agent, Runnable):1123            try:1124                output_type = agent.OutputType1125            except TypeError:1126                multi_action = False1127            except Exception:1128                logger.exception("Unexpected error getting OutputType from agent")1129                multi_action = False1130            else:1131                multi_action = output_type == list[AgentAction] | AgentFinish11321133            stream_runnable = values.pop("stream_runnable", True)1134            if multi_action:1135                values["agent"] = RunnableMultiActionAgent(1136                    runnable=agent,1137                    stream_runnable=stream_runnable,1138                )1139            else:1140                values["agent"] = RunnableAgent(1141                    runnable=agent,1142                    stream_runnable=stream_runnable,1143                )1144        return values11451146    @property1147    def _action_agent(self) -> BaseSingleActionAgent | BaseMultiActionAgent:1148        """Type cast self.agent.11491150        If the `agent` attribute is a Runnable, it will be converted one of1151        RunnableAgentType in the validate_runnable_agent root_validator.11521153        To support instantiating with a Runnable, here we explicitly cast the type1154        to reflect the changes made in the root_validator.1155        """1156        if isinstance(self.agent, Runnable):1157            return cast("RunnableAgentType", self.agent)1158        return self.agent11591160    @override1161    def save(self, file_path: Path | str) -> None:1162        """Raise error - saving not supported for Agent Executors.11631164        Args:1165            file_path: Path to save to.11661167        Raises:1168            ValueError: Saving not supported for agent executors.1169        """1170        msg = (1171            "Saving not supported for agent executors. "1172            "If you are trying to save the agent, please use the "1173            "`.save_agent(...)`"1174        )1175        raise ValueError(msg)11761177    def save_agent(self, file_path: Path | str) -> None:1178        """Save the underlying agent.11791180        Args:1181            file_path: Path to save to.1182        """1183        return self._action_agent.save(file_path)11841185    def iter(1186        self,1187        inputs: Any,1188        callbacks: Callbacks = None,1189        *,1190        include_run_info: bool = False,1191        async_: bool = False,  # noqa: ARG002 arg kept for backwards compat, but ignored1192    ) -> AgentExecutorIterator:1193        """Enables iteration over steps taken to reach final output.11941195        Args:1196            inputs: Inputs to the agent.1197            callbacks: Callbacks to run.1198            include_run_info: Whether to include run info.1199            async_: Whether to run async. (Ignored)12001201        Returns:1202            Agent executor iterator object.1203        """1204        return AgentExecutorIterator(1205            self,1206            inputs,1207            callbacks,1208            tags=self.tags,1209            include_run_info=include_run_info,1210        )12111212    @property1213    def input_keys(self) -> list[str]:1214        """Return the input keys."""1215        return self._action_agent.input_keys12161217    @property1218    def output_keys(self) -> list[str]:1219        """Return the singular output key."""1220        if self.return_intermediate_steps:1221            return [*self._action_agent.return_values, "intermediate_steps"]1222        return self._action_agent.return_values12231224    def lookup_tool(self, name: str) -> BaseTool:1225        """Lookup tool by name.12261227        Args:1228            name: Name of tool.12291230        Returns:1231            Tool object.1232        """1233        return {tool.name: tool for tool in self.tools}[name]12341235    def _should_continue(self, iterations: int, time_elapsed: float) -> bool:1236        if self.max_iterations is not None and iterations >= self.max_iterations:1237            return False1238        return self.max_execution_time is None or time_elapsed < self.max_execution_time12391240    def _return(1241        self,1242        output: AgentFinish,1243        intermediate_steps: list,1244        run_manager: CallbackManagerForChainRun | None = None,1245    ) -> dict[str, Any]:1246        if run_manager:1247            run_manager.on_agent_finish(output, color="green", verbose=self.verbose)1248        final_output = output.return_values1249        if self.return_intermediate_steps:1250            final_output["intermediate_steps"] = intermediate_steps1251        return final_output12521253    async def _areturn(1254        self,1255        output: AgentFinish,1256        intermediate_steps: list,1257        run_manager: AsyncCallbackManagerForChainRun | None = None,1258    ) -> dict[str, Any]:1259        if run_manager:1260            await run_manager.on_agent_finish(1261                output,1262                color="green",1263                verbose=self.verbose,1264            )1265        final_output = output.return_values1266        if self.return_intermediate_steps:1267            final_output["intermediate_steps"] = intermediate_steps1268        return final_output12691270    def _consume_next_step(1271        self,1272        values: NextStepOutput,1273    ) -> AgentFinish | list[tuple[AgentAction, str]]:1274        if isinstance(values[-1], AgentFinish):1275            if len(values) != 1:1276                msg = "Expected a single AgentFinish output, but got multiple values."1277                raise ValueError(msg)1278            return values[-1]1279        return [(a.action, a.observation) for a in values if isinstance(a, AgentStep)]12801281    def _take_next_step(1282        self,1283        name_to_tool_map: dict[str, BaseTool],1284        color_mapping: dict[str, str],1285        inputs: dict[str, str],1286        intermediate_steps: list[tuple[AgentAction, str]],1287        run_manager: CallbackManagerForChainRun | None = None,1288    ) -> AgentFinish | list[tuple[AgentAction, str]]:1289        return self._consume_next_step(1290            list(1291                self._iter_next_step(1292                    name_to_tool_map,1293                    color_mapping,1294                    inputs,1295                    intermediate_steps,1296                    run_manager,1297                ),1298            ),1299        )13001301    def _iter_next_step(1302        self,1303        name_to_tool_map: dict[str, BaseTool],1304        color_mapping: dict[str, str],1305        inputs: dict[str, str],1306        intermediate_steps: list[tuple[AgentAction, str]],1307        run_manager: CallbackManagerForChainRun | None = None,1308    ) -> Iterator[AgentFinish | AgentAction | AgentStep]:1309        """Take a single step in the thought-action-observation loop.13101311        Override this to take control of how the agent makes and acts on choices.1312        """1313        try:1314            intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)13151316            # Call the LLM to see what to do.1317            output = self._action_agent.plan(1318                intermediate_steps,1319                callbacks=run_manager.get_child() if run_manager else None,1320                **inputs,1321            )1322        except OutputParserException as e:1323            if isinstance(self.handle_parsing_errors, bool):1324                raise_error = not self.handle_parsing_errors1325            else:1326                raise_error = False1327            if raise_error:1328                msg = (1329                    "An output parsing error occurred. "1330                    "In order to pass this error back to the agent and have it try "1331                    "again, pass `handle_parsing_errors=True` to the AgentExecutor. "1332                    f"This is the error: {e!s}"1333                )1334                raise ValueError(msg) from e1335            text = str(e)1336            if isinstance(self.handle_parsing_errors, bool):1337                if e.send_to_llm:1338                    observation = str(e.observation)1339                    text = str(e.llm_output)1340                else:1341                    observation = "Invalid or incomplete response"1342            elif isinstance(self.handle_parsing_errors, str):1343                observation = self.handle_parsing_errors1344            elif callable(self.handle_parsing_errors):1345                observation = self.handle_parsing_errors(e)1346            else:1347                msg = "Got unexpected type of `handle_parsing_errors`"  # type: ignore[unreachable]1348                raise ValueError(msg) from e  # noqa: TRY0041349            output = AgentAction("_Exception", observation, text)1350            if run_manager:1351                run_manager.on_agent_action(output, color="green")1352            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1353            observation = ExceptionTool().run(1354                output.tool_input,1355                verbose=self.verbose,1356                color=None,1357                callbacks=run_manager.get_child() if run_manager else None,1358                **tool_run_kwargs,1359            )1360            yield AgentStep(action=output, observation=observation)1361            return13621363        # If the tool chosen is the finishing tool, then we end and return.1364        if isinstance(output, AgentFinish):1365            yield output1366            return13671368        actions: list[AgentAction]1369        actions = [output] if isinstance(output, AgentAction) else output1370        for agent_action in actions:1371            yield agent_action1372        for agent_action in actions:1373            yield self._perform_agent_action(1374                name_to_tool_map,1375                color_mapping,1376                agent_action,1377                run_manager,1378            )13791380    def _perform_agent_action(1381        self,1382        name_to_tool_map: dict[str, BaseTool],1383        color_mapping: dict[str, str],1384        agent_action: AgentAction,1385        run_manager: CallbackManagerForChainRun | None = None,1386    ) -> AgentStep:1387        if run_manager:1388            run_manager.on_agent_action(agent_action, color="green")1389        # Otherwise we lookup the tool1390        if agent_action.tool in name_to_tool_map:1391            tool = name_to_tool_map[agent_action.tool]1392            return_direct = tool.return_direct1393            color = color_mapping[agent_action.tool]1394            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1395            if return_direct:1396                tool_run_kwargs["llm_prefix"] = ""1397            # We then call the tool on the tool input to get an observation1398            observation = tool.run(1399                agent_action.tool_input,1400                verbose=self.verbose,1401                color=color,1402                callbacks=run_manager.get_child() if run_manager else None,1403                **tool_run_kwargs,1404            )1405        else:1406            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1407            observation = InvalidTool().run(1408                {1409                    "requested_tool_name": agent_action.tool,1410                    "available_tool_names": list(name_to_tool_map.keys()),1411                },1412                verbose=self.verbose,1413                color=None,1414                callbacks=run_manager.get_child() if run_manager else None,1415                **tool_run_kwargs,1416            )1417        return AgentStep(action=agent_action, observation=observation)14181419    async def _atake_next_step(1420        self,1421        name_to_tool_map: dict[str, BaseTool],1422        color_mapping: dict[str, str],1423        inputs: dict[str, str],1424        intermediate_steps: list[tuple[AgentAction, str]],1425        run_manager: AsyncCallbackManagerForChainRun | None = None,1426    ) -> AgentFinish | list[tuple[AgentAction, str]]:1427        return self._consume_next_step(1428            [1429                a1430                async for a in self._aiter_next_step(1431                    name_to_tool_map,1432                    color_mapping,1433                    inputs,1434                    intermediate_steps,1435                    run_manager,1436                )1437            ],1438        )14391440    async def _aiter_next_step(1441        self,1442        name_to_tool_map: dict[str, BaseTool],1443        color_mapping: dict[str, str],1444        inputs: dict[str, str],1445        intermediate_steps: list[tuple[AgentAction, str]],1446        run_manager: AsyncCallbackManagerForChainRun | None = None,1447    ) -> AsyncIterator[AgentFinish | AgentAction | AgentStep]:1448        """Take a single step in the thought-action-observation loop.14491450        Override this to take control of how the agent makes and acts on choices.1451        """1452        try:1453            intermediate_steps = self._prepare_intermediate_steps(intermediate_steps)14541455            # Call the LLM to see what to do.1456            output = await self._action_agent.aplan(1457                intermediate_steps,1458                callbacks=run_manager.get_child() if run_manager else None,1459                **inputs,1460            )1461        except OutputParserException as e:1462            if isinstance(self.handle_parsing_errors, bool):1463                raise_error = not self.handle_parsing_errors1464            else:1465                raise_error = False1466            if raise_error:1467                msg = (1468                    "An output parsing error occurred. "1469                    "In order to pass this error back to the agent and have it try "1470                    "again, pass `handle_parsing_errors=True` to the AgentExecutor. "1471                    f"This is the error: {e!s}"1472                )1473                raise ValueError(msg) from e1474            text = str(e)1475            if isinstance(self.handle_parsing_errors, bool):1476                if e.send_to_llm:1477                    observation = str(e.observation)1478                    text = str(e.llm_output)1479                else:1480                    observation = "Invalid or incomplete response"1481            elif isinstance(self.handle_parsing_errors, str):1482                observation = self.handle_parsing_errors1483            elif callable(self.handle_parsing_errors):1484                observation = self.handle_parsing_errors(e)1485            else:1486                msg = "Got unexpected type of `handle_parsing_errors`"  # type: ignore[unreachable]1487                raise ValueError(msg) from e  # noqa: TRY0041488            output = AgentAction("_Exception", observation, text)1489            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1490            observation = await ExceptionTool().arun(1491                output.tool_input,1492                verbose=self.verbose,1493                color=None,1494                callbacks=run_manager.get_child() if run_manager else None,1495                **tool_run_kwargs,1496            )1497            yield AgentStep(action=output, observation=observation)1498            return14991500        # If the tool chosen is the finishing tool, then we end and return.1501        if isinstance(output, AgentFinish):1502            yield output1503            return15041505        actions: list[AgentAction]1506        actions = [output] if isinstance(output, AgentAction) else output1507        for agent_action in actions:1508            yield agent_action15091510        # Use asyncio.gather to run multiple tool.arun() calls concurrently1511        result = await asyncio.gather(1512            *[1513                self._aperform_agent_action(1514                    name_to_tool_map,1515                    color_mapping,1516                    agent_action,1517                    run_manager,1518                )1519                for agent_action in actions1520            ],1521        )15221523        # TODO: This could yield each result as it becomes available1524        for chunk in result:1525            yield chunk15261527    async def _aperform_agent_action(1528        self,1529        name_to_tool_map: dict[str, BaseTool],1530        color_mapping: dict[str, str],1531        agent_action: AgentAction,1532        run_manager: AsyncCallbackManagerForChainRun | None = None,1533    ) -> AgentStep:1534        if run_manager:1535            await run_manager.on_agent_action(1536                agent_action,1537                verbose=self.verbose,1538                color="green",1539            )1540        # Otherwise we lookup the tool1541        if agent_action.tool in name_to_tool_map:1542            tool = name_to_tool_map[agent_action.tool]1543            return_direct = tool.return_direct1544            color = color_mapping[agent_action.tool]1545            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1546            if return_direct:1547                tool_run_kwargs["llm_prefix"] = ""1548            # We then call the tool on the tool input to get an observation1549            observation = await tool.arun(1550                agent_action.tool_input,1551                verbose=self.verbose,1552                color=color,1553                callbacks=run_manager.get_child() if run_manager else None,1554                **tool_run_kwargs,1555            )1556        else:1557            tool_run_kwargs = self._action_agent.tool_run_logging_kwargs()1558            observation = await InvalidTool().arun(1559                {1560                    "requested_tool_name": agent_action.tool,1561                    "available_tool_names": list(name_to_tool_map.keys()),1562                },1563                verbose=self.verbose,1564                color=None,1565                callbacks=run_manager.get_child() if run_manager else None,1566                **tool_run_kwargs,1567            )1568        return AgentStep(action=agent_action, observation=observation)15691570    def _call(1571        self,1572        inputs: dict[str, str],1573        run_manager: CallbackManagerForChainRun | None = None,1574    ) -> dict[str, Any]:1575        """Run text through and get agent response."""1576        # Construct a mapping of tool name to tool for easy lookup1577        name_to_tool_map = {tool.name: tool for tool in self.tools}1578        # We construct a mapping from each tool to a color, used for logging.1579        color_mapping = get_color_mapping(1580            [tool.name for tool in self.tools],1581            excluded_colors=["green", "red"],1582        )1583        intermediate_steps: list[tuple[AgentAction, str]] = []1584        # Let's start tracking the number of iterations and time elapsed1585        iterations = 01586        time_elapsed = 0.01587        start_time = time.time()1588        # We now enter the agent loop (until it returns something).1589        while self._should_continue(iterations, time_elapsed):1590            next_step_output = self._take_next_step(1591                name_to_tool_map,1592                color_mapping,1593                inputs,1594                intermediate_steps,1595                run_manager=run_manager,1596            )1597            if isinstance(next_step_output, AgentFinish):1598                return self._return(1599                    next_step_output,1600                    intermediate_steps,1601                    run_manager=run_manager,1602                )16031604            intermediate_steps.extend(next_step_output)1605            if len(next_step_output) == 1:1606                next_step_action = next_step_output[0]1607                # See if tool should return directly1608                tool_return = self._get_tool_return(next_step_action)1609                if tool_return is not None:1610                    return self._return(1611                        tool_return,1612                        intermediate_steps,1613                        run_manager=run_manager,1614                    )1615            iterations += 11616            time_elapsed = time.time() - start_time1617        output = self._action_agent.return_stopped_response(1618            self.early_stopping_method,1619            intermediate_steps,1620            **inputs,1621        )1622        return self._return(output, intermediate_steps, run_manager=run_manager)16231624    async def _acall(1625        self,1626        inputs: dict[str, str],1627        run_manager: AsyncCallbackManagerForChainRun | None = None,1628    ) -> dict[str, str]:1629        """Async run text through and get agent response."""1630        # Construct a mapping of tool name to tool for easy lookup1631        name_to_tool_map = {tool.name: tool for tool in self.tools}1632        # We construct a mapping from each tool to a color, used for logging.1633        color_mapping = get_color_mapping(1634            [tool.name for tool in self.tools],1635            excluded_colors=["green"],1636        )1637        intermediate_steps: list[tuple[AgentAction, str]] = []1638        # Let's start tracking the number of iterations and time elapsed1639        iterations = 01640        time_elapsed = 0.01641        start_time = time.time()1642        # We now enter the agent loop (until it returns something).1643        try:1644            async with asyncio_timeout(self.max_execution_time):1645                while self._should_continue(iterations, time_elapsed):1646                    next_step_output = await self._atake_next_step(1647                        name_to_tool_map,1648                        color_mapping,1649                        inputs,1650                        intermediate_steps,1651                        run_manager=run_manager,1652                    )1653                    if isinstance(next_step_output, AgentFinish):1654                        return await self._areturn(1655                            next_step_output,1656                            intermediate_steps,1657                            run_manager=run_manager,1658                        )16591660                    intermediate_steps.extend(next_step_output)1661                    if len(next_step_output) == 1:1662                        next_step_action = next_step_output[0]1663                        # See if tool should return directly1664                        tool_return = self._get_tool_return(next_step_action)1665                        if tool_return is not None:1666                            return await self._areturn(1667                                tool_return,1668                                intermediate_steps,1669                                run_manager=run_manager,1670                            )16711672                    iterations += 11673                    time_elapsed = time.time() - start_time1674                output = self._action_agent.return_stopped_response(1675                    self.early_stopping_method,1676                    intermediate_steps,1677                    **inputs,1678                )1679                return await self._areturn(1680                    output,1681                    intermediate_steps,1682                    run_manager=run_manager,1683                )1684        except (TimeoutError, asyncio.TimeoutError):1685            # stop early when interrupted by the async timeout1686            output = self._action_agent.return_stopped_response(1687                self.early_stopping_method,1688                intermediate_steps,1689                **inputs,1690            )1691            return await self._areturn(1692                output,1693                intermediate_steps,1694                run_manager=run_manager,1695            )16961697    def _get_tool_return(1698        self,1699        next_step_output: tuple[AgentAction, str],1700    ) -> AgentFinish | None:1701        """Check if the tool is a returning tool."""1702        agent_action, observation = next_step_output1703        name_to_tool_map = {tool.name: tool for tool in self.tools}1704        return_value_key = "output"1705        if len(self._action_agent.return_values) > 0:1706            return_value_key = self._action_agent.return_values[0]1707        # Invalid tools won't be in the map, so we return False.1708        if (1709            agent_action.tool in name_to_tool_map1710            and name_to_tool_map[agent_action.tool].return_direct1711        ):1712            return AgentFinish(1713                {return_value_key: observation},1714                "",1715            )1716        return None17171718    def _prepare_intermediate_steps(1719        self,1720        intermediate_steps: list[tuple[AgentAction, str]],1721    ) -> list[tuple[AgentAction, str]]:1722        if (1723            isinstance(self.trim_intermediate_steps, int)1724            and self.trim_intermediate_steps > 01725        ):1726            return intermediate_steps[-self.trim_intermediate_steps :]1727        if callable(self.trim_intermediate_steps):1728            return self.trim_intermediate_steps(intermediate_steps)1729        return intermediate_steps17301731    @override1732    def stream(1733        self,1734        input: dict[str, Any] | Any,1735        config: RunnableConfig | None = None,1736        **kwargs: Any,1737    ) -> Iterator[AddableDict]:1738        """Enables streaming over steps taken to reach final output.17391740        Args:1741            input: Input to the agent.1742            config: Config to use.1743            kwargs: Additional arguments.17441745        Yields:1746            Addable dictionary.1747        """1748        config = ensure_config(config)1749        iterator = AgentExecutorIterator(1750            self,1751            input,1752            config.get("callbacks"),1753            tags=config.get("tags"),1754            metadata=config.get("metadata"),1755            run_name=config.get("run_name"),1756            run_id=config.get("run_id"),1757            yield_actions=True,1758            **kwargs,1759        )1760        yield from iterator17611762    @override1763    async def astream(1764        self,1765        input: dict[str, Any] | Any,1766        config: RunnableConfig | None = None,1767        **kwargs: Any,1768    ) -> AsyncIterator[AddableDict]:1769        """Async enables streaming over steps taken to reach final output.17701771        Args:1772            input: Input to the agent.1773            config: Config to use.1774            kwargs: Additional arguments.17751776        Yields:1777            Addable dictionary.1778        """1779        config = ensure_config(config)1780        iterator = AgentExecutorIterator(1781            self,1782            input,1783            config.get("callbacks"),1784            tags=config.get("tags"),1785            metadata=config.get("metadata"),1786            run_name=config.get("run_name"),1787            run_id=config.get("run_id"),1788            yield_actions=True,1789            **kwargs,1790        )1791        async for step in iterator:1792            yield step

Code quality findings 54

Ensure functions have docstrings for documentation
missing-docstring
def plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Ensure functions have docstrings for documentation
missing-docstring
def return_stopped_response(
Ensure functions have docstrings for documentation
missing-docstring
def from_llm_and_tools(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(_type, AgentType):
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 plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Ensure functions have docstrings for documentation
missing-docstring
def return_stopped_response(
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 plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Ensure functions have docstrings for documentation
missing-docstring
def plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
return list(set(self.llm_chain.input_keys) - {"intermediate_steps"})
Avoid unless necessary; Python's garbage collector typically handles object deletion
unnecessary-del
del _dict["output_parser"]
Ensure functions have docstrings for documentation
missing-docstring
def plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Avoid unless necessary; Python's garbage collector typically handles object deletion
unnecessary-del
del _dict["output_parser"]
Ensure functions have docstrings for documentation
missing-docstring
def plan(
Ensure functions have docstrings for documentation
missing-docstring
async def aplan(
Ensure functions have docstrings for documentation
missing-docstring
def get_full_inputs(
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
return list(set(self.llm_chain.input_keys) - {"agent_scratchpad"})
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(prompt, PromptTemplate):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(prompt, FewShotPromptTemplate):
Ensure functions have docstrings for documentation
missing-docstring
def from_llm_and_tools(
Ensure functions have docstrings for documentation
missing-docstring
def return_stopped_response(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(parsed_output, AgentFinish):
Ensure functions have docstrings for documentation
missing-docstring
def from_agent_and_tools(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if agent and isinstance(agent, Runnable):
Catch specific exceptions instead of Exception to avoid masking bugs
broad-except
except Exception:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.agent, Runnable):
Ensure functions have docstrings for documentation
missing-docstring
def iter(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(values[-1], AgentFinish):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
return [(a.action, a.observation) for a in values if isinstance(a, AgentStep)]
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
list(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.handle_parsing_errors, bool):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.handle_parsing_errors, bool):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(self.handle_parsing_errors, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(output, AgentFinish):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
actions = [output] if isinstance(output, AgentAction) else output
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
"available_tool_names": list(name_to_tool_map.keys()),
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.handle_parsing_errors, bool):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(self.handle_parsing_errors, bool):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(self.handle_parsing_errors, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(output, AgentFinish):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
actions = [output] if isinstance(output, AgentAction) else output
Avoid unnecessary list conversions; use generators where possible
unnecessary-list
"available_tool_names": list(name_to_tool_map.keys()),
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(next_step_output, AgentFinish):
Ensure try blocks have corresponding except or finally blocks
try-without-except
try:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(next_step_output, AgentFinish):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(self.trim_intermediate_steps, int)
Ensure functions have docstrings for documentation
missing-docstring
def stream(
Ensure functions have docstrings for documentation
missing-docstring
async def astream(

Get this view in your editor

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