From 035aed8dc94352ab38d7db092a38152677e58935 Mon Sep 17 00:00:00 2001 From: Harrison Chase Date: Sun, 2 Apr 2023 09:12:54 -0700 Subject: [PATCH] Harrison/base agent (#2137) --- docs/modules/agents/agents/custom_agent.ipynb | 282 +++---------- .../agents/agents/custom_llm_agent.ipynb | 388 ++++++++++++++++++ .../agents/agents/custom_mrkl_agent.ipynb | 348 ++++++++++++++++ langchain/agents/__init__.py | 11 +- 4 files changed, 801 insertions(+), 228 deletions(-) create mode 100644 docs/modules/agents/agents/custom_llm_agent.ipynb create mode 100644 docs/modules/agents/agents/custom_mrkl_agent.ipynb diff --git a/docs/modules/agents/agents/custom_agent.ipynb b/docs/modules/agents/agents/custom_agent.ipynb index ae61870f..08a7bbb8 100644 --- a/docs/modules/agents/agents/custom_agent.ipynb +++ b/docs/modules/agents/agents/custom_agent.ipynb @@ -12,48 +12,26 @@ "An agent consists of three parts:\n", " \n", " - Tools: The tools the agent has available to use.\n", - " - LLMChain: The LLMChain that produces the text that is parsed in a certain way to determine which action to take.\n", - " - The agent class itself: this parses the output of the LLMChain to determine which action to take.\n", + " - The agent class itself: this decides which action to take.\n", " \n", " \n", - "In this notebook we walk through two types of custom agents. The first type shows how to create a custom LLMChain, but still use an existing agent class to parse the output. The second shows how to create a custom agent class." - ] - }, - { - "cell_type": "markdown", - "id": "6064f080", - "metadata": {}, - "source": [ - "### Custom LLMChain\n", - "\n", - "The first way to create a custom agent is to use an existing Agent class, but use a custom LLMChain. This is the simplest way to create a custom Agent. It is highly reccomended that you work with the `ZeroShotAgent`, as at the moment that is by far the most generalizable one. \n", - "\n", - "Most of the work in creating the custom LLMChain comes down to the prompt. Because we are using an existing agent class to parse the output, it is very important that the prompt say to produce text in that format. Additionally, we currently require an `agent_scratchpad` input variable to put notes on previous actions and observations. This should almost always be the final part of the prompt. However, besides those instructions, you can customize the prompt as you wish.\n", - "\n", - "To ensure that the prompt contains the appropriate instructions, we will utilize a helper method on that class. The helper method for the `ZeroShotAgent` takes the following arguments:\n", - "\n", - "- tools: List of tools the agent will have access to, used to format the prompt.\n", - "- prefix: String to put before the list of tools.\n", - "- suffix: String to put after the list of tools.\n", - "- input_variables: List of input variables the final prompt will expect.\n", - "\n", - "For this exercise, we will give our agent access to Google Search, and we will customize it in that we will have it answer as a pirate." + "In this notebook we walk through how to create a custom agent." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 1, "id": "9af9734e", "metadata": {}, "outputs": [], "source": [ - "from langchain.agents import ZeroShotAgent, Tool, AgentExecutor\n", - "from langchain import OpenAI, SerpAPIWrapper, LLMChain" + "from langchain.agents import Tool, AgentExecutor, BaseSingleActionAgent\n", + "from langchain import OpenAI, SerpAPIWrapper" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 2, "id": "becda2a1", "metadata": {}, "outputs": [], @@ -63,110 +41,73 @@ " 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\",\n", + " return_direct=True\n", " )\n", "]" ] }, { "cell_type": "code", - "execution_count": 25, - "id": "339b1bb8", + "execution_count": 4, + "id": "a33e2f7e", "metadata": {}, "outputs": [], "source": [ - "prefix = \"\"\"Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\"\"\"\n", - "suffix = \"\"\"Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Args\"\n", + "from typing import List, Tuple, Any, Union\n", + "from langchain.schema import AgentAction, AgentFinish\n", "\n", - "Question: {input}\n", - "{agent_scratchpad}\"\"\"\n", + "class FakeAgent(BaseSingleActionAgent):\n", + " \"\"\"Fake Custom Agent.\"\"\"\n", + " \n", + " @property\n", + " def input_keys(self):\n", + " return [\"input\"]\n", + " \n", + " def plan(\n", + " self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any\n", + " ) -> Union[AgentAction, AgentFinish]:\n", + " \"\"\"Given input, decided what to do.\n", "\n", - "prompt = ZeroShotAgent.create_prompt(\n", - " tools, \n", - " prefix=prefix, \n", - " suffix=suffix, \n", - " input_variables=[\"input\", \"agent_scratchpad\"]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "59db7b58", - "metadata": {}, - "source": [ - "In case we are curious, we can now take a look at the final prompt template to see what it looks like when its all put together." + " Args:\n", + " intermediate_steps: Steps the LLM has taken to date,\n", + " along with observations\n", + " **kwargs: User inputs.\n", + "\n", + " Returns:\n", + " Action specifying what tool to use.\n", + " \"\"\"\n", + " return AgentAction(tool=\"Search\", tool_input=\"foo\", log=\"\")\n", + "\n", + " async def aplan(\n", + " self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any\n", + " ) -> Union[AgentAction, AgentFinish]:\n", + " \"\"\"Given input, decided what to do.\n", + "\n", + " Args:\n", + " intermediate_steps: Steps the LLM has taken to date,\n", + " along with observations\n", + " **kwargs: User inputs.\n", + "\n", + " Returns:\n", + " Action specifying what tool to use.\n", + " \"\"\"\n", + " return AgentAction(tool=\"Search\", tool_input=\"foo\", log=\"\")" ] }, { "cell_type": "code", - "execution_count": 26, - "id": "e21d2098", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\n", - "\n", - "Search: useful for when you need to answer questions about current events\n", - "\n", - "Use the following format:\n", - "\n", - "Question: the input question you must answer\n", - "Thought: you should always think about what to do\n", - "Action: the action to take, should be one of [Search]\n", - "Action Input: the input to the action\n", - "Observation: the result of the action\n", - "... (this Thought/Action/Action Input/Observation can repeat N times)\n", - "Thought: I now know the final answer\n", - "Final Answer: the final answer to the original input question\n", - "\n", - "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Args\"\n", - "\n", - "Question: {input}\n", - "{agent_scratchpad}\n" - ] - } - ], - "source": [ - "print(prompt.template)" - ] - }, - { - "cell_type": "markdown", - "id": "5e028e6d", - "metadata": {}, - "source": [ - "Note that we are able to feed agents a self-defined prompt template, i.e. not restricted to the prompt generated by the `create_prompt` function, assuming it meets the agent's requirements. \n", - "\n", - "For example, for `ZeroShotAgent`, we will need to ensure that it meets the following requirements. There should a string starting with \"Action:\" and a following string starting with \"Action Input:\", and both should be separated by a newline.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "9b1cc2a2", + "execution_count": 5, + "id": "655d72f6", "metadata": {}, "outputs": [], "source": [ - "llm_chain = LLMChain(llm=OpenAI(temperature=0), prompt=prompt)" + "agent = FakeAgent()" ] }, { "cell_type": "code", - "execution_count": 28, - "id": "e4f5092f", - "metadata": {}, - "outputs": [], - "source": [ - "tool_names = [tool.name for tool in tools]\n", - "agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, + "execution_count": 6, "id": "490604e9", "metadata": {}, "outputs": [], @@ -176,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 7, "id": "653b1617", "metadata": {}, "outputs": [ @@ -187,12 +128,7 @@ "\n", "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mThought: I need to find out the population of Canada\n", - "Action: Search\n", - "Action Input: Population of Canada 2023\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mThe current population of Canada is 38,610,447 as of Saturday, February 18, 2023, based on Worldometer elaboration of the latest United Nations data. Canada 2020 population is estimated at 37,742,154 people at mid year according to UN data.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: Arrr, Canada be havin' 38,610,447 scallywags livin' there as of 2023!\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m\u001b[0m\u001b[36;1m\u001b[1;3mFoo Fighters is an American rock band formed in Seattle in 1994. Foo Fighters was initially formed as a one-man project by former Nirvana drummer Dave Grohl. Following the success of the 1995 eponymous debut album, Grohl recruited a band consisting of Nate Mendel, William Goldsmith, and Pat Smear.\u001b[0m\u001b[32;1m\u001b[1;3m\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -200,10 +136,10 @@ { "data": { "text/plain": [ - "\"Arrr, Canada be havin' 38,610,447 scallywags livin' there as of 2023!\"" + "'Foo Fighters is an American rock band formed in Seattle in 1994. Foo Fighters was initially formed as a one-man project by former Nirvana drummer Dave Grohl. Following the success of the 1995 eponymous debut album, Grohl recruited a band consisting of Nate Mendel, William Goldsmith, and Pat Smear.'" ] }, - "execution_count": 31, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -212,114 +148,6 @@ "agent_executor.run(\"How many people live in canada as of 2023?\")" ] }, - { - "cell_type": "markdown", - "id": "040eb343", - "metadata": {}, - "source": [ - "### Multiple inputs\n", - "Agents can also work with prompts that require multiple inputs." - ] - }, - { - "cell_type": "code", - "execution_count": 32, - "id": "43dbfa2f", - "metadata": {}, - "outputs": [], - "source": [ - "prefix = \"\"\"Answer the following questions as best you can. You have access to the following tools:\"\"\"\n", - "suffix = \"\"\"When answering, you MUST speak in the following language: {language}.\n", - "\n", - "Question: {input}\n", - "{agent_scratchpad}\"\"\"\n", - "\n", - "prompt = ZeroShotAgent.create_prompt(\n", - " tools, \n", - " prefix=prefix, \n", - " suffix=suffix, \n", - " input_variables=[\"input\", \"language\", \"agent_scratchpad\"]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 33, - "id": "0f087313", - "metadata": {}, - "outputs": [], - "source": [ - "llm_chain = LLMChain(llm=OpenAI(temperature=0), prompt=prompt)" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "id": "92c75a10", - "metadata": {}, - "outputs": [], - "source": [ - "agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools)" - ] - }, - { - "cell_type": "code", - "execution_count": 35, - "id": "ac5b83bf", - "metadata": {}, - "outputs": [], - "source": [ - "agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 36, - "id": "c960e4ff", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mThought: I need to find out the population of Canada in 2023.\n", - "Action: Search\n", - "Action Input: Population of Canada in 2023\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mThe current population of Canada is 38,610,447 as of Saturday, February 18, 2023, based on Worldometer elaboration of the latest United Nations data. Canada 2020 population is estimated at 37,742,154 people at mid year according to UN data.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", - "Final Answer: La popolazione del Canada nel 2023 è stimata in 38.610.447 persone.\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "'La popolazione del Canada nel 2023 è stimata in 38.610.447 persone.'" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "agent_executor.run(input=\"How many people live in canada as of 2023?\", language=\"italian\")" - ] - }, - { - "cell_type": "markdown", - "id": "90171b2b", - "metadata": {}, - "source": [ - "### Custom Agent Class\n", - "\n", - "Coming soon." - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/docs/modules/agents/agents/custom_llm_agent.ipynb b/docs/modules/agents/agents/custom_llm_agent.ipynb new file mode 100644 index 00000000..7951fd5e --- /dev/null +++ b/docs/modules/agents/agents/custom_llm_agent.ipynb @@ -0,0 +1,388 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ba5f8741", + "metadata": {}, + "source": [ + "# Custom LLM Agent\n", + "\n", + "This notebook goes through how to create your own custom LLM agent.\n", + "\n", + "An LLM agent consists of three parts:\n", + "\n", + "- PromptTemplate: This is the prompt template that can be used to instruct the language model on what to do\n", + "- LLM: This is the language model that powers the agent\n", + "- `stop` sequence: Instructs the LLM to stop generating as soon as this string is found\n", + "- OutputParser: This determines how to parse the LLMOutput into an AgentAction or AgentFinish object\n", + "\n", + "\n", + "The LLMAgent is used in an AgentExecutor. This AgentExecutor can largely be thought of as a loop that:\n", + "1. Passes user input and any previous steps to the Agent (in this case, the LLMAgent)\n", + "2. If the Agent returns an `AgentFinish`, then return that directly to the user\n", + "3. If the Agent returns an `AgentAction`, then use that to call a tool and get an `Observation`\n", + "4. Repeat, passing the `AgentAction` and `Observation` back to the Agent until an `AgentFinish` is emitted.\n", + " \n", + "`AgentAction` is a response that consists of `action` and `action_input`. `action` refers to which tool to use, and `action_input` refers to the input to that tool. `log` can also be provided as more context (that can be used for logging, tracing, etc).\n", + "\n", + "`AgentFinish` is a response that contains the final message to be sent back to the user. This should be used to end an agent run.\n", + " \n", + "In this notebook we walk through how to create a custom LLM agent." + ] + }, + { + "cell_type": "markdown", + "id": "fea4812c", + "metadata": {}, + "source": [ + "## Set up environment\n", + "\n", + "Do necessary imports, etc." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9af9734e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser\n", + "from langchain.prompts import StringPromptTemplate\n", + "from langchain import OpenAI, SerpAPIWrapper, LLMChain\n", + "from typing import List, Union\n", + "from langchain.schema import AgentAction, AgentFinish\n", + "import re" + ] + }, + { + "cell_type": "markdown", + "id": "6df0253f", + "metadata": {}, + "source": [ + "# Set up tool\n", + "\n", + "Set up any tools the agent may want to use. This may be necessary to put in the prompt (so that the agent knows to use these tools)." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "becda2a1", + "metadata": {}, + "outputs": [], + "source": [ + "# Define which tools the agent can use to answer user queries\n", + "search = SerpAPIWrapper()\n", + "tools = [\n", + " Tool(\n", + " name = \"Search\",\n", + " func=search.run,\n", + " description=\"useful for when you need to answer questions about current events\"\n", + " )\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "2e7a075c", + "metadata": {}, + "source": [ + "## Prompt Teplate\n", + "\n", + "This instructs the agent on what to do. Generally, the template should incorporate:\n", + " \n", + "- `tools`: which tools the agent has access and how and when to call them.\n", + "- `intermediate_steps`: These are tuples of previous (`AgentAction`, `Observation`) pairs. These are generally not passed directly to the model, but the prompt template formats them in a specific way.\n", + "- `input`: generic user input" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "339b1bb8", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up the base template\n", + "template = \"\"\"Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\n", + "\n", + "{tools}\n", + "\n", + "Use the following format:\n", + "\n", + "Question: the input question you must answer\n", + "Thought: you should always think about what to do\n", + "Action: the action to take, should be one of [{tool_names}]\n", + "Action Input: the input to the action\n", + "Observation: the result of the action\n", + "... (this Thought/Action/Action Input/Observation can repeat N times)\n", + "Thought: I now know the final answer\n", + "Final Answer: the final answer to the original input question\n", + "\n", + "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Arg\"s\n", + "\n", + "Question: {input}\n", + "{agent_scratchpad}\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "fd969d31", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a prompt template\n", + "class CustomPromptTemplate(StringPromptTemplate):\n", + " # The template to use\n", + " template: str\n", + " # The list of tools available\n", + " tools: List[Tool]\n", + " \n", + " def format(self, **kwargs) -> str:\n", + " # Get the intermediate steps (AgentAction, Observation tuples)\n", + " # Format them in a particular way\n", + " intermediate_steps = kwargs.pop(\"intermediate_steps\")\n", + " thoughts = \"\"\n", + " for action, observation in intermediate_steps:\n", + " thoughts += action.log\n", + " thoughts += f\"\\nObservation: {observation}\\nThought: \"\n", + " # Set the agent_scratchpad variable to that value\n", + " kwargs[\"agent_scratchpad\"] = thoughts\n", + " # Create a tools variable from the list of tools provided\n", + " kwargs[\"tools\"] = \"\\n\".join([f\"{tool.name}: {tool.description}\" for tool in self.tools])\n", + " # Create a list of tool names for the tools provided\n", + " kwargs[\"tool_names\"] = \", \".join([tool.name for tool in self.tools])\n", + " return self.template.format(**kwargs)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "798ef9fb", + "metadata": {}, + "outputs": [], + "source": [ + "prompt = CustomPromptTemplate(\n", + " template=template,\n", + " tools=tools,\n", + " # This omits the `agent_scratchpad`, `tools`, and `tool_names` variables because those are generated dynamically\n", + " # This includes the `intermediate_steps` variable because that is needed\n", + " input_variables=[\"input\", \"intermediate_steps\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ef3a1af3", + "metadata": {}, + "source": [ + "## Output Parser\n", + "\n", + "The output parser is responsible for parsing the LLM output into `AgentAction` and `AgentFinish`. This usually depends heavily on the prompt used.\n", + "\n", + "This is where you can change the parsing to do retries, handle whitespace, etc" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "7c6fe0d3", + "metadata": {}, + "outputs": [], + "source": [ + "class CustomOutputParser(AgentOutputParser):\n", + " \n", + " def parse(self, llm_output: str) -> Union[AgentAction, AgentFinish]:\n", + " # Check if agent should finish\n", + " if \"Final Answer:\" in llm_output:\n", + " return AgentFinish(\n", + " # Return values is generally always a dictionary with a single `output` key\n", + " # It is not recommended to try anything else at the moment :)\n", + " return_values={\"output\": llm_output.split(\"Final Answer:\")[-1].strip()},\n", + " log=llm_output,\n", + " )\n", + " # Parse out the action and action input\n", + " regex = r\"Action: (.*?)[\\n]*Action Input:[\\s]*(.*)\"\n", + " match = re.search(regex, llm_output, re.DOTALL)\n", + " if not match:\n", + " raise ValueError(f\"Could not parse LLM output: `{llm_output}`\")\n", + " action = match.group(1).strip()\n", + " action_input = match.group(2)\n", + " # Return the action and action input\n", + " return AgentAction(tool=action, tool_input=action_input.strip(\" \").strip('\"'), log=llm_output)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d278706a", + "metadata": {}, + "outputs": [], + "source": [ + "output_parser = CustomOutputParser()" + ] + }, + { + "cell_type": "markdown", + "id": "170587b1", + "metadata": {}, + "source": [ + "## Set up LLM\n", + "\n", + "Choose the LLM you want to use!" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f9d4c374", + "metadata": {}, + "outputs": [], + "source": [ + "llm = OpenAI(temperature=0)" + ] + }, + { + "cell_type": "markdown", + "id": "caeab5e4", + "metadata": {}, + "source": [ + "## Define the stop sequence\n", + "\n", + "This is important because it tells the LLM when to stop generation.\n", + "\n", + "This depends heavily on the prompt and model you are using. Generally, you want this to be whatever token you use in the prompt to denote the start of an `Observation` (otherwise, the LLM may hallucinate an observation for you)." + ] + }, + { + "cell_type": "markdown", + "id": "34be9f65", + "metadata": {}, + "source": [ + "## Set up the Agent\n", + "\n", + "We can now combine everything to set up our agent" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9b1cc2a2", + "metadata": {}, + "outputs": [], + "source": [ + "# LLM chain consisting of the LLM and a prompt\n", + "llm_chain = LLMChain(llm=llm, prompt=prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "e4f5092f", + "metadata": {}, + "outputs": [], + "source": [ + "tool_names = [tool.name for tool in tools]\n", + "agent = LLMSingleActionAgent(\n", + " llm_chain=llm_chain, \n", + " output_parser=output_parser,\n", + " stop=[\"\\nObservation:\"], \n", + " allowed_tools=tool_names\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "aa8a5326", + "metadata": {}, + "source": [ + "## Use the Agent\n", + "\n", + "Now we can use it!" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "490604e9", + "metadata": {}, + "outputs": [], + "source": [ + "agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "653b1617", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mAction: Search\n", + "Action Input: Population of Canada in 2023\u001b[0m\n", + "\n", + "Observation:\u001b[36;1m\u001b[1;3m38,648,380\u001b[0m\u001b[32;1m\u001b[1;3m That's a lot of people!\n", + "Final Answer: Arrr, there be 38,648,380 people livin' in Canada come 2023!\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "\"Arrr, there be 38,648,380 people livin' in Canada come 2023!\"" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.run(\"How many people live in canada as of 2023?\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adefb4c2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + }, + "vscode": { + "interpreter": { + "hash": "18784188d7ecd866c0586ac068b02361a6896dc3a29b64f5cc957f09c590acef" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/modules/agents/agents/custom_mrkl_agent.ipynb b/docs/modules/agents/agents/custom_mrkl_agent.ipynb new file mode 100644 index 00000000..1892b5be --- /dev/null +++ b/docs/modules/agents/agents/custom_mrkl_agent.ipynb @@ -0,0 +1,348 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ba5f8741", + "metadata": {}, + "source": [ + "# Custom MRKL Agent\n", + "\n", + "This notebook goes through how to create your own custom MRKL agent.\n", + "\n", + "A MRKL agent consists of three parts:\n", + " \n", + " - Tools: The tools the agent has available to use.\n", + " - LLMChain: The LLMChain that produces the text that is parsed in a certain way to determine which action to take.\n", + " - The agent class itself: this parses the output of the LLMChain to determin which action to take.\n", + " \n", + " \n", + "In this notebook we walk through how to create a custom MRKL agent by creating a custom LLMChain." + ] + }, + { + "cell_type": "markdown", + "id": "6064f080", + "metadata": {}, + "source": [ + "### Custom LLMChain\n", + "\n", + "The first way to create a custom agent is to use an existing Agent class, but use a custom LLMChain. This is the simplest way to create a custom Agent. It is highly reccomended that you work with the `ZeroShotAgent`, as at the moment that is by far the most generalizable one. \n", + "\n", + "Most of the work in creating the custom LLMChain comes down to the prompt. Because we are using an existing agent class to parse the output, it is very important that the prompt say to produce text in that format. Additionally, we currently require an `agent_scratchpad` input variable to put notes on previous actions and observations. This should almost always be the final part of the prompt. However, besides those instructions, you can customize the prompt as you wish.\n", + "\n", + "To ensure that the prompt contains the appropriate instructions, we will utilize a helper method on that class. The helper method for the `ZeroShotAgent` takes the following arguments:\n", + "\n", + "- tools: List of tools the agent will have access to, used to format the prompt.\n", + "- prefix: String to put before the list of tools.\n", + "- suffix: String to put after the list of tools.\n", + "- input_variables: List of input variables the final prompt will expect.\n", + "\n", + "For this exercise, we will give our agent access to Google Search, and we will customize it in that we will have it answer as a pirate." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "9af9734e", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import ZeroShotAgent, Tool, AgentExecutor\n", + "from langchain import OpenAI, SerpAPIWrapper, LLMChain" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "becda2a1", + "metadata": {}, + "outputs": [], + "source": [ + "search = SerpAPIWrapper()\n", + "tools = [\n", + " Tool(\n", + " name = \"Search\",\n", + " func=search.run,\n", + " description=\"useful for when you need to answer questions about current events\"\n", + " )\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "339b1bb8", + "metadata": {}, + "outputs": [], + "source": [ + "prefix = \"\"\"Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\"\"\"\n", + "suffix = \"\"\"Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Args\"\n", + "\n", + "Question: {input}\n", + "{agent_scratchpad}\"\"\"\n", + "\n", + "prompt = ZeroShotAgent.create_prompt(\n", + " tools, \n", + " prefix=prefix, \n", + " suffix=suffix, \n", + " input_variables=[\"input\", \"agent_scratchpad\"]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "59db7b58", + "metadata": {}, + "source": [ + "In case we are curious, we can now take a look at the final prompt template to see what it looks like when its all put together." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "e21d2098", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Answer the following questions as best you can, but speaking as a pirate might speak. You have access to the following tools:\n", + "\n", + "Search: useful for when you need to answer questions about current events\n", + "\n", + "Use the following format:\n", + "\n", + "Question: the input question you must answer\n", + "Thought: you should always think about what to do\n", + "Action: the action to take, should be one of [Search]\n", + "Action Input: the input to the action\n", + "Observation: the result of the action\n", + "... (this Thought/Action/Action Input/Observation can repeat N times)\n", + "Thought: I now know the final answer\n", + "Final Answer: the final answer to the original input question\n", + "\n", + "Begin! Remember to speak as a pirate when giving your final answer. Use lots of \"Args\"\n", + "\n", + "Question: {input}\n", + "{agent_scratchpad}\n" + ] + } + ], + "source": [ + "print(prompt.template)" + ] + }, + { + "cell_type": "markdown", + "id": "5e028e6d", + "metadata": {}, + "source": [ + "Note that we are able to feed agents a self-defined prompt template, i.e. not restricted to the prompt generated by the `create_prompt` function, assuming it meets the agent's requirements. \n", + "\n", + "For example, for `ZeroShotAgent`, we will need to ensure that it meets the following requirements. There should a string starting with \"Action:\" and a following string starting with \"Action Input:\", and both should be separated by a newline.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "9b1cc2a2", + "metadata": {}, + "outputs": [], + "source": [ + "llm_chain = LLMChain(llm=OpenAI(temperature=0), prompt=prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "e4f5092f", + "metadata": {}, + "outputs": [], + "source": [ + "tool_names = [tool.name for tool in tools]\n", + "agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "490604e9", + "metadata": {}, + "outputs": [], + "source": [ + "agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "653b1617", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mThought: I need to find out the population of Canada\n", + "Action: Search\n", + "Action Input: Population of Canada 2023\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mThe current population of Canada is 38,610,447 as of Saturday, February 18, 2023, based on Worldometer elaboration of the latest United Nations data. Canada 2020 population is estimated at 37,742,154 people at mid year according to UN data.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", + "Final Answer: Arrr, Canada be havin' 38,610,447 scallywags livin' there as of 2023!\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "\"Arrr, Canada be havin' 38,610,447 scallywags livin' there as of 2023!\"" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.run(\"How many people live in canada as of 2023?\")" + ] + }, + { + "cell_type": "markdown", + "id": "040eb343", + "metadata": {}, + "source": [ + "### Multiple inputs\n", + "Agents can also work with prompts that require multiple inputs." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "43dbfa2f", + "metadata": {}, + "outputs": [], + "source": [ + "prefix = \"\"\"Answer the following questions as best you can. You have access to the following tools:\"\"\"\n", + "suffix = \"\"\"When answering, you MUST speak in the following language: {language}.\n", + "\n", + "Question: {input}\n", + "{agent_scratchpad}\"\"\"\n", + "\n", + "prompt = ZeroShotAgent.create_prompt(\n", + " tools, \n", + " prefix=prefix, \n", + " suffix=suffix, \n", + " input_variables=[\"input\", \"language\", \"agent_scratchpad\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "0f087313", + "metadata": {}, + "outputs": [], + "source": [ + "llm_chain = LLMChain(llm=OpenAI(temperature=0), prompt=prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "92c75a10", + "metadata": {}, + "outputs": [], + "source": [ + "agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "ac5b83bf", + "metadata": {}, + "outputs": [], + "source": [ + "agent_executor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "c960e4ff", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mThought: I need to find out the population of Canada in 2023.\n", + "Action: Search\n", + "Action Input: Population of Canada in 2023\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mThe current population of Canada is 38,610,447 as of Saturday, February 18, 2023, based on Worldometer elaboration of the latest United Nations data. Canada 2020 population is estimated at 37,742,154 people at mid year according to UN data.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", + "Final Answer: La popolazione del Canada nel 2023 è stimata in 38.610.447 persone.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "'La popolazione del Canada nel 2023 è stimata in 38.610.447 persone.'" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent_executor.run(input=\"How many people live in canada as of 2023?\", language=\"italian\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adefb4c2", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.1" + }, + "vscode": { + "interpreter": { + "hash": "18784188d7ecd866c0586ac068b02361a6896dc3a29b64f5cc957f09c590acef" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/langchain/agents/__init__.py b/langchain/agents/__init__.py index 3f1c38f9..928ded6e 100644 --- a/langchain/agents/__init__.py +++ b/langchain/agents/__init__.py @@ -1,5 +1,11 @@ """Interface for agents.""" -from langchain.agents.agent import Agent, AgentExecutor +from langchain.agents.agent import ( + Agent, + AgentExecutor, + AgentOutputParser, + BaseSingleActionAgent, + LLMSingleActionAgent, +) from langchain.agents.agent_toolkits import ( create_csv_agent, create_json_agent, @@ -42,4 +48,7 @@ __all__ = [ "create_vectorstore_agent", "create_pandas_dataframe_agent", "create_csv_agent", + "LLMSingleActionAgent", + "AgentOutputParser", + "BaseSingleActionAgent", ]