Compare commits

...

11 Commits

Author SHA1 Message Date
Harrison Chase f646c94bc1 cr 1 year ago
Harrison Chase ac208f85c8 agent refactor 1 year ago
Harrison Chase 85e7c5fd6c stash 1 year ago
Harrison Chase 3dd367cc60 merge with multi inputs 1 year ago
Harrison Chase 18d856822b merge 1 year ago
Harrison Chase c7c38dd3df Merge branch 'master' into harrison/agent_multi_inputs 1 year ago
Harrison Chase 27601a13c4 return agent intermediate steps 1 year ago
Harrison Chase 81383474c4 cr 1 year ago
Harrison Chase aacd417076 cr 1 year ago
Harrison Chase 16201f1d77 stash 1 year ago
Harrison Chase 440083fb35 agent multi inputs 1 year ago

@ -224,7 +224,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.10.8"
}
},
"nbformat": 4,

@ -46,7 +46,7 @@
" Tool(\n",
" name = \"Search\",\n",
" func=search.run,\n",
" description=\"useful for when you need to answer questions about current events\"\n",
" description=\"useful for when you need to answer questions about current events. You should ask targeted questions\"\n",
" ),\n",
" Tool(\n",
" name=\"Calculator\",\n",
@ -56,7 +56,7 @@
" Tool(\n",
" name=\"FooBar DB\",\n",
" func=db_chain.run,\n",
" description=\"useful for when you need to answer questions about FooBar. Input should be in the form of a question\"\n",
" description=\"useful for when you need to answer questions about FooBar. Input should be in the form of a question containing full context\"\n",
" )\n",
"]"
]
@ -81,40 +81,44 @@
"name": "stdout",
"output_type": "stream",
"text": [
"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Olivia Wilde's boyfriend\n",
"\n",
"\n",
"\u001b[1m> Entering new ZeroShotAgent chain...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out who Olivia Wilde's boyfriend is and then calculate his age raised to the 0.23 power.\n",
"Action: Search\n",
"Action Input: \"Olivia Wilde's boyfriend\"\u001b[0m\n",
"Action Input: \"Who is Olivia Wilde's boyfriend?\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mOlivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Harry Styles\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out Harry Styles' age.\n",
"Action: Search\n",
"Action Input: \"Harry Styles age\"\u001b[0m\n",
"Action Input: \"How old is Harry Styles?\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 28 to the 0.23 power\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 28 raised to the 0.23 power.\n",
"Action: Calculator\n",
"Action Input: 28^0.23\u001b[0m\n",
"\n",
"\u001b[1m> Entering new chain...\u001b[0m\n",
"\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
"28^0.23\u001b[32;1m\u001b[1;3m\n",
"\n",
"```python\n",
"print(28**0.23)\n",
"import math\n",
"print(math.pow(28, 0.23))\n",
"```\n",
"\u001b[0m\n",
"Answer: \u001b[33;1m\u001b[1;3m2.1520202182226886\n",
"\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\u001b[1m> Finished LLMMathChain chain.\u001b[0m\n",
"\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1520202182226886\n",
"\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
"Final Answer: 2.1520202182226886\u001b[0m"
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n",
"Final Answer: Harry Styles, Olivia Wilde's boyfriend, is 28 years old and his age raised to the 0.23 power is 2.1520202182226886.\u001b[0m\n",
"\u001b[1m> Finished ZeroShotAgent chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"'2.1520202182226886'"
"\"Harry Styles, Olivia Wilde's boyfriend, is 28 years old and his age raised to the 0.23 power is 2.1520202182226886.\""
]
},
"execution_count": 4,
@ -123,7 +127,7 @@
}
],
"source": [
"mrkl.run(\"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\")"
"mrkl.run(\"Who is Olivia Wilde's boyfriend? What is his current age raised to the 0.23 power?\")"
]
},
{
@ -136,43 +140,34 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Who recently released an album called 'The Storm Before the Calm' and are they in the FooBar database? If so, what albums of theirs are in the FooBar database?\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find an album called 'The Storm Before the Calm'\n",
"Action: Search\n",
"Action Input: \"The Storm Before the Calm album\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mThe Storm Before the Calm (stylized in all lowercase) is the tenth (and eighth international) studio album by Canadian-American singer-songwriter Alanis ...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to check if Alanis is in the FooBar database\n",
"Action: FooBar DB\n",
"Action Input: \"Does Alanis Morissette exist in the FooBar database?\"\u001b[0m\n",
"\n",
"\u001b[1m> Entering new chain...\u001b[0m\n",
"Does Alanis Morissette exist in the FooBar database?\n",
"SQLQuery:\u001b[32;1m\u001b[1;3m SELECT * FROM Artist WHERE Name = 'Alanis Morissette'\u001b[0m\n",
"SQLResult: \u001b[33;1m\u001b[1;3m[(4, 'Alanis Morissette')]\u001b[0m\n",
"Answer:\u001b[32;1m\u001b[1;3m Yes\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\n",
"Observation: \u001b[38;5;200m\u001b[1;3m Yes\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out what albums of Alanis's are in the FooBar database\n",
"\u001b[1m> Entering new ZeroShotAgent chain...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out the artist's full name and then search the FooBar database for their albums.\n",
"Action: Search\n",
"Action Input: \"The Storm Before the Calm\" artist\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mThe Storm Before the Calm (stylized in all lowercase) is the tenth (and eighth international) studio album by Canadian-American singer-songwriter Alanis ...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now need to search the FooBar database for Alanis Morissette's albums\n",
"Action: FooBar DB\n",
"Action Input: \"What albums by Alanis Morissette are in the FooBar database?\"\u001b[0m\n",
"Action Input: What albums by Alanis Morissette are in the FooBar database?\u001b[0m\n",
"\n",
"\u001b[1m> Entering new chain...\u001b[0m\n",
"What albums by Alanis Morissette are in the FooBar database?\n",
"SQLQuery:\u001b[32;1m\u001b[1;3m SELECT Album.Title FROM Album JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alanis Morissette'\u001b[0m\n",
"\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n",
"What albums by Alanis Morissette are in the FooBar database? \n",
"SQLQuery:\u001b[32;1m\u001b[1;3m SELECT Title FROM Album INNER JOIN Artist ON Album.ArtistId = Artist.ArtistId WHERE Artist.Name = 'Alanis Morissette';\u001b[0m\n",
"SQLResult: \u001b[33;1m\u001b[1;3m[('Jagged Little Pill',)]\u001b[0m\n",
"Answer:\u001b[32;1m\u001b[1;3m Jagged Little Pill\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"Answer:\u001b[32;1m\u001b[1;3m The album 'Jagged Little Pill' by Alanis Morissette is in the FooBar database.\u001b[0m\n",
"\u001b[1m> Finished SQLDatabaseChain chain.\u001b[0m\n",
"\n",
"Observation: \u001b[38;5;200m\u001b[1;3m Jagged Little Pill\u001b[0m\n",
"Observation: \u001b[38;5;200m\u001b[1;3m The album 'Jagged Little Pill' by Alanis Morissette is in the FooBar database.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
"Final Answer: The album is by Alanis Morissette and the albums in the FooBar database by her are Jagged Little Pill\u001b[0m"
"Final Answer: Alanis Morissette's album 'Jagged Little Pill' is in the FooBar database.\u001b[0m\n",
"\u001b[1m> Finished ZeroShotAgent chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"'The album is by Alanis Morissette and the albums in the FooBar database by her are Jagged Little Pill'"
"\"Alanis Morissette's album 'Jagged Little Pill' is in the FooBar database.\""
]
},
"execution_count": 5,
@ -181,13 +176,13 @@
}
],
"source": [
"mrkl.run(\"Who recently released an album called 'The Storm Before the Calm' and are they in the FooBar database? If so, what albums of theirs are in the FooBar database?\")"
"mrkl.run(\"What is the full name of the artist who recently released an album called 'The Storm Before the Calm' and are they in the FooBar database? If so, what albums of theirs are in the FooBar database?\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d7c2e6ac",
"id": "af016a70",
"metadata": {},
"outputs": [],
"source": []
@ -209,7 +204,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.10.8"
}
},
"nbformat": 4,

