diff --git a/docs/examples/agents/react.ipynb b/docs/examples/agents/react.ipynb index aef1239030f..4af4c6727f1 100644 --- a/docs/examples/agents/react.ipynb +++ b/docs/examples/agents/react.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 6, "id": "4e272b47", "metadata": {}, "outputs": [], @@ -32,13 +32,13 @@ " )\n", "]\n", "\n", - "llm = OpenAI(temperature=0)\n", + "llm = OpenAI(temperature=0, model_name=\"text-davinci-002\")\n", "react = initialize_agent(tools, llm, agent=\"react-docstore\", verbose=True)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "8078c8f1", "metadata": {}, "outputs": [ @@ -46,9 +46,31 @@ "name": "stdout", "output_type": "stream", "text": [ + "\n", + "\n", + "\u001b[1m> Entering new ReActDocstoreAgent chain...\u001b[0m\n", "Author David Chanoff has collaborated with a U.S. Navy admiral who served as the ambassador to the United Kingdom under which President?\n", - "Thought 1:" + "Thought 1:\u001b[32;1m\u001b[1;3m I need to search David Chanoff and find the U.S. Navy admiral he collaborated\n", + "with.\n", + "Action 1: Search[David Chanoff]\u001b[0m\n", + "Observation 1: \u001b[36;1m\u001b[1;3mDavid Chanoff is a noted author of non-fiction work. His work has typically involved collaborations with the principal protagonist of the work concerned. His collaborators have included; Augustus A. White, Joycelyn Elders, Đoàn Văn Toại, William J. Crowe, Ariel Sharon, Kenneth Good and Felix Zandman. He has also written about a wide range of subjects including literary history, education and foreign for The Washington Post, The New Republic and The New York Times Magazine. He has published more than twelve books.\u001b[0m\n", + "Thought 2:\u001b[32;1m\u001b[1;3m The U.S. Navy admiral David Chanoff collaborated with is William J. Crowe.\n", + "Action 2: Search[William J. Crowe]\u001b[0m\n", + "Observation 2: \u001b[36;1m\u001b[1;3mWilliam James Crowe Jr. (January 2, 1925 – October 18, 2007) was a United States Navy admiral and diplomat who served as the 11th chairman of the Joint Chiefs of Staff under Presidents Ronald Reagan and George H. W. Bush, and as the ambassador to the United Kingdom and Chair of the Intelligence Oversight Board under President Bill Clinton.\u001b[0m\n", + "Thought 3:\u001b[32;1m\u001b[1;3m William J. Crowe served as the ambassador to the United Kingdom under President Bill Clinton.\n", + "Action 3: Finish[Bill Clinton]\u001b[0m\n", + "\u001b[1m> Finished ReActDocstoreAgent chain.\u001b[0m\n" ] + }, + { + "data": { + "text/plain": [ + "'Bill Clinton'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -81,7 +103,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.10.8" } }, "nbformat": 4, diff --git a/langchain/__init__.py b/langchain/__init__.py index 647ab939019..31ec09eb1c3 100644 --- a/langchain/__init__.py +++ b/langchain/__init__.py @@ -15,6 +15,7 @@ from langchain.chains import ( ) from langchain.docstore import InMemoryDocstore, Wikipedia from langchain.llms import Cohere, HuggingFaceHub, OpenAI +from langchain.logger import BaseLogger, StdOutLogger from langchain.prompts import ( BasePromptTemplate, FewShotPromptTemplate, @@ -25,6 +26,9 @@ from langchain.serpapi import SerpAPIChain, SerpAPIWrapper from langchain.sql_database import SQLDatabase from langchain.vectorstores import FAISS, ElasticVectorSearch +logger: BaseLogger = StdOutLogger() +verbose: bool = False + __all__ = [ "LLMChain", "LLMBashChain", diff --git a/langchain/agents/agent.py b/langchain/agents/agent.py index 62c4dccad8b..fb60115ce53 100644 --- a/langchain/agents/agent.py +++ b/langchain/agents/agent.py @@ -2,24 +2,18 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, ClassVar, Dict, List, NamedTuple, Optional, Tuple +from typing import Any, ClassVar, Dict, List, Optional, Tuple from pydantic import BaseModel +from langchain.agents.input import ChainedInput from langchain.agents.tools import Tool from langchain.chains.base import Chain from langchain.chains.llm import LLMChain -from langchain.input import ChainedInput, get_color_mapping +from langchain.input import get_color_mapping from langchain.llms.base import LLM from langchain.prompts.base import BasePromptTemplate - - -class Action(NamedTuple): - """Action to take.""" - - tool: str - tool_input: str - log: str +from langchain.schema import AgentAction class Agent(Chain, BaseModel, ABC): @@ -99,7 +93,7 @@ class Agent(Chain, BaseModel, ABC): llm_chain = LLMChain(llm=llm, prompt=cls.create_prompt(tools)) return cls(llm_chain=llm_chain, tools=tools, **kwargs) - def get_action(self, text: str) -> Action: + def get_action(self, text: str) -> AgentAction: """Given input, decided what to do. Args: @@ -119,7 +113,7 @@ class Agent(Chain, BaseModel, ABC): full_output += output parsed_output = self._extract_tool_and_input(full_output) tool, tool_input = parsed_output - return Action(tool, tool_input, full_output) + return AgentAction(tool, tool_input, full_output) def _call(self, inputs: Dict[str, str]) -> Dict[str, str]: """Run text through and get agent response.""" @@ -145,7 +139,7 @@ class Agent(Chain, BaseModel, ABC): # Call the LLM to see what to do. output = self.get_action(chained_input.input) # Add the log to the Chained Input. - chained_input.add(output.log, color="green") + chained_input.add_action(output, color="green") # If the tool chosen is the finishing tool, then we end and return. if output.tool == self.finish_tool_name: return {self.output_key: output.tool_input} @@ -159,8 +153,9 @@ class Agent(Chain, BaseModel, ABC): observation = f"{output.tool} is not a valid tool, try another one." color = None # We then log the observation - chained_input.add(f"\n{self.observation_prefix}") - chained_input.add(observation, color=color) - # We then add the LLM prefix into the prompt to get the LLM to start - # thinking, and start the loop all over. - chained_input.add(f"\n{self.llm_prefix}") + chained_input.add_observation( + observation, + self.observation_prefix, + self.llm_prefix, + color=color, + ) diff --git a/langchain/agents/input.py b/langchain/agents/input.py new file mode 100644 index 00000000000..6659ad091c0 --- /dev/null +++ b/langchain/agents/input.py @@ -0,0 +1,44 @@ +"""Input manager for agents.""" +from typing import Optional + +import langchain +from langchain.schema import AgentAction + + +class ChainedInput: + """Class for working with input that is the result of chains.""" + + def __init__(self, text: str, verbose: bool = False): + """Initialize with verbose flag and initial text.""" + self._verbose = verbose + if self._verbose: + langchain.logger.log_agent_start(text) + self._input = text + + def add_action(self, action: AgentAction, color: Optional[str] = None) -> None: + """Add text to input, print if in verbose mode.""" + if self._verbose: + langchain.logger.log_agent_action(action, color=color) + self._input += action.log + + def add_observation( + self, + observation: str, + observation_prefix: str, + llm_prefix: str, + color: Optional[str], + ) -> None: + """Add observation to input, print if in verbose mode.""" + if self._verbose: + langchain.logger.log_agent_observation( + observation, + color=color, + observation_prefix=observation_prefix, + llm_prefix=llm_prefix, + ) + self._input += f"\n{observation_prefix}{observation}\n{llm_prefix}" + + @property + def input(self) -> str: + """Return the accumulated input.""" + return self._input diff --git a/langchain/chains/base.py b/langchain/chains/base.py index 529e86dda41..44f0bdbf95b 100644 --- a/langchain/chains/base.py +++ b/langchain/chains/base.py @@ -2,7 +2,9 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional -from pydantic import BaseModel, Extra +from pydantic import BaseModel, Extra, Field + +import langchain class Memory(BaseModel, ABC): @@ -28,12 +30,16 @@ class Memory(BaseModel, ABC): """Save the context of this model run to memory.""" +def _get_verbosity() -> bool: + return langchain.verbose + + class Chain(BaseModel, ABC): """Base interface that all chains should implement.""" memory: Optional[Memory] = None - verbose: bool = False + verbose: bool = Field(default_factory=_get_verbosity) """Whether to print out response text.""" @property diff --git a/langchain/chains/llm.py b/langchain/chains/llm.py index 544628524c8..a1575ed450f 100644 --- a/langchain/chains/llm.py +++ b/langchain/chains/llm.py @@ -3,8 +3,8 @@ from typing import Any, Dict, List, Union from pydantic import BaseModel, Extra +import langchain from langchain.chains.base import Chain -from langchain.input import print_text from langchain.llms.base import LLM from langchain.prompts.base import BasePromptTemplate @@ -55,12 +55,13 @@ class LLMChain(Chain, BaseModel): selected_inputs = {k: inputs[k] for k in self.prompt.input_variables} prompt = self.prompt.format(**selected_inputs) if self.verbose: - print("Prompt after formatting:") - print_text(prompt, color="green", end="\n") + langchain.logger.log_llm_inputs(selected_inputs, prompt) kwargs = {} if "stop" in inputs: kwargs["stop"] = inputs["stop"] response = self.llm(prompt, **kwargs) + if self.verbose: + langchain.logger.log_llm_response(response) return {self.output_key: response} def predict(self, **kwargs: Any) -> str: diff --git a/langchain/input.py b/langchain/input.py index 782cd41a8bf..680685fff51 100644 --- a/langchain/input.py +++ b/langchain/input.py @@ -27,25 +27,3 @@ def print_text(text: str, color: Optional[str] = None, end: str = "") -> None: else: color_str = _TEXT_COLOR_MAPPING[color] print(f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m", end=end) - - -class ChainedInput: - """Class for working with input that is the result of chains.""" - - def __init__(self, text: str, verbose: bool = False): - """Initialize with verbose flag and initial text.""" - self._verbose = verbose - if self._verbose: - print_text(text, color=None) - self._input = text - - def add(self, text: str, color: Optional[str] = None) -> None: - """Add text to input, print if in verbose mode.""" - if self._verbose: - print_text(text, color=color) - self._input += text - - @property - def input(self) -> str: - """Return the accumulated input.""" - return self._input diff --git a/langchain/logger.py b/langchain/logger.py new file mode 100644 index 00000000000..27b8fd84af1 --- /dev/null +++ b/langchain/logger.py @@ -0,0 +1,65 @@ +"""BETA: everything in here is highly experimental, do not rely on.""" +from typing import Any, Optional + +from langchain.input import print_text +from langchain.schema import AgentAction + + +class BaseLogger: + """Base logging interface.""" + + def log_agent_start(self, text: str, **kwargs: Any) -> None: + """Log the start of an agent interaction.""" + pass + + def log_agent_end(self, text: str, **kwargs: Any) -> None: + """Log the end of an agent interaction.""" + pass + + def log_agent_action(self, action: AgentAction, **kwargs: Any) -> None: + """Log agent action decision.""" + pass + + def log_agent_observation(self, observation: str, **kwargs: Any) -> None: + """Log agent observation.""" + pass + + def log_llm_inputs(self, inputs: dict, prompt: str, **kwargs: Any) -> None: + """Log LLM inputs.""" + pass + + def log_llm_response(self, output: str, **kwargs: Any) -> None: + """Log LLM response.""" + pass + + +class StdOutLogger(BaseLogger): + """Interface for printing things to stdout.""" + + def log_agent_start(self, text: str, **kwargs: Any) -> None: + """Print the text to start the agent.""" + print_text(text) + + def log_agent_action( + self, action: AgentAction, color: Optional[str] = None, **kwargs: Any + ) -> None: + """Print the log of the action in a certain color.""" + print_text(action.log, color=color) + + def log_agent_observation( + self, + observation: str, + color: Optional[str] = None, + observation_prefix: Optional[str] = None, + llm_prefix: Optional[str] = None, + **kwargs: Any, + ) -> None: + """Print the observation in a special color.""" + print_text(f"\n{observation_prefix}") + print_text(observation, color=color) + print_text(f"\n{llm_prefix}") + + def log_llm_inputs(self, inputs: dict, prompt: str, **kwargs: Any) -> None: + """Print the prompt in green.""" + print("Prompt after formatting:") + print_text(prompt, color="green", end="\n") diff --git a/langchain/schema.py b/langchain/schema.py new file mode 100644 index 00000000000..67a64c01caf --- /dev/null +++ b/langchain/schema.py @@ -0,0 +1,11 @@ +"""Common schema objects.""" + +from typing import NamedTuple + + +class AgentAction(NamedTuple): + """Agent's action to take.""" + + tool: str + tool_input: str + log: str diff --git a/tests/unit_tests/test_input.py b/tests/unit_tests/test_input.py index 43dd3b080b8..5d26535fc62 100644 --- a/tests/unit_tests/test_input.py +++ b/tests/unit_tests/test_input.py @@ -3,7 +3,8 @@ import sys from io import StringIO -from langchain.input import ChainedInput, get_color_mapping +from langchain.agents.input import ChainedInput +from langchain.input import get_color_mapping def test_chained_input_not_verbose() -> None: @@ -18,11 +19,11 @@ def test_chained_input_not_verbose() -> None: old_stdout = sys.stdout sys.stdout = mystdout = StringIO() - chained_input.add("bar") + chained_input.add_observation("bar", "1", "2", None) sys.stdout = old_stdout output = mystdout.getvalue() assert output == "" - assert chained_input.input == "foobar" + assert chained_input.input == "foo\n1bar\n2" def test_chained_input_verbose() -> None: @@ -37,19 +38,19 @@ def test_chained_input_verbose() -> None: old_stdout = sys.stdout sys.stdout = mystdout = StringIO() - chained_input.add("bar") + chained_input.add_observation("bar", "1", "2", None) sys.stdout = old_stdout output = mystdout.getvalue() - assert output == "bar" - assert chained_input.input == "foobar" + assert output == "\n1bar\n2" + assert chained_input.input == "foo\n1bar\n2" old_stdout = sys.stdout sys.stdout = mystdout = StringIO() - chained_input.add("baz", color="blue") + chained_input.add_observation("baz", "3", "4", "blue") sys.stdout = old_stdout output = mystdout.getvalue() - assert output == "\x1b[36;1m\x1b[1;3mbaz\x1b[0m" - assert chained_input.input == "foobarbaz" + assert output == "\n3\x1b[36;1m\x1b[1;3mbaz\x1b[0m\n4" + assert chained_input.input == "foo\n1bar\n2\n3baz\n4" def test_get_color_mapping() -> None: