diff --git a/docs/modules/agents/agents/custom_mrkl_agent.ipynb b/docs/modules/agents/agents/custom_mrkl_agent.ipynb index 8bfaa2f3..fb12e3e2 100644 --- a/docs/modules/agents/agents/custom_mrkl_agent.ipynb +++ b/docs/modules/agents/agents/custom_mrkl_agent.ipynb @@ -42,7 +42,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 1, "id": "9af9734e", "metadata": {}, "outputs": [], @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 2, "id": "becda2a1", "metadata": {}, "outputs": [], @@ -70,7 +70,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 3, "id": "339b1bb8", "metadata": {}, "outputs": [], @@ -99,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 4, "id": "e21d2098", "metadata": {}, "outputs": [ @@ -145,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 5, "id": "9b1cc2a2", "metadata": {}, "outputs": [], @@ -155,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 6, "id": "e4f5092f", "metadata": {}, "outputs": [], @@ -166,7 +166,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 7, "id": "490604e9", "metadata": {}, "outputs": [], @@ -176,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 8, "id": "653b1617", "metadata": {}, "outputs": [ @@ -190,9 +190,9 @@ "\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", + "Observation: \u001b[36;1m\u001b[1;3mThe current population of Canada is 38,661,927 as of Sunday, April 16, 2023, based on Worldometer elaboration of the latest United Nations 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", + "Final Answer: Arrr, Canada be havin' 38,661,927 people livin' there as of 2023!\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -200,10 +200,10 @@ { "data": { "text/plain": [ - "\"Arrr, Canada be havin' 38,610,447 scallywags livin' there as of 2023!\"" + "\"Arrr, Canada be havin' 38,661,927 people livin' there as of 2023!\"" ] }, - "execution_count": 31, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -223,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 9, "id": "43dbfa2f", "metadata": {}, "outputs": [], @@ -244,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 10, "id": "0f087313", "metadata": {}, "outputs": [], @@ -254,7 +254,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 11, "id": "92c75a10", "metadata": {}, "outputs": [], @@ -264,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 12, "id": "ac5b83bf", "metadata": {}, "outputs": [], @@ -274,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 13, "id": "c960e4ff", "metadata": {}, "outputs": [ @@ -285,12 +285,16 @@ "\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", + "\u001b[32;1m\u001b[1;3mThought: I should look for recent population estimates.\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", + "Action Input: Canada population 2023\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m39,566,248\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I should double check this number.\n", + "Action: Search\n", + "Action Input: Canada population estimates 2023\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mCanada's population was estimated at 39,566,248 on January 1, 2023, after a record population growth of 1,050,110 people from January 1, 2022, to January 1, 2023.\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", + "Final Answer: La popolazione del Canada è stata stimata a 39.566.248 il 1° gennaio 2023, dopo un record di crescita demografica di 1.050.110 persone dal 1° gennaio 2022 al 1° gennaio 2023.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -298,10 +302,10 @@ { "data": { "text/plain": [ - "'La popolazione del Canada nel 2023 è stimata in 38.610.447 persone.'" + "'La popolazione del Canada è stata stimata a 39.566.248 il 1° gennaio 2023, dopo un record di crescita demografica di 1.050.110 persone dal 1° gennaio 2022 al 1° gennaio 2023.'" ] }, - "execution_count": 36, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } diff --git a/docs/modules/agents/agents/examples/chat_conversation_agent.ipynb b/docs/modules/agents/agents/examples/chat_conversation_agent.ipynb index 91015a7b..28299e3c 100644 --- a/docs/modules/agents/agents/examples/chat_conversation_agent.ipynb +++ b/docs/modules/agents/agents/examples/chat_conversation_agent.ipynb @@ -28,7 +28,15 @@ "execution_count": 2, "id": "f65308ab", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to default session, using empty session: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /sessions (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + } + ], "source": [ "from langchain.agents import Tool\n", "from langchain.memory import ConversationBufferMemory\n", @@ -88,7 +96,20 @@ "text": [ "\n", "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to persist run: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /chain-runs (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[32;1m\u001b[1;3m{\n", " \"action\": \"Final Answer\",\n", " \"action_input\": \"Hello Bob! How can I assist you today?\"\n", @@ -124,7 +145,20 @@ "text": [ "\n", "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to persist run: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /chain-runs (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[32;1m\u001b[1;3m{\n", " \"action\": \"Final Answer\",\n", " \"action_input\": \"Your name is Bob.\"\n", @@ -167,10 +201,24 @@ " \"action\": \"Current Search\",\n", " \"action_input\": \"Thai food dinner recipes\"\n", "}\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3m59 easy Thai recipes for any night of the week · Marion Grasby's Thai spicy chilli and basil fried rice · Thai curry noodle soup · Marion Grasby's ...\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m{\n", + "Observation: \u001b[36;1m\u001b[1;3m59 easy Thai recipes for any night of the week · Marion Grasby's Thai spicy chilli and basil fried rice · Thai curry noodle soup · Marion Grasby's Thai Spicy ...\u001b[0m\n", + "Thought:" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to persist run: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /chain-runs (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m{\n", " \"action\": \"Final Answer\",\n", - " \"action_input\": \"Here are some Thai food dinner recipes you can make this week: Thai spicy chilli and basil fried rice, Thai curry noodle soup, and many more. You can find 59 easy Thai recipes for any night of the week on Marion Grasby's website.\"\n", + " \"action_input\": \"Here are some Thai food dinner recipes you can make this week: Thai spicy chilli and basil fried rice, Thai curry noodle soup, and Thai Spicy ... (59 recipes in total).\"\n", "}\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" @@ -179,7 +227,7 @@ { "data": { "text/plain": [ - "\"Here are some Thai food dinner recipes you can make this week: Thai spicy chilli and basil fried rice, Thai curry noodle soup, and many more. You can find 59 easy Thai recipes for any night of the week on Marion Grasby's website.\"" + "'Here are some Thai food dinner recipes you can make this week: Thai spicy chilli and basil fried rice, Thai curry noodle soup, and Thai Spicy ... (59 recipes in total).'" ] }, "execution_count": 8, @@ -210,11 +258,25 @@ " \"action_input\": \"who won the world cup in 1978\"\n", "}\n", "```\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mThe Argentina national football team represents Argentina in men's international football and is administered by the Argentine Football Association, the governing body for football in Argentina. Nicknamed La Albiceleste, they are the reigning world champions, having won the most recent World Cup in 2022.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m```json\n", + "Observation: \u001b[36;1m\u001b[1;3mArgentina national football team\u001b[0m\n", + "Thought:" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to persist run: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /chain-runs (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m```json\n", "{\n", " \"action\": \"Final Answer\",\n", - " \"action_input\": \"The last letter in your name is 'b'. The Argentina national football team won the World Cup in 1978.\"\n", + " \"action_input\": \"The last letter in your name is 'b', and the winner of the 1978 World Cup was the Argentina national football team.\"\n", "}\n", "```\u001b[0m\n", "\n", @@ -224,7 +286,7 @@ { "data": { "text/plain": [ - "\"The last letter in your name is 'b'. The Argentina national football team won the World Cup in 1978.\"" + "\"The last letter in your name is 'b', and the winner of the 1978 World Cup was the Argentina national football team.\"" ] }, "execution_count": 9, @@ -253,10 +315,24 @@ " \"action\": \"Current Search\",\n", " \"action_input\": \"weather in pomfret\"\n", "}\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mMostly cloudy with gusty winds developing during the afternoon. A few flurries or snow showers possible. High near 40F. Winds NNW at 20 to 30 mph.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m{\n", + "Observation: \u001b[36;1m\u001b[1;3m10 Day Weather-Pomfret, CT ; Sun 16. 64° · 50°. 24% · NE 7 mph ; Mon 17. 58° · 45°. 70% · ESE 8 mph ; Tue 18. 57° · 37°. 8% · WSW 15 mph.\u001b[0m\n", + "Thought:" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING:root:Failed to persist run: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /chain-runs (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m{\n", " \"action\": \"Final Answer\",\n", - " \"action_input\": \"The weather in Pomfret is mostly cloudy with gusty winds developing during the afternoon. A few flurries or snow showers are possible. High near 40F. Winds NNW at 20 to 30 mph.\"\n", + " \"action_input\": \"The weather in Pomfret, CT for the next 10 days is as follows: Sun 16. 64° · 50°. 24% · NE 7 mph ; Mon 17. 58° · 45°. 70% · ESE 8 mph ; Tue 18. 57° · 37°. 8% · WSW 15 mph.\"\n", "}\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" @@ -265,7 +341,7 @@ { "data": { "text/plain": [ - "'The weather in Pomfret is mostly cloudy with gusty winds developing during the afternoon. A few flurries or snow showers are possible. High near 40F. Winds NNW at 20 to 30 mph.'" + "'The weather in Pomfret, CT for the next 10 days is as follows: Sun 16. 64° · 50°. 24% · NE 7 mph ; Mon 17. 58° · 45°. 70% · ESE 8 mph ; Tue 18. 57° · 37°. 8% · WSW 15 mph.'" ] }, "execution_count": 10, diff --git a/docs/modules/agents/agents/examples/conversational_agent.ipynb b/docs/modules/agents/agents/examples/conversational_agent.ipynb index 12e592b5..e7bafcb2 100644 --- a/docs/modules/agents/agents/examples/conversational_agent.ipynb +++ b/docs/modules/agents/agents/examples/conversational_agent.ipynb @@ -23,7 +23,7 @@ "from langchain.agents import AgentType\n", "from langchain.memory import ConversationBufferMemory\n", "from langchain import OpenAI\n", - "from langchain.utilities import GoogleSearchAPIWrapper\n", + "from langchain.utilities import SerpAPIWrapper\n", "from langchain.agents import initialize_agent" ] }, @@ -34,7 +34,7 @@ "metadata": {}, "outputs": [], "source": [ - "search = GoogleSearchAPIWrapper()\n", + "search = SerpAPIWrapper()\n", "tools = [\n", " Tool(\n", " name = \"Current Search\",\n", @@ -149,8 +149,12 @@ "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m\n", - "Thought: Do I need to use a tool? No\n", - "AI: If you like Thai food, some great dinner options this week could include Thai green curry, Pad Thai, or a Thai-style stir-fry. You could also try making a Thai-style soup or salad. Enjoy!\u001b[0m\n", + "Thought: Do I need to use a tool? Yes\n", + "Action: Current Search\n", + "Action Input: Thai food dinner recipes\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m59 easy Thai recipes for any night of the week · Marion Grasby's Thai spicy chilli and basil fried rice · Thai curry noodle soup · Marion Grasby's Thai Spicy ...\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m Do I need to use a tool? No\n", + "AI: Here are some great Thai dinner recipes you can try this week: Marion Grasby's Thai Spicy Chilli and Basil Fried Rice, Thai Curry Noodle Soup, Thai Green Curry with Coconut Rice, Thai Red Curry with Vegetables, and Thai Coconut Soup. I hope you enjoy them!\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -158,7 +162,7 @@ { "data": { "text/plain": [ - "'If you like Thai food, some great dinner options this week could include Thai green curry, Pad Thai, or a Thai-style stir-fry. You could also try making a Thai-style soup or salad. Enjoy!'" + "\"Here are some great Thai dinner recipes you can try this week: Marion Grasby's Thai Spicy Chilli and Basil Fried Rice, Thai Curry Noodle Soup, Thai Green Curry with Coconut Rice, Thai Red Curry with Vegetables, and Thai Coconut Soup. I hope you enjoy them!\"" ] }, "execution_count": 7, @@ -187,9 +191,9 @@ "Thought: Do I need to use a tool? Yes\n", "Action: Current Search\n", "Action Input: Who won the World Cup in 1978\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mThe Cup was won by the host nation, Argentina, who defeated the Netherlands 3–1 in the final, after extra time. The final was held at River Plate's home stadium ... Amid Argentina's celebrations, there was sympathy for the Netherlands, runners-up for the second tournament running, following a 3-1 final defeat at the Estadio ... The match was won by the Argentine squad in extra time by a score of 3–1. Mario Kempes, who finished as the tournament's top scorer, was named the man of the ... May 21, 2022 ... Argentina won the World Cup for the first time in their history, beating Netherlands 3-1 in the final. This edition of the World Cup was full of ... The adidas Golden Ball is presented to the best player at each FIFA World Cup finals. Those who finish as runners-up in the vote receive the adidas Silver ... Holders West Germany failed to beat Holland and Italy and were eliminated when Berti Vogts' own goal gave Austria a 3-2 victory. Holland thrashed the Austrians ... Jun 14, 2018 ... On a clear afternoon on 1 June 1978 at the revamped El Monumental stadium in Buenos Aires' Belgrano barrio, several hundred children in white ... Dec 15, 2022 ... The tournament couldn't have gone better for the ruling junta. Argentina went on to win the championship, defeating the Netherlands, 3-1, in the ... Nov 9, 2022 ... Host: Argentina Teams: 16. Format: Group stage, second round, third-place playoff, final. Matches: 38. Goals: 102. Winner: Argentina Feb 19, 2009 ... Argentina sealed their first World Cup win on home soil when they defeated the Netherlands in an exciting final that went to extra-time. For the ...\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mArgentina national football team\u001b[0m\n", "Thought:\u001b[32;1m\u001b[1;3m Do I need to use a tool? No\n", - "AI: The last letter in your name is 'b'. Argentina won the World Cup in 1978.\u001b[0m\n", + "AI: The last letter in your name is \"b\" and the winner of the 1978 World Cup was the Argentina national football team.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -197,7 +201,7 @@ { "data": { "text/plain": [ - "\"The last letter in your name is 'b'. Argentina won the World Cup in 1978.\"" + "'The last letter in your name is \"b\" and the winner of the 1978 World Cup was the Argentina national football team.'" ] }, "execution_count": 8, @@ -226,9 +230,9 @@ "Thought: Do I need to use a tool? Yes\n", "Action: Current Search\n", "Action Input: Current temperature in Pomfret\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mA mixture of rain and snow showers. High 39F. Winds NNW at 5 to 10 mph. Chance of precip 50%. Snow accumulations less than one inch. Pomfret, CT Weather Forecast, with current conditions, wind, air quality, and what to expect for the next 3 days. Pomfret Center Weather Forecasts. ... Pomfret Center, CT Weather Conditionsstar_ratehome ... Tomorrow's temperature is forecast to be COOLER than today. It is 46 degrees fahrenheit, or 8 degrees celsius and feels like 46 degrees fahrenheit. The barometric pressure is 29.78 - measured by inch of mercury units - ... Pomfret Weather Forecasts. ... Pomfret, MD Weather Conditionsstar_ratehome ... Tomorrow's temperature is forecast to be MUCH COOLER than today. Additional Headlines. En Español · Share |. Current conditions at ... Pomfret CT. Tonight ... Past Weather Information · Interactive Forecast Map. Pomfret MD detailed current weather report for 20675 in Charles county, Maryland. ... Pomfret, MD weather condition is Mostly Cloudy and 43°F. Mostly Cloudy. Hazardous Weather Conditions. Hazardous Weather Outlook · En Español · Share |. Current conditions at ... South Pomfret VT. Tonight. Pomfret Center, CT Weather. Current Report for Thu Jan 5 2023. As of 2:00 PM EST. 5-Day Forecast | Road Conditions. 45°F 7°c. Feels Like 44°F. Pomfret Center CT. Today. Today: Areas of fog before 9am. Otherwise, cloudy, with a ... Otherwise, cloudy, with a temperature falling to around 33 by 5pm.\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mPartly cloudy skies. High around 70F. Winds W at 5 to 10 mph. Humidity41%.\u001b[0m\n", "Thought:\u001b[32;1m\u001b[1;3m Do I need to use a tool? No\n", - "AI: The current temperature in Pomfret is 45°F (7°C) and it feels like 44°F.\u001b[0m\n", + "AI: The current temperature in Pomfret is around 70F with partly cloudy skies and winds W at 5 to 10 mph. The humidity is 41%.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -236,7 +240,7 @@ { "data": { "text/plain": [ - "'The current temperature in Pomfret is 45°F (7°C) and it feels like 44°F.'" + "'The current temperature in Pomfret is around 70F with partly cloudy skies and winds W at 5 to 10 mph. The humidity is 41%.'" ] }, "execution_count": 9, diff --git a/docs/modules/agents/agents/examples/mrkl.ipynb b/docs/modules/agents/agents/examples/mrkl.ipynb index 4b885b00..3a099c38 100644 --- a/docs/modules/agents/agents/examples/mrkl.ipynb +++ b/docs/modules/agents/agents/examples/mrkl.ipynb @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "07e96d99", "metadata": {}, "outputs": [], @@ -41,7 +41,7 @@ "llm = OpenAI(temperature=0)\n", "search = SerpAPIWrapper()\n", "llm_math_chain = LLMMathChain(llm=llm, verbose=True)\n", - "db = SQLDatabase.from_uri(\"sqlite:///../../../../notebooks/Chinook.db\")\n", + "db = SQLDatabase.from_uri(\"sqlite:///../../../../../notebooks/Chinook.db\")\n", "db_chain = SQLDatabaseChain(llm=llm, database=db, verbose=True)\n", "tools = [\n", " Tool(\n", @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "a069c4b6", "metadata": {}, "outputs": [], @@ -74,7 +74,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "e603cd7d", "metadata": {}, "outputs": [ @@ -88,30 +88,24 @@ "\u001b[32;1m\u001b[1;3m I need to find out who Leo DiCaprio's girlfriend is and then calculate her age raised to the 0.43 power.\n", "Action: Search\n", "Action Input: \"Who is Leo DiCaprio's girlfriend?\"\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mCamila Morrone\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I need to find out Camila Morrone's age\n", - "Action: Search\n", - "Action Input: \"How old is Camila Morrone?\"\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3m25 years\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I need to calculate 25 raised to the 0.43 power\n", + "Observation: \u001b[36;1m\u001b[1;3mDiCaprio met actor Camila Morrone in December 2017, when she was 20 and he was 43. They were spotted at Coachella and went on multiple vacations together. Some reports suggested that DiCaprio was ready to ask Morrone to marry him. The couple made their red carpet debut at the 2020 Academy Awards.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I need to calculate Camila Morrone's age raised to the 0.43 power.\n", "Action: Calculator\n", - "Action Input: 25^0.43\u001b[0m\n", + "Action Input: 21^0.43\u001b[0m\n", "\n", "\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n", - "25^0.43\u001b[32;1m\u001b[1;3m\n", - "```python\n", - "import math\n", - "print(math.pow(25, 0.43))\n", + "21^0.43\u001b[32;1m\u001b[1;3m\n", + "```text\n", + "21**0.43\n", "```\n", + "...numexpr.evaluate(\"21**0.43\")...\n", "\u001b[0m\n", - "Answer: \u001b[33;1m\u001b[1;3m3.991298452658078\n", - "\u001b[0m\n", + "Answer: \u001b[33;1m\u001b[1;3m3.7030049853137306\u001b[0m\n", "\u001b[1m> Finished chain.\u001b[0m\n", "\n", - "Observation: \u001b[33;1m\u001b[1;3mAnswer: 3.991298452658078\n", - "\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: Camila Morrone is 25 years old and her age raised to the 0.43 power is 3.991298452658078.\u001b[0m\n", + "Observation: \u001b[33;1m\u001b[1;3mAnswer: 3.7030049853137306\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", + "Final Answer: Camila Morrone is Leo DiCaprio's girlfriend and her current age raised to the 0.43 power is 3.7030049853137306.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -119,10 +113,10 @@ { "data": { "text/plain": [ - "'Camila Morrone is 25 years old and her age raised to the 0.43 power is 3.991298452658078.'" + "\"Camila Morrone is Leo DiCaprio's girlfriend and her current age raised to the 0.43 power is 3.7030049853137306.\"" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -133,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "a5c07010", "metadata": {}, "outputs": [ @@ -147,21 +141,36 @@ "\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", + "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 Morissette, released June 17, 2022, via Epiphany Music and Thirty Tigers, as well as by RCA Records in Europe.\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", "\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' LIMIT 5;\u001b[0m\n", + "What albums by Alanis Morissette are in the FooBar database?\n", + "SQLQuery:" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/harrisonchase/workplace/langchain/langchain/sql_database.py:191: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.\n", + " sample_rows = connection.execute(command)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32;1m\u001b[1;3m SELECT \"Title\" FROM \"Album\" INNER JOIN \"Artist\" ON \"Album\".\"ArtistId\" = \"Artist\".\"ArtistId\" WHERE \"Name\" = 'Alanis Morissette' LIMIT 5;\u001b[0m\n", "SQLResult: \u001b[33;1m\u001b[1;3m[('Jagged Little Pill',)]\u001b[0m\n", "Answer:\u001b[32;1m\u001b[1;3m The albums by Alanis Morissette in the FooBar database are Jagged Little Pill.\u001b[0m\n", "\u001b[1m> Finished chain.\u001b[0m\n", "\n", "Observation: \u001b[38;5;200m\u001b[1;3m The albums by Alanis Morissette in the FooBar database are Jagged Little Pill.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer\n", - "Final Answer: The artist who released the album The Storm Before the Calm is Alanis Morissette and the albums of theirs in the FooBar database are Jagged Little Pill.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3m I now know the final answer.\n", + "Final Answer: The artist who released the album 'The Storm Before the Calm' is Alanis Morissette and the albums of hers in the FooBar database are Jagged Little Pill.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -169,10 +178,10 @@ { "data": { "text/plain": [ - "'The artist who released the album The Storm Before the Calm is Alanis Morissette and the albums of theirs in the FooBar database are Jagged Little Pill.'" + "\"The artist who released the album 'The Storm Before the Calm' is Alanis Morissette and the albums of hers in the FooBar database are Jagged Little Pill.\"" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } diff --git a/docs/modules/agents/agents/examples/mrkl_chat.ipynb b/docs/modules/agents/agents/examples/mrkl_chat.ipynb index 6fa8a082..44b53b43 100644 --- a/docs/modules/agents/agents/examples/mrkl_chat.ipynb +++ b/docs/modules/agents/agents/examples/mrkl_chat.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 8, "id": "ac561cc4", "metadata": {}, "outputs": [], @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "id": "07e96d99", "metadata": {}, "outputs": [], @@ -43,7 +43,7 @@ "llm1 = OpenAI(temperature=0)\n", "search = SerpAPIWrapper()\n", "llm_math_chain = LLMMathChain(llm=llm1, verbose=True)\n", - "db = SQLDatabase.from_uri(\"sqlite:///../../../../notebooks/Chinook.db\")\n", + "db = SQLDatabase.from_uri(\"sqlite:///../../../../../notebooks/Chinook.db\")\n", "db_chain = SQLDatabaseChain(llm=llm1, database=db, verbose=True)\n", "tools = [\n", " Tool(\n", @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 11, "id": "a069c4b6", "metadata": {}, "outputs": [], @@ -76,7 +76,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "id": "e603cd7d", "metadata": {}, "outputs": [ @@ -92,37 +92,34 @@ "```\n", "{\n", " \"action\": \"Search\",\n", - " \"action_input\": \"Who is Leo DiCaprio's girlfriend?\"\n", + " \"action_input\": \"Leo DiCaprio girlfriend\"\n", "}\n", "```\n", "\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mCamila Morrone\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3mFor the second question, I need to use the calculator tool to raise her current age to the 0.43 power.\n", + "Observation: \u001b[36;1m\u001b[1;3mGigi Hadid: 2022 Leo and Gigi were first linked back in September 2022, when a source told Us Weekly that Leo had his “sights set\" on her (alarming way to put it, but okay).\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mFor the second question, I need to calculate the age raised to the 0.43 power. I will use the calculator tool.\n", "Action:\n", "```\n", "{\n", " \"action\": \"Calculator\",\n", - " \"action_input\": \"22.0^(0.43)\"\n", + " \"action_input\": \"((2022-1995)^0.43)\"\n", "}\n", "```\n", - "\n", "\u001b[0m\n", "\n", "\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n", - "22.0^(0.43)\u001b[32;1m\u001b[1;3m\n", - "```python\n", - "import math\n", - "print(math.pow(22.0, 0.43))\n", + "((2022-1995)^0.43)\u001b[32;1m\u001b[1;3m\n", + "```text\n", + "(2022-1995)**0.43\n", "```\n", + "...numexpr.evaluate(\"(2022-1995)**0.43\")...\n", "\u001b[0m\n", - "Answer: \u001b[33;1m\u001b[1;3m3.777824273683966\n", - "\u001b[0m\n", + "Answer: \u001b[33;1m\u001b[1;3m4.125593352125936\u001b[0m\n", "\u001b[1m> Finished chain.\u001b[0m\n", "\n", - "Observation: \u001b[33;1m\u001b[1;3mAnswer: 3.777824273683966\n", - "\u001b[0m\n", + "Observation: \u001b[33;1m\u001b[1;3mAnswer: 4.125593352125936\u001b[0m\n", "Thought:\u001b[32;1m\u001b[1;3mI now know the final answer.\n", - "Final Answer: Camila Morrone, 3.777824273683966.\u001b[0m\n", + "Final Answer: Gigi Hadid is Leo DiCaprio's girlfriend and her current age raised to the 0.43 power is approximately 4.13.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -130,10 +127,10 @@ { "data": { "text/plain": [ - "'Camila Morrone, 3.777824273683966.'" + "\"Gigi Hadid is Leo DiCaprio's girlfriend and her current age raised to the 0.43 power is approximately 4.13.\"" ] }, - "execution_count": 4, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -144,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 13, "id": "a5c07010", "metadata": {}, "outputs": [ @@ -156,7 +153,7 @@ "\n", "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3mQuestion: 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?\n", - "Thought: I should use the Search tool to find the answer to the first part of the question and then use the FooBar DB tool to find the answer to the second part of the question.\n", + "Thought: I should use the Search tool to find the answer to the first part of the question and then use the FooBar DB tool to find the answer to the second part.\n", "Action:\n", "```\n", "{\n", @@ -166,7 +163,7 @@ "```\n", "\u001b[0m\n", "Observation: \u001b[36;1m\u001b[1;3mAlanis Morissette\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3mNow that I have the name of the artist, I can use the FooBar DB tool to find their albums in the database.\n", + "Thought:\u001b[32;1m\u001b[1;3mNow that I know the artist's name, I can use the FooBar DB tool to find out if they are in the database and what albums of theirs are in it.\n", "Action:\n", "```\n", "{\n", @@ -178,7 +175,7 @@ "\u001b[0m\n", "\n", "\u001b[1m> Entering new SQLDatabaseChain chain...\u001b[0m\n", - "What albums does Alanis Morissette have in the database? \n", + "What albums does Alanis Morissette have in the database?\n", "SQLQuery:" ] }, @@ -186,7 +183,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/Users/harrisonchase/workplace/langchain/langchain/sql_database.py:141: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.\n", + "/Users/harrisonchase/workplace/langchain/langchain/sql_database.py:191: SAWarning: Dialect sqlite+pysqlite does *not* support Decimal objects natively, and SQLAlchemy must convert from floating point - rounding errors and other issues may occur. Please consider storing Decimal numbers as strings or integers on this platform for lossless storage.\n", " sample_rows = connection.execute(command)\n" ] }, @@ -194,14 +191,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32;1m\u001b[1;3m SELECT Title FROM Album WHERE ArtistId IN (SELECT ArtistId FROM Artist WHERE Name = 'Alanis Morissette') LIMIT 5;\u001b[0m\n", + "\u001b[32;1m\u001b[1;3m SELECT \"Title\" FROM \"Album\" WHERE \"ArtistId\" IN (SELECT \"ArtistId\" FROM \"Artist\" WHERE \"Name\" = 'Alanis Morissette') LIMIT 5;\u001b[0m\n", "SQLResult: \u001b[33;1m\u001b[1;3m[('Jagged Little Pill',)]\u001b[0m\n", - "Answer:\u001b[32;1m\u001b[1;3m Alanis Morissette has the album 'Jagged Little Pill' in the database.\u001b[0m\n", + "Answer:\u001b[32;1m\u001b[1;3m Alanis Morissette has the album Jagged Little Pill in the database.\u001b[0m\n", "\u001b[1m> Finished chain.\u001b[0m\n", "\n", - "Observation: \u001b[38;5;200m\u001b[1;3m Alanis Morissette has the album 'Jagged Little Pill' in the database.\u001b[0m\n", - "Thought:\u001b[32;1m\u001b[1;3mI have found the answer to both parts of the question.\n", - "Final Answer: The artist who recently released an album called 'The Storm Before the Calm' is Alanis Morissette. The album 'Jagged Little Pill' is in the FooBar database.\u001b[0m\n", + "Observation: \u001b[38;5;200m\u001b[1;3m Alanis Morissette has the album Jagged Little Pill in the database.\u001b[0m\n", + "Thought:\u001b[32;1m\u001b[1;3mThe artist Alanis Morissette is in the FooBar database and has the album Jagged Little Pill in it.\n", + "Final Answer: Alanis Morissette is in the FooBar database and has the album Jagged Little Pill in it.\u001b[0m\n", "\n", "\u001b[1m> Finished chain.\u001b[0m\n" ] @@ -209,10 +206,10 @@ { "data": { "text/plain": [ - "\"The artist who recently released an album called 'The Storm Before the Calm' is Alanis Morissette. The album 'Jagged Little Pill' is in the FooBar database.\"" + "'Alanis Morissette is in the FooBar database and has the album Jagged Little Pill in it.'" ] }, - "execution_count": 5, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } diff --git a/docs/modules/agents/agents/examples/self_ask_with_search.ipynb b/docs/modules/agents/agents/examples/self_ask_with_search.ipynb index 707f0245..9c95f49e 100644 --- a/docs/modules/agents/agents/examples/self_ask_with_search.ipynb +++ b/docs/modules/agents/agents/examples/self_ask_with_search.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "7e3b513e", "metadata": {}, "outputs": [ @@ -25,11 +25,12 @@ "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m 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 won the 2022 Men's single title while Poland's Iga Swiatek won the Women's single title defeating Tunisian's Ons Jabeur.\u001b[0m\n", - "\u001b[32;1m\u001b[1;3mFollow up: Where is Carlos Alcaraz from?\u001b[0m\n", + "Intermediate answer: \u001b[36;1m\u001b[1;3mCarlos Alcaraz Garfia\u001b[0m\n", + "\u001b[32;1m\u001b[1;3mFollow up: Where is Carlos Alcaraz Garfia from?\u001b[0m\n", "Intermediate answer: \u001b[36;1m\u001b[1;3mEl Palmar, Spain\u001b[0m\n", "\u001b[32;1m\u001b[1;3mSo the final answer is: El Palmar, Spain\u001b[0m\n", - "\u001b[1m> Finished AgentExecutor chain.\u001b[0m\n" + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" ] }, { @@ -38,7 +39,7 @@ "'El Palmar, Spain'" ] }, - "execution_count": 2, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -61,6 +62,14 @@ "self_ask_with_search = initialize_agent(tools, llm, agent=AgentType.SELF_ASK_WITH_SEARCH, verbose=True)\n", "self_ask_with_search.run(\"What is the hometown of the reigning men's U.S. Open champion?\")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2e4d6bc", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -79,7 +88,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.9.1" }, "vscode": { "interpreter": { diff --git a/docs/modules/agents/toolkits/examples/csv.ipynb b/docs/modules/agents/toolkits/examples/csv.ipynb index 7f674b85..1377d200 100644 --- a/docs/modules/agents/toolkits/examples/csv.ipynb +++ b/docs/modules/agents/toolkits/examples/csv.ipynb @@ -35,7 +35,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 3, "id": "16c4dc59", "metadata": {}, "outputs": [], @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 4, "id": "46b9489d", "metadata": {}, "outputs": [ @@ -72,7 +72,7 @@ "'There are 891 rows in the dataframe.'" ] }, - "execution_count": 12, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "a96309be", "metadata": {}, "outputs": [ @@ -110,7 +110,7 @@ "'30 people have more than 3 siblings.'" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -121,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "964a09f7", "metadata": {}, "outputs": [ @@ -143,7 +143,7 @@ "Thought:\u001b[32;1m\u001b[1;3m I need to import the math library\n", "Action: python_repl_ast\n", "Action Input: import math\u001b[0m\n", - "Observation: \u001b[36;1m\u001b[1;3mNone\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3m\u001b[0m\n", "Thought:\u001b[32;1m\u001b[1;3m I can now calculate the square root\n", "Action: python_repl_ast\n", "Action Input: math.sqrt(df['Age'].mean())\u001b[0m\n", @@ -160,7 +160,7 @@ "'5.449689683556195'" ] }, - "execution_count": 7, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } diff --git a/langchain/agents/agent.py b/langchain/agents/agent.py index fb4621ab..e7082f7b 100644 --- a/langchain/agents/agent.py +++ b/langchain/agents/agent.py @@ -336,6 +336,7 @@ class Agent(BaseSingleActionAgent): """ llm_chain: LLMChain + output_parser: AgentOutputParser allowed_tools: Optional[List[str]] = None def get_allowed_tools(self) -> Optional[List[str]]: @@ -345,10 +346,6 @@ class Agent(BaseSingleActionAgent): def return_values(self) -> List[str]: return ["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.") @@ -370,32 +367,6 @@ class Agent(BaseSingleActionAgent): thoughts += f"\n{self.observation_prefix}{observation}\n{self.llm_prefix}" return thoughts - def _get_next_action(self, full_inputs: Dict[str, str]) -> AgentAction: - 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) - return AgentAction( - tool=parsed_output[0], tool_input=parsed_output[1], log=full_output - ) - - async def _aget_next_action(self, full_inputs: Dict[str, str]) -> AgentAction: - full_output = await self.llm_chain.apredict(**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 = await self.llm_chain.apredict(**full_inputs) - full_output += output - parsed_output = self._extract_tool_and_input(full_output) - return AgentAction( - tool=parsed_output[0], tool_input=parsed_output[1], log=full_output - ) - def plan( self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any ) -> Union[AgentAction, AgentFinish]: @@ -410,10 +381,8 @@ class Agent(BaseSingleActionAgent): Action specifying what tool to use. """ full_inputs = self.get_full_inputs(intermediate_steps, **kwargs) - action = self._get_next_action(full_inputs) - if action.tool == self.finish_tool_name: - return AgentFinish({"output": action.tool_input}, action.log) - return action + full_output = self.llm_chain.predict(**full_inputs) + return self.output_parser.parse(full_output) async def aplan( self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any @@ -429,10 +398,8 @@ class Agent(BaseSingleActionAgent): Action specifying what tool to use. """ full_inputs = self.get_full_inputs(intermediate_steps, **kwargs) - action = await self._aget_next_action(full_inputs) - if action.tool == self.finish_tool_name: - return AgentFinish({"output": action.tool_input}, action.log) - return action + full_output = await self.llm_chain.apredict(**full_inputs) + return self.output_parser.parse(full_output) def get_full_inputs( self, intermediate_steps: List[Tuple[AgentAction, str]], **kwargs: Any @@ -443,11 +410,6 @@ class Agent(BaseSingleActionAgent): full_inputs = {**kwargs, **new_inputs} return full_inputs - @property - 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. @@ -494,12 +456,18 @@ class Agent(BaseSingleActionAgent): """Validate that appropriate tools are passed in.""" pass + @classmethod + @abstractmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + """Get default output parser for this class.""" + @classmethod def from_llm_and_tools( cls, llm: BaseLanguageModel, tools: Sequence[BaseTool], callback_manager: Optional[BaseCallbackManager] = None, + output_parser: Optional[AgentOutputParser] = None, **kwargs: Any, ) -> Agent: """Construct an agent from an LLM and tools.""" @@ -510,7 +478,13 @@ class Agent(BaseSingleActionAgent): callback_manager=callback_manager, ) tool_names = [tool.name for tool in tools] - return cls(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) + _output_parser = output_parser or cls._get_default_output_parser() + return cls( + llm_chain=llm_chain, + allowed_tools=tool_names, + output_parser=_output_parser, + **kwargs, + ) def return_stopped_response( self, @@ -540,14 +514,10 @@ class Agent(BaseSingleActionAgent): full_inputs = {**kwargs, **new_inputs} full_output = self.llm_chain.predict(**full_inputs) # We try to extract a final answer - parsed_output = self._extract_tool_and_input(full_output) - if parsed_output is None: - # If we cannot extract, we just return the full output - return AgentFinish({"output": full_output}, full_output) - tool, tool_input = parsed_output - if tool == self.finish_tool_name: + parsed_output = self.output_parser.parse(full_output) + if isinstance(parsed_output, AgentFinish): # If we can extract, we send the correct stuff - return AgentFinish({"output": tool_input}, full_output) + return parsed_output else: # If we can extract, but the tool is not the final tool, # we just return the full output diff --git a/langchain/agents/agent_types.py b/langchain/agents/agent_types.py index c4c374cd..117f59a5 100644 --- a/langchain/agents/agent_types.py +++ b/langchain/agents/agent_types.py @@ -8,4 +8,3 @@ class AgentType(str, Enum): CONVERSATIONAL_REACT_DESCRIPTION = "conversational-react-description" CHAT_ZERO_SHOT_REACT_DESCRIPTION = "chat-zero-shot-react-description" CHAT_CONVERSATIONAL_REACT_DESCRIPTION = "chat-conversational-react-description" - CHAT_ZERO_SHOT_REACT_DESCRIPTION_V2 = "chat-zero-shot-react-description-002" diff --git a/langchain/agents/chat/base.py b/langchain/agents/chat/base.py index 1f843d83..7245c10d 100644 --- a/langchain/agents/chat/base.py +++ b/langchain/agents/chat/base.py @@ -1,7 +1,9 @@ -import json from typing import Any, List, Optional, Sequence, Tuple -from langchain.agents.agent import Agent +from pydantic import Field + +from langchain.agents.agent import Agent, AgentOutputParser +from langchain.agents.chat.output_parser import ChatOutputParser from langchain.agents.chat.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.callbacks.base import BaseCallbackManager from langchain.chains.llm import LLMChain @@ -14,10 +16,10 @@ from langchain.prompts.chat import ( from langchain.schema import AgentAction, BaseLanguageModel from langchain.tools import BaseTool -FINAL_ANSWER_ACTION = "Final Answer:" - class ChatAgent(Agent): + output_parser: AgentOutputParser = Field(default_factory=ChatOutputParser) + @property def observation_prefix(self) -> str: """Prefix to append the observation with.""" @@ -43,16 +45,9 @@ class ChatAgent(Agent): else: return agent_scratchpad - def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]: - if FINAL_ANSWER_ACTION in text: - return "Final Answer", text.split(FINAL_ANSWER_ACTION)[-1].strip() - try: - _, action, _ = text.split("```") - response = json.loads(action.strip()) - return response["action"], response["action_input"] - - except Exception: - raise ValueError(f"Could not parse LLM output: {text}") + @classmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + return ChatOutputParser() @property def _stop(self) -> List[str]: @@ -85,6 +80,7 @@ class ChatAgent(Agent): llm: BaseLanguageModel, tools: Sequence[BaseTool], callback_manager: Optional[BaseCallbackManager] = None, + output_parser: Optional[AgentOutputParser] = None, prefix: str = PREFIX, suffix: str = SUFFIX, format_instructions: str = FORMAT_INSTRUCTIONS, @@ -106,7 +102,13 @@ class ChatAgent(Agent): callback_manager=callback_manager, ) tool_names = [tool.name for tool in tools] - return cls(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) + _output_parser = output_parser or cls._get_default_output_parser() + return cls( + llm_chain=llm_chain, + allowed_tools=tool_names, + output_parser=_output_parser, + **kwargs, + ) @property def _agent_type(self) -> str: diff --git a/langchain/agents/chat/output_parser.py b/langchain/agents/chat/output_parser.py new file mode 100644 index 00000000..7c79a5b2 --- /dev/null +++ b/langchain/agents/chat/output_parser.py @@ -0,0 +1,22 @@ +import json +from typing import Union + +from langchain.agents.agent import AgentOutputParser +from langchain.schema import AgentAction, AgentFinish + +FINAL_ANSWER_ACTION = "Final Answer:" + + +class ChatOutputParser(AgentOutputParser): + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + if FINAL_ANSWER_ACTION in text: + return AgentFinish( + {"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text + ) + try: + _, action, _ = text.split("```") + response = json.loads(action.strip()) + return AgentAction(response["action"], response["action_input"], text) + + except Exception: + raise ValueError(f"Could not parse LLM output: {text}") diff --git a/langchain/agents/chat_v2/__init__.py b/langchain/agents/chat_v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/langchain/agents/chat_v2/base.py b/langchain/agents/chat_v2/base.py deleted file mode 100644 index 618716b1..00000000 --- a/langchain/agents/chat_v2/base.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Any, List, Optional, Sequence - -from langchain.agents.agent import AgentOutputParser, LLMSingleActionAgent -from langchain.agents.chat_v2.prompt import ( - FORMAT_INSTRUCTIONS, - PREFIX, - SUFFIX, - ChatOutputParser, - create_prompt, -) -from langchain.callbacks.base import BaseCallbackManager -from langchain.chains.llm import LLMChain -from langchain.schema import BaseLanguageModel -from langchain.tools import BaseTool - - -class ChatAgentV2(LLMSingleActionAgent): - @classmethod - def from_llm_and_tools( - cls, - llm: BaseLanguageModel, - tools: Sequence[BaseTool], - callback_manager: Optional[BaseCallbackManager] = None, - prefix: str = PREFIX, - suffix: str = SUFFIX, - format_instructions: str = FORMAT_INSTRUCTIONS, - input_variables: Optional[List[str]] = None, - output_parser: Optional[AgentOutputParser] = None, - stop: Optional[List[str]] = None, - **kwargs: Any, - ) -> LLMSingleActionAgent: - """Construct an agent from an LLM and tools.""" - _stop = stop or ["Observation:"] - _output_parser = output_parser or ChatOutputParser() - prompt = create_prompt( - tools, - prefix=prefix, - suffix=suffix, - format_instructions=format_instructions, - input_variables=input_variables, - ) - llm_chain = LLMChain( - llm=llm, - prompt=prompt, - callback_manager=callback_manager, - ) - return cls( - llm_chain=llm_chain, output_parser=_output_parser, stop=_stop, **kwargs - ) - - @property - def _agent_type(self) -> str: - raise ValueError diff --git a/langchain/agents/chat_v2/prompt.py b/langchain/agents/chat_v2/prompt.py deleted file mode 100644 index 20c42350..00000000 --- a/langchain/agents/chat_v2/prompt.py +++ /dev/null @@ -1,84 +0,0 @@ -# flake8: noqa -import json -from langchain.prompts.chat import ( - HumanMessagePromptTemplate, - SystemMessagePromptTemplate, -) -from langchain.agents.schema import AgentScratchPadChatPromptTemplate -from langchain.prompts.base import BasePromptTemplate -from langchain.schema import AgentAction, AgentFinish -from langchain.tools.base import BaseTool -from typing import Sequence, Optional, List, Union -from langchain.agents.agent import AgentOutputParser - -PREFIX = """Answer the following questions as best you can. You have access to the following tools:""" -FORMAT_INSTRUCTIONS = """The way you use the tools is by specifying a json blob. -Specifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here). - -The only values that should be in the "action" field are: {tool_names} - -The $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB: - -``` -{{{{ - "action": $TOOL_NAME, - "action_input": $INPUT -}}}} -``` - -ALWAYS use the following format: - -Question: the input question you must answer -Thought: you should always think about what to do -Action: -``` -$JSON_BLOB -``` -Observation: the result of the action -... (this Thought/Action/Observation can repeat N times) -Thought: I now know the final answer -Final Answer: the final answer to the original input question""" -SUFFIX = """Begin! Reminder to always use the exact characters `Final Answer` when responding.""" - - -def create_prompt( - tools: Sequence[BaseTool], - prefix: str = PREFIX, - suffix: str = SUFFIX, - format_instructions: str = FORMAT_INSTRUCTIONS, - input_variables: Optional[List[str]] = None, -) -> BasePromptTemplate: - tool_strings = "\n".join([f"{tool.name}: {tool.description}" for tool in tools]) - tool_names = ", ".join([tool.name for tool in tools]) - format_instructions = format_instructions.format(tool_names=tool_names) - template = "\n\n".join([prefix, tool_strings, format_instructions, suffix]) - messages = [ - SystemMessagePromptTemplate.from_template(template), - HumanMessagePromptTemplate.from_template("{input}\n\n{agent_scratchpad}"), - ] - if input_variables is None: - input_variables = ["input", "intermediate_steps"] - return AgentScratchPadChatPromptTemplate( - input_variables=input_variables, messages=messages - ) - - -class ChatOutputParser(AgentOutputParser): - def parse(self, text: str) -> Union[AgentAction, AgentFinish]: - if "Final Answer:" in text: - return AgentFinish( - # Return values is generally always a dictionary with a single `output` key - # It is not recommended to try anything else at the moment :) - return_values={"output": text.split("Final Answer:")[-1].strip()}, - log=text, - ) - try: - _, action, _ = text.split("```") - response = json.loads(action.strip()) - agent_action = AgentAction( - tool=response["action"], tool_input=response["action_input"], log=text - ) - return agent_action - - except Exception: - raise ValueError(f"Could not parse LLM output: {text}") diff --git a/langchain/agents/conversational/base.py b/langchain/agents/conversational/base.py index 921649d1..75018314 100644 --- a/langchain/agents/conversational/base.py +++ b/langchain/agents/conversational/base.py @@ -1,11 +1,13 @@ """An agent designed to hold a conversation in addition to using tools.""" from __future__ import annotations -import re -from typing import Any, List, Optional, Sequence, Tuple +from typing import Any, List, Optional, Sequence -from langchain.agents.agent import Agent +from pydantic import Field + +from langchain.agents.agent import Agent, AgentOutputParser from langchain.agents.agent_types import AgentType +from langchain.agents.conversational.output_parser import ConvoOutputParser from langchain.agents.conversational.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.callbacks.base import BaseCallbackManager from langchain.chains import LLMChain @@ -18,6 +20,13 @@ class ConversationalAgent(Agent): """An agent designed to hold a conversation in addition to using tools.""" ai_prefix: str = "AI" + output_parser: AgentOutputParser = Field(default_factory=ConvoOutputParser) + + @classmethod + def _get_default_output_parser( + cls, ai_prefix: str = "AI", **kwargs: Any + ) -> AgentOutputParser: + return ConvoOutputParser(ai_prefix=ai_prefix) @property def _agent_type(self) -> str: @@ -71,28 +80,13 @@ class ConversationalAgent(Agent): input_variables = ["input", "chat_history", "agent_scratchpad"] return PromptTemplate(template=template, input_variables=input_variables) - @property - def finish_tool_name(self) -> str: - """Name of the tool to use to finish the chain.""" - return self.ai_prefix - - def _extract_tool_and_input(self, llm_output: str) -> Optional[Tuple[str, str]]: - if f"{self.ai_prefix}:" in llm_output: - return self.ai_prefix, llm_output.split(f"{self.ai_prefix}:")[-1].strip() - regex = r"Action: (.*?)[\n]*Action Input: (.*)" - match = re.search(regex, llm_output) - if not match: - raise ValueError(f"Could not parse LLM output: `{llm_output}`") - action = match.group(1) - action_input = match.group(2) - return action.strip(), action_input.strip(" ").strip('"') - @classmethod def from_llm_and_tools( cls, llm: BaseLanguageModel, tools: Sequence[BaseTool], callback_manager: Optional[BaseCallbackManager] = None, + output_parser: Optional[AgentOutputParser] = None, prefix: str = PREFIX, suffix: str = SUFFIX, format_instructions: str = FORMAT_INSTRUCTIONS, @@ -118,6 +112,13 @@ class ConversationalAgent(Agent): callback_manager=callback_manager, ) tool_names = [tool.name for tool in tools] - return cls( - llm_chain=llm_chain, allowed_tools=tool_names, ai_prefix=ai_prefix, **kwargs + _output_parser = output_parser or cls._get_default_output_parser( + ai_prefix=ai_prefix + ) + return cls( + llm_chain=llm_chain, + allowed_tools=tool_names, + ai_prefix=ai_prefix, + output_parser=_output_parser, + **kwargs, ) diff --git a/langchain/agents/conversational/output_parser.py b/langchain/agents/conversational/output_parser.py new file mode 100644 index 00000000..a8cdf844 --- /dev/null +++ b/langchain/agents/conversational/output_parser.py @@ -0,0 +1,22 @@ +import re +from typing import Union + +from langchain.agents.agent import AgentOutputParser +from langchain.schema import AgentAction, AgentFinish + + +class ConvoOutputParser(AgentOutputParser): + ai_prefix: str = "AI" + + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + if f"{self.ai_prefix}:" in text: + return AgentFinish( + {"output": text.split(f"{self.ai_prefix}:")[-1].strip()}, text + ) + regex = r"Action: (.*?)[\n]*Action Input: (.*)" + match = re.search(regex, text) + if not match: + raise ValueError(f"Could not parse LLM output: `{text}`") + action = match.group(1) + action_input = match.group(2) + return AgentAction(action.strip(), action_input.strip(" ").strip('"'), text) diff --git a/langchain/agents/conversational_chat/base.py b/langchain/agents/conversational_chat/base.py index 2a1c8b7f..a91915c0 100644 --- a/langchain/agents/conversational_chat/base.py +++ b/langchain/agents/conversational_chat/base.py @@ -1,12 +1,13 @@ """An agent designed to hold a conversation in addition to using tools.""" from __future__ import annotations -import json from typing import Any, List, Optional, Sequence, Tuple -from langchain.agents.agent import Agent +from pydantic import Field + +from langchain.agents.agent import Agent, AgentOutputParser +from langchain.agents.conversational_chat.output_parser import ConvoOutputParser from langchain.agents.conversational_chat.prompt import ( - FORMAT_INSTRUCTIONS, PREFIX, SUFFIX, TEMPLATE_TOOL_RESPONSE, @@ -31,31 +32,14 @@ from langchain.schema import ( from langchain.tools.base import BaseTool -class AgentOutputParser(BaseOutputParser): - def get_format_instructions(self) -> str: - return FORMAT_INSTRUCTIONS - - def parse(self, text: str) -> Any: - cleaned_output = text.strip() - if "```json" in cleaned_output: - _, cleaned_output = cleaned_output.split("```json") - if "```" in cleaned_output: - cleaned_output, _ = cleaned_output.split("```") - if cleaned_output.startswith("```json"): - cleaned_output = cleaned_output[len("```json") :] - if cleaned_output.startswith("```"): - cleaned_output = cleaned_output[len("```") :] - if cleaned_output.endswith("```"): - cleaned_output = cleaned_output[: -len("```")] - cleaned_output = cleaned_output.strip() - response = json.loads(cleaned_output) - return {"action": response["action"], "action_input": response["action_input"]} - - class ConversationalChatAgent(Agent): """An agent designed to hold a conversation in addition to using tools.""" - output_parser: BaseOutputParser + output_parser: AgentOutputParser = Field(default_factory=ConvoOutputParser) + + @classmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + return ConvoOutputParser() @property def _agent_type(self) -> str: @@ -84,7 +68,7 @@ class ConversationalChatAgent(Agent): [f"> {tool.name}: {tool.description}" for tool in tools] ) tool_names = ", ".join([tool.name for tool in tools]) - _output_parser = output_parser or AgentOutputParser() + _output_parser = output_parser or cls._get_default_output_parser() format_instructions = human_message.format( format_instructions=_output_parser.get_format_instructions() ) @@ -101,13 +85,6 @@ class ConversationalChatAgent(Agent): ] return ChatPromptTemplate(input_variables=input_variables, messages=messages) - def _extract_tool_and_input(self, llm_output: str) -> Optional[Tuple[str, str]]: - try: - response = self.output_parser.parse(llm_output) - return response["action"], response["action_input"] - except Exception: - raise ValueError(f"Could not parse LLM output: {llm_output}") - def _construct_scratchpad( self, intermediate_steps: List[Tuple[AgentAction, str]] ) -> List[BaseMessage]: @@ -127,15 +104,15 @@ class ConversationalChatAgent(Agent): llm: BaseLanguageModel, tools: Sequence[BaseTool], callback_manager: Optional[BaseCallbackManager] = None, + output_parser: Optional[AgentOutputParser] = None, system_message: str = PREFIX, human_message: str = SUFFIX, input_variables: Optional[List[str]] = None, - output_parser: Optional[BaseOutputParser] = None, **kwargs: Any, ) -> Agent: """Construct an agent from an LLM and tools.""" cls._validate_tools(tools) - _output_parser = output_parser or AgentOutputParser() + _output_parser = output_parser or cls._get_default_output_parser() prompt = cls.create_prompt( tools, system_message=system_message, diff --git a/langchain/agents/conversational_chat/output_parser.py b/langchain/agents/conversational_chat/output_parser.py new file mode 100644 index 00000000..3b2e7b52 --- /dev/null +++ b/langchain/agents/conversational_chat/output_parser.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +import json +from typing import Union + +from langchain.agents import AgentOutputParser +from langchain.agents.conversational_chat.prompt import FORMAT_INSTRUCTIONS +from langchain.schema import AgentAction, AgentFinish + + +class ConvoOutputParser(AgentOutputParser): + def get_format_instructions(self) -> str: + return FORMAT_INSTRUCTIONS + + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + cleaned_output = text.strip() + if "```json" in cleaned_output: + _, cleaned_output = cleaned_output.split("```json") + if "```" in cleaned_output: + cleaned_output, _ = cleaned_output.split("```") + if cleaned_output.startswith("```json"): + cleaned_output = cleaned_output[len("```json") :] + if cleaned_output.startswith("```"): + cleaned_output = cleaned_output[len("```") :] + if cleaned_output.endswith("```"): + cleaned_output = cleaned_output[: -len("```")] + cleaned_output = cleaned_output.strip() + response = json.loads(cleaned_output) + action, action_input = response["action"], response["action_input"] + if action == "Final Answer": + return AgentFinish({"output": action_input}, text) + else: + return AgentAction(action, action_input, text) diff --git a/langchain/agents/loading.py b/langchain/agents/loading.py index 8824b4b5..fffe4997 100644 --- a/langchain/agents/loading.py +++ b/langchain/agents/loading.py @@ -1,14 +1,13 @@ """Functionality for loading agents.""" import json from pathlib import Path -from typing import Any, List, Optional, Union +from typing import Any, Dict, List, Optional, Type, Union import yaml from langchain.agents.agent import BaseSingleActionAgent from langchain.agents.agent_types import AgentType from langchain.agents.chat.base import ChatAgent -from langchain.agents.chat_v2.base import ChatAgentV2 from langchain.agents.conversational.base import ConversationalAgent from langchain.agents.conversational_chat.base import ConversationalChatAgent from langchain.agents.mrkl.base import ZeroShotAgent @@ -19,14 +18,13 @@ from langchain.chains.loading import load_chain, load_chain_from_config from langchain.llms.base import BaseLLM from langchain.utilities.loading import try_load_from_hub -AGENT_TO_CLASS = { +AGENT_TO_CLASS: Dict[AgentType, Type[BaseSingleActionAgent]] = { AgentType.ZERO_SHOT_REACT_DESCRIPTION: ZeroShotAgent, AgentType.REACT_DOCSTORE: ReActDocstoreAgent, AgentType.SELF_ASK_WITH_SEARCH: SelfAskWithSearchAgent, AgentType.CONVERSATIONAL_REACT_DESCRIPTION: ConversationalAgent, AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION: ChatAgent, AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION: ConversationalChatAgent, - AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION_V2: ChatAgentV2, } URL_BASE = "https://raw.githubusercontent.com/hwchase17/langchain-hub/master/agents/" diff --git a/langchain/agents/mrkl/base.py b/langchain/agents/mrkl/base.py index c44c477b..4bb1d519 100644 --- a/langchain/agents/mrkl/base.py +++ b/langchain/agents/mrkl/base.py @@ -1,11 +1,13 @@ """Attempt to implement MRKL systems as described in arxiv.org/pdf/2205.00445.pdf.""" from __future__ import annotations -import re -from typing import Any, Callable, List, NamedTuple, Optional, Sequence, Tuple +from typing import Any, Callable, List, NamedTuple, Optional, Sequence -from langchain.agents.agent import Agent, AgentExecutor +from pydantic import Field + +from langchain.agents.agent import Agent, AgentExecutor, AgentOutputParser from langchain.agents.agent_types import AgentType +from langchain.agents.mrkl.output_parser import MRKLOutputParser from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.agents.tools import Tool from langchain.callbacks.base import BaseCallbackManager @@ -14,8 +16,6 @@ from langchain.prompts import PromptTemplate from langchain.schema import BaseLanguageModel from langchain.tools.base import BaseTool -FINAL_ANSWER_ACTION = "Final Answer:" - class ChainConfig(NamedTuple): """Configuration for chain to use in MRKL system. @@ -31,29 +31,15 @@ class ChainConfig(NamedTuple): action_description: str -def get_action_and_input(llm_output: str) -> Tuple[str, str]: - """Parse out the action and input from the LLM output. - - Note: if you're specifying a custom prompt for the ZeroShotAgent, - you will need to ensure that it meets the following Regex requirements. - The string starting with "Action:" and the following string starting - with "Action Input:" should be separated by a newline. - """ - if FINAL_ANSWER_ACTION in llm_output: - return "Final Answer", llm_output.split(FINAL_ANSWER_ACTION)[-1].strip() - # \s matches against tab/newline/whitespace - regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" - match = re.search(regex, llm_output, re.DOTALL) - if not match: - raise ValueError(f"Could not parse LLM output: `{llm_output}`") - action = match.group(1).strip() - action_input = match.group(2) - return action, action_input.strip(" ").strip('"') - - class ZeroShotAgent(Agent): """Agent for the MRKL chain.""" + output_parser: AgentOutputParser = Field(default_factory=MRKLOutputParser) + + @classmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + return MRKLOutputParser() + @property def _agent_type(self) -> str: """Return Identifier of agent type.""" @@ -104,6 +90,7 @@ class ZeroShotAgent(Agent): llm: BaseLanguageModel, tools: Sequence[BaseTool], callback_manager: Optional[BaseCallbackManager] = None, + output_parser: Optional[AgentOutputParser] = None, prefix: str = PREFIX, suffix: str = SUFFIX, format_instructions: str = FORMAT_INSTRUCTIONS, @@ -125,7 +112,13 @@ class ZeroShotAgent(Agent): callback_manager=callback_manager, ) tool_names = [tool.name for tool in tools] - return cls(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) + _output_parser = output_parser or cls._get_default_output_parser() + return cls( + llm_chain=llm_chain, + allowed_tools=tool_names, + output_parser=_output_parser, + **kwargs, + ) @classmethod def _validate_tools(cls, tools: Sequence[BaseTool]) -> None: @@ -136,9 +129,6 @@ class ZeroShotAgent(Agent): f"a description must always be provided." ) - def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]: - return get_action_and_input(text) - class MRKLChain(AgentExecutor): """Chain that implements the MRKL system. diff --git a/langchain/agents/mrkl/output_parser.py b/langchain/agents/mrkl/output_parser.py new file mode 100644 index 00000000..8e9c0f16 --- /dev/null +++ b/langchain/agents/mrkl/output_parser.py @@ -0,0 +1,23 @@ +import re +from typing import Union + +from langchain.agents.agent import AgentOutputParser +from langchain.schema import AgentAction, AgentFinish + +FINAL_ANSWER_ACTION = "Final Answer:" + + +class MRKLOutputParser(AgentOutputParser): + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + if FINAL_ANSWER_ACTION in text: + return AgentFinish( + {"output": text.split(FINAL_ANSWER_ACTION)[-1].strip()}, text + ) + # \s matches against tab/newline/whitespace + regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)" + match = re.search(regex, text, re.DOTALL) + if not match: + raise ValueError(f"Could not parse LLM output: `{text}`") + action = match.group(1).strip() + action_input = match.group(2) + return AgentAction(action, action_input.strip(" ").strip('"'), text) diff --git a/langchain/agents/react/base.py b/langchain/agents/react/base.py index a16fbede..08d27d4d 100644 --- a/langchain/agents/react/base.py +++ b/langchain/agents/react/base.py @@ -1,9 +1,11 @@ """Chain that implements the ReAct paper from https://arxiv.org/pdf/2210.03629.pdf.""" -import re -from typing import Any, List, Optional, Sequence, Tuple +from typing import Any, List, Optional, Sequence -from langchain.agents.agent import Agent, AgentExecutor +from pydantic import Field + +from langchain.agents.agent import Agent, AgentExecutor, AgentOutputParser from langchain.agents.agent_types import AgentType +from langchain.agents.react.output_parser import ReActOutputParser from langchain.agents.react.textworld_prompt import TEXTWORLD_PROMPT from langchain.agents.react.wiki_prompt import WIKI_PROMPT from langchain.agents.tools import Tool @@ -17,6 +19,12 @@ from langchain.tools.base import BaseTool class ReActDocstoreAgent(Agent): """Agent for the ReAct chain.""" + output_parser: AgentOutputParser = Field(default_factory=ReActOutputParser) + + @classmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + return ReActOutputParser() + @property def _agent_type(self) -> str: """Return Identifier of agent type.""" @@ -37,27 +45,6 @@ class ReActDocstoreAgent(Agent): f"Tool names should be Lookup and Search, got {tool_names}" ) - def _fix_text(self, text: str) -> str: - return text + "\nAction:" - - def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]: - action_prefix = "Action: " - if not text.strip().split("\n")[-1].startswith(action_prefix): - return None - action_block = text.strip().split("\n")[-1] - - action_str = action_block[len(action_prefix) :] - # Parse out the action and the directive. - re_matches = re.search(r"(.*?)\[(.*?)\]", action_str) - if re_matches is None: - raise ValueError(f"Could not parse action directive: {action_str}") - return re_matches.group(1), re_matches.group(2) - - @property - def finish_tool_name(self) -> str: - """Name of the tool of when to finish the chain.""" - return "Finish" - @property def observation_prefix(self) -> str: """Prefix to append the observation with.""" diff --git a/langchain/agents/react/output_parser.py b/langchain/agents/react/output_parser.py new file mode 100644 index 00000000..6b96f216 --- /dev/null +++ b/langchain/agents/react/output_parser.py @@ -0,0 +1,24 @@ +import re +from typing import Union + +from langchain.agents.agent import AgentOutputParser +from langchain.schema import AgentAction, AgentFinish + + +class ReActOutputParser(AgentOutputParser): + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + action_prefix = "Action: " + if not text.strip().split("\n")[-1].startswith(action_prefix): + raise ValueError(f"Could not parse LLM Output: {text}") + action_block = text.strip().split("\n")[-1] + + action_str = action_block[len(action_prefix) :] + # Parse out the action and the directive. + re_matches = re.search(r"(.*?)\[(.*?)\]", action_str) + if re_matches is None: + raise ValueError(f"Could not parse action directive: {action_str}") + action, action_input = re_matches.group(1), re_matches.group(2) + if action == "Finish": + return AgentFinish({"output": action_input}, text) + else: + return AgentAction(action, action_input, text) diff --git a/langchain/agents/self_ask_with_search/base.py b/langchain/agents/self_ask_with_search/base.py index 4bc888e7..297b0345 100644 --- a/langchain/agents/self_ask_with_search/base.py +++ b/langchain/agents/self_ask_with_search/base.py @@ -1,8 +1,11 @@ """Chain that does self ask with search.""" -from typing import Any, Optional, Sequence, Tuple, Union +from typing import Any, Sequence, Union -from langchain.agents.agent import Agent, AgentExecutor +from pydantic import Field + +from langchain.agents.agent import Agent, AgentExecutor, AgentOutputParser from langchain.agents.agent_types import AgentType +from langchain.agents.self_ask_with_search.output_parser import SelfAskOutputParser from langchain.agents.self_ask_with_search.prompt import PROMPT from langchain.agents.tools import Tool from langchain.llms.base import BaseLLM @@ -15,6 +18,12 @@ from langchain.utilities.serpapi import SerpAPIWrapper class SelfAskWithSearchAgent(Agent): """Agent for the self-ask-with-search paper.""" + output_parser: AgentOutputParser = Field(default_factory=SelfAskOutputParser) + + @classmethod + def _get_default_output_parser(cls, **kwargs: Any) -> AgentOutputParser: + return SelfAskOutputParser() + @property def _agent_type(self) -> str: """Return Identifier of agent type.""" @@ -35,26 +44,6 @@ class SelfAskWithSearchAgent(Agent): f"Tool name should be Intermediate Answer, got {tool_names}" ) - def _extract_tool_and_input(self, text: str) -> Optional[Tuple[str, str]]: - followup = "Follow up:" - last_line = text.split("\n")[-1] - - if followup not in last_line: - finish_string = "So the final answer is: " - if finish_string not in last_line: - return None - return "Final Answer", last_line[len(finish_string) :] - - after_colon = text.split(":")[-1] - - if " " == after_colon[0]: - after_colon = after_colon[1:] - - return "Intermediate Answer", after_colon - - def _fix_text(self, text: str) -> str: - return f"{text}\nSo the final answer is:" - @property def observation_prefix(self) -> str: """Prefix to append the observation with.""" @@ -65,11 +54,6 @@ class SelfAskWithSearchAgent(Agent): """Prefix to append the LLM call with.""" return "" - @property - def starter_string(self) -> str: - """Put this string after user input but before first LLM call.""" - return "Are follow up questions needed here:" - class SelfAskWithSearchChain(AgentExecutor): """Chain that does self ask with search. diff --git a/langchain/agents/self_ask_with_search/output_parser.py b/langchain/agents/self_ask_with_search/output_parser.py new file mode 100644 index 00000000..241a9317 --- /dev/null +++ b/langchain/agents/self_ask_with_search/output_parser.py @@ -0,0 +1,22 @@ +from typing import Union + +from langchain.agents.agent import AgentOutputParser +from langchain.schema import AgentAction, AgentFinish + + +class SelfAskOutputParser(AgentOutputParser): + def parse(self, text: str) -> Union[AgentAction, AgentFinish]: + followup = "Follow up:" + last_line = text.split("\n")[-1] + + if followup not in last_line: + finish_string = "So the final answer is: " + if finish_string not in last_line: + raise ValueError(f"Could not parse output: {text}") + return AgentFinish({"output": last_line[len(finish_string) :]}, text) + + after_colon = text.split(":")[-1] + + if " " == after_colon[0]: + after_colon = after_colon[1:] + return AgentAction("Intermediate Answer", after_colon, text) diff --git a/langchain/callbacks/stdout.py b/langchain/callbacks/stdout.py index cabd8f0c..18eb0d21 100644 --- a/langchain/callbacks/stdout.py +++ b/langchain/callbacks/stdout.py @@ -74,10 +74,10 @@ class StdOutCallbackHandler(BaseCallbackHandler): **kwargs: Any, ) -> None: """If not the final action, print out observation.""" - if observation_prefix: + if observation_prefix is not None: print_text(f"\n{observation_prefix}") print_text(output, color=color if color else self.color) - if llm_prefix: + if llm_prefix is not None: print_text(f"\n{llm_prefix}") def on_tool_error( diff --git a/tests/unit_tests/agents/test_agent.py b/tests/unit_tests/agents/test_agent.py index ce052170..f30b3f05 100644 --- a/tests/unit_tests/agents/test_agent.py +++ b/tests/unit_tests/agents/test_agent.py @@ -37,7 +37,7 @@ def _get_agent(**kwargs: Any) -> AgentExecutor: bad_action_name = "BadAction" responses = [ f"I'm turning evil\nAction: {bad_action_name}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses) tools = [ @@ -92,7 +92,7 @@ def test_agent_with_callbacks_global() -> None: tool = "Search" responses = [ f"FooBarBaz\nAction: {tool}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses, callback_manager=manager, verbose=True) tools = [ @@ -138,7 +138,7 @@ def test_agent_with_callbacks_local() -> None: tool = "Search" responses = [ f"FooBarBaz\nAction: {tool}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses, callback_manager=manager, verbose=True) tools = [ @@ -186,7 +186,7 @@ def test_agent_with_callbacks_not_verbose() -> None: tool = "Search" responses = [ f"FooBarBaz\nAction: {tool}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses, callback_manager=manager) tools = [ @@ -217,7 +217,7 @@ def test_agent_tool_return_direct() -> None: tool = "Search" responses = [ f"FooBarBaz\nAction: {tool}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses) tools = [ @@ -243,7 +243,7 @@ def test_agent_tool_return_direct_in_intermediate_steps() -> None: tool = "Search" responses = [ f"FooBarBaz\nAction: {tool}\nAction Input: misalignment", - "Oh well\nAction: Final Answer\nAction Input: curses foiled again", + "Oh well\nFinal Answer: curses foiled again", ] fake_llm = FakeListLLM(responses=responses) tools = [ diff --git a/tests/unit_tests/agents/test_mrkl.py b/tests/unit_tests/agents/test_mrkl.py index 15079238..d88fbf02 100644 --- a/tests/unit_tests/agents/test_mrkl.py +++ b/tests/unit_tests/agents/test_mrkl.py @@ -1,14 +1,26 @@ """Test MRKL functionality.""" +from typing import Tuple + import pytest -from langchain.agents.mrkl.base import ZeroShotAgent, get_action_and_input +from langchain.agents.mrkl.base import ZeroShotAgent +from langchain.agents.mrkl.output_parser import MRKLOutputParser from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS, PREFIX, SUFFIX from langchain.agents.tools import Tool from langchain.prompts import PromptTemplate +from langchain.schema import AgentAction from tests.unit_tests.llms.fake_llm import FakeLLM +def get_action_and_input(text: str) -> Tuple[str, str]: + output = MRKLOutputParser().parse(text) + if isinstance(output, AgentAction): + return output.tool, output.tool_input + else: + return "Final Answer", output.return_values["output"] + + def test_get_action_and_input() -> None: """Test getting an action from text.""" llm_output = ( diff --git a/tests/unit_tests/agents/test_react.py b/tests/unit_tests/agents/test_react.py index 0c54a9ca..2689ea36 100644 --- a/tests/unit_tests/agents/test_react.py +++ b/tests/unit_tests/agents/test_react.py @@ -65,20 +65,6 @@ def test_predict_until_observation_normal() -> None: assert output == expected_output -def test_predict_until_observation_repeat() -> None: - """Test when no action is generated initially.""" - outputs = ["foo", " Search[foo]"] - fake_llm = FakeListLLM(responses=outputs) - tools = [ - Tool(name="Search", func=lambda x: x, description="foo"), - Tool(name="Lookup", func=lambda x: x, description="bar"), - ] - agent = ReActDocstoreAgent.from_llm_and_tools(fake_llm, tools) - output = agent.plan([], input="") - expected_output = AgentAction("Search", "foo", "foo\nAction: Search[foo]") - assert output == expected_output - - def test_react_chain() -> None: """Test react chain.""" responses = [