@ -12,7 +12,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 1,
"id": "4e272b47",
"metadata": {},
"outputs": [],
@ -38,7 +38,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 2,
"id": "8078c8f1",
"metadata": {},
"outputs": [
@ -49,7 +49,6 @@
"\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:\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",
@ -68,7 +67,7 @@
"'Bill Clinton'"
]
},
"execution_count": 7,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}

@ -12,7 +12,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 2,
"id": "7e3b513e",
"metadata": {},
"outputs": [
@ -23,8 +23,7 @@
"\n",
"\n",
"\u001b[1m> Entering new SelfAskWithSearchAgent chain...\u001b[0m\n",
"What is the hometown of the reigning men's U.S. Open champion?\n",
"Are follow up questions needed here:\u001b[32;1m\u001b[1;3m Yes.\n",
"\u001b[32;1m\u001b[1;3mAre follow up questions needed here: Yes.\n",
"Follow up: Who is the reigning men's U.S. Open champion?\u001b[0m\n",
"Intermediate answer: \u001b[36;1m\u001b[1;3mCarlos Alcaraz\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mFollow up: Where is Carlos Alcaraz from?\u001b[0m\n",
@ -39,7 +38,7 @@
"'El Palmar, Spain'"
]
},
"execution_count": 1,
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
@ -58,7 +57,6 @@
"]\n",
"\n",
"self_ask_with_search = initialize_agent(tools, llm, agent=\"self-ask-with-search\", verbose=True)\n",
"\n",
"self_ask_with_search.run(\"What is the hometown of the reigning men's U.S. Open champion?\")"
]
},
@ -87,7 +85,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.10.8"
}
},
"nbformat": 4,

