Ensure functions have docstrings for documentation
def plan(
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
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.