Harrison/plan and solve (#4422)

parallel_dir_loader
Harrison Chase 1 year ago committed by GitHub
parent d383c0cb43
commit 6b8d144ccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,12 +10,24 @@ but potentially an unknown chain that depends on the user's input.
In these types of chains, there is a “agent” which has access to a suite of tools.
Depending on the user input, the agent can then decide which, if any, of these tools to call.
At the moment, there are two main types of agents:
1. "Action Agents": these agents decide an action to take and take that action one step at a time
2. "Plan-and-Execute Agents": these agents first decide a plan of actions to take, and then execute those actions one at a time.
When should you use each one? Action Agents are more conventional, and good for small tasks.
For more complex or long running tasks, the initial planning step helps to maintain long term objectives and focus. However, that comes at the expense of generally more calls and higher latency.
These two agents are also not mutually exclusive - in fact, it is often best to have an Action Agent be in change of the execution for the Plan and Execute agent.
Action Agents
-------------
High level pseudocode of agents looks something like:
- Some user input is received
- The `agent` decides which `tool` - if any - to use, and what the input to that tool should be
- That `tool` is then called with that `tool input`, and an `observation` is recorded (this is just the output of calling that tool with that tool input.
- That history of `tool`, `tool input`, and `observation` is passed back into the `agent`, and it decides what steps to take next
- That `tool` is then called with that `tool input`, and an `observation` is recorded (this is just the output of calling that tool with that tool input)
- That history of `tool`, `tool input`, and `observation` is passed back into the `agent`, and it decides what step to take next
- This is repeated until the `agent` decides it no longer needs to use a `tool`, and then it responds directly to the user.
The different abstractions involved in agents are as follows:
@ -69,8 +81,7 @@ In this section we go over the Agent Executor class, which is responsible for ca
the agent and tools in a loop. We go over different ways to customize this, and options you
can use for more control.
Go Deeper
---------
**Go Deeper**
.. toctree::
:maxdepth: 1
@ -79,3 +90,23 @@ Go Deeper
./agents/agents.rst
./agents/toolkits.rst
./agents/agent_executors.rst
Plan-and-Execute Agents
-----------------------
High level pseudocode of agents looks something like:
- Some user input is received
- The planner lists out the steps to take
- The executor goes through the list of steps, executing them
The most typical implementation is to have the planner be a language model,
and the executor be an action agent.
**Go Deeper**
.. toctree::
:maxdepth: 1
./agents/plan_and_execute.ipynb

@ -42,7 +42,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 1,
"id": "9af9734e",
"metadata": {},
"outputs": [],
@ -100,13 +100,13 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 12,
"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",
"template = \"\"\"Complete the objective as best you can. You have access to the following tools:\n",
"\n",
"{tools}\n",
"\n",
@ -121,7 +121,11 @@
"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",
"These were previous tasks you completed:\n",
"\n",
"\n",
"\n",
"Begin!\n",
"\n",
"Question: {input}\n",
"{agent_scratchpad}\"\"\""
@ -129,7 +133,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 13,
"id": "fd969d31",
"metadata": {},
"outputs": [],
@ -161,7 +165,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 14,
"id": "798ef9fb",
"metadata": {},
"outputs": [],
@ -189,7 +193,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 15,
"id": "7c6fe0d3",
"metadata": {},
"outputs": [],
@ -218,7 +222,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 16,
"id": "d278706a",
"metadata": {},
"outputs": [],
@ -238,7 +242,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 17,
"id": "f9d4c374",
"metadata": {},
"outputs": [],
@ -270,7 +274,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 18,
"id": "9b1cc2a2",
"metadata": {},
"outputs": [],
@ -281,7 +285,7 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 19,
"id": "e4f5092f",
"metadata": {},
"outputs": [],
@ -307,7 +311,7 @@
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 20,
"id": "490604e9",
"metadata": {},
"outputs": [],
@ -317,7 +321,7 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 21,
"id": "653b1617",
"metadata": {},
"outputs": [
@ -328,16 +332,13 @@
"\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mThought: Wot year be it now? That be important to know the answer.\n",
"Action: Search\n",
"Action Input: \"current population canada 2023\"\u001b[0m\n",
"\n",
"Observation:\u001b[36;1m\u001b[1;3m38,649,283\u001b[0m\u001b[32;1m\u001b[1;3mAhoy! That be the correct year, but the answer be in regular numbers. 'Tis time to translate to pirate speak.\n",
"\u001b[32;1m\u001b[1;3mThought: I should use a reliable search engine to get accurate information.\n",
"Action: Search\n",
"Action Input: \"38,649,283 in pirate speak\"\u001b[0m\n",
"Action Input: \"Leo DiCaprio girlfriend\"\u001b[0m\n",
"\n",
"Observation:\u001b[36;1m\u001b[1;3mBrush up on your “Pirate Talk” with these helpful pirate phrases. Aaaarrrrgggghhhh! Pirate catch phrase of grumbling or disgust. Ahoy! Hello! Ahoy, Matey, Hello ...\u001b[0m\u001b[32;1m\u001b[1;3mThat be not helpful, I'll just do the translation meself.\n",
"Final Answer: Arrrr, thar be 38,649,283 scallywags in Canada as of 2023.\u001b[0m\n",
"Observation:\u001b[36;1m\u001b[1;3mHe went on to date Gisele Bündchen, Bar Refaeli, Blake Lively, Toni Garrn and Nina Agdal, among others, before finally settling down with current girlfriend Camila Morrone, who is 23 years his junior.\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mI have found the answer to the question.\n",
"Final Answer: Leo DiCaprio's current girlfriend is Camila Morrone.\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
@ -345,16 +346,16 @@
{
"data": {
"text/plain": [
"'Arrrr, thar be 38,649,283 scallywags in Canada as of 2023.'"
"\"Leo DiCaprio's current girlfriend is Camila Morrone.\""
]
},
"execution_count": 16,
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent_executor.run(\"How many people live in canada as of 2023?\")"
"agent_executor.run(\"Search for Leo DiCaprio's girlfriend on the internet.\")"
]
},
{

@ -0,0 +1,363 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "406483c4",
"metadata": {},
"source": [
"## Plan and Execute\n",
"\n",
"Plan and execute agents accomplish an objective by first planning what to do, then executing the sub tasks. This idea is largely inspired by [BabyAGI](https://github.com/yoheinakajima/babyagi) and then the [\"Plan-and-Solve\" paper](https://arxiv.org/abs/2305.04091).\n",
"\n",
"The planning is almost always done by an LLM.\n",
"\n",
"The execution is usually done by a separate agent (equipped with tools)."
]
},
{
"cell_type": "markdown",
"id": "91192118",
"metadata": {},
"source": [
"## Imports"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "6ccd1dc5",
"metadata": {},
"outputs": [],
"source": [
"from langchain.chat_models import ChatOpenAI\n",
"from langchain.llms import OpenAI\n",
"from langchain.agents import PlanAndExecute, load_agent_executor, load_chat_planner\n",
"from langchain import SerpAPIWrapper\n",
"from langchain.agents.tools import Tool\n",
"from langchain import LLMMathChain"
]
},
{
"cell_type": "markdown",
"id": "0b10d200",
"metadata": {},
"source": [
"## Tools"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "3c00f724",
"metadata": {},
"outputs": [],
"source": [
"search = SerpAPIWrapper()\n",
"llm = OpenAI(temperature=0)\n",
"search = SerpAPIWrapper()\n",
"llm_math_chain = LLMMathChain.from_llm(llm=llm, verbose=True)\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",
" Tool(\n",
" name=\"Calculator\",\n",
" func=llm_math_chain.run,\n",
" description=\"useful for when you need to answer questions about math\"\n",
" ),\n",
"]"
]
},
{
"cell_type": "markdown",
"id": "ce38ae84",
"metadata": {},
"source": [
"## Planner, Executor, and Agent"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "0ab2cadd",
"metadata": {},
"outputs": [],
"source": [
"model = ChatOpenAI(temperature=0)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "7b2419f2",
"metadata": {},
"outputs": [],
"source": [
"planner = load_chat_planner(model)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "ed9f518b",
"metadata": {},
"outputs": [],
"source": [
"executor = load_agent_executor(model, tools, verbose=True)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "36943178",
"metadata": {},
"outputs": [],
"source": [
"agent = PlanAndExecute(planner=planner, executer=executor, verbose=True)"
]
},
{
"cell_type": "markdown",
"id": "8be9f1bd",
"metadata": {},
"source": [
"## Run Example"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4891062e",
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\n",
"\u001b[1m> Entering new PlanAndExecute chain...\u001b[0m\n",
"steps=[Step(value=\"Search for Leo DiCaprio's girlfriend on the internet.\"), Step(value='Find her current age.'), Step(value='Raise her current age to the 0.43 power using a calculator or programming language.'), Step(value='Output the result.'), Step(value=\"Given the above steps taken, respond to the user's original question.\\n\\n\")]\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mAction:\n",
"```\n",
"{\n",
" \"action\": \"Search\",\n",
" \"action_input\": \"Who is Leo DiCaprio's girlfriend?\"\n",
"}\n",
"``` \n",
"\n",
"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3mDiCaprio broke up with girlfriend Camila Morrone, 25, in the summer of 2022, after dating for four years. He's since been linked to another famous supermodel Gigi Hadid. The power couple were first supposedly an item in September after being spotted getting cozy during a party at New York Fashion Week.\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mBased on the previous observation, I can provide the answer to the current objective. \n",
"Action:\n",
"```\n",
"{\n",
" \"action\": \"Final Answer\",\n",
" \"action_input\": \"Leo DiCaprio is currently linked to Gigi Hadid.\"\n",
"}\n",
"```\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"*****\n",
"\n",
"Step: Search for Leo DiCaprio's girlfriend on the internet.\n",
"\n",
"Response: Leo DiCaprio is currently linked to Gigi Hadid.\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mAction:\n",
"```\n",
"{\n",
" \"action\": \"Search\",\n",
" \"action_input\": \"What is Gigi Hadid's current age?\"\n",
"}\n",
"```\n",
"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mPrevious steps: steps=[(Step(value=\"Search for Leo DiCaprio's girlfriend on the internet.\"), StepResponse(response='Leo DiCaprio is currently linked to Gigi Hadid.'))]\n",
"\n",
"Current objective: value='Find her current age.'\n",
"\n",
"Action:\n",
"```\n",
"{\n",
" \"action\": \"Search\",\n",
" \"action_input\": \"What is Gigi Hadid's current age?\"\n",
"}\n",
"```\n",
"\n",
"\u001b[0m\n",
"Observation: \u001b[36;1m\u001b[1;3m28 years\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mBased on my search, Gigi Hadid's current age is 26 years old. \n",
"Action:\n",
"```\n",
"{\n",
" \"action\": \"Final Answer\",\n",
" \"action_input\": \"Gigi Hadid's current age is 26 years old.\"\n",
"}\n",
"```\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"*****\n",
"\n",
"Step: Find her current age.\n",
"\n",
"Response: Gigi Hadid's current age is 26 years old.\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mAction:\n",
"```\n",
"{\n",
" \"action\": \"Calculator\",\n",
" \"action_input\": \"26 ** 0.43\"\n",
"}\n",
"```\n",
"\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
"26 ** 0.43\u001b[32;1m\u001b[1;3m\n",
"```text\n",
"26 ** 0.43\n",
"```\n",
"...numexpr.evaluate(\"26 ** 0.43\")...\n",
"\u001b[0m\n",
"Answer: \u001b[33;1m\u001b[1;3m4.059182145592686\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 4.059182145592686\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mThe current objective is to raise Gigi Hadid's age to the 0.43 power. \n",
"\n",
"Action:\n",
"```\n",
"{\n",
" \"action\": \"Calculator\",\n",
" \"action_input\": \"26 ** 0.43\"\n",
"}\n",
"```\n",
"\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
"26 ** 0.43\u001b[32;1m\u001b[1;3m\n",
"```text\n",
"26 ** 0.43\n",
"```\n",
"...numexpr.evaluate(\"26 ** 0.43\")...\n",
"\u001b[0m\n",
"Answer: \u001b[33;1m\u001b[1;3m4.059182145592686\u001b[0m\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"\n",
"Observation: \u001b[33;1m\u001b[1;3mAnswer: 4.059182145592686\u001b[0m\n",
"Thought:\u001b[32;1m\u001b[1;3mThe answer to the current objective is 4.059182145592686.\n",
"\n",
"Action:\n",
"```\n",
"{\n",
" \"action\": \"Final Answer\",\n",
" \"action_input\": \"Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\"\n",
"}\n",
"```\n",
"\n",
"\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"*****\n",
"\n",
"Step: Raise her current age to the 0.43 power using a calculator or programming language.\n",
"\n",
"Response: Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mAction:\n",
"```\n",
"{\n",
" \"action\": \"Final Answer\",\n",
" \"action_input\": \"Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\"\n",
"}\n",
"```\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"*****\n",
"\n",
"Step: Output the result.\n",
"\n",
"Response: Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\n",
"\n",
"\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n",
"\u001b[32;1m\u001b[1;3mAction:\n",
"```\n",
"{\n",
" \"action\": \"Final Answer\",\n",
" \"action_input\": \"Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\"\n",
"}\n",
"```\n",
"\u001b[0m\n",
"\n",
"\u001b[1m> Finished chain.\u001b[0m\n",
"*****\n",
"\n",
"Step: Given the above steps taken, respond to the user's original question.\n",
"\n",
"\n",
"\n",
"Response: Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\n",
"\u001b[1m> Finished chain.\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"\"Gigi Hadid's age raised to the 0.43 power is approximately 4.059 years.\""
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"agent.run(\"Who is Leo DiCaprio's girlfriend? What is her current age raised to the 0.43 power?\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aa3ec998",
"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"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -26,6 +26,11 @@ from langchain.agents.initialize import initialize_agent
from langchain.agents.load_tools import get_all_tool_names, load_tools
from langchain.agents.loading import load_agent
from langchain.agents.mrkl.base import MRKLChain, ZeroShotAgent
from langchain.agents.plan_and_execute.agent_executor import PlanAndExecute
from langchain.agents.plan_and_execute.executors.agent_executor import (
load_agent_executor,
)
from langchain.agents.plan_and_execute.planners.chat_planner import load_chat_planner
from langchain.agents.react.base import ReActChain, ReActTextWorldAgent
from langchain.agents.self_ask_with_search.base import SelfAskWithSearchChain
from langchain.agents.structured_chat.base import StructuredChatAgent
@ -63,4 +68,7 @@ __all__ = [
"load_agent",
"load_tools",
"tool",
"PlanAndExecute",
"load_chat_planner",
"load_agent_executor",
]

@ -0,0 +1,7 @@
from langchain.agents.plan_and_execute.agent_executor import PlanAndExecute
from langchain.agents.plan_and_execute.executors.agent_executor import (
load_agent_executor,
)
from langchain.agents.plan_and_execute.planners.chat_planner import load_chat_planner
__all__ = ["PlanAndExecute", "load_agent_executor", "load_chat_planner"]

@ -0,0 +1,56 @@
from typing import Any, Dict, List, Optional
from pydantic import Field
from langchain.agents.plan_and_execute.executors.base import BaseExecutor
from langchain.agents.plan_and_execute.planners.base import BasePlanner
from langchain.agents.plan_and_execute.schema import (
BaseStepContainer,
ListStepContainer,
)
from langchain.callbacks.manager import CallbackManagerForChainRun
from langchain.chains.base import Chain
class PlanAndExecute(Chain):
planner: BasePlanner
executer: BaseExecutor
step_container: BaseStepContainer = Field(default_factory=ListStepContainer)
input_key: str = "input"
output_key: str = "output"
@property
def input_keys(self) -> List[str]:
return [self.input_key]
@property
def output_keys(self) -> List[str]:
return [self.output_key]
def _call(
self,
inputs: Dict[str, Any],
run_manager: Optional[CallbackManagerForChainRun] = None,
) -> Dict[str, Any]:
plan = self.planner.plan(
inputs,
callbacks=run_manager.get_child() if run_manager else None,
)
if run_manager:
run_manager.on_text(str(plan), verbose=self.verbose)
for step in plan.steps:
_new_inputs = {"previous_steps": self.step_container, "current_step": step}
new_inputs = {**_new_inputs, **inputs}
response = self.executer.step(
new_inputs,
callbacks=run_manager.get_child() if run_manager else None,
)
if run_manager:
run_manager.on_text(
f"*****\n\nStep: {step.value}", verbose=self.verbose
)
run_manager.on_text(
f"\n\nResponse: {response.response}", verbose=self.verbose
)
self.step_container.add_step(step, response)
return {self.output_key: self.step_container.get_final_response()}

@ -0,0 +1,28 @@
from typing import List
from langchain.agents.agent import AgentExecutor
from langchain.agents.plan_and_execute.executors.base import ChainExecutor
from langchain.agents.structured_chat.base import StructuredChatAgent
from langchain.base_language import BaseLanguageModel
from langchain.tools import BaseTool
HUMAN_MESSAGE_TEMPLATE = """Previous steps: {previous_steps}
Current objective: {current_step}
{agent_scratchpad}"""
def load_agent_executor(
llm: BaseLanguageModel, tools: List[BaseTool], verbose: bool = False
) -> ChainExecutor:
agent = StructuredChatAgent.from_llm_and_tools(
llm,
tools,
human_message_template=HUMAN_MESSAGE_TEMPLATE,
input_variables=["previous_steps", "current_step", "agent_scratchpad"],
)
agent_executor = AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=verbose
)
return ChainExecutor(chain=agent_executor)

@ -0,0 +1,40 @@
from abc import abstractmethod
from typing import Any
from pydantic import BaseModel
from langchain.agents.plan_and_execute.schema import StepResponse
from langchain.callbacks.manager import Callbacks
from langchain.chains.base import Chain
class BaseExecutor(BaseModel):
@abstractmethod
def step(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> StepResponse:
"""Take step."""
@abstractmethod
async def astep(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> StepResponse:
"""Take step."""
class ChainExecutor(BaseExecutor):
chain: Chain
def step(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> StepResponse:
"""Take step."""
response = self.chain.run(**inputs, callbacks=callbacks)
return StepResponse(response=response)
async def astep(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> StepResponse:
"""Take step."""
response = await self.chain.arun(**inputs, callbacks=callbacks)
return StepResponse(response=response)

@ -0,0 +1,40 @@
from abc import abstractmethod
from typing import Any, List, Optional
from pydantic import BaseModel
from langchain.agents.plan_and_execute.schema import Plan, PlanOutputParser
from langchain.callbacks.manager import Callbacks
from langchain.chains.llm import LLMChain
class BasePlanner(BaseModel):
@abstractmethod
def plan(self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any) -> Plan:
"""Given input, decided what to do."""
@abstractmethod
async def aplan(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> Plan:
"""Given input, decided what to do."""
class LLMPlanner(BasePlanner):
llm_chain: LLMChain
output_parser: PlanOutputParser
stop: Optional[List] = None
def plan(self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any) -> Plan:
"""Given input, decided what to do."""
llm_response = self.llm_chain.run(**inputs, stop=self.stop, callbacks=callbacks)
return self.output_parser.parse(llm_response)
async def aplan(
self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
) -> Plan:
"""Given input, decided what to do."""
llm_response = await self.llm_chain.arun(
**inputs, stop=self.stop, callbacks=callbacks
)
return self.output_parser.parse(llm_response)

@ -0,0 +1,42 @@
import re
from langchain.agents.plan_and_execute.planners.base import LLMPlanner
from langchain.agents.plan_and_execute.schema import Plan, PlanOutputParser, Step
from langchain.base_language import BaseLanguageModel
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.schema import SystemMessage
SYSTEM_PROMPT = (
"Let's first understand the problem and devise a plan to solve the problem."
" Please output the plan starting with the header 'Plan:' "
"and then followed by a numbered list of steps. "
"Please make the plan the minimum number of steps required "
"to accurately complete the task. If the task is a question, "
"the final step should almost always be 'Given the above steps taken, "
"please respond to the users original question'. "
"At the end of your plan, say '<END_OF_PLAN>'"
)
class PlanningOutputParser(PlanOutputParser):
def parse(self, text: str) -> Plan:
steps = [Step(value=v) for v in re.split("\n\d+\. ", text)[1:]]
return Plan(steps=steps)
def load_chat_planner(
llm: BaseLanguageModel, system_prompt: str = SYSTEM_PROMPT
) -> LLMPlanner:
prompt_template = ChatPromptTemplate.from_messages(
[
SystemMessage(content=system_prompt),
HumanMessagePromptTemplate.from_template("{input}"),
]
)
llm_chain = LLMChain(llm=llm, prompt=prompt_template)
return LLMPlanner(
llm_chain=llm_chain,
output_parser=PlanningOutputParser(),
stop=["<END_OF_PLAN>"],
)

@ -0,0 +1,47 @@
from abc import abstractmethod
from typing import List, Tuple
from pydantic import BaseModel, Field
from langchain.schema import BaseOutputParser
class Step(BaseModel):
value: str
class Plan(BaseModel):
steps: List[Step]
class StepResponse(BaseModel):
response: str
class BaseStepContainer(BaseModel):
@abstractmethod
def add_step(self, step: Step, step_response: StepResponse) -> None:
"""Add step and step response to the container."""
@abstractmethod
def get_final_response(self) -> str:
"""Return the final response based on steps taken."""
class ListStepContainer(BaseModel):
steps: List[Tuple[Step, StepResponse]] = Field(default_factory=list)
def add_step(self, step: Step, step_response: StepResponse) -> None:
self.steps.append((step, step_response))
def get_steps(self) -> List[Tuple[Step, StepResponse]]:
return self.steps
def get_final_response(self) -> str:
return self.steps[-1][1].response
class PlanOutputParser(BaseOutputParser):
@abstractmethod
def parse(self, text: str) -> Plan:
"""Parse into a plan."""

@ -20,6 +20,8 @@ from langchain.prompts.chat import (
from langchain.schema import AgentAction
from langchain.tools import BaseTool
HUMAN_MESSAGE_TEMPLATE = "{input}\n\n{agent_scratchpad}"
class StructuredChatAgent(Agent):
output_parser: AgentOutputParser = Field(
@ -71,6 +73,7 @@ class StructuredChatAgent(Agent):
tools: Sequence[BaseTool],
prefix: str = PREFIX,
suffix: str = SUFFIX,
human_message_template: str = HUMAN_MESSAGE_TEMPLATE,
format_instructions: str = FORMAT_INSTRUCTIONS,
input_variables: Optional[List[str]] = None,
) -> BasePromptTemplate:
@ -84,7 +87,7 @@ class StructuredChatAgent(Agent):
template = "\n\n".join([prefix, formatted_tools, format_instructions, suffix])
messages = [
SystemMessagePromptTemplate.from_template(template),
HumanMessagePromptTemplate.from_template("{input}\n\n{agent_scratchpad}"),
HumanMessagePromptTemplate.from_template(human_message_template),
]
if input_variables is None:
input_variables = ["input", "agent_scratchpad"]
@ -99,6 +102,7 @@ class StructuredChatAgent(Agent):
output_parser: Optional[AgentOutputParser] = None,
prefix: str = PREFIX,
suffix: str = SUFFIX,
human_message_template: str = HUMAN_MESSAGE_TEMPLATE,
format_instructions: str = FORMAT_INSTRUCTIONS,
input_variables: Optional[List[str]] = None,
**kwargs: Any,
@ -109,6 +113,7 @@ class StructuredChatAgent(Agent):
tools,
prefix=prefix,
suffix=suffix,
human_message_template=human_message_template,
format_instructions=format_instructions,
input_variables=input_variables,
)

@ -32,9 +32,12 @@ _EXPECTED = [
"load_agent",
"load_tools",
"tool",
"PlanAndExecute",
"load_chat_planner",
"load_agent_executor",
]
def test_public_api() -> None:
"""Test for regressions or changes in the agents public API."""
assert agents_all == sorted(_EXPECTED)
assert sorted(agents_all) == sorted(_EXPECTED)

Loading…
Cancel
Save