@ -118,40 +118,40 @@
"name": "stdout",
"output_type": "stream",
"text": [
"What is the age of Olivia Wilde's boyfriend raised to the 0.23 power?\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Olivia Wilde's boyfriend\n",
"Action: Search\n",
"Action Input: \"Olivia Wilde's boyfriend\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mOlivia Wilde started dating Harry Styles after ending her years-long engagement to Jason Sudeikis — see their relationship timeline.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find the age of Harry Styles\n",
"\n",
"\n",
"\u001b[1m> Entering new ZeroShotAgent chain...\u001b[0m\n",
"How old is Olivia Wilde's boyfriend? What is that number raised to the 0.23 power?\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out how old Olivia Wilde's boyfriend is, and then use a calculator to calculate the power.\n",
"Action: Search\n",
"Action Input: \"Harry Styles age\"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to calculate 28 to the 0.23 power\n",
"Action Input: Olivia Wilde's boyfriend age\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mWhile Wilde, 37, and Styles, 27, have both kept a low profile when it comes to talking about their relationship, Wilde did address their ...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m Olivia Wilde's boyfriend is 27 years old.\n",
"Action: Calculator\n",
"Action Input: 28^0.23\u001b[0m\n",
"Action Input: 27^0.23\u001b[0m\n",
"\n",
"\u001b[1m> Entering new chain...\u001b[0m\n",
"28^0.23\u001b[32;1m\u001b[1;3m\n",
"\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
"27^0.23\u001b[32;1m\u001b[1;3m\n",
"\n",
"```python\n",
"print(28**0.23)\n",
"import math\n",
"print(math.pow(27, 0.23))\n",
"```\n",
"\u001b[0m\n",
"Answer: \u001b[33;1m\u001b[1;3m2.1520202182226886\n",
"Answer: \u001b[33;1m\u001b[1;3m2.1340945944237553\n",
"\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\u001b[1m> Finished LLMMathChain chain.\u001b[0m\n",
"\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1520202182226886\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1340945944237553\n",
"\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n",
"Final Answer: 2.1520202182226886\u001b[0m"
"Thought:\n",
"\u001b[1m> Finished ZeroShotAgent chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"'2.1520202182226886'"
"'2.1340945944237553'"
]
},
"execution_count": 4,
@ -165,10 +165,86 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "2f0852ff",
"metadata": {},
"outputs": [],
"source": [
"# We can also return the intermediate steps\n",
"llm = OpenAI(temperature=0)\n",
"agent = initialize_agent(tools, llm, agent=\"zero-shot-react-description\", verbose=True, return_intermediate_steps=True)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "837211e8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new ZeroShotAgent chain...\u001b[0m\n",
"How old is Olivia Wilde's boyfriend? What is that number raised to the 0.23 power?\n",
"Thought:\u001b[32;1m\u001b[1;3m I need to find out how old Olivia Wilde's boyfriend is, and then use a calculator to calculate the power.\n",
"Action: Search\n",
"Action Input: Olivia Wilde's boyfriend age\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mWhile Wilde, 37, and Styles, 27, have both kept a low profile when it comes to talking about their relationship, Wilde did address their ...\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3m Olivia Wilde's boyfriend is 27 years old.\n",
"Action: Calculator\n",
"Action Input: 27^0.23\u001b[0m\n",
"\n",
"\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
"27^0.23\u001b[32;1m\u001b[1;3m\n",
"\n",
"```python\n",
"import math\n",
"print(math.pow(27, 0.23))\n",
"```\n",
"\u001b[0m\n",
"Answer: \u001b[33;1m\u001b[1;3m2.1340945944237553\n",
"\u001b[0m\n",
"\u001b[1m> Finished LLMMathChain chain.\u001b[0m\n",
"\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 2.1340945944237553\n",
"\u001b[0m\n",
"Thought:\n",
"\u001b[1m> Finished ZeroShotAgent chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"{'input': \"How old is Olivia Wilde's boyfriend? What is that number raised to the 0.23 power?\",\n",
" 'output': '2.1340945944237553',\n",
" 'intermediate_steps': [{'log': \" I need to find out how old Olivia Wilde's boyfriend is, and then use a calculator to calculate the power.\\nAction: Search\\nAction Input: Olivia Wilde's boyfriend age\",\n",
" 'tool': 'Search',\n",
" 'tool_input': \"Olivia Wilde's boyfriend age\",\n",
" 'observation': 'While Wilde, 37, and Styles, 27, have both kept a low profile when it comes to talking about their relationship, Wilde did address their ...'},\n",
" {'log': \" Olivia Wilde's boyfriend is 27 years old.\\nAction: Calculator\\nAction Input: 27^0.23\",\n",
" 'tool': 'Calculator',\n",
" 'tool_input': '27^0.23',\n",
" 'observation': 'Answer: 2.1340945944237553\\n'}]}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent({\"input\":\"How old is Olivia Wilde's boyfriend? What is that number raised to the 0.23 power?\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9256ff6b",
"metadata": {},
"outputs": [],
"source": []
}
],
@ -188,7 +264,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.10.8"
}
},
"nbformat": 4,

@ -1,5 +1,5 @@
"""Routing chains."""
from langchain.agents.agent import Agent
from langchain.agents.agent import AgentWithTools
from langchain.agents.loading import initialize_agent
from langchain.agents.mrkl.base import MRKLChain, ZeroShotAgent
from langchain.agents.react.base import ReActChain, ReActTextWorldAgent
@ -10,7 +10,7 @@ __all__ = [
"MRKLChain",
"SelfAskWithSearchChain",
"ReActChain",
"Agent",
"AgentWithTools",
"Tool",
"initialize_agent",
"ZeroShotAgent",

@ -1,45 +1,101 @@
"""Chain that takes in an input and produces an action and action input."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, ClassVar, Dict, List, Optional, Tuple
from abc import abstractmethod
from typing import Any, Dict, List, Optional, Tuple, Union
from pydantic import BaseModel
from pydantic import BaseModel, root_validator
from langchain.agents.input import ChainedInput
import langchain
from langchain.agents.tools import Tool
from langchain.chains.base import Chain
from langchain.chains.llm import LLMChain
from langchain.input import get_color_mapping
from langchain.llms.base import LLM
from langchain.prompts.base import BasePromptTemplate
from langchain.schema import AgentAction
from langchain.schema import AgentAction, AgentFinish
class Agent(Chain, BaseModel, ABC):
"""Agent that uses an LLM."""
class Agent(BaseModel):
"""Class responsible for calling the language model and deciding the action.
This is driven by an LLMChain. The prompt in the LLMChain MUST include
a variable called "agent_scratchpad" where the agent can put its
intermediary work.
"""
prompt: ClassVar[BasePromptTemplate]
llm_chain: LLMChain
tools: List[Tool]
input_key: str = "input" #: :meta private:
output_key: str = "output" #: :meta private:
return_values: List[str] = ["output"]
@abstractmethod
def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]:
"""Extract tool and tool input from llm output."""
def _fix_text(self, text: str) -> str:
"""Fix the text."""
raise ValueError("fix_text not implemented for this agent.")
@property
def input_keys(self) -> List[str]:
"""Return the singular input key.
def _stop(self) -> List[str]:
return [f"\n{self.observation_prefix}"]
:meta private:
def plan(
self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any
) -> Union[AgentFinish, AgentAction]:
"""Given input, decided what to do.
Args:
thoughts: LLM thoughts
inputs: user inputs
Returns:
Action specifying what tool to use.
"""
return [self.input_key]
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\n{self.observation_prefix}{observation}\n{self.llm_prefix}"
new_inputs = {"agent_scratchpad": thoughts, "stop": self._stop}
full_inputs = {**kwargs, **new_inputs}
full_output = self.llm_chain.predict(**full_inputs)
parsed_output = self._extract_tool_and_input(full_output)
while parsed_output is None:
full_output = self._fix_text(full_output)
full_inputs["agent_scratchpad"] += full_output
output = self.llm_chain.predict(**full_inputs)
full_output += output
parsed_output = self._extract_tool_and_input(full_output)
tool, tool_input = parsed_output
if tool == self.finish_tool_name:
return AgentFinish({"output": tool_input}, full_output)
return AgentAction(tool, tool_input, full_output)
def prepare_for_new_call(self) -> None:
"""Prepare the agent for new call, if needed."""
pass
@property
def output_keys(self) -> List[str]:
"""Return the singular output key.
def finish_tool_name(self) -> str:
"""Name of the tool to use to finish the chain."""
return "Final Answer"
@property
def input_keys(self) -> List[str]:
"""Return the input keys.
:meta private:
"""
return [self.output_key]
return list(set(self.llm_chain.input_keys) - {"agent_scratchpad"})
@root_validator()
def validate_prompt(cls, values: Dict) -> Dict:
"""Validate that prompt matches format."""
prompt = values["llm_chain"].prompt
if "agent_scratchpad" not in prompt.input_variables:
raise ValueError(
"`agent_scratchpad` should be a variable in prompt.input_variables"
)
return values
@property
@abstractmethod
@ -51,27 +107,10 @@ class Agent(Chain, BaseModel, ABC):
def llm_prefix(self) -> str:
"""Prefix to append the LLM call with."""
@property
def finish_tool_name(self) -> str:
"""Name of the tool to use to finish the chain."""
return "Final Answer"
@property
def starter_string(self) -> str:
"""Put this string after user input but before first LLM call."""
return "\n"
@classmethod
@abstractmethod
def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]:
"""Extract tool and tool input from llm output."""
def _fix_text(self, text: str) -> str:
"""Fix the text."""
raise ValueError("fix_text not implemented for this agent.")
@property
def _stop(self) -> List[str]:
return [f"\n{self.observation_prefix}"]
def create_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
"""Create a prompt for this class."""
@classmethod
def _validate_tools(cls, tools: List[Tool]) -> None:
@ -79,71 +118,65 @@ class Agent(Chain, BaseModel, ABC):
pass
@classmethod
def create_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
"""Create a prompt for this class."""
return cls.prompt
def _prepare_for_new_call(self) -> None:
pass
@classmethod
def from_llm_and_tools(cls, llm: LLM, tools: List[Tool], **kwargs: Any) -> Agent:
def from_llm_and_tools(cls, llm: LLM, tools: List[Tool]) -> Agent:
"""Construct an agent from an LLM and tools."""
cls._validate_tools(tools)
llm_chain = LLMChain(llm=llm, prompt=cls.create_prompt(tools))
return cls(llm_chain=llm_chain, tools=tools, **kwargs)
return cls(llm_chain=llm_chain)
def get_action(self, text: str) -> AgentAction:
"""Given input, decided what to do.
Args:
text: input string
class AgentWithTools(Chain, BaseModel):
"""Consists of an agent using tools."""
Returns:
Action specifying what tool to use.
agent: Agent
tools: List[Tool]
return_intermediate_steps: bool = False
@property
def input_keys(self) -> List[str]:
"""Return the input keys.
:meta private:
"""
input_key = self.llm_chain.input_keys[0]
inputs = {input_key: text, "stop": self._stop}
full_output = self.llm_chain.predict(**inputs)
parsed_output = self._extract_tool_and_input(full_output)
while parsed_output is None:
full_output = self._fix_text(full_output)
inputs = {input_key: text + full_output, "stop": self._stop}
output = self.llm_chain.predict(**inputs)
full_output += output
parsed_output = self._extract_tool_and_input(full_output)
tool, tool_input = parsed_output
return AgentAction(tool, tool_input, full_output)
return self.agent.input_keys
def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
@property
def output_keys(self) -> List[str]:
"""Return the singular output key.
:meta private:
"""
if self.return_intermediate_steps:
return self.agent.return_values + ["intermediate_steps"]
else:
return self.agent.return_values
def _call(self, inputs: Dict[str, str]) -> Dict[str, Any]:
"""Run text through and get agent response."""
text = inputs[self.input_key]
# Do any preparation necessary when receiving a new input.
self._prepare_for_new_call()
self.agent.prepare_for_new_call()
# Construct a mapping of tool name to tool for easy lookup
name_to_tool_map = {tool.name: tool.func for tool in self.tools}
# Construct the initial string to pass into the LLM. This is made up
# of the user input, the special starter string, and then the LLM prefix.
# The starter string is a special string that may be used by a LLM to
# immediately follow the user input. The LLM prefix is a string that
# prompts the LLM to take an action.
starter_string = text + self.starter_string + self.llm_prefix
# We use the ChainedInput class to iteratively add to the input over time.
chained_input = ChainedInput(starter_string, verbose=self.verbose)
# We construct a mapping from each tool to a color, used for logging.
color_mapping = get_color_mapping(
[tool.name for tool in self.tools], excluded_colors=["green"]
)
intermediate_steps: List[Tuple[AgentAction, str]] = []
# We now enter the agent loop (until it returns something).
while True:
# 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_action(output, color="green")
output = self.agent.plan(intermediate_steps, **inputs)
# 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}
# Otherwise we lookup the tool
if isinstance(output, AgentFinish):
if self.verbose:
langchain.logger.log_agent_end(output, color="green")
final_output = output.return_values
if self.return_intermediate_steps:
final_output["intermediate_steps"] = intermediate_steps
return final_output
if self.verbose:
langchain.logger.log_agent_action(output, color="green")
# And then we lookup the tool
if output.tool in name_to_tool_map:
chain = name_to_tool_map[output.tool]
# We then call the tool on the tool input to get an observation
@ -152,10 +185,6 @@ class Agent(Chain, BaseModel, ABC):
else:
observation = f"{output.tool} is not a valid tool, try another one."
color = None
# We then log the observation
chained_input.add_observation(
observation,
self.observation_prefix,
self.llm_prefix,
color=color,
)
if self.verbose:
langchain.logger.log_agent_observation(observation, color=color)
intermediate_steps.append((output, observation))

@ -1,44 +0,0 @@
"""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

@ -1,7 +1,7 @@
"""Load agent."""
from typing import Any, List
from langchain.agents.agent import Agent
from langchain.agents.agent import AgentWithTools
from langchain.agents.mrkl.base import ZeroShotAgent
from langchain.agents.react.base import ReActDocstoreAgent
from langchain.agents.self_ask_with_search.base import SelfAskWithSearchAgent
@ -20,7 +20,7 @@ def initialize_agent(
llm: LLM,
agent: str = "zero-shot-react-description",
**kwargs: Any,
) -> Agent:
) -> AgentWithTools:
"""Load agent given tools and LLM.
Args:
@ -39,4 +39,5 @@ def initialize_agent(
f"Valid types are: {AGENT_TO_CLASS.keys()}."
)
agent_cls = AGENT_TO_CLASS[agent]
return agent_cls.from_llm_and_tools(llm, tools, **kwargs)
agent_obj = agent_cls.from_llm_and_tools(llm, tools)
return AgentWithTools(agent=agent_obj, tools=tools, **kwargs)

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any, Callable, List, NamedTuple, Optional, Tuple
from langchain.agents.agent import Agent
from langchain.agents.agent import Agent, AgentWithTools
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX
from langchain.agents.tools import Tool
from langchain.llms.base import LLM
@ -85,7 +85,7 @@ class ZeroShotAgent(Agent):
format_instructions = FORMAT_INSTRUCTIONS.format(tool_names=tool_names)
template = "\n\n".join([prefix, tool_strings, format_instructions, suffix])
if input_variables is None:
input_variables = ["input"]
input_variables = ["input", "agent_scratchpad"]
return PromptTemplate(template=template, input_variables=input_variables)
@classmethod
@ -101,7 +101,7 @@ class ZeroShotAgent(Agent):
return get_action_and_input(text)
class MRKLChain(ZeroShotAgent):
class MRKLChain(AgentWithTools):
"""Chain that implements the MRKL system.
Example:
@ -116,7 +116,9 @@ class MRKLChain(ZeroShotAgent):
"""
@classmethod
def from_chains(cls, llm: LLM, chains: List[ChainConfig], **kwargs: Any) -> Agent:
def from_chains(
cls, llm: LLM, chains: List[ChainConfig], **kwargs: Any
) -> AgentWithTools:
"""User friendly way to initialize the MRKL chain.
This is intended to be an easy way to get up and running with the
@ -156,4 +158,5 @@ class MRKLChain(ZeroShotAgent):
Tool(name=c.action_name, func=c.action, description=c.action_description)
for c in chains
]
return cls.from_llm_and_tools(llm, tools, **kwargs)
agent = ZeroShotAgent.from_llm_and_tools(llm, tools)
return cls(agent=agent, tools=tools, **kwargs)

@ -12,4 +12,5 @@ Thought: I now know the final answer
Final Answer: the final answer to the original input question"""
SUFFIX = """Begin!
Question: {input}"""
Question: {input}
Thought:{agent_scratchpad}"""

@ -1,14 +1,13 @@
"""Chain that implements the ReAct paper from https://arxiv.org/pdf/2210.03629.pdf."""
import re
from typing import Any, ClassVar, List, Optional, Tuple
from typing import Any, List, Optional, Tuple
from pydantic import BaseModel
from langchain.agents.agent import Agent
from langchain.agents.agent import Agent, AgentWithTools
from langchain.agents.react.textworld_prompt import TEXTWORLD_PROMPT
from langchain.agents.react.wiki_prompt import WIKI_PROMPT
from langchain.agents.tools import Tool
from langchain.chains.llm import LLMChain
from langchain.docstore.base import Docstore
from langchain.docstore.document import Document
from langchain.llms.base import LLM
@ -18,7 +17,10 @@ from langchain.prompts.base import BasePromptTemplate
class ReActDocstoreAgent(Agent, BaseModel):
"""Agent for the ReAct chin."""
prompt: ClassVar[BasePromptTemplate] = WIKI_PROMPT
@classmethod
def create_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
"""Return default prompt."""
return WIKI_PROMPT
i: int = 1
@ -100,9 +102,10 @@ class DocstoreExplorer:
class ReActTextWorldAgent(ReActDocstoreAgent, BaseModel):
"""Agent for the ReAct TextWorld chain."""
prompt: ClassVar[BasePromptTemplate] = TEXTWORLD_PROMPT
i: int = 1
@classmethod
def create_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
"""Return default prompt."""
return TEXTWORLD_PROMPT
@classmethod
def _validate_tools(cls, tools: List[Tool]) -> None:
@ -113,7 +116,7 @@ class ReActTextWorldAgent(ReActDocstoreAgent, BaseModel):
raise ValueError(f"Tool name should be Play, got {tool_names}")
class ReActChain(ReActDocstoreAgent):
class ReActChain(AgentWithTools):
"""Chain that implements the ReAct paper.
Example:
@ -130,5 +133,5 @@ class ReActChain(ReActDocstoreAgent):
Tool(name="Search", func=docstore_explorer.search),
Tool(name="Lookup", func=docstore_explorer.lookup),
]
llm_chain = LLMChain(llm=llm, prompt=WIKI_PROMPT)
super().__init__(llm_chain=llm_chain, tools=tools, **kwargs)
agent = ReActDocstoreAgent.from_llm_and_tools(llm, tools)
super().__init__(agent=agent, tools=tools, **kwargs)

@ -44,6 +44,9 @@ Action 4: Finish[yes]
"""
]
SUFFIX = """\n\nSetup: {input}"""
SUFFIX = """\n\nSetup: {input}
{agent_scratchpad}"""
TEXTWORLD_PROMPT = PromptTemplate.from_examples(EXAMPLES, SUFFIX, ["input"])
TEXTWORLD_PROMPT = PromptTemplate.from_examples(
EXAMPLES, SUFFIX, ["input", "agent_scratchpad"]
)

@ -107,6 +107,9 @@ Thought 3: Leonid Levin is a mathematician and computer scientist. So Pavel Urys
and Leonid Levin have the same type of work.
Action 3: Finish[yes]""",
]
SUFFIX = """\n\nQuestion: {input}"""
SUFFIX = """\n\nQuestion: {input}
{agent_scratchpad}"""
WIKI_PROMPT = PromptTemplate.from_examples(EXAMPLES, SUFFIX, ["input"])
WIKI_PROMPT = PromptTemplate.from_examples(
EXAMPLES, SUFFIX, ["input", "agent_scratchpad"]
)

@ -1,10 +1,9 @@
"""Chain that does self ask with search."""
from typing import Any, ClassVar, List, Optional, Tuple
from typing import Any, List, Optional, Tuple
from langchain.agents.agent import Agent
from langchain.agents.agent import Agent, AgentWithTools
from langchain.agents.self_ask_with_search.prompt import PROMPT
from langchain.agents.tools import Tool
from langchain.chains.llm import LLMChain
from langchain.llms.base import LLM
from langchain.prompts.base import BasePromptTemplate
from langchain.serpapi import SerpAPIWrapper
@ -13,7 +12,10 @@ from langchain.serpapi import SerpAPIWrapper
class SelfAskWithSearchAgent(Agent):
"""Agent for the self-ask-with-search paper."""
prompt: ClassVar[BasePromptTemplate] = PROMPT
@classmethod
def create_prompt(cls, tools: List[Tool]) -> BasePromptTemplate:
"""Prompt does not depend on tools."""
return PROMPT
@classmethod
def _validate_tools(cls, tools: List[Tool]) -> None:
@ -58,10 +60,10 @@ class SelfAskWithSearchAgent(Agent):
@property
def starter_string(self) -> str:
"""Put this string after user input but before first LLM call."""
return "\nAre follow up questions needed here:"
return "Are follow up questions needed here:"
class SelfAskWithSearchChain(SelfAskWithSearchAgent):
class SelfAskWithSearchChain(AgentWithTools):
"""Chain that does self ask with search.
Example:
@ -75,5 +77,5 @@ class SelfAskWithSearchChain(SelfAskWithSearchAgent):
def __init__(self, llm: LLM, search_chain: SerpAPIWrapper, **kwargs: Any):
"""Initialize with just an LLM and a search chain."""
search_tool = Tool(name="Intermediate Answer", func=search_chain.run)
llm_chain = LLMChain(llm=llm, prompt=PROMPT)
super().__init__(llm_chain=llm_chain, tools=[search_tool], **kwargs)
agent = SelfAskWithSearchAgent.from_llm_and_tools(llm, [search_tool])
super().__init__(agent=agent, tools=[search_tool], **kwargs)

@ -37,5 +37,8 @@ Follow up: Where is Martin Campbell from?
Intermediate answer: New Zealand.
So the final answer is: No
Question: {input}"""
PROMPT = PromptTemplate(input_variables=["input"], template=_DEFAULT_TEMPLATE)
Question: {input}
Are followup questions needed here:{agent_scratchpad}"""
PROMPT = PromptTemplate(
input_variables=["input", "agent_scratchpad"], template=_DEFAULT_TEMPLATE
)

@ -75,7 +75,7 @@ class Chain(BaseModel, ABC):
def __call__(
self, inputs: Union[Dict[str, Any], Any], return_only_outputs: bool = False
) -> Dict[str, str]:
) -> Dict[str, Any]:
"""Run the logic of this chain and add to output if desired.
Args:

@ -2,7 +2,7 @@
from typing import Any, Optional
from langchain.input import print_text
from langchain.schema import AgentAction
from langchain.schema import AgentAction, AgentFinish
class BaseLogger:
@ -12,7 +12,7 @@ class BaseLogger:
"""Log the start of an agent interaction."""
pass
def log_agent_end(self, text: str, **kwargs: Any) -> None:
def log_agent_end(self, finish: AgentFinish, **kwargs: Any) -> None:
"""Log the end of an agent interaction."""
pass

@ -11,6 +11,13 @@ class AgentAction(NamedTuple):
log: str
class AgentFinish(NamedTuple):
"""Agent's return value."""
return_values: dict
log: str
class Generation(NamedTuple):
"""Output of a single generation."""

@ -10,6 +10,7 @@ from langchain.docstore.base import Docstore
from langchain.docstore.document import Document
from langchain.llms.base import LLM
from langchain.prompts.prompt import PromptTemplate
from langchain.schema import AgentAction
_PAGE_CONTENT = """This is a page about LangChain.
@ -61,10 +62,9 @@ def test_predict_until_observation_normal() -> None:
Tool("Lookup", lambda x: x),
]
agent = ReActDocstoreAgent.from_llm_and_tools(fake_llm, tools)
output = agent.get_action("")
assert output.log == outputs[0]
assert output.tool == "Search"
assert output.tool_input == "foo"
output = agent.plan([], input="")
expected_output = AgentAction("Search", "foo", outputs[0])
assert output == expected_output
def test_predict_until_observation_repeat() -> None:
@ -76,10 +76,9 @@ def test_predict_until_observation_repeat() -> None:
Tool("Lookup", lambda x: x),
]
agent = ReActDocstoreAgent.from_llm_and_tools(fake_llm, tools)
output = agent.get_action("")
assert output.log == "foo\nAction 1: Search[foo]"
assert output.tool == "Search"
assert output.tool_input == "foo"
output = agent.plan([], input="")
expected_output = AgentAction("Search", "foo", "foo\nAction 1: Search[foo]")
assert output == expected_output
def test_react_chain() -> None:

@ -1,75 +0,0 @@
"""Test input manipulating logic."""
import sys
from io import StringIO
from langchain.agents.input import ChainedInput
from langchain.input import get_color_mapping
def test_chained_input_not_verbose() -> None:
"""Test chained input logic."""
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
chained_input = ChainedInput("foo")
sys.stdout = old_stdout
output = mystdout.getvalue()
assert output == ""
assert chained_input.input == "foo"
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
chained_input.add_observation("bar", "1", "2", None)
sys.stdout = old_stdout
output = mystdout.getvalue()
assert output == ""
assert chained_input.input == "foo\n1bar\n2"
def test_chained_input_verbose() -> None:
"""Test chained input logic, making sure verbose doesn't mess it up."""
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
chained_input = ChainedInput("foo", verbose=True)
sys.stdout = old_stdout
output = mystdout.getvalue()
assert output == "foo"
assert chained_input.input == "foo"
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
chained_input.add_observation("bar", "1", "2", None)
sys.stdout = old_stdout
output = mystdout.getvalue()
assert output == "\n1bar\n2"
assert chained_input.input == "foo\n1bar\n2"
old_stdout = sys.stdout
sys.stdout = mystdout = StringIO()
chained_input.add_observation("baz", "3", "4", "blue")
sys.stdout = old_stdout
output = mystdout.getvalue()
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:
"""Test getting of color mapping."""
# Test on few inputs.
items = ["foo", "bar"]
output = get_color_mapping(items)
expected_output = {"foo": "blue", "bar": "yellow"}
assert output == expected_output
# Test on a lot of inputs.
items = [f"foo-{i}" for i in range(20)]
output = get_color_mapping(items)
assert len(output) == 20
def test_get_color_mapping_excluded_colors() -> None:
"""Test getting of color mapping with excluded colors."""
items = ["foo", "bar"]
output = get_color_mapping(items, excluded_colors=["blue"])
expected_output = {"foo": "yellow", "bar": "pink"}
assert output == expected_output
Loading…
Cancel
Save