From e0770d68b31b3fbf38d091857c86017ab2d9c853 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Sat, 4 May 2024 01:30:52 -0700 Subject: [PATCH] Update notebooks to use bind_tools (#394) --- examples/async.ipynb | 198 ++++++-------- .../base.ipynb | 241 ++++++++++++------ .../dynamically-returning-directly.ipynb | 108 ++++---- .../force-calling-a-tool-first.ipynb | 109 +++++--- .../human-in-the-loop.ipynb | 178 +++++++------ .../managing-agent-steps.ipynb | 93 ++++--- .../respond-in-format.ipynb | 100 +++++--- .../information-gather-prompting.ipynb | 17 +- .../langgraph_code_assistant.ipynb | 202 ++++++++++----- examples/configuration.ipynb | 21 +- examples/llm-compiler/LLMCompiler.ipynb | 16 +- .../multi-agent-collaboration.ipynb | 222 ++++++++-------- examples/persistence_postgres.ipynb | 4 +- examples/rag/langgraph_adaptive_rag.ipynb | 124 ++++++--- .../rag/langgraph_adaptive_rag_cohere.ipynb | 115 ++++++--- .../rag/langgraph_adaptive_rag_local.ipynb | 76 ++++-- examples/rag/langgraph_agentic_rag.ipynb | 36 +-- examples/rag/langgraph_crag.ipynb | 57 +++-- examples/rag/langgraph_crag_local.ipynb | 44 +++- .../langgraph_rag_agent_llama3_local.ipynb | 81 ++++-- examples/rag/langgraph_self_rag.ipynb | 91 ++++--- examples/rag/langgraph_self_rag_local.ipynb | 61 +++-- examples/state-model.ipynb | 69 +++-- examples/storm/storm.ipynb | 5 +- examples/streaming-tokens.ipynb | 83 +++--- examples/time-travel.ipynb | 14 +- examples/usaco/usaco.ipynb | 4 + examples/visualization.ipynb | 44 ++-- langgraph/checkpoint/sqlite.py | 2 +- 29 files changed, 1466 insertions(+), 949 deletions(-) diff --git a/examples/async.ipynb b/examples/async.ipynb index 13ee2e29b..dc5597a44 100644 --- a/examples/async.ipynb +++ b/examples/async.ipynb @@ -56,7 +56,7 @@ "metadata": {}, "outputs": [ { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ "OpenAI API Key: ········\n", @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 1, "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e", "metadata": {}, "outputs": [], @@ -127,7 +127,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7", "metadata": {}, "outputs": [], @@ -155,7 +155,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], @@ -179,15 +179,12 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -210,18 +207,25 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "id": "ea793afa-2eab-4901-910d-6eed90cd6564", "metadata": {}, "outputs": [], "source": [ "from typing import TypedDict, Annotated, Sequence\n", - "import operator\n", "from langchain_core.messages import BaseMessage\n", "\n", "\n", + "def add_messages(left: list | None, right: list | None) -> list:\n", + " if not left:\n", + " left = []\n", + " if not right:\n", + " right = []\n", + " return left + right\n", + "\n", + "\n", "class AgentState(TypedDict):\n", - " messages: Annotated[Sequence[BaseMessage], operator.add]" + " messages: Annotated[Sequence[BaseMessage], add_messages]" ] }, { @@ -257,22 +261,22 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 22, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ + "from typing import Literal\n", "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", - "def should_continue(state):\n", + "def should_continue(state) -> Literal[\"end\", \"continue\"]:\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", - " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " # If there is no tool call, then we finish\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -295,15 +299,17 @@ " last_message = messages[-1]\n", " # We construct an ToolInvocation from the function_call\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=last_message.tool_calls[0][\"name\"],\n", + " tool_input=last_message.tool_calls[0][\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", " response = await tool_executor.ainvoke(action)\n", " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " function_message = ToolMessage(\n", + " content=str(response),\n", + " name=action.tool,\n", + " tool_call_id=last_message.tool_calls[0][\"id\"],\n", + " )\n", " # We return a list, because this will get added to the existing list\n", " return {\"messages\": [function_message]}" ] @@ -320,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 23, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -369,6 +375,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 24, + "id": "4b369a6f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -382,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 25, "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", "metadata": {}, "outputs": [ @@ -390,12 +423,12 @@ "data": { "text/plain": [ "{'messages': [HumanMessage(content='what is the weather in sf'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}),\n", - " FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'),\n", - " AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}" + " AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_UCKe4ydjxDCQPaawAbIWAuwQ', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-f0d86d19-0bdd-46b5-9e14-fd3c9649eac3-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_UCKe4ydjxDCQPaawAbIWAuwQ'}]),\n", + " ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714807578, \\'localtime\\': \\'2024-05-04 0:26\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714806900, \\'last_updated\\': \\'2024-05-04 00:15\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_UCKe4ydjxDCQPaawAbIWAuwQ'),\n", + " AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph (19.1 kph) from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Feels like: 52.4°F (11.4°C)\\n- Visibility: 9.0 miles (16.0 km)\\n- UV Index: 1.0\\n\\nFor more detailed information, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-0415f86c-579a-404d-a1f8-cd9224f8b7bb-0')]}" ] }, - "execution_count": 10, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -426,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 26, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -436,25 +469,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_os6sSAwICFGXtN8z5Isgnp63', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-3498553b-4ca0-4920-bd9a-0780632d4607-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_os6sSAwICFGXtN8z5Isgnp63'}])]}\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714807578, \\'localtime\\': \\'2024-05-04 0:26\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714806900, \\'last_updated\\': \\'2024-05-04 00:15\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_os6sSAwICFGXtN8z5Isgnp63')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph (19.1 kph) from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Feels like: 52.4°F (11.4°C)\\n- Visibility: 9.0 miles (16.0 km)\\n- UV Index: 1.0\\n\\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-bc828ced-d6f4-45ca-babd-75282b71af82-0')]}\n", "\n", "---\n", "\n" @@ -486,7 +513,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 29, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -494,84 +521,15 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '\":', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': 'weather', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' in', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' San', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' Francisco', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '\"\\n', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '}', 'name': ''}}\n", - "content=''\n", - "content=''\n", - "content='The'\n", - "content=' current'\n", - "content=' weather'\n", - "content=' in'\n", - "content=' San'\n", - "content=' Francisco'\n", - "content=' is'\n", - "content=' '\n", - "content='52'\n", - "content='.'\n", - "content='0'\n", - "content=' °'\n", - "content='F'\n", - "content=' with'\n", - "content=' a'\n", - "content=' light'\n", - "content=' breeze'\n", - "content=' of'\n", - "content=' '\n", - "content='6'\n", - "content='.'\n", - "content='9'\n", - "content=' mph'\n", - "content=' coming'\n", - "content=' from'\n", - "content=' the'\n", - "content=' east'\n", - "content='.'\n", - "content=' The'\n", - "content=' sky'\n", - "content=' is'\n", - "content=' mostly'\n", - "content=' cloudy'\n", - "content=' with'\n", - "content=' cloud'\n", - "content=' cover'\n", - "content=' at'\n", - "content=' '\n", - "content='18'\n", - "content=','\n", - "content='000'\n", - "content=' ft'\n", - "content=','\n", - "content=' mostly'\n", - "content=' clear'\n", - "content=' at'\n", - "content=' '\n", - "content='4'\n", - "content=','\n", - "content='000'\n", - "content=' ft'\n", - "content=','\n", - "content=' and'\n", - "content=' partly'\n", - "content=' cloudy'\n", - "content=' at'\n", - "content=' '\n", - "content='15'\n", - "content=','\n", - "content='000'\n", - "content=' ft'\n", - "content='.'\n", - "content=''\n" + "|||||||||||The| current| weather| in| San| Francisco| is| as| follows|:\n", + "|-| Temperature|:| |55|.|0|°F| (|12|.|8|°C|)\n", + "|-| Condition|:| Over|cast|\n", + "|-| Wind|:| |11|.|9| mph| from| W|SW|\n", + "|-| Hum|idity|:| |96|%\n", + "|-| Cloud| Cover|:| |100|%\n", + "|-| Visibility|:| |9|.|0| miles|\n", + "\n", + "|For| more| detailed| information|,| you| can| visit| [|Weather| API|](|https|://|www|.weather|api|.com|/|).||" ] } ], @@ -587,7 +545,7 @@ " \"/streamed_output/-\"\n", " ):\n", " # because we chose to only include LLMs, these are LLM tokens\n", - " print(op[\"value\"])" + " print(op[\"value\"].content, end=\"|\")" ] }, { @@ -615,7 +573,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/base.ipynb b/examples/chat_agent_executor_with_function_calling/base.ipynb index 5890d05a3..414795112 100644 --- a/examples/chat_agent_executor_with_function_calling/base.ipynb +++ b/examples/chat_agent_executor_with_function_calling/base.ipynb @@ -165,10 +165,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -240,8 +237,7 @@ "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", @@ -249,7 +245,7 @@ " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -271,16 +267,17 @@ " # we know the last message involves a function call\n", " last_message = messages[-1]\n", " # We construct an ToolInvocation from the function_call\n", + " tool_call = last_message.tool_calls[0]\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", " response = tool_executor.invoke(action)\n", " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " function_message = ToolMessage(\n", + " content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n", + " )\n", " # We return a list, because this will get added to the existing list\n", " return {\"messages\": [function_message]}" ] @@ -297,7 +294,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -346,6 +343,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a4fab459", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -359,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", "metadata": {}, "outputs": [ @@ -367,12 +391,12 @@ "data": { "text/plain": [ "{'messages': [HumanMessage(content='what is the weather in sf'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}),\n", - " FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'),\n", - " AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}" + " AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_GWqmwbBTTMPniOg7gqn1XsID', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-aeabfb65-72bf-499a-8922-aae4878dcae6-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_GWqmwbBTTMPniOg7gqn1XsID'}]),\n", + " ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714807910, \\'localtime\\': \\'2024-05-04 0:31\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714807800, \\'last_updated\\': \\'2024-05-04 00:30\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_GWqmwbBTTMPniOg7gqn1XsID'),\n", + " AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Feels like: 52.4°F (11.4°C)\\n- Visibility: 9.0 miles\\n\\nFor more detailed information, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-925ab339-7da5-4fd0-851f-e765710408fd-0')]}" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -403,7 +427,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -413,25 +437,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_ccF3KsXlSfJbl6JQcTpgDLbt', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-e784ed37-cab3-4363-a9fd-cf48929246de-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_ccF3KsXlSfJbl6JQcTpgDLbt'}])]}\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714807910, \\'localtime\\': \\'2024-05-04 0:31\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714807800, \\'last_updated\\': \\'2024-05-04 00:30\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_ccF3KsXlSfJbl6JQcTpgDLbt')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Visibility: 9.0 miles\\n\\nFor more detailed information, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-4e9656bd-bf8f-484a-8e44-e5cb1f0e8d34-0')]}\n", "\n", "---\n", "\n" @@ -463,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ @@ -471,59 +489,116 @@ "name": "stdout", "output_type": "stream", "text": [ - "content='' additional_kwargs={'function_call': {'arguments': '', 'name': 'tavily_search_results_json'}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '{\\n', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' ', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': 'query', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '\":', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' \"', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': 'weather', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' in', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' San', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': ' Francisco', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '\"\\n', 'name': ''}}\n", - "content='' additional_kwargs={'function_call': {'arguments': '}', 'name': ''}}\n", - "content=''\n", - "content=''\n", - "content='I'\n", - "content=\"'m\"\n", - "content=' sorry'\n", - "content=','\n", - "content=' but'\n", - "content=' I'\n", - "content=' couldn'\n", - "content=\"'t\"\n", - "content=' find'\n", - "content=' the'\n", - "content=' current'\n", - "content=' weather'\n", - "content=' in'\n", - "content=' San'\n", - "content=' Francisco'\n", - "content='.'\n", - "content=' However'\n", - "content=','\n", - "content=' you'\n", - "content=' can'\n", - "content=' check'\n", - "content=' the'\n", - "content=' weather'\n", - "content=' forecast'\n", - "content=' for'\n", - "content=' San'\n", - "content=' Francisco'\n", - "content=' on'\n", - "content=' websites'\n", - "content=' like'\n", - "content=' Weather'\n", - "content='.com'\n", - "content=' or'\n", - "content=' Acc'\n", - "content='u'\n", - "content='Weather'\n", - "content='.'\n", - "content=''\n" + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_n0NjukNhoDLRhoFbNLBJDHtr', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_n0NjukNhoDLRhoFbNLBJDHtr', 'error': None}] tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_n0NjukNhoDLRhoFbNLBJDHtr', 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '{\"', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' tool_calls=[{'name': '', 'args': {}, 'id': None}] tool_call_chunks=[{'name': None, 'args': '{\"', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'query', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': 'query', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': 'query', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '\":\"', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': '\":\"', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': '\":\"', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': 'weather', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': 'weather', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': 'weather', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' in', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': ' in', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': ' in', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' San', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': ' San', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': ' San', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': ' Francisco', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': ' Francisco', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': ' Francisco', 'id': None, 'index': 0}]\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': None, 'function': {'arguments': '\"}', 'name': None}, 'type': None}]} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc' invalid_tool_calls=[{'name': None, 'args': '\"}', 'id': None, 'error': None}] tool_call_chunks=[{'name': None, 'args': '\"}', 'id': None, 'index': 0}]\n", + "content='' response_metadata={'finish_reason': 'tool_calls'} id='run-dbd086ea-6277-4e0b-8c05-4c0e979445bc'\n", + "content='' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='The' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' current' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' weather' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' in' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' San' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Francisco' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' is' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' as' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' follows' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Temperature' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='12' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='8' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='°C' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' (' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='55' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='0' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='°F' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=')\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Condition' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Over' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='cast' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Wind' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='11' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='9' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' mph' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' from' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' W' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='SW' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Hum' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='idity' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='96' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='%\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Cloud' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Cover' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='100' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='%\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Visibility' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='16' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='0' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' km' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' (' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='9' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='0' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' miles' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=')\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='-' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' UV' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' Index' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=':' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' ' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='1' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='0' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='\\n\\n' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='For' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' more' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' details' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=',' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' you' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' can' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' visit' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' [' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='Weather' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=' API' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='](' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='https' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='://' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='www' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.weather' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='api' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='.com' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='/' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content=').' id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n", + "content='' response_metadata={'finish_reason': 'stop'} id='run-62ad24a3-0db2-40a1-8905-109438327a83'\n" ] } ], @@ -568,7 +643,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/dynamically-returning-directly.ipynb b/examples/chat_agent_executor_with_function_calling/dynamically-returning-directly.ipynb index a3f5aeaac..ec0e5a45e 100644 --- a/examples/chat_agent_executor_with_function_calling/dynamically-returning-directly.ipynb +++ b/examples/chat_agent_executor_with_function_calling/dynamically-returning-directly.ipynb @@ -31,8 +31,8 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture --no-stderr\",\n", - " \"%pip install --quiet -U langchain langchain_openai tavily-python" + "%%capture --no-stderr\n", + "%pip install --quiet -U langchain langchain_openai tavily-python" ] }, { @@ -94,7 +94,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "4a1b9990-3b11-4a51-bd51-76117afd38b9", "metadata": {}, "outputs": [], @@ -114,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e", "metadata": {}, "outputs": [], @@ -137,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7", "metadata": {}, "outputs": [], @@ -165,7 +165,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], @@ -189,15 +189,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -220,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "ea793afa-2eab-4901-910d-6eed90cd6564", "metadata": {}, "outputs": [], @@ -263,14 +260,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "03308b6b-de72-4cdc-b6c6-47e654df340e", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage" + "from langchain_core.messages import ToolMessage" ] }, { @@ -285,7 +281,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 16, "id": "55e088b1-f3c8-4798-9ca8-5b0be961b49a", "metadata": {}, "outputs": [], @@ -295,13 +291,11 @@ " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we check if it's suppose to return direct\n", " else:\n", - " arguments = json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " )\n", + " arguments = last_message.tool_calls[0][\"args\"]\n", " if arguments.get(\"return_direct\", False):\n", " return \"final\"\n", " else:\n", @@ -310,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 17, "id": "2b45da72-1afa-4cd7-9b7f-49a7c99cdb8a", "metadata": {}, "outputs": [], @@ -335,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 18, "id": "dd876f5d-88d6-4f93-b1d0-f2f0b6f4d991", "metadata": {}, "outputs": [], @@ -347,8 +341,9 @@ " # we know the last message involves a function call\n", " last_message = messages[-1]\n", " # We construct an ToolInvocation from the function_call\n", - " tool_name = last_message.additional_kwargs[\"function_call\"][\"name\"]\n", - " arguments = json.loads(last_message.additional_kwargs[\"function_call\"][\"arguments\"])\n", + " tool_call = last_message.tool_calls[0]\n", + " tool_name = tool_call[\"name\"]\n", + " arguments = tool_call[\"args\"]\n", " if tool_name == \"tavily_search_results_json\":\n", " if \"return_direct\" in arguments:\n", " del arguments[\"return_direct\"]\n", @@ -358,10 +353,12 @@ " )\n", " # We call the tool_executor and get back a response\n", " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " # We use the response to create a ToolMessage\n", + " tool_message = ToolMessage(\n", + " content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n", + " )\n", " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + " return {\"messages\": [tool_message]}" ] }, { @@ -380,7 +377,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 19, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -433,6 +430,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 20, + "id": "05b43439", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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W0wcDsF5d60hHh8FvxtJAKnKIoVVzVtatdyq5ONUiIigrEREBV/ldqrqri9gldDmjLPRUsNeJ8ZMvK68F0QDXBvMObsT63wXa34KwFVOdVWExcfuGEF4o62XNZae6Gw1MJPYRMELfORIOYAks0Btp6/EgtZERAREQEREBERAREQEREBERAREQEREBERAREQEREBYzJ/3NXb8Em/2FZNY/IIJKmw3KGJpfLJTSsa0d5JaQAgpnyGf4KPD38Em/KZVey138grIrfc/JrxuzwVAN2sJnoLpQvaWTUc4nkdySMIBadOB/v8AEHWxCAiIgIiICIiAoPk11yim4pYZRW3HqWvxepirTdrzJrtaBzYwYAz1gfujttPQ93gpwqzurnZRxysLLPncVMMXpZ5L7icEhc6qFTHy075Wh2hyFpcNg/CHdsbCzEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREGt3HHhZkPDrNH8Z+FdJ5xf4ow3JMZj2I79SN73NA7qhg6tIGzrps7a+4+FXFPH+MuEW/Kcaq/OrdVt05juktPKPhxSt/ivaehH3iNggmXLUrjnAPI/zJ/GLGpIIsUvlZFS5VipmZF53K8nlqqNriAagesXMHwgHOOhzOAbaotJfJC8uPIvKC46XvHsgordaLRU2989noaUEvifFKXFrpHHcj3RSesdNb9wBaxm3c27SAiIgIvjnBrS5xAAGyT4L84sO/VPLtbstzWhvFpiyW3Vdykdi8rZoqEU8bpgyOGokcA3suz9ftXesHAh22uBjDevijfsqtuKV7cBttuvuXNdCyKir6oRRQtkfymaXqCWtAc7QILuU62RpZbHcNs+O190ulFaKG33i8vZPdKmjiDTUytYGhzna2dddb+MnvJ3g8AwHFbderzndkiZUXPL2U9ZU3EVhq2yxiJvZtik2R2WjzDkPKd9OgaBOUBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBFZuKWLQTywvu8Qkie6J4EbzpzSQ4bDfAgj5lrb5WPAzCPKdnt9yPEOusl2tlKaejp3wvnt424uc4w8oLXu9Vrntd1DGbB5Qrcxb9rqn8YV35VKswqL+V2rN2q1mTObMx50c0/tecvcLzauVW8zZMxt3fB+anCzgdn3k7+ULheQQQ0mQ2ujuUTZ7lZ5i6MU8pMU22yBj2kRvfsluvl8V+o3vr4p7Yj+ik/NWERU6fa6Ofmj6VPHc9H2/Zm/fXxT2xH9FJ+anvr4p7Yj+ik/NWERNPtdHPzR9Jx3PR9v2Rvj/xeo4OC+YtxaeW6ZDUW6SloaakheZe0lHZh7dgfA5+f/0r84+FfkOX/MHx1OXZHbsIoHb5mSMfWVh+IiOP1NH/AJpAR8S/UBE0+10c/NH0nHc9H2/ZG+BdBw/4CcOLfh9myWsuNJSufK6ouL5ZHOke7mfyt5eWJmz0YwAd5O3Oc51n2POLHklY+kttwjqqlkZlMQa5p5AQCeoHTZH96ii4Yx++ZT/iio/80C2LGU28orzIomNUztx2Rjuht5JwpOU3otZmGOPP6vcstERWu8IiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgp/Fv2uqfxhXflUqzCw+LftdU/jCu/KpVhq7jNw/tlbUUdZnWNUlXTyOimp57vTskie06c1zS/YIIIIPUELi5ZEzlV3D0p75fPMqpqqym5mxjyp70xUDyzi/RYzf5rLR2K+5RcqaBtTWQ2GkbN5nG/fIZC57RzO5XEMaS4gb5daXa7jlw4YdO4gYsDoHRvVN3Hu/jqoMk4bQXjiZfc3ocCsXGDHcnp6Z9NUNq6QuopoY+xcGvlPK6J4a07YSQ5p9UrWoo18pG1ajGfKxh2d+HesaXyhLDU1dqpbHar3lNRdLULzSx2ilY7np+cscXGR7AxzXDRDiOpAGz0XbVcf8bbi+NXi3010vc+Rl4tlot1KH1sxZvtgWOc0N7PRDy5wAPj1C8WK8PKyw8XbTdqSwUtjx6DEfRxp6J8fY01U6qbK6FjRokAcx5g0A/fOlX+I8Ms54d0uB5DSY4283K0i80FwsYroYpewqq508U0UjndnsBrCWlwOna6EEKebRP5710W7E7O/1T4R1rI4C59deIdBmVZdBVQ+Z5JVUNLS1tOyGalgZHEWxPa3vLS53Ukk77yNK0FSnC69Q8L6PKqniFVWrB6vIMkrLpRUlzu1MDJC6OAba7n04gjRHh08CCZp7+fDfRPvg4todN+mqb89QrpmauTGpRdtzNczRTq9WxN1wxj98yn/ABRUf+aBYLG+IWLZlPNBj+S2e+TQtD5Y7bXxVDmNJ0C4McSBvxKzuMfvmU/4oqP/ADQLe4PiYv691XdLd4MiacrpiY390rLREXVe5EREBERAREQEREBERAREQEREBERAREQEREBERAREQU/i37XVP4wrvyqVZF1DTPcXOp4i4nZJYNlSWbhdi088sz7RH2kr3SvIkeNucSXHQd4kk/OuHvU4p7IZ9LJ+cqL+SWr12q7nzGdMz5sc8/uecvcETduVXPKYYzM7N/xRz0fS/wBWh+jC7mMbG0NY0NaO4NGgFnfepxT2Qz6WT85PepxT2Qz6WT85U6Ba6Sflj6lPEk9J2fdhEWb96nFPZDPpZPzk96nFPZDPpZPzk0C10k/LH1HEk9J2fdgJaeKfXaxMk13c7QdLr9H0v9Wh+jCkfvU4p7IZ9LJ+cnvU4p7IZ9LJ+cmgWukn5Y+o4knpOz7o/FTQwEmOJkZPeWNAXLGP3zKf8UVH/mgWe96nFPZDPpZPzlkLHg9jxusfV223x0tS+MxGUOc48hIJHUnpsD+5bFjJ7eT158VzOqY2YbYw3y28k4MnJr0Xc/HDHm9XvZ1ERWu8IiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIP/9k=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -446,7 +470,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 21, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -456,25 +480,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_zUSn183pDK7QjqaJLkznOWv9', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-1c45e1ae-6b0d-40df-9727-e76b391caa03-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_zUSn183pDK7QjqaJLkznOWv9'}])]}\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in january59°F Clear/Sunny 47% of time 26% 14% 9% Weather at 6pm 54°F Clear/Sunny 50% of time 23% 14%'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714808273, \\'localtime\\': \\'2024-05-04 0:37\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714807800, \\'last_updated\\': \\'2024-05-04 00:30\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_zUSn183pDK7QjqaJLkznOWv9')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather in San Francisco in January 2024 on this website: [San Francisco Weather in January 2024](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/).')]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in january59°F Clear/Sunny 47% of time 26% 14% 9% Weather at 6pm 54°F Clear/Sunny 50% of time 23% 14%'}]\", name='tavily_search_results_json'), AIMessage(content='The weather in San Francisco is currently not available. However, you can check the weather in San Francisco in January 2024 on this website: [San Francisco Weather in January 2024](https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/).')]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Feels like: 52.4°F (11.4°C)\\n- Visibility: 9.0 miles\\n\\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-ba632ae4-5910-48c8-a550-889e59608895-0')]}\n", "\n", "---\n", "\n" @@ -496,7 +514,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 22, "id": "08ae8246-11d5-40e1-8567-361e5bef8917", "metadata": {}, "outputs": [ @@ -506,19 +524,13 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\",\\n \"return_direct\": true\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_Xks2mun3a2rITryq3Y7EMMOU', 'function': {'arguments': '{\"query\":\"weather in San Francisco\",\"return_direct\":true}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-890b9e87-b203-4314-acf0-a09ff18dcb6f-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco', 'return_direct': True}, 'id': 'call_Xks2mun3a2rITryq3Y7EMMOU'}])]}\n", "\n", "---\n", "\n", "Output from node 'final':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.weather2travel.com/california/san-francisco/january/', 'content': 'San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. San Francisco January sunrise & sunset times How sunny is it in San Francisco in January?San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures in the shade with on average 6 hours of sunshine per day in San Francisco in January. Check more long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. 13 13°C max day temperature 6'}]\", name='tavily_search_results_json')]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf? return this result directly by setting return_direct = True'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\",\\n \"return_direct\": true\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.weather2travel.com/california/san-francisco/january/', 'content': 'San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. San Francisco January sunrise & sunset times How sunny is it in San Francisco in January?San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures in the shade with on average 6 hours of sunshine per day in San Francisco in January. Check more long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. 13 13°C max day temperature 6'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714808273, \\'localtime\\': \\'2024-05-04 0:37\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714807800, \\'last_updated\\': \\'2024-05-04 00:30\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_Xks2mun3a2rITryq3Y7EMMOU')]}\n", "\n", "---\n", "\n" @@ -569,7 +581,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb b/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb index 10521f603..4e1964ffe 100644 --- a/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb +++ b/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb @@ -140,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], @@ -164,15 +164,12 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -195,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "id": "ea793afa-2eab-4901-910d-6eed90cd6564", "metadata": {}, "outputs": [], @@ -238,14 +235,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", @@ -253,7 +249,7 @@ " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -274,19 +270,33 @@ " # Based on the continue condition\n", " # we know the last message involves a function call\n", " last_message = messages[-1]\n", - " # We construct an ToolInvocation from the function_call\n", + " # We construct an ToolInvocation for each tool call\n", + " tool_invocations = []\n", + " for tool_call in last_message.tool_calls:\n", + " action = ToolInvocation(\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", + " )\n", + " tool_invocations.append(action)\n", + "\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", - " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n", + " # We use the response to create tool messages\n", + " tool_messages = [\n", + " ToolMessage(\n", + " content=str(response),\n", + " name=tc[\"name\"],\n", + " tool_call_id=tc[\"id\"],\n", + " )\n", + " for tc, response in zip(last_message.tool_calls, responses)\n", + " ]\n", + "\n", " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + " return {\"messages\": tool_messages}" ] }, { @@ -301,14 +311,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 16, "id": "1bfd2b22-292a-4f4d-91a0-46bb704f5e38", "metadata": {}, "outputs": [], "source": [ "# This is the new first - the first call of the model we want to explicitly hard-code some action\n", "from langchain_core.messages import AIMessage\n", - "import json\n", "\n", "\n", "def first_model(state):\n", @@ -317,12 +326,15 @@ " \"messages\": [\n", " AIMessage(\n", " content=\"\",\n", - " additional_kwargs={\n", - " \"function_call\": {\n", + " tool_calls=[\n", + " {\n", " \"name\": \"tavily_search_results_json\",\n", - " \"arguments\": json.dumps({\"query\": human_input}),\n", + " \"args\": {\n", + " \"query\": human_input,\n", + " },\n", + " \"id\": \"tool_abcd123\",\n", " }\n", - " },\n", + " ],\n", " )\n", " ]\n", " }" @@ -344,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 17, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -399,6 +411,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 18, + "id": "a8afd6ef", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -412,7 +451,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 19, "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", "metadata": {}, "outputs": [ @@ -422,25 +461,19 @@ "text": [ "Output from node 'first_agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"what is the weather in sf\"}'}})]}\n", + "{'messages': [AIMessage(content='', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'what is the weather in sf'}, 'id': 'tool_abcd123'}])]}\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january The climate of San Francisco in january is tolerable49°F Clear/Sunny 47% of time 24% 13% 8% Weather at 12pm 59°F Clear/Sunny 47% of time 26% 14% 9% Weather at 6pm 54°F Clear/Sunny 50% of time 23% 14%'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714808650, \\'localtime\\': \\'2024-05-04 0:44\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714807800, \\'last_updated\\': \\'2024-05-04 00:30\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='tool_abcd123')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content=\"I couldn't find the current weather in San Francisco. However, the average weather in January is around 49°F (9.4°C) during the day and 54°F (12.2°C) in the evening. It is mostly clear and sunny, with a 47% chance of clear/sunny weather during the day and 50% chance in the evening.\")]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'name': 'tavily_search_results_json', 'arguments': '{\"query\": \"what is the weather in sf\"}'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january The climate of San Francisco in january is tolerable49°F Clear/Sunny 47% of time 24% 13% 8% Weather at 12pm 59°F Clear/Sunny 47% of time 26% 14% 9% Weather at 6pm 54°F Clear/Sunny 50% of time 23% 14%'}]\", name='tavily_search_results_json'), AIMessage(content=\"I couldn't find the current weather in San Francisco. However, the average weather in January is around 49°F (9.4°C) during the day and 54°F (12.2°C) in the evening. It is mostly clear and sunny, with a 47% chance of clear/sunny weather during the day and 50% chance in the evening.\")]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 12.8°C (55.0°F)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Cloud Cover: 100%\\n- Visibility: 16.0 km (9.0 miles)\\n- UV Index: 1.0\\n\\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-57b5d14c-08c3-481d-9875-fc3a9472475c-0')]}\n", "\n", "---\n", "\n" @@ -485,7 +518,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb b/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb index 0dc4969df..5cc584ef9 100644 --- a/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb +++ b/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb @@ -32,7 +32,7 @@ "outputs": [], "source": [ "%%capture --no-stderr\n", - "%pip install --quiet -U langchain langchain_openai tavily-python" + "%pip install --quiet -U langgraph langchain_community langchain_openai tavily-python" ] }, { @@ -50,7 +50,7 @@ "metadata": {}, "outputs": [ { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ "OpenAI API Key: ········\n", @@ -81,7 +81,7 @@ "metadata": {}, "outputs": [ { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ "LangSmith API Key: ········\n" @@ -107,7 +107,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "id": "d7ef57dd-5d6e-4ad3-9377-a92201c1310e", "metadata": {}, "outputs": [], @@ -129,7 +129,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "id": "5cf3331e-ccb3-41c8-aeb9-a840a94d41e7", "metadata": {}, "outputs": [], @@ -157,7 +157,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "id": "892b54b9-75f0-4804-9ed0-88b5e5532989", "metadata": {}, "outputs": [], @@ -181,15 +181,12 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -212,7 +209,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "id": "ea793afa-2eab-4901-910d-6eed90cd6564", "metadata": {}, "outputs": [], @@ -255,14 +252,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "b547109f-f9e8-4e77-a7e7-ed2bae7a72ab", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", @@ -270,7 +266,7 @@ " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -285,19 +281,9 @@ " return {\"messages\": [response]}" ] }, - { - "cell_type": "markdown", - "id": "ac402f66-4442-4a1f-9f9b-4a5d97532ceb", - "metadata": {}, - "source": [ - "**MODIFICATION**\n", - "\n", - "We modify the function that is calling the tool to first ask for user approval to continue. Note that this is a simple example and we could modify it to change the tool input, use some other channel besides input, etc." - ] - }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "id": "73fd6432-42e8-472a-89ca-bb5ddbbcc35a", "metadata": {}, "outputs": [], @@ -308,22 +294,33 @@ " # Based on the continue condition\n", " # we know the last message involves a function call\n", " last_message = messages[-1]\n", - " # We construct an ToolInvocation from the function_call\n", + " # We construct an ToolInvocation for each tool call\n", + " tool_invocations = []\n", + " for tool_call in last_message.tool_calls:\n", + " action = ToolInvocation(\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", + " )\n", + " tool_invocations.append(action)\n", + "\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", - " response = input(f\"[y/n] continue with: {action}?\")\n", - " if response == \"n\":\n", - " raise ValueError\n", " # We call the tool_executor and get back a response\n", - " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n", + " # We use the response to create tool messages\n", + " tool_messages = [\n", + " ToolMessage(\n", + " content=str(response),\n", + " name=tc[\"name\"],\n", + " tool_call_id=tc[\"id\"],\n", + " )\n", + " for tc, response in zip(last_message.tool_calls, responses)\n", + " ]\n", + "\n", " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + " return {\"messages\": tool_messages}" ] }, { @@ -333,17 +330,22 @@ "source": [ "## Define the graph\n", "\n", - "We can now put it all together and define the graph!" + "We can now put it all together and define the graph!\n", + "\n", + "**MODIFICATION**\n", + "\n", + "We modify the graph to **interrupt** before calling the tools. This lets the user give approval to continue. Note that this is a simple example and we could modify it to change the tool input, use some other channel besides input, etc." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 20, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], "source": [ "from langgraph.graph import StateGraph, END\n", + "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "# Define a new graph\n", "workflow = StateGraph(AgentState)\n", @@ -384,7 +386,34 @@ "# Finally, we compile it!\n", "# This compiles it into a LangChain Runnable,\n", "# meaning you can use it as you would any other runnable\n", - "app = workflow.compile()" + "app = workflow.compile(checkpointer=MemorySaver(), interrupt_before=[\"action\"])" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "a0212d00", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" ] }, { @@ -400,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 22, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -410,38 +439,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_9yzjV53mMUwOgnoSDTWnZnsm', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-e0f2fbb3-994f-4742-ad5a-e273e595686a-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_9yzjV53mMUwOgnoSDTWnZnsm'}])]}\n", "\n", "---\n", - "\n" - ] - }, - { - "name": "stdin", - "output_type": "stream", - "text": [ - "[y/n] continue with: tool='tavily_search_results_json' tool_input={'query': 'weather in San Francisco'}? y\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.weather2travel.com/california/san-francisco/january/', 'content': 'San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. San Francisco January sunrise & sunset times How sunny is it in San Francisco in January?Expect 13°C daytime maximum temperatures in the shade with on average 6 hours of sunshine per day in San Francisco in January. Check more long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. 13. 13°C max day temperature. 6. 6 hours of sunshine per day. 10. 10 days with some ...'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714809084, \\'localtime\\': \\'2024-05-04 0:51\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714808700, \\'last_updated\\': \\'2024-05-04 00:45\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_9yzjV53mMUwOgnoSDTWnZnsm')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='The weather in San Francisco varies depending on the time of year. In January, the average daytime maximum temperature is around 13°C (55°F) with about 6 hours of sunshine per day. If you need more detailed information or want to check the weather for a specific date, you can visit this [website](https://www.weather2travel.com/california/san-francisco/january/).')]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.weather2travel.com/california/san-francisco/january/', 'content': 'San Francisco weather in January 2024 Expect 13°C daytime maximum temperatures long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. San Francisco January sunrise & sunset times How sunny is it in San Francisco in January?Expect 13°C daytime maximum temperatures in the shade with on average 6 hours of sunshine per day in San Francisco in January. Check more long-term weather averages for San Francisco in January before you book your next holiday to California in 2024/2025. 13. 13°C max day temperature. 6. 6 hours of sunshine per day. 10. 10 days with some ...'}]\", name='tavily_search_results_json'), AIMessage(content='The weather in San Francisco varies depending on the time of year. In January, the average daytime maximum temperature is around 13°C (55°F) with about 6 hours of sunshine per day. If you need more detailed information or want to check the weather for a specific date, you can visit this [website](https://www.weather2travel.com/california/san-francisco/january/).')]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Visibility: 9.0 miles\\n\\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-65726e33-9cf2-4adf-975b-859dc37eef67-0')]}\n", "\n", "---\n", "\n" @@ -452,22 +462,26 @@ "from langchain_core.messages import HumanMessage\n", "\n", "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n", - "for output in app.stream(inputs):\n", - " # stream() yields dictionaries with output keyed by node name\n", - " for key, value in output.items():\n", - " print(f\"Output from node '{key}':\")\n", - " print(\"---\")\n", - " print(value)\n", - " print(\"\\n---\\n\")" + "config = {\"configurable\": {\"thread_id\": \"thread-1\"}}\n", + "while True:\n", + " for output in app.stream(inputs, config):\n", + " # stream() yields dictionaries with output keyed by node name\n", + " for key, value in output.items():\n", + " print(f\"Output from node '{key}':\")\n", + " print(\"---\")\n", + " print(value)\n", + " print(\"\\n---\\n\")\n", + " snapshot = app.get_state(config)\n", + " # If \"next\" is present, it means we've interrupted mid-execution\n", + " if not snapshot.next:\n", + " break\n", + " inputs = None\n", + " response = input(\n", + " \"Do you approve the next step? Type y if you do, anything else to stop: \"\n", + " )\n", + " if response != \"y\":\n", + " break" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "08ae8246-11d5-40e1-8567-361e5bef8917", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -486,7 +500,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/managing-agent-steps.ipynb b/examples/chat_agent_executor_with_function_calling/managing-agent-steps.ipynb index cdeebbddd..7161d55b2 100644 --- a/examples/chat_agent_executor_with_function_calling/managing-agent-steps.ipynb +++ b/examples/chat_agent_executor_with_function_calling/managing-agent-steps.ipynb @@ -169,10 +169,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -238,14 +235,13 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "e718a9c5-6596-457f-ac25-a25d8cb8c259", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", @@ -253,7 +249,7 @@ " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -272,7 +268,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "714e4135-7cb5-4f17-b2ae-46f7e98bde61", "metadata": {}, "outputs": [], @@ -287,7 +283,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "b3ca9564-63cc-4309-b158-5e8d3e907164", "metadata": {}, "outputs": [], @@ -298,19 +294,33 @@ " # Based on the continue condition\n", " # we know the last message involves a function call\n", " last_message = messages[-1]\n", - " # We construct an ToolInvocation from the function_call\n", + " # We construct an ToolInvocation for each tool call\n", + " tool_invocations = []\n", + " for tool_call in last_message.tool_calls:\n", + " action = ToolInvocation(\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", + " )\n", + " tool_invocations.append(action)\n", + "\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", - " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n", + " # We use the response to create tool messages\n", + " tool_messages = [\n", + " ToolMessage(\n", + " content=str(response),\n", + " name=tc[\"name\"],\n", + " tool_call_id=tc[\"id\"],\n", + " )\n", + " for tc, response in zip(last_message.tool_calls, responses)\n", + " ]\n", + "\n", " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + " return {\"messages\": tool_messages}" ] }, { @@ -325,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -374,6 +384,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1f6af5f2", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -387,7 +424,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 12, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -397,25 +434,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "{'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_oM8jnkEnSBCI5CCANcMlsQ7R', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-96556a35-990f-4663-abe1-20b067c59a90-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_oM8jnkEnSBCI5CCANcMlsQ7R'}])]}\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json')]}\n", + "{'messages': [ToolMessage(content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714809084, \\'localtime\\': \\'2024-05-04 0:51\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714808700, \\'last_updated\\': \\'2024-05-04 00:45\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]', name='tavily_search_results_json', tool_call_id='call_oM8jnkEnSBCI5CCANcMlsQ7R')]}\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content=\"I'm sorry, but I couldn't find the current weather in San Francisco. However, you can check the weather forecast for San Francisco on websites like Weather.com or AccuWeather.\")]}\n", + "{'messages': [AIMessage(content='The current weather in San Francisco is as follows:\\n- Temperature: 55.0°F (12.8°C)\\n- Condition: Overcast\\n- Wind: 11.9 mph from WSW\\n- Humidity: 96%\\n- Visibility: 9.0 miles\\n- UV Index: 1.0\\n\\nFor more details, you can visit [Weather API](https://www.weatherapi.com/).', response_metadata={'finish_reason': 'stop'}, id='run-3c299e44-e6ff-42ac-b15c-da8839c1a986-0')]}\n", "\n", "---\n", "\n" @@ -460,7 +491,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb b/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb index 7edab0381..da28bac7c 100644 --- a/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb +++ b/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb @@ -174,9 +174,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", "from langchain_core.pydantic_v1 import BaseModel, Field\n", - "from langchain_core.utils.function_calling import convert_pydantic_to_openai_function\n", "\n", "\n", "class Response(BaseModel):\n", @@ -186,9 +184,7 @@ " other_notes: str = Field(description=\"any other notes about the weather\")\n", "\n", "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "functions.append(convert_pydantic_to_openai_function(Response))\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools + [Response])" ] }, { @@ -258,29 +254,28 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", + "from typing import Literal\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", - "def should_continue(state):\n", + "def should_continue(state) -> Literal[\"continue\", \"end\"]:\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we need to check what type of function call it is\n", - " elif last_message.additional_kwargs[\"function_call\"][\"name\"] == \"Response\":\n", + " if last_message.tool_calls[0][\"name\"] == \"Response\":\n", " return \"end\"\n", " # Otherwise we continue\n", - " else:\n", - " return \"continue\"\n", + " return \"continue\"\n", "\n", "\n", "# Define the function that calls the model\n", @@ -297,19 +292,33 @@ " # Based on the continue condition\n", " # we know the last message involves a function call\n", " last_message = messages[-1]\n", - " # We construct an ToolInvocation from the function_call\n", + " # We construct an ToolInvocation for each tool call\n", + " tool_invocations = []\n", + " for tool_call in last_message.tool_calls:\n", + " action = ToolInvocation(\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", + " )\n", + " tool_invocations.append(action)\n", + "\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", - " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " responses = tool_executor.batch(tool_invocations, return_exceptions=True)\n", + " # We use the response to create tool messages\n", + " tool_messages = [\n", + " ToolMessage(\n", + " content=str(response),\n", + " name=tc[\"name\"],\n", + " tool_call_id=tc[\"id\"],\n", + " )\n", + " for tc, response in zip(last_message.tool_calls, responses)\n", + " ]\n", + "\n", " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + " return {\"messages\": tool_messages}" ] }, { @@ -324,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -373,6 +382,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "2271a1ee", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -386,7 +422,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "f544977e-31f7-41f0-88c4-ec9c27b8cecb", "metadata": {}, "outputs": [ @@ -396,25 +432,19 @@ "text": [ "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}})]}\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_aArQcUvPzoWtjem4yr5y9ttC', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls'} id='run-fd99027e-2608-45cd-9bfb-32b6e4c3dd5e-0' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_aArQcUvPzoWtjem4yr5y9ttC'}]\n", "\n", "---\n", "\n", "Output from node 'action':\n", "---\n", - "{'messages': [FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json')]}\n", + "content='[{\\'url\\': \\'https://www.weatherapi.com/\\', \\'content\\': \"{\\'location\\': {\\'name\\': \\'San Francisco\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 37.78, \\'lon\\': -122.42, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1714809361, \\'localtime\\': \\'2024-05-04 0:56\\'}, \\'current\\': {\\'last_updated_epoch\\': 1714808700, \\'last_updated\\': \\'2024-05-04 00:45\\', \\'temp_c\\': 12.8, \\'temp_f\\': 55.0, \\'is_day\\': 0, \\'condition\\': {\\'text\\': \\'Overcast\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/night/122.png\\', \\'code\\': 1009}, \\'wind_mph\\': 11.9, \\'wind_kph\\': 19.1, \\'wind_degree\\': 240, \\'wind_dir\\': \\'WSW\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.9, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 96, \\'cloud\\': 100, \\'feelslike_c\\': 11.4, \\'feelslike_f\\': 52.4, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 1.0, \\'gust_mph\\': 14.9, \\'gust_kph\\': 23.9}}\"}]' name='tavily_search_results_json' tool_call_id='call_aArQcUvPzoWtjem4yr5y9ttC'\n", "\n", "---\n", "\n", "Output from node 'agent':\n", "---\n", - "{'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"temperature\": 45,\\n \"other_notes\": \"Partly cloudy\"\\n}', 'name': 'Response'}})]}\n", - "\n", - "---\n", - "\n", - "Output from node '__end__':\n", - "---\n", - "{'messages': [HumanMessage(content='what is the weather in sf'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"query\": \"weather in San Francisco\"\\n}', 'name': 'tavily_search_results_json'}}), FunctionMessage(content=\"[{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 16-01-2023 45°F to 52°F. 17-01-2023 45°F to 54°F. 18-01-2023 47°F to ...'}]\", name='tavily_search_results_json'), AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"temperature\": 45,\\n \"other_notes\": \"Partly cloudy\"\\n}', 'name': 'Response'}})]}\n", + "content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_TiqvNjxNlFMf4Wnzs1xhicVf', 'function': {'arguments': '{\"temperature\":12.8,\"other_notes\":\"The weather in San Francisco is currently overcast with a temperature of 12.8°C. The wind speed is 11.9 mph coming from WSW direction. Humidity is at 96%.\"}', 'name': 'Response'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls'} id='run-09cca9c1-1104-4fc1-9a85-7927d2492200-0' tool_calls=[{'name': 'Response', 'args': {'temperature': 12.8, 'other_notes': 'The weather in San Francisco is currently overcast with a temperature of 12.8°C. The wind speed is 11.9 mph coming from WSW direction. Humidity is at 96%.'}, 'id': 'call_TiqvNjxNlFMf4Wnzs1xhicVf'}]\n", "\n", "---\n", "\n" @@ -430,7 +460,7 @@ " for key, value in output.items():\n", " print(f\"Output from node '{key}':\")\n", " print(\"---\")\n", - " print(value)\n", + " print(value[\"messages\"][-1])\n", " print(\"\\n---\\n\")" ] }, @@ -459,7 +489,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/chatbots/information-gather-prompting.ipynb b/examples/chatbots/information-gather-prompting.ipynb index ae21291ab..6029bce16 100644 --- a/examples/chatbots/information-gather-prompting.ipynb +++ b/examples/chatbots/information-gather-prompting.ipynb @@ -83,6 +83,7 @@ "\n", "class PromptInstructions(BaseModel):\n", " \"\"\"Instructions on how to prompt the LLM.\"\"\"\n", + "\n", " objective: str\n", " variables: List[str]\n", " constraints: List[str]\n", @@ -121,7 +122,7 @@ "source": [ "# Helper function for determining if tool was called\n", "def _is_tool_call(msg):\n", - " return hasattr(msg, \"additional_kwargs\") and 'tool_calls' in msg.additional_kwargs" + " return hasattr(msg, \"additional_kwargs\") and \"tool_calls\" in msg.additional_kwargs" ] }, { @@ -136,6 +137,7 @@ "\n", "{reqs}\"\"\"\n", "\n", + "\n", "# Function to get the messages for the prompt\n", "# Will only get messages AFTER the tool call\n", "def get_prompt_messages(messages):\n", @@ -143,11 +145,10 @@ " other_msgs = []\n", " for m in messages:\n", " if _is_tool_call(m):\n", - " tool_call = m.additional_kwargs['tool_calls'][0]['function']['arguments']\n", + " tool_call = m.additional_kwargs[\"tool_calls\"][0][\"function\"][\"arguments\"]\n", " elif tool_call is not None:\n", " other_msgs.append(m)\n", - " return [SystemMessage(content=prompt_system.format(reqs=tool_call))] + other_msgs\n", - " " + " return [SystemMessage(content=prompt_system.format(reqs=tool_call))] + other_msgs" ] }, { @@ -215,7 +216,7 @@ "\n", "memory = SqliteSaver.from_conn_string(\":memory:\")\n", "\n", - "nodes = {k:k for k in ['info', 'prompt', END]}\n", + "nodes = {k: k for k in [\"info\", \"prompt\", END]}\n", "workflow = MessageGraph()\n", "workflow.add_node(\"info\", chain)\n", "workflow.add_node(\"prompt\", prompt_gen_chain)\n", @@ -401,9 +402,9 @@ "\n", "config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n", "while True:\n", - " user = input('User (q/Q to quit): ')\n", - " if user in {'q', 'Q'}:\n", - " print('AI: Byebye')\n", + " user = input(\"User (q/Q to quit): \")\n", + " if user in {\"q\", \"Q\"}:\n", + " print(\"AI: Byebye\")\n", " break\n", " for output in graph.stream([HumanMessage(content=user)], config=config):\n", " if \"__end__\" in output:\n", diff --git a/examples/code_assistant/langgraph_code_assistant.ipynb b/examples/code_assistant/langgraph_code_assistant.ipynb index 19db6e88d..da94205e1 100644 --- a/examples/code_assistant/langgraph_code_assistant.ipynb +++ b/examples/code_assistant/langgraph_code_assistant.ipynb @@ -34,7 +34,7 @@ "metadata": {}, "outputs": [], "source": [ - " ! pip install -U langchain_community langchain-openai langchain-anthropic langchain langgraph bs4" + "! pip install -U langchain_community langchain-openai langchain-anthropic langchain langgraph bs4" ] }, { @@ -97,16 +97,22 @@ "\n", "### OpenAI\n", "\n", - "# Grader prompt \n", + "# Grader prompt\n", "code_gen_prompt = ChatPromptTemplate.from_messages(\n", - " [(\"system\",\"\"\"You are a coding assistant with expertise in LCEL, LangChain expression language. \\n \n", + " [\n", + " (\n", + " \"system\",\n", + " \"\"\"You are a coding assistant with expertise in LCEL, LangChain expression language. \\n \n", " Here is a full set of LCEL documentation: \\n ------- \\n {context} \\n ------- \\n Answer the user \n", " question based on the above provided documentation. Ensure any code you provide can be executed \\n \n", " with all required imports and variables defined. Structure your answer with a description of the code solution. \\n\n", - " Then list the imports. And finally list the functioning code block. Here is the user question:\"\"\"),\n", - " (\"placeholder\", \"{messages}\")]\n", + " Then list the imports. And finally list the functioning code block. Here is the user question:\"\"\",\n", + " ),\n", + " (\"placeholder\", \"{messages}\"),\n", + " ]\n", ")\n", "\n", + "\n", "# Data model\n", "class code(BaseModel):\n", " \"\"\"Code output\"\"\"\n", @@ -116,6 +122,7 @@ " code: str = Field(description=\"Code block not including import statements\")\n", " description = \"Schema for code solutions to questions about LCEL.\"\n", "\n", + "\n", "expt_llm = \"gpt-4-0125-preview\"\n", "llm = ChatOpenAI(temperature=0, model=expt_llm)\n", "code_gen_chain = code_gen_prompt | llm.with_structured_output(code)\n", @@ -138,12 +145,19 @@ "\n", "# Prompt to enforce tool use\n", "code_gen_prompt_claude = ChatPromptTemplate.from_messages(\n", - " [(\"system\",\"\"\" You are a coding assistant with expertise in LCEL, LangChain expression language. \\n \n", + " [\n", + " (\n", + " \"system\",\n", + " \"\"\" You are a coding assistant with expertise in LCEL, LangChain expression language. \\n \n", " Here is the LCEL documentation: \\n ------- \\n {context} \\n ------- \\n Answer the user question based on the \\n \n", " above provided documentation. Ensure any code you provide can be executed with all required imports and variables \\n\n", " defined. Structure your answer: 1) a prefix describing the code solution, 2) the imports, 3) the functioning code block. \\n\n", - " Invoke the code tool to structure the output correctly. \\n Here is the user question:\"\"\",),\n", - " (\"placeholder\", \"{messages}\"),])\n", + " Invoke the code tool to structure the output correctly. \\n Here is the user question:\"\"\",\n", + " ),\n", + " (\"placeholder\", \"{messages}\"),\n", + " ]\n", + ")\n", + "\n", "\n", "# Data model\n", "class code(BaseModel):\n", @@ -156,8 +170,8 @@ "\n", "\n", "# LLM\n", - "# expt_llm = \"claude-3-haiku-20240307\" \n", - "expt_llm = \"claude-3-opus-20240229\" \n", + "# expt_llm = \"claude-3-haiku-20240307\"\n", + "expt_llm = \"claude-3-opus-20240229\"\n", "llm = ChatAnthropic(\n", " model=expt_llm,\n", " default_headers={\"anthropic-beta\": \"tools-2024-04-04\"},\n", @@ -165,6 +179,7 @@ "\n", "structured_llm_claude = llm.with_structured_output(code, include_raw=True)\n", "\n", + "\n", "# Optional: Check for errors in case tool use is flaky\n", "def check_claude_output(tool_output):\n", " \"\"\"Check for parse error or failure to call the tool\"\"\"\n", @@ -179,7 +194,7 @@ " f\"Error parsing your output! Be sure to invoke the tool. Output: {raw_output}. \\n Parse error: {error}\"\n", " )\n", "\n", - " # Tool was not invoked \n", + " # Tool was not invoked\n", " elif not tool_output[\"parsed\"]:\n", " print(\"Failed to invoke tool!\")\n", " raise ValueError(\n", @@ -187,12 +202,16 @@ " )\n", " return tool_output\n", "\n", + "\n", "# Chain with output check\n", - "code_chain_claude_raw = code_gen_prompt_claude | structured_llm_claude | check_claude_output\n", + "code_chain_claude_raw = (\n", + " code_gen_prompt_claude | structured_llm_claude | check_claude_output\n", + ")\n", + "\n", "\n", "def insert_errors(inputs):\n", " \"\"\"Insert errors for tool parsing in the messages\"\"\"\n", - " \n", + "\n", " # Get errors\n", " error = inputs[\"error\"]\n", " messages = inputs[\"messages\"]\n", @@ -207,19 +226,24 @@ " \"context\": inputs[\"context\"],\n", " }\n", "\n", + "\n", "# This will be run as a fallback chain\n", "fallback_chain = insert_errors | code_chain_claude_raw\n", "N = 3 # Max re-tries\n", - "code_gen_chain_re_try = code_chain_claude_raw.with_fallbacks(fallbacks=[fallback_chain] * N, exception_key=\"error\")\n", + "code_gen_chain_re_try = code_chain_claude_raw.with_fallbacks(\n", + " fallbacks=[fallback_chain] * N, exception_key=\"error\"\n", + ")\n", + "\n", "\n", "def parse_output(solution):\n", - " \"\"\"When we add 'include_raw=True' to structured output, \n", - " it will return a dict w 'raw', 'parsed', 'parsing_error'. \"\"\"\n", - " \n", - " return solution['parsed']\n", + " \"\"\"When we add 'include_raw=True' to structured output,\n", + " it will return a dict w 'raw', 'parsed', 'parsing_error'.\"\"\"\n", + "\n", + " return solution[\"parsed\"]\n", + "\n", "\n", "# Wtih re-try to correct for failure to invoke tool\n", - "# TODO: Annoying errors w/ \"user\" vs \"assistant\" \n", + "# TODO: Annoying errors w/ \"user\" vs \"assistant\"\n", "# Roles must alternate between \"user\" and \"assistant\", but found multiple \"user\" roles in a row\n", "code_gen_chain = code_gen_chain_re_try | parse_output\n", "\n", @@ -238,7 +262,9 @@ "source": [ "# Test\n", "question = \"How do I build a RAG chain in LCEL?\"\n", - "solution = code_gen_chain.invoke({\"context\":concatenated_content,\"messages\":[(\"user\",question)]})\n", + "solution = code_gen_chain.invoke(\n", + " {\"context\": concatenated_content, \"messages\": [(\"user\", question)]}\n", + ")\n", "solution" ] }, @@ -261,6 +287,7 @@ "source": [ "from typing import Dict, TypedDict, List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -269,13 +296,13 @@ " error : Binary flag for control flow to indicate whether test error was tripped\n", " messages : With user question, error messages, reasoning\n", " generation : Code solution\n", - " iterations : Number of tries \n", + " iterations : Number of tries\n", " \"\"\"\n", "\n", - " error : str\n", - " messages : List\n", - " generation : str\n", - " iterations : int" + " error: str\n", + " messages: List\n", + " generation: str\n", + " iterations: int" ] }, { @@ -306,10 +333,11 @@ "max_iterations = 3\n", "# Reflect\n", "# flag = 'reflect'\n", - "flag = 'do not reflect'\n", - " \n", + "flag = \"do not reflect\"\n", + "\n", "### Nodes\n", "\n", + "\n", "def generate(state: GraphState):\n", " \"\"\"\n", " Generate a code solution\n", @@ -322,7 +350,7 @@ " \"\"\"\n", "\n", " print(\"---GENERATING CODE SOLUTION---\")\n", - " \n", + "\n", " # State\n", " messages = state[\"messages\"]\n", " iterations = state[\"iterations\"]\n", @@ -330,16 +358,29 @@ "\n", " # We have been routed back to generation with an error\n", " if error == \"yes\":\n", - " messages += [(\"user\",\"Now, try again. Invoke the code tool to structure the output with a prefix, imports, and code block:\")]\n", - " \n", + " messages += [\n", + " (\n", + " \"user\",\n", + " \"Now, try again. Invoke the code tool to structure the output with a prefix, imports, and code block:\",\n", + " )\n", + " ]\n", + "\n", " # Solution\n", - " code_solution = code_gen_chain.invoke({\"context\": concatenated_content, \"messages\" : messages})\n", - " messages += [(\"assistant\",f\"{code_solution.prefix} \\n Imports: {code_solution.imports} \\n Code: {code_solution.code}\")]\n", - " \n", + " code_solution = code_gen_chain.invoke(\n", + " {\"context\": concatenated_content, \"messages\": messages}\n", + " )\n", + " messages += [\n", + " (\n", + " \"assistant\",\n", + " f\"{code_solution.prefix} \\n Imports: {code_solution.imports} \\n Code: {code_solution.code}\",\n", + " )\n", + " ]\n", + "\n", " # Increment\n", " iterations = iterations + 1\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations}\n", "\n", + "\n", "def code_check(state: GraphState):\n", " \"\"\"\n", " Check code\n", @@ -352,7 +393,7 @@ " \"\"\"\n", "\n", " print(\"---CHECKING CODE---\")\n", - " \n", + "\n", " # State\n", " messages = state[\"messages\"]\n", " code_solution = state[\"generation\"]\n", @@ -370,8 +411,13 @@ " print(\"---CODE IMPORT CHECK: FAILED---\")\n", " error_message = [(\"user\", f\"Your solution failed the import test: {e}\")]\n", " messages += error_message\n", - " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"yes\"}\n", - " \n", + " return {\n", + " \"generation\": code_solution,\n", + " \"messages\": messages,\n", + " \"iterations\": iterations,\n", + " \"error\": \"yes\",\n", + " }\n", + "\n", " # Check execution\n", " try:\n", " exec(imports + \"\\n\" + code)\n", @@ -379,11 +425,22 @@ " print(\"---CODE BLOCK CHECK: FAILED---\")\n", " error_message = [(\"user\", f\"Your solution failed the code execution test: {e}\")]\n", " messages += error_message\n", - " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"yes\"}\n", - " \n", + " return {\n", + " \"generation\": code_solution,\n", + " \"messages\": messages,\n", + " \"iterations\": iterations,\n", + " \"error\": \"yes\",\n", + " }\n", + "\n", " # No errors\n", " print(\"---NO CODE TEST FAILURES---\")\n", - " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations, \"error\": \"no\"}\n", + " return {\n", + " \"generation\": code_solution,\n", + " \"messages\": messages,\n", + " \"iterations\": iterations,\n", + " \"error\": \"no\",\n", + " }\n", + "\n", "\n", "def reflect(state: GraphState):\n", " \"\"\"\n", @@ -397,24 +454,33 @@ " \"\"\"\n", "\n", " print(\"---GENERATING CODE SOLUTION---\")\n", - " \n", + "\n", " # State\n", " messages = state[\"messages\"]\n", " iterations = state[\"iterations\"]\n", " code_solution = state[\"generation\"]\n", "\n", " # Prompt reflection\n", - " reflection_message = [(\"user\", \"\"\"You tried to solve this problem and failed a unit test. Reflect on this failure\n", + " reflection_message = [\n", + " (\n", + " \"user\",\n", + " \"\"\"You tried to solve this problem and failed a unit test. Reflect on this failure\n", " given the provided documentation. Write a few key suggestions based on the \n", - " documentation to avoid making this mistake again.\"\"\")]\n", - " \n", + " documentation to avoid making this mistake again.\"\"\",\n", + " )\n", + " ]\n", + "\n", " # Add reflection\n", - " reflections = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : messages})\n", - " messages += [(\"assistant\" , f\"Here are reflections on the error: {reflections}\")]\n", + " reflections = code_gen_chain.invoke(\n", + " {\"context\": concatenated_content, \"messages\": messages}\n", + " )\n", + " messages += [(\"assistant\", f\"Here are reflections on the error: {reflections}\")]\n", " return {\"generation\": code_solution, \"messages\": messages, \"iterations\": iterations}\n", "\n", + "\n", "### Edges\n", "\n", + "\n", "def decide_to_finish(state: GraphState):\n", " \"\"\"\n", " Determines whether to finish.\n", @@ -433,7 +499,7 @@ " return \"end\"\n", " else:\n", " print(\"---DECISION: RE-TRY SOLUTION---\")\n", - " if flag == 'reflect':\n", + " if flag == \"reflect\":\n", " return \"reflect\"\n", " else:\n", " return \"generate\"" @@ -479,7 +545,7 @@ "outputs": [], "source": [ "question = \"How can I directly pass a string to a runnable and use it to construct the input needed for my prompt?\"\n", - "app.invoke({\"messages\":[(\"user\",question)],\"iterations\":0})" + "app.invoke({\"messages\": [(\"user\", question)], \"iterations\": 0})" ] }, { @@ -510,6 +576,7 @@ "outputs": [], "source": [ "import langsmith\n", + "\n", "client = langsmith.Client()" ] }, @@ -521,7 +588,9 @@ "outputs": [], "source": [ "# Clone the dataset to your tenant to use it\n", - "public_dataset = (\"https://smith.langchain.com/public/326674a6-62bd-462d-88ae-eea49d503f9d/d\")\n", + "public_dataset = (\n", + " \"https://smith.langchain.com/public/326674a6-62bd-462d-88ae-eea49d503f9d/d\"\n", + ")\n", "client.clone_public_dataset(public_dataset)" ] }, @@ -542,22 +611,24 @@ "source": [ "from langsmith.schemas import Example, Run\n", "\n", - "def check_import(run: Run, example: Example) -> dict: \n", + "\n", + "def check_import(run: Run, example: Example) -> dict:\n", " imports = run.outputs.get(\"imports\")\n", " try:\n", " exec(imports)\n", - " return {\"key\": \"import_check\" , \"score\": 1} \n", + " return {\"key\": \"import_check\", \"score\": 1}\n", " except:\n", - " return {\"key\": \"import_check\" , \"score\": 0} \n", + " return {\"key\": \"import_check\", \"score\": 0}\n", "\n", - "def check_execution(run: Run, example: Example) -> dict: \n", + "\n", + "def check_execution(run: Run, example: Example) -> dict:\n", " imports = run.outputs.get(\"imports\")\n", " code = run.outputs.get(\"code\")\n", " try:\n", " exec(imports + \"\\n\" + code)\n", - " return {\"key\": \"code_execution_check\" , \"score\": 1} \n", + " return {\"key\": \"code_execution_check\", \"score\": 1}\n", " except:\n", - " return {\"key\": \"code_execution_check\" , \"score\": 0} " + " return {\"key\": \"code_execution_check\", \"score\": 0}" ] }, { @@ -576,14 +647,17 @@ "outputs": [], "source": [ "def predict_base_case(example: dict):\n", - " \"\"\" Context stuffing \"\"\"\n", - " solution = code_gen_chain.invoke({\"context\" : concatenated_content, \"messages\" : [(\"user\",example[\"question\"])]})\n", - " solution_structured = structured_code_formatter.invoke([(\"code\",solution)])\n", + " \"\"\"Context stuffing\"\"\"\n", + " solution = code_gen_chain.invoke(\n", + " {\"context\": concatenated_content, \"messages\": [(\"user\", example[\"question\"])]}\n", + " )\n", + " solution_structured = structured_code_formatter.invoke([(\"code\", solution)])\n", " return {\"imports\": solution_structured.imports, \"code\": solution_structured.code}\n", "\n", + "\n", "def predict_langgraph(example: dict):\n", - " \"\"\" LangGraph \"\"\"\n", - " graph = app.invoke({\"messages\":[(\"user\",example[\"question\"])],\"iterations\":0})\n", + " \"\"\"LangGraph\"\"\"\n", + " graph = app.invoke({\"messages\": [(\"user\", example[\"question\"])], \"iterations\": 0})\n", " solution = graph[\"generation\"]\n", " return {\"imports\": solution.imports, \"code\": solution.code}" ] @@ -598,7 +672,7 @@ "from langsmith.evaluation import evaluate\n", "\n", "# Evaluator\n", - "code_evalulator = [check_import,check_execution]\n", + "code_evalulator = [check_import, check_execution]\n", "\n", "# Dataset\n", "dataset_name = \"test-LCEL-code-gen\"" @@ -616,10 +690,10 @@ " predict_base_case,\n", " data=dataset_name,\n", " evaluators=code_evalulator,\n", - " experiment_prefix=f\"test-without-langgraph-{expt_llm}\", \n", + " experiment_prefix=f\"test-without-langgraph-{expt_llm}\",\n", " max_concurrency=2,\n", " metadata={\n", - " \"llm\": expt_llm,\n", + " \"llm\": expt_llm,\n", " },\n", ")" ] @@ -639,8 +713,8 @@ " experiment_prefix=f\"test-with-langgraph-{expt_llm}-{flag}\",\n", " max_concurrency=2,\n", " metadata={\n", - " \"llm\": expt_llm,\n", - " \"feedback\": flag,\n", + " \"llm\": expt_llm,\n", + " \"feedback\": flag,\n", " },\n", ")" ] diff --git a/examples/configuration.ipynb b/examples/configuration.ipynb index 5a8d4cb02..fc4ca27c4 100644 --- a/examples/configuration.ipynb +++ b/examples/configuration.ipynb @@ -44,9 +44,10 @@ "\n", "\n", "def _call_model(state):\n", - " response = model.invoke(state['messages'])\n", + " response = model.invoke(state[\"messages\"])\n", " return {\"messages\": [response]}\n", "\n", + "\n", "# Define a new graph\n", "workflow = StateGraph(AgentState)\n", "workflow.add_node(\"model\", _call_model)\n", @@ -106,11 +107,13 @@ " \"openai\": openai_model,\n", "}\n", "\n", + "\n", "def _call_model(state, config):\n", - " m = models[config['configurable'].get('model', 'anthropic')]\n", - " response = m.invoke(state['messages'])\n", + " m = models[config[\"configurable\"].get(\"model\", \"anthropic\")]\n", + " response = m.invoke(state[\"messages\"])\n", " return {\"messages\": [response]}\n", "\n", + "\n", "# Define a new graph\n", "workflow = StateGraph(AgentState)\n", "workflow.add_node(\"model\", _call_model)\n", @@ -198,14 +201,18 @@ "source": [ "from langchain_core.messages import SystemMessage\n", "\n", + "\n", "def _call_model(state, config):\n", - " m = models[config['configurable'].get('model', 'anthropic')]\n", - " messages = state['messages']\n", - " if 'system_message' in config['configurable']:\n", - " messages = [SystemMessage(content=config['configurable']['system_message'])] + messages\n", + " m = models[config[\"configurable\"].get(\"model\", \"anthropic\")]\n", + " messages = state[\"messages\"]\n", + " if \"system_message\" in config[\"configurable\"]:\n", + " messages = [\n", + " SystemMessage(content=config[\"configurable\"][\"system_message\"])\n", + " ] + messages\n", " response = m.invoke(messages)\n", " return {\"messages\": [response]}\n", "\n", + "\n", "# Define a new graph\n", "workflow = StateGraph(AgentState)\n", "workflow.add_node(\"model\", _call_model)\n", diff --git a/examples/llm-compiler/LLMCompiler.ipynb b/examples/llm-compiler/LLMCompiler.ipynb index 24d9fcc52..e2eb6d027 100644 --- a/examples/llm-compiler/LLMCompiler.ipynb +++ b/examples/llm-compiler/LLMCompiler.ipynb @@ -222,11 +222,15 @@ " llm: BaseChatModel, tools: Sequence[BaseTool], base_prompt: ChatPromptTemplate\n", "):\n", " tool_descriptions = \"\\n\".join(\n", - " f\"{i+1}. {tool.description}\\n\" for i, tool in enumerate(tools) # +1 to offset the 0 starting index, we want it count normally from 1.\n", + " f\"{i+1}. {tool.description}\\n\"\n", + " for i, tool in enumerate(\n", + " tools\n", + " ) # +1 to offset the 0 starting index, we want it count normally from 1.\n", " )\n", " planner_prompt = base_prompt.partial(\n", " replan=\"\",\n", - " num_tools=len(tools)+1, # Add one because we're adding the join() tool at the end.\n", + " num_tools=len(tools)\n", + " + 1, # Add one because we're adding the join() tool at the end.\n", " tool_descriptions=tool_descriptions,\n", " )\n", " replanner_prompt = base_prompt.partial(\n", @@ -236,7 +240,7 @@ " ' - When starting the Current Plan, you should start with \"Thought\" that outlines the strategy for the next plan.\\n'\n", " \" - In the Current Plan, you should NEVER repeat the actions that are already executed in the Previous Plan.\\n\"\n", " \" - You must continue the task index from the end of the previous one. Do not repeat task indices.\",\n", - " num_tools=len(tools)+1,\n", + " num_tools=len(tools) + 1,\n", " tool_descriptions=tool_descriptions,\n", " )\n", "\n", @@ -465,7 +469,7 @@ " task_names[task[\"idx\"]] = (\n", " task[\"tool\"] if isinstance(task[\"tool\"], str) else task[\"tool\"].name\n", " )\n", - " args_for_tasks[task[\"idx\"]] = (task[\"args\"])\n", + " args_for_tasks[task[\"idx\"]] = task[\"args\"]\n", " if (\n", " # Depends on other tasks\n", " deps\n", @@ -491,7 +495,9 @@ " for k in sorted(observations.keys() - originals)\n", " }\n", " tool_messages = [\n", - " FunctionMessage(name=name, content=str(obs), additional_kwargs={\"idx\": k, 'args':task_args})\n", + " FunctionMessage(\n", + " name=name, content=str(obs), additional_kwargs={\"idx\": k, \"args\": task_args}\n", + " )\n", " for k, (name, task_args, obs) in new_observations.items()\n", " ]\n", " return tool_messages" diff --git a/examples/multi_agent/multi-agent-collaboration.ipynb b/examples/multi_agent/multi-agent-collaboration.ipynb index 1889943da..e22a3c41d 100644 --- a/examples/multi_agent/multi-agent-collaboration.ipynb +++ b/examples/multi_agent/multi-agent-collaboration.ipynb @@ -56,14 +56,6 @@ "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "075c91c3-c249-471d-b259-41975faa83fb", - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "markdown", "id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb", @@ -78,30 +70,22 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 31, "id": "4325a10e-38dc-4a98-9004-e1525eaba377", "metadata": {}, "outputs": [], "source": [ - "import json\n", - "\n", "from langchain_core.messages import (\n", - " AIMessage,\n", " BaseMessage,\n", - " ChatMessage,\n", - " FunctionMessage,\n", + " ToolMessage,\n", " HumanMessage,\n", ")\n", - "from langchain.tools.render import format_tool_to_openai_function\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "from langgraph.graph import END, StateGraph\n", - "from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation\n", "\n", "\n", "def create_agent(llm, tools, system_message: str):\n", " \"\"\"Create an agent.\"\"\"\n", - " functions = [format_tool_to_openai_function(t) for t in tools]\n", - "\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", @@ -119,7 +103,7 @@ " )\n", " prompt = prompt.partial(system_message=system_message)\n", " prompt = prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", - " return prompt | llm.bind_functions(functions)" + " return prompt | llm.bind_tools(tools)" ] }, { @@ -134,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 63, "id": "ca076f3b-a729-4ca9-8f91-05c2ba58d610", "metadata": {}, "outputs": [], @@ -161,7 +145,10 @@ " result = repl.run(code)\n", " except BaseException as e:\n", " return f\"Failed to execute. Error: {repr(e)}\"\n", - " return f\"Succesfully executed:\\n```python\\n{code}\\n```\\nStdout: {result}\"" + " result_str = f\"Succesfully executed:\\n```python\\n{code}\\n```\\nStdout: {result}\"\n", + " return (\n", + " result_str + \"\\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\"\n", + " )" ] }, { @@ -186,17 +173,13 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 64, "id": "290c91d4-f6f4-443c-8181-233d39102974", "metadata": {}, "outputs": [], "source": [ "import operator\n", - "from typing import Annotated, List, Sequence, Tuple, TypedDict, Union\n", - "\n", - "from langchain.agents import create_openai_functions_agent\n", - "from langchain.tools.render import format_tool_to_openai_function\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from typing import Annotated, Sequence, TypedDict\n", "\n", "from langchain_openai import ChatOpenAI\n", "from typing_extensions import TypedDict\n", @@ -221,22 +204,23 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 65, "id": "71b790ca-9cef-4b22-b469-4b1d5d8424d6", "metadata": {}, "outputs": [], "source": [ "import functools\n", + "from langchain_core.messages import AIMessage\n", "\n", "\n", "# Helper function to create a node for a given agent\n", "def agent_node(state, agent, name):\n", " result = agent.invoke(state)\n", " # We convert the agent output into a format that is suitable to append to the global state\n", - " if isinstance(result, FunctionMessage):\n", + " if isinstance(result, ToolMessage):\n", " pass\n", " else:\n", - " result = HumanMessage(**result.dict(exclude={\"type\", \"name\"}), name=name)\n", + " result = AIMessage(**result.dict(exclude={\"type\", \"name\"}), name=name)\n", " return {\n", " \"messages\": [result],\n", " # Since we have a strict workflow, we can\n", @@ -251,17 +235,17 @@ "research_agent = create_agent(\n", " llm,\n", " [tavily_tool],\n", - " system_message=\"You should provide accurate data for the chart generator to use.\",\n", + " system_message=\"You should provide accurate data for the chart_generator to use.\",\n", ")\n", "research_node = functools.partial(agent_node, agent=research_agent, name=\"Researcher\")\n", "\n", - "# Chart Generator\n", + "# chart_generator\n", "chart_agent = create_agent(\n", " llm,\n", " [python_repl],\n", " system_message=\"Any charts you display will be visible by the user.\",\n", ")\n", - "chart_node = functools.partial(agent_node, agent=chart_agent, name=\"Chart Generator\")" + "chart_node = functools.partial(agent_node, agent=chart_agent, name=\"chart_generator\")" ] }, { @@ -276,43 +260,15 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 66, "id": "d9a79c76-5c7c-42f6-91cf-635bc8305804", "metadata": {}, "outputs": [], "source": [ + "from langgraph.prebuilt import ToolNode\n", + "\n", "tools = [tavily_tool, python_repl]\n", - "tool_executor = ToolExecutor(tools)\n", - "\n", - "\n", - "def tool_node(state):\n", - " \"\"\"This runs tools in the graph\n", - "\n", - " It takes in an agent action and calls that tool and returns the result.\"\"\"\n", - " messages = state[\"messages\"]\n", - " # Based on the continue condition\n", - " # we know the last message involves a function call\n", - " last_message = messages[-1]\n", - " # We construct an ToolInvocation from the function_call\n", - " tool_input = json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " )\n", - " # We can pass single-arg inputs by value\n", - " if len(tool_input) == 1 and \"__arg1\" in tool_input:\n", - " tool_input = next(iter(tool_input.values()))\n", - " tool_name = last_message.additional_kwargs[\"function_call\"][\"name\"]\n", - " action = ToolInvocation(\n", - " tool=tool_name,\n", - " tool_input=tool_input,\n", - " )\n", - " # We call the tool_executor and get back a response\n", - " response = tool_executor.invoke(action)\n", - " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(\n", - " content=f\"{tool_name} response: {str(response)}\", name=action.tool\n", - " )\n", - " # We return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}" + "tool_node = ToolNode(tools)" ] }, { @@ -327,22 +283,25 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 67, "id": "4f4b4d37-e8a3-4abb-8d42-eaea26016f35", "metadata": {}, "outputs": [], "source": [ "# Either agent can decide to end\n", - "def router(state):\n", + "from typing import Literal\n", + "\n", + "\n", + "def router(state) -> Literal[\"call_tool\", \"__end__\", \"continue\"]:\n", " # This is the router\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", - " if \"function_call\" in last_message.additional_kwargs:\n", + " if last_message.tool_calls:\n", " # The previus agent is invoking a tool\n", " return \"call_tool\"\n", " if \"FINAL ANSWER\" in last_message.content:\n", " # Any agent decided the work is done\n", - " return \"end\"\n", + " return \"__end__\"\n", " return \"continue\"" ] }, @@ -358,7 +317,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 68, "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", "metadata": {}, "outputs": [], @@ -366,18 +325,18 @@ "workflow = StateGraph(AgentState)\n", "\n", "workflow.add_node(\"Researcher\", research_node)\n", - "workflow.add_node(\"Chart Generator\", chart_node)\n", + "workflow.add_node(\"chart_generator\", chart_node)\n", "workflow.add_node(\"call_tool\", tool_node)\n", "\n", "workflow.add_conditional_edges(\n", " \"Researcher\",\n", " router,\n", - " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", + " {\"continue\": \"chart_generator\", \"call_tool\": \"call_tool\", \"__end__\": END},\n", ")\n", "workflow.add_conditional_edges(\n", - " \"Chart Generator\",\n", + " \"chart_generator\",\n", " router,\n", - " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"end\": END},\n", + " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"__end__\": END},\n", ")\n", "\n", "workflow.add_conditional_edges(\n", @@ -389,13 +348,40 @@ " lambda x: x[\"sender\"],\n", " {\n", " \"Researcher\": \"Researcher\",\n", - " \"Chart Generator\": \"Chart Generator\",\n", + " \"chart_generator\": \"chart_generator\",\n", " },\n", ")\n", "workflow.set_entry_point(\"Researcher\")\n", "graph = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 69, + "id": "97f8e0eb", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "8c9447e7-9ab6-43eb-8ae6-9b52f8ba8425", @@ -408,7 +394,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 70, "id": "176a99b0-b457-45cf-8901-90facaa852da", "metadata": {}, "outputs": [ @@ -416,36 +402,63 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'Researcher': {'messages': [HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"UK GDP data for the past 5 years\"}', 'name': 'tavily_search_results_json'}}, name='Researcher')], 'sender': 'Researcher'}}\n", + "{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_3zDlnDMUkWEJxnHASo59doCL', 'function': {'arguments': '{\"query\":\"UK GDP 2018 to 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 221, 'total_tokens': 247}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-ac6640c6-2bb4-478f-b3c4-eabf98cf4900-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2018 to 2023'}, 'id': 'call_3zDlnDMUkWEJxnHASo59doCL'}])], 'sender': 'Researcher'}}\n", "----\n", - "{'call_tool': {'messages': [FunctionMessage(content=\"tavily_search_results_json response: [{'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly GDP of the UK 2019-2023 Monthly GDP growth of the UK 2019-2023 Quarterly GDP growth of the UK 2019-2023 Quarterly GDP per capita in the UK 2019-2023Monthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100) GVA of the UK 2022, by sector GVA of the UK 2022, by sector Gross value added...'}, {'url': 'https://www.statista.com/topics/6500/the-british-economy/', 'content': 'Monthly GDP growth of the UK 2020-2023 Quarterly GDP growth of the UK 2015-2023 Monthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023 Monthly GDP of the UK 1997-2023Economy The UK economy - Statistics & Facts United Kingdom The gross domestic product of the British economy was 2.23 trillion British pounds in 2022 and was the fifth- largest global...'}, {'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/november2023', 'content': 'data from January 2022 to September 2023, as published in our\\\\xa0GDP quarterly national accounts, UK: July to September Figure 1: UK GDP is estimated to have grown by 0.3% in November 2023 GDP monthly estimate, UK: November 2023 Source: Monthly GDP estimate from Office for National Statistics The main reasons for revisions in October 2023 are:Monthly GDP is estimated to have grown by 0.3% in November 2023, following an unrevised fall of 0.3% in October 2023. This release provides data for November 2023 and has revisions from January 2022 to September 2023, consistent with our Quarterly national accounts bulletin, published on 22 December 2023. October 2023 is also open for revision.'}, {'url': 'https://www.statista.com/statistics/375195/gdp-growth-forecast-uk/', 'content': 'Forecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028 Additional Information GDP growth forecast for the UK 2000-2028 Weak economic growth throughout 2023 United Kingdom 2000 to 2028 *Forecast data Other statistics on the topicThe UK economy Economy Economy Average annual earnings for full-time employees in the UK 1999-2023 Economy Inflation rate in the UK 1989-2023In 2022 the gross domestic product (GDP) of the United Kingdom grew by four percent but is expected to grow by just 0.6 percent in 2023, and 0.7 percent in 2024. More robust growth is...'}, {'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/october2023', 'content': 'GDP monthly estimate, UK: October 2023 Figure 1: UK GDP is estimated to have fallen by 0.3% in October 2023 Monthly estimate of gross domestic product (GDP) containing constant price gross value added (GVA) data for the UK. domestic product (GDP) in October 2023.1. Main points Monthly real gross domestic product (GDP) is estimated to have shown no growth in the three months to October 2023, compared with the three months to July 2023. Monthly GDP...'}]\", name='tavily_search_results_json')]}}\n", + "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/timeseries/ihyp/pn2\", \"content\": \"Preliminary estimate of GDP time series (PGDP), released on 27 April 2018\\\\nPublications that use this data\\\\nContact details for this data\\\\nFooter links\\\\nHelp\\\\nAbout ONS\\\\nConnect with us\\\\nAll content is available under the Open Government Licence v3.0, except where otherwise stated Year on Year growth: CVM SA %\\\\nDownload full time series as:\\\\nDownload filtered time series as:\\\\nTable\\\\nNotes\\\\nFollowing a quality review it has been identified that the methodology used to estimate elements of purchased software within gross fixed capital formation (GFCF) has led to some double counting from 1997 onwards. GDP quarterly national accounts time series (QNA), released on 22 December 2023\\\\nIHYP: UK Economic Accounts time series (UKEA), released on 22 December 2023\\\\nIHYP: GDP first quarterly estimate time series\\\\n(PN2), released on 10 November 2023\\\\nIHYP: Year on Year growth: CVM SA %\\\\nSource dataset: GDP first quarterly estimate time series (PN2)\\\\nContact: Niamh McAuley\\\\nRelease date: 10 November 2023\\\\nView previous versions\\\\n %\\\\nFilters\\\\nCustom time period\\\\nChart\\\\nDownload this time seriesGross Domestic Product:\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.macrotrends.net/global-metrics/countries/GBR/united-kingdom/gdp-gross-domestic-product\", \"content\": \"U.K. gdp for 2021 was $3,141.51B, a 16.45% increase from 2020. U.K. gdp for 2020 was $2,697.81B, a 5.39% decline from 2019. U.K. gdp for 2019 was $2,851.41B, a 0.69% decline from 2018. GDP at purchaser\\'s prices is the sum of gross value added by all resident producers in the economy plus any product taxes and minus any subsidies not included in ...\"}, {\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}]', name='tavily_search_results_json', tool_call_id='call_3zDlnDMUkWEJxnHASo59doCL')]}}\n", "----\n", - "{'Researcher': {'messages': [HumanMessage(content=\"The search results provide information on the UK's GDP, including monthly and quarterly data from various years, but they do not provide a clear year-on-year GDP figure for each of the past five years. To graph the UK's GDP over the past five years, we need annual GDP figures for each year.\\n\\nI will attempt to extract the annual GDP figures mentioned in the search results and then graph them. However, please note that the information provided in the search results may be incomplete or not formatted in a way that allows for a straightforward extraction of annual GDP figures for each of the last five years. If necessary, I may need to perform additional searches to fill in any gaps.\\n\\nFrom the search results, we have the following relevant information regarding the UK's GDP:\\n\\n- The gross domestic product of the British economy was 2.23 trillion British pounds in 2022.\\n- In 2022, the gross domestic product (GDP) of the United Kingdom grew by four percent.\\n\\nUnfortunately, the search results do not provide explicit annual GDP figures for 2018, 2019, 2020, or 2021. Therefore, I will need to perform an additional search to find the missing GDP figures for these years. Let's proceed with that search.\", additional_kwargs={'function_call': {'arguments': '{\"query\":\"UK GDP 2018 2019 2020 2021\"}', 'name': 'tavily_search_results_json'}}, name='Researcher')], 'sender': 'Researcher'}}\n", + "{'Researcher': {'messages': [AIMessage(content=\"The search results provide some information about the UK's GDP over the past years, but most of the relevant data is either not in a structured format that can be easily extracted or it is behind a source that requires further access for detailed statistics. To proceed with generating a line graph, we need specific GDP values for each year from 2018 to 2023.\\n\\nHowever, one of the search results from macrotrends.net does provide specific GDP values for the years 2018 to 2021:\\n\\n- U.K. GDP for 2021 was $3,141.51 billion, a 16.45% increase from 2020.\\n- U.K. GDP for 2020 was $2,697.81 billion, a 5.39% decline from 2019.\\n- U.K. GDP for 2019 was $2,851.41 billion, a 0.69% decline from 2018.\\n\\nWe still need the GDP values for 2022 and 2023 to complete the dataset for the past five years. I will now conduct a further search to find the missing GDP data for 2022 and 2023.\", additional_kwargs={'tool_calls': [{'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 263, 'prompt_tokens': 3199, 'total_tokens': 3462}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-25901401-0d62-485f-b7d5-37e3c159effe-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023'}, 'id': 'call_nvB1wQyQuNeTrOXQZnEtgNDZ'}])], 'sender': 'Researcher'}}\n", "----\n", - "{'call_tool': {'messages': [FunctionMessage(content='tavily_search_results_json response: [{\\'url\\': \\'https://www.statista.com/topics/3795/gdp-of-the-uk/\\', \\'content\\': \"GDP of the UK 2021, by country Gross domestic product of the United Kingdom in 2021, by country (in million GBP) Monthly GDP of the UK 2019-2023 United Kingdom\\'s share of global gross domestic product (GDP) 2028 Quarterly GDP per capita in the UK 2019-2023In 2022, the gross domestic product of the United Kingdom amounted to approximately 2.2 trillion British pounds, compared with 2.14 trillion pounds in 2021, and 1.99 trillion in 2020. Although the ...\"}, {\\'url\\': \\'https://www.beta.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2022/nationalaccountsataglance\\', \\'content\\': \"of UK GDP over 2020 and 2021 Figure 1: The UK economy increased by 7.5% in 2021, having seen the largest fall in over 300 years in 2020 accounts at a glance, UK National Accounts, The Blue Book: 2022 Figure 3: In 2021, nominal GDP in most of the G20 countries had recovered to its 2019 levelsIn 2021, the UK\\'s implied GDP deflator rose by 0.4%, the lowest among the developed countries shown (Figure 4). However, these comparisons are likely to reflect some of the measurement challenges over the coronavirus pandemic. Movements in the implied GDP deflator in 2020 and 2021 have been largely affected by the government consumption deflator.\"}, {\\'url\\': \\'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/december2021\\', \\'content\\': \\'GDP monthly estimate, UK : December 2021 gross domestic product (GDP), UK. data from January 2021 to November 2021. This is consistent with GDP first quarterly estimate, UK: October to December on gross domestic product (GDP) in December 2021, with output of these services increasing by 51% and 19% respectively2. Monthly GDP. Monthly real gross domestic product (GDP) is estimated to have fallen by 0.2% in December 2021, compared with a 0.7% growth in November 2021 (revised down from 0.9% growth). This release includes revisions to the monthly data back to January 2021, consistent with the first quarterly estimate of GDP.\\'}, {\\'url\\': \\'https://www.beta.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/september2021\\', \\'content\\': \\'GDP monthly estimate, UK: September 2021 2021, while detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021. More detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021. while detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021.Source: Office for National Statistics - GDP monthly estimate. Download this chart. Image .csv .xls. Monthly real gross domestic product (GDP) grew by 0.6% in September 2021, and follows a revised 0.2% growth in August 2021 (down from 0.4% growth) and a revised 0.2% fall in July 2021 (down from a 0.1% fall).\\'}, {\\'url\\': \\'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/october2021\\', \\'content\\': \\'GDP monthly estimate, UK : October 2021 Figure 1: UK GDP is estimated to have grown by 0.1% in October 2021, but remains 0.5% below its pre-pandemic level Monthly estimate of gross domestic product (GDP) containing constant price gross value added (GVA) data for the UK. Source: Office for National Statistics – GDP monthly estimate NotesTable 1: UK GDP in October 2021 was 0.5% below its pre-pandemic level, however services has now returned to its pre-pandemic levelChange in output, percentage change, February 2020 to October 2021, UK. Source: Office for National Statistics - GDP monthly estimate. This table uses data from the output measure of GDP.\\'}]', name='tavily_search_results_json')]}}\n", + "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpfirstquarterlyestimateuk/octobertodecember2023\", \"content\": \"This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and have set out our plans on how we will improve the way we communicate uncertainty.\\\\n Source: GDP first quarterly estimate from the Office for National Statistics\\\\nNotes\\\\nOffice for Statistics Regulation Revisions of estimates of UK GDP review\\\\nThe Office for Statistics Regulation (OSR) have completed a review of the practices around the preparation and release of information about revisions to estimates of GDP in our Impact of Blue Book 2023 article released on 1 September 2023, as announced on 6 September 2023 on the OSR website. Across 2023, the services sector sees revisions for the following reasons, with only Quarter 1 2023 seeing growth revised from our previous publication, including:\\\\nupdated input data for the deflator used for telecommunications\\\\nupdated seasonal adjustment which now uses a complete year of data for 2023\\\\nProduction\\\\nThe production sector is estimated to have decreased by 1.0% in the latest quarter after growth of 0.1% in Quarter 3 2023 (unrevised from our previous publication). Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are often based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\u201cerrors\\\\u201d in the popular sense of the word. Construction output in Great Britain: December 2023, new orders and Construction Output Price Indices, October to December 2023\\\\nBulletin | Released 15 February 2024\\\\nShort-term measures of output by the construction industry, contracts awarded for new construction work in Great Britain and a summary of the Construction Output Price Indices (OPIs) in the UK for Quarter 4 (October to December) 2023.\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/quarterlynationalaccounts/latest\", \"content\": \"Looking at the quarters open to revision, real GDP growth is unrevised in five of the seven quarters compared with the first quarterly estimate; however, it is important to note that the typical absolute average revision between the initial quarterly GDP estimate and the estimate three years later is 0.2 percentage points, as there is potential for revision to GDP when the annual supply and use balance occurs as more comprehensive annual data sources are available at a detailed industry and product level; all the GDP growth vintages for these quarters are shown in Table 4.\\\\n Overall the revisions to production reflect:\\\\nrevised volume data from the\\\\u00a0Department for Energy Security and Net Zero (DESNZ) for electricity, gas, steam and air conditioning supply\\\\nnew Value Added Tax (VAT) turnover data for Quarter 2 2023\\\\nnew and revised Monthly Business Survey data\\\\nseasonal adjustment models\\\\nFigure 7: Revisions to production output across 2022 and 2023 are mainly driven by manufacturing; and the electricity, gas and steam subsectors\\\\nConstruction\\\\nConstruction output rose by 0.4% in Quarter 3 2023, revised up from a first estimate increase of 0.1%. Professional, scientific and technical activities: the upward revision in Quarter 4 (Oct to Dec) 2022 and Quarter 1 2023 are driven by new and revised survey data within the advertising and market research industry; in Quarter 3 2023, six of the eight industries in this section are revised down, with the largest contribution coming from architecture and engineering activities; technical testing and analysis, because of revised survey data since our last publication and the new VAT data for Quarter 2 2023.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\\"errors\\\\\" in the popular sense of the word.\"}]', name='tavily_search_results_json', tool_call_id='call_nvB1wQyQuNeTrOXQZnEtgNDZ')]}}\n", "----\n", - "{'Researcher': {'messages': [HumanMessage(content=\"Based on the search results, we can now extract some annual GDP figures for the UK:\\n\\n- In 2022, the gross domestic product of the United Kingdom amounted to approximately 2.2 trillion British pounds.\\n- In 2021, the gross domestic product of the United Kingdom was about 2.14 trillion British pounds.\\n- In 2020, the gross domestic product of the United Kingdom was about 1.99 trillion British pounds.\\n\\nUnfortunately, the search results still do not provide explicit figures for 2018 and 2019. To complete the dataset, we would need to perform another search or access historical economic data from a reliable source such as the Office for National Statistics (ONS) or an economic database. If additional searches are not possible, we may need to use these three data points to give a partial visualization of the trend. \\n\\nSince we cannot perform additional searches, I will provide the data points we have, and you will need to add the missing years' data manually if required:\\n\\n- 2020: 1.99 trillion GBP\\n- 2021: 2.14 trillion GBP\\n- 2022: 2.2 trillion GBP\\n\\nPlease note that the data for 2018 and 2019 is missing, and you would need to find these figures to complete the five-year view. With the data at hand, the line graph would only represent the years 2020 to 2022.\", name='Researcher')], 'sender': 'Researcher'}}\n", - "----\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Python REPL can execute arbitrary code. Use with caution.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'Chart Generator': {'messages': [HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# Define the data\\\\nyears = [2020, 2021, 2022]\\\\ngdp_values = [1.99, 2.14, 2.2] # Trillion GBP\\\\n\\\\n# Create the line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(years, gdp_values, marker=\\'o\\')\\\\n\\\\n# Labeling the graph\\\\nplt.title(\\'UK GDP from 2020 to 2022\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Trillion GBP)\\')\\\\nplt.grid(True)\\\\nplt.xticks(years)\\\\nplt.tight_layout()\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}}, name='Chart Generator')], 'sender': 'Chart Generator'}}\n", + "{'Researcher': {'messages': [AIMessage(content=\"The search results did not provide exact figures for the UK's GDP in 2022 and 2023. While there are several references to GDP data, growth rates, and quarterly figures, we do not have the specific annual GDP values in a consistent currency format (such as USD or GBP) that would allow us to compile a complete dataset for the past five years.\\n\\nTo proceed, we will need to find another source or use a different method to obtain the missing GDP data for 2022 and 2023. If this data is not available, we may not be able to draw an accurate line graph of the UK's GDP over the past five years.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 6996, 'total_tokens': 7130}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-aa7d307d-cfdd-4c83-ad09-b6b0efbffe6e-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"It seems we have hit a roadblock in finding the exact GDP figures for the UK for the years 2022 and 2023. The information provided by the search results does not include the specific data we need. Therefore, we currently do not have the complete dataset to generate a line graph of the UK's GDP over the past five years.\\n\\nTo proceed, we might need to look for an official statistical release or a comprehensive economic report that includes the GDP figures for 2022 and 2023. If such data can be obtained, we can then use it to create the desired line graph. Without this data, we cannot fulfill the request as specified.\", response_metadata={'token_usage': {'completion_tokens': 134, 'prompt_tokens': 7150, 'total_tokens': 7284}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-a667e647-45b2-414e-b301-81f846fa59ad-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"I am unable to proceed with generating the line graph as we lack the specific GDP data for the UK for the years 2022 and 2023. To complete this task, we would need to acquire the missing data points. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, we must conclude this attempt.\", response_metadata={'token_usage': {'completion_tokens': 87, 'prompt_tokens': 7276, 'total_tokens': 7363}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-1731ab9f-7ee9-4ff9-a920-7b998a41fe4d-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"As of my last attempt, I have not been able to find the exact GDP figures for the UK for the years 2022 and 2023. Without these figures, it's not possible to generate a complete and accurate line graph of the UK's GDP over the past five years. Should the data become available or if an alternative source can provide the figures, we can proceed with creating the graph. For the time being, this task cannot be completed.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 7383, 'total_tokens': 7476}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-6e12dccd-b1b2-4c7d-befd-4a338d027756-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"I have attempted to gather the UK's GDP data for the past five years to draw a line graph, but I was only able to obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 111, 'prompt_tokens': 7468, 'total_tokens': 7579}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-9d93e637-cedd-4b17-945a-cc97e490432c-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"I've exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7599, 'total_tokens': 7735}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-82063d76-6655-473e-9f6a-a5c005d92bd3-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"I have exhausted the available tools to find the UK's GDP for the past five years, but I was able to obtain data up to the year 2021. Unfortunately, without the GDP data for 2022 and 2023, I cannot create a complete line graph for the UK's GDP over the past five years.\\n\\nTo generate the line graph, we would need to have all the necessary data points for each year from 2018 to 2023. If you can provide the GDP data for 2022 and 2023 or direct me to a specific source where these figures can be found, I will be able to create the line graph for you.\", response_metadata={'token_usage': {'completion_tokens': 136, 'prompt_tokens': 7727, 'total_tokens': 7863}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-954e7bb2-ae3e-4610-9927-4b16d733414e-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"Having attempted to find the UK's GDP figures for the years 2022 and 2023 through available data sources and coming up short, it appears we are currently unable to produce a complete line graph of the UK's GDP over the past five years as initially requested. To create a graph, we would need precise GDP data for each year from 2018 to 2023.\\n\\nIf the missing GDP data for 2022 and 2023 becomes available or if an alternative source can provide these figures, we can then proceed to generate the line graph. As of now, this task must be paused until the necessary data can be obtained.\", response_metadata={'token_usage': {'completion_tokens': 130, 'prompt_tokens': 7883, 'total_tokens': 8013}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8d1382e2-a77c-4d2f-b06a-2597be59542b-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"The search results do not provide the exact GDP figures for the UK for 2022 and 2023. Without this information, it is not possible to generate a line graph of the UK's GDP over the past five years. We would require the GDP values for those two years to complete the dataset and create the graph. As of now, I must conclude this task until the necessary data becomes available.\", response_metadata={'token_usage': {'completion_tokens': 82, 'prompt_tokens': 8005, 'total_tokens': 8087}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-246b9b29-ffc7-4da9-a09a-0dcfbbb3bd7a-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"I have attempted to find the UK's GDP for the past five years to create a line graph, but I could only obtain confirmed figures for the years 2018 through 2021. The GDP data for 2022 and 2023 was not available in the search results. Without complete data, it is not possible to generate the line graph as requested. If the missing data for 2022 and 2023 becomes available or an alternative verified source can provide these figures, we can revisit this task to create the graph.\", response_metadata={'token_usage': {'completion_tokens': 108, 'prompt_tokens': 8107, 'total_tokens': 8215}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': 'fp_85bf4c41a2', 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-f2847a80-610d-49c5-924a-ccffccb7cd5a-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"As of now, I was unable to obtain the complete data for the UK's GDP over the past five years due to lack of specific information for the years 2022 and 2023. Therefore, it's not possible to draw a line graph of the UK's GDP for this period without the complete dataset. Further action to acquire the missing data would be required to proceed.\", response_metadata={'token_usage': {'completion_tokens': 77, 'prompt_tokens': 8207, 'total_tokens': 8284}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-28e09000-8787-4ac0-a7d8-0aba888c2520-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content=\"It appears we have encountered a limitation in obtaining the complete GDP data for the UK for 2022 and 2023. Without these figures, we cannot create the line graph of the UK's GDP over the past five years as requested. If the data becomes available, or if there's an alternative source that can provide the figures, we can revisit this task and generate the line graph accordingly. For now, this task will have to be concluded without completion.\", response_metadata={'token_usage': {'completion_tokens': 93, 'prompt_tokens': 8304, 'total_tokens': 8397}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-8bf8f247-cb86-4ef0-a81b-14da2d27b6f1-0')], 'sender': 'chart_generator'}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_df3UdS3vJkJFB30O0WYq38k8', 'function': {'arguments': '{\"query\":\"UK GDP 2022 2023 statistics\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 26, 'prompt_tokens': 8389, 'total_tokens': 8415}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='Researcher', id='run-e1577cc7-5673-4821-9683-34947c7a2bc5-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'UK GDP 2022 2023 statistics'}, 'id': 'call_df3UdS3vJkJFB30O0WYq38k8'}])], 'sender': 'Researcher'}}\n", + "----\n", + "{'call_tool': {'messages': [ToolMessage(content='[{\"url\": \"https://www.statista.com/statistics/281744/gdp-of-the-united-kingdom/\", \"content\": \"Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nGDP of the UK 1948-2022\\\\nUK economy expected to shrink in 2023\\\\nHow big is the UK economy compared to others?\\\\nGross domestic product of the United Kingdom from 1948 to 2022\\\\n(in million GBP)\\\\nAdditional Information\\\\nShow sources information\\\\nShow publisher information\\\\nUse Ask Statista Research Service\\\\nDecember 2023\\\\nUnited Kingdom\\\\n1948 to 2022\\\\n*GDP is displayed in real terms (seasonally adjusted chained volume measure with 2019 as the reference year)\\\\n Statistics on\\\\n\\\\\"\\\\nEconomy of the UK\\\\n\\\\\"\\\\nOther statistics that may interest you Economy of the UK\\\\nGross domestic product\\\\nLabor Market\\\\nInflation\\\\nGovernment finances\\\\nBusiness Enterprise\\\\nFurther related statistics\\\\nFurther Content: You might find this interesting as well\\\\nStatistics\\\\nTopics Other statistics on the topicThe UK economy\\\\nEconomy\\\\nRPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nCPI annual inflation rate UK 2000-2028\\\\nEconomy\\\\nAverage annual earnings for full-time employees in the UK 1999-2023\\\\nEconomy\\\\nInflation rate in the UK 1989-2023\\\\nYou only have access to basic statistics.\\\\n Customized Research & Analysis projects:\\\\nGet quick analyses with our professional research service\\\\nThe best of the best: the portal for top lists & rankings:\\\\n\"}, {\"url\": \"https://www.statista.com/topics/3795/gdp-of-the-uk/\", \"content\": \"Monthly growth of gross domestic product in the United Kingdom from January 2019 to November 2023\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nContribution to gross domestic product growth in the United Kingdom in January 2023, by sector\\\\nGDP growth rate in the UK 1999-2021, by country\\\\nAnnual growth rates of gross domestic product in the United Kingdom from 1999 to 2021, by country\\\\nGDP growth rate in the UK 2021, by region\\\\nAnnual growth rates of gross domestic product in the United Kingdom in 2021, by region\\\\nGDP growth of Scotland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Scotland in 2021, by local (ITL 3) area\\\\nGDP growth of Wales 2021, by local area\\\\nAnnual growth rates of gross domestic product in Wales in 2021, by local (ITL 3) area\\\\nGDP growth of Northern Ireland 2021, by local area\\\\nAnnual growth rates of gross domestic product in Northern Ireland in 2021, by local (ITL 3) area\\\\nGDP per capita\\\\nGDP per capita\\\\nGDP per capita in the UK 1955-2022\\\\nGross domestic product per capita in the United Kingdom from 1955 to 2022 (in GBP)\\\\nAnnual GDP per capita growth in the UK 1956-2022\\\\nAnnual GDP per capita growth in the United Kingdom from 1956 to 2022\\\\nQuarterly GDP per capita in the UK 2019-2023\\\\nQuarterly GDP per capita in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nQuarterly GDP per capita growth in the UK 2019-2023\\\\nQuarterly GDP per capita growth in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in GBP)\\\\nGDP per capita of the UK 1999-2021, by country\\\\nGross domestic product per capita of the United Kingdom from 1999 to 2021, by country (in GBP)\\\\nGDP per capita of the UK 2021, by region\\\\nGross domestic product per capita of the United Kingdom in 2021, by region (in GBP)\\\\nGlobal Comparisons\\\\nGlobal Comparisons\\\\nCountries with the largest gross domestic product (GDP) 2022\\\\n Monthly GDP of the UK 2019-2023\\\\nMonthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100)\\\\nGVA of the UK 2022, by sector\\\\nGross value added of the United Kingdom in 2022, by industry sector (in million GBP)\\\\nGDP of the UK 2021, by country\\\\nGross domestic product of the United Kingdom in 2021, by country (in million GBP)\\\\nGDP of the UK 2021, by region\\\\nGross domestic product of the United Kingdom in 2021, by region (in million GBP)\\\\nGDP of Scotland 2021, by local area\\\\nGross domestic product of Scotland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Wales 2021, by local area\\\\nGross domestic product of Wales in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP of Northern Ireland 2021, by local area\\\\nGross domestic product of Northern Ireland in 2021, by local (ITL 3) area (in million GBP)\\\\nGDP growth\\\\nGDP growth\\\\nGDP growth forecast for the UK 2000-2028\\\\nForecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028\\\\nAnnual GDP growth in the UK 1949-2022\\\\nAnnual growth of gross domestic product in the United Kingdom from 1949 to 2022\\\\nQuarterly GDP growth of the UK 2019-2023\\\\nQuarterly growth of gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023\\\\nMonthly GDP growth of the UK 2019-2023\\\\n Transforming data into design:\\\\nStatista Content & Design\\\\nStrategy and business building for the data-driven economy:\\\\nUK GDP - Statistics & Facts\\\\nUK economy expected to shrink in 2023\\\\nCharacteristics of UK GDP\\\\nKey insights\\\\nDetailed statistics\\\\nGDP of the UK 1948-2022\\\\nDetailed statistics\\\\nAnnual GDP growth in the UK 1949-2022\\\\nDetailed statistics\\\\nGDP per capita in the UK 1955-2022\\\\nEditor\\\\u2019s Picks\\\\nCurrent statistics on this topic\\\\nCurrent statistics on this topic\\\\nKey Economic Indicators\\\\nMonthly GDP growth of the UK 2019-2023\\\\nKey Economic Indicators\\\\nMonthly GDP of the UK 2019-2023\\\\nKey Economic Indicators\\\\nContribution to GDP growth in the UK 2023, by sector\\\\nRelated topics\\\\nRecommended\\\\nRecommended statistics\\\\nGDP\\\\nGDP\\\\nGDP of the UK 1948-2022\\\\nGross domestic product of the United Kingdom from 1948 to 2022 (in million GBP)\\\\nQuarterly GDP of the UK 2019-2023\\\\nQuarterly gross domestic product in the United Kingdom from 1st quarter 2019 to 3rd quarter 2023 (in million GBP)\\\\n The 20 countries with the largest gross domestic product (GDP) in 2022 (in billion U.S. dollars)\\\\nGDP of European countries in 2022\\\\nGross domestic product at current market prices of selected European countries in 2022 (in million euros)\\\\nReal GDP growth rates in Europe 2023\\\\nAnnual real gross domestic product (GDP) growth rate in European countries in 2023\\\\nGross domestic product (GDP) of Europe\\'s largest economies 1980-2028\\\\nGross domestic product (GDP) at current prices of Europe\\'s largest economies from 1980 to 2028 (in billion U.S dollars)\\\\nUnited Kingdom\\'s share of global gross domestic product (GDP) 2028\\\\nUnited Kingdom (UK): Share of global gross domestic product (GDP) adjusted for Purchasing Power Parity (PPP) from 2018 to 2028\\\\nRelated topics\\\\nRecommended\\\\nReport on the topic\\\\nKey figures\\\\nThe most important key figures provide you with a compact summary of the topic of \\\\\"UK GDP\\\\\" and take you straight to the corresponding statistics.\\\\n Industry Overview\\\\nDigital & Trend reports\\\\nOverview and forecasts on trending topics\\\\nIndustry & Market reports\\\\nIndustry and market insights and forecasts\\\\nCompanies & Products reports\\\\nKey figures and rankings about companies and products\\\\nConsumer & Brand reports\\\\nConsumer and brand insights and preferences in various industries\\\\nPolitics & Society reports\\\\nDetailed information about political and social topics\\\\nCountry & Region reports\\\\nAll key figures about countries and regions\\\\nMarket forecast and expert KPIs for 1000+ markets in 190+ countries & territories\\\\nInsights on consumer attitudes and behavior worldwide\\\\nBusiness information on 100m+ public and private companies\\\\nExplore Company Insights\\\\nDetailed information for 39,000+ online stores and marketplaces\\\\nDirectly accessible data for 170 industries from 150+ countries\\\\nand over 1\\\\u00a0Mio. facts.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/quarterlynationalaccounts/latest\", \"content\": \"Looking at the quarters open to revision, real GDP growth is unrevised in five of the seven quarters compared with the first quarterly estimate; however, it is important to note that the typical absolute average revision between the initial quarterly GDP estimate and the estimate three years later is 0.2 percentage points, as there is potential for revision to GDP when the annual supply and use balance occurs as more comprehensive annual data sources are available at a detailed industry and product level; all the GDP growth vintages for these quarters are shown in Table 4.\\\\n Overall the revisions to production reflect:\\\\nrevised volume data from the\\\\u00a0Department for Energy Security and Net Zero (DESNZ) for electricity, gas, steam and air conditioning supply\\\\nnew Value Added Tax (VAT) turnover data for Quarter 2 2023\\\\nnew and revised Monthly Business Survey data\\\\nseasonal adjustment models\\\\nFigure 7: Revisions to production output across 2022 and 2023 are mainly driven by manufacturing; and the electricity, gas and steam subsectors\\\\nConstruction\\\\nConstruction output rose by 0.4% in Quarter 3 2023, revised up from a first estimate increase of 0.1%. Professional, scientific and technical activities: the upward revision in Quarter 4 (Oct to Dec) 2022 and Quarter 1 2023 are driven by new and revised survey data within the advertising and market research industry; in Quarter 3 2023, six of the eight industries in this section are revised down, with the largest contribution coming from architecture and engineering activities; technical testing and analysis, because of revised survey data since our last publication and the new VAT data for Quarter 2 2023.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n Important quality information\\\\nThere are common pitfalls in interpreting data series, and these include:\\\\nexpectations of accuracy and reliability in early estimates are often too high\\\\nrevisions are an inevitable consequence of the trade-off between timeliness and accuracy\\\\nearly estimates are based on incomplete data\\\\nVery few statistical revisions arise as a result of \\\\\"errors\\\\\" in the popular sense of the word.\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/latest\", \"content\": \"The following list contains the full SIC names of industries included in consumer-facing services and their corresponding shortened industry name where this has been used in Figure 5:\\\\nwholesale and retail trade and repair of motor vehicles and motorcycles - sales and repairs of motor vehicles\\\\nretail trade, except of motor vehicles and motorcycles - retail except motor vehicles\\\\nrail transport\\\\naccommodation\\\\nfood and beverage service activities - food and beverage\\\\nbuying and selling, renting and operating of own or leased real estate, excluding imputed rent - real estate activities\\\\nveterinary activities\\\\ntravel agency, tour operator and other reservation service and related activities - travel and tourism activities\\\\ngambling and betting services\\\\nsports activities and amusement and recreation activities - sports, amusement and recreation\\\\nactivities of membership organisations\\\\nother personal service activities\\\\nactivities of households as employers of domestic personnel - households as employers of domestic personnel\\\\nAdditional bank holiday in May 2023 for the Coronation of King Charles III\\\\nThere was an additional bank holiday for the coronation of King Charles III on Monday 8 May 2023. Source: Monthly GDP estimate from Office for National Statistics\\\\nThe main reasons for revisions in October 2023 are:\\\\nin the services sector, the upwards revision is mainly from updated and late monthly business survey responses primarily in the information and communication subsection\\\\nin the production sector, the downward revision is from source data replacing forecasts in mining and quarrying and electricity, gas, steam and air conditioning supply, as well as revised and late monthly business survey responses predominantly in the manufacture of pharmaceutical products and pharmaceutical preparations, and sewerage industries\\\\nin the construction sector, the upwards revisions is because of updated and late monthly business survey responses for new public housing and other public new work\\\\nDetails on the revisions to monthly GDP prior to October 2023 are provided in our GDP quarterly national accounts, UK: July to September 2023 bulletin.\\\\n This review covered:\\\\nprocesses and quality assurance in making revisions to GDP\\\\npotential improvements to early estimates of GDP enabled through enhanced access to data\\\\ncommunication of revisions to GDP, the story behind the most recent set of revisions in particular, and uncertainty in early estimates of GDP\\\\nWe have already started work looking into the recommendations of this review and will set out plans more fully during January 2024.\\\\n11. The main data source for these statistics is the Monthly Business Survey (MBS) and response rates for each can be found in our:\\\\nOutput in the construction industry dataset\\\\nMonthly Business Survey (production) response rates dataset\\\\nCurrent and historical Monthly Business Survey (services) response rates dataset\\\\nOur monthly gross domestic product (GDP) data sources catalogue provides a full breakdown of the data used in this publication.\\\\n On the negative side, the lack of demand for construction products was prevalent across manufacturing, with manufacture of wood, rubber and plastic, glass, cement and plaster all seeing declines on the month in November 2023 in line with the two consecutive monthly falls in construction output in October and November 2023.\\\\n\"}, {\"url\": \"https://www.ons.gov.uk/economy/grossdomesticproductgdp\", \"content\": \"Quarter on Quarter growth: CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: q-on-q4 growth rate CVM SA %\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product at market prices: Current price: Seasonally adjusted \\\\u00a3m\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: quarter on quarter growth rate: CP SA %\\\\nCurrent Prices (CP)\\\\nGross Domestic Product: q-on-q4 growth quarter growth: CP SA %\\\\nCurrent Prices (CP)\\\\nDatasets related to Gross Domestic Product (GDP)\\\\n A roundup of the latest data and trends on the economy, business and jobs\\\\nTime series related to Gross Domestic Product (GDP)\\\\nGross Domestic Product: chained volume measures: Seasonally adjusted \\\\u00a3m\\\\nChained Volume Measures (CVM)\\\\nGross Domestic Product: Hide\\\\nData and analysis from Census 2021\\\\nGross Domestic Product (GDP)\\\\nGross domestic product (GDP) estimates as the main measure of UK economic growth based on the value of goods and services produced during a given period. Contains current and constant price data on the value of goods and services to indicate the economic performance of the UK.\\\\nEstimates of short-term indicators of investment in non-financial assets; business investment and asset and sector breakdowns of total gross fixed capital formation.\\\\n Monthly gross domestic product by gross value added\\\\nThe gross value added (GVA) tables showing the monthly and annual growths and indices as published within the monthly gross domestic product (GDP) statistical bulletin.\\\\n\"}]', name='tavily_search_results_json', tool_call_id='call_df3UdS3vJkJFB30O0WYq38k8')]}}\n", + "----\n", + "{'Researcher': {'messages': [AIMessage(content=\"The search results unfortunately do not provide specific figures for the UK's GDP in 2022 and 2023. While there are references to GDP data and related economic indicators, the exact annual GDP values needed to create the line graph are not present.\\n\\nOne possible approach to obtain these figures would be to access detailed statistical databases or reports from official sources such as the Office for National Statistics (ONS) or economic research institutions that publish historical GDP data. These sources might have the most recent and accurate GDP figures available for the UK, which are necessary to complete the line graph.\\n\\nSince I cannot directly access or retrieve the data from these sources using the tools available to me, I recommend consulting such databases or reports to find the UK's GDP for 2022 and 2023. Once the data is obtained, it can be used to create the line graph.\", response_metadata={'token_usage': {'completion_tokens': 172, 'prompt_tokens': 12099, 'total_tokens': 12271}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='Researcher', id='run-7b4bcbc3-3ed0-4fa0-8e5d-a366c5a80d5a-0')], 'sender': 'Researcher'}}\n", + "----\n", + "{'chart_generator': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021, 2022, 2023],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2697.81, 3141.51, None, None]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2023\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 240, 'prompt_tokens': 12291, 'total_tokens': 12531}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-6cff57bc-ba87-4690-9528-4d15bba7986c-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_JPVxDAzEFi21crVT7Rt6SRJl'}])], 'sender': 'chart_generator'}}\n", + "----\n", + "{'call_tool': {'messages': [ToolMessage(content=\"Succesfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# The data for 2022 and 2023 are placeholders and should be replaced with actual figures when available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021, 2022, 2023],\\n 'GDP (Billion USD)': [2851.41, 2697.81, 3141.51, None, None]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2023')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: ValueError('x and y must have same first dimension, but have shapes (6,) and (5,)')\\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_JPVxDAzEFi21crVT7Rt6SRJl')]}}\n", "----\n" ] }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAHqCAYAAAAZLi26AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAABv/UlEQVR4nO3deVyVZf7/8fc57DsCAoIo4K5gWu6WtribRjbN1NSUTU0zhTOZLeaSS1bYbuWMTc20/DJr6qtmmaloroWWminuCu6CCrJvB879+8M8dRIVlMNheT0fDx55rvu67/O5j+TNm+u+r8tkGIYhAAAAAABQ48zOLgAAAAAAgIaK0A0AAAAAgIMQugEAAAAAcBBCNwAAAAAADkLoBgAAAADAQQjdAAAAAAA4CKEbAAAAAAAHIXQDAAAAAOAghG4AAAAAAByE0A0AgJOVl5frySefVFRUlMxmsxISEpxdEgAAqCGEbgBArZk2bZpMJpNOnz5d6fa4uDhdf/31ttcHDx6UyWTSyy+/bNfPMAz99a9/lclk0rRp0y75vqWlpXrzzTd17bXXqkmTJnJ3d1dERIRGjhypjz/+WBUVFee957kvNzc3hYSEqE+fPpo4caIOHz583vFXr1593j6xsbG65557lJaWdsn63n33Xb300kv63e9+pw8++ECPPvroJfdxlqKiIv3zn//UoEGD1KxZM/n5+alr166aM2eO3ed4jtVq1YsvvqiYmBh5enqqc+fO+vjjj8/r8/7772vkyJGKioqSj4+P4uLi9Oyzz6qkpKTSOv773/+qQ4cO8vT0VJs2bfTmm29Wqf7vvvtO06ZNU05OTrXP/WJWrlypP//5z2rbtq28vb0VGxurBx54QCdOnLhgHddee628vb0VHh6uf/zjHyooKLDr88MPP2jMmDHq1KmTfHx81KJFC/3+97/X3r177fpdzucHAKg9rs4uAACA6jAMQw8//LDefvttPf3005cM3adOndLQoUO1efNmDR48WJMnT1ZQUJAyMjK0YsUK/fGPf9T+/fv19NNP2+135513atiwYbJarTpz5ox++OEHzZo1S6+//rr++9//6o477jjvvf7xj3+oe/fuslgs2rJli95++2199dVX2r59uyIiIi5Y4zfffKPIyEi99tprl/WZ1Ka0tDT9/e9/10033aRx48bJ399fy5Yt08MPP6wNGzbogw8+sOs/adIkzZw5U3/5y1/UvXt3LVq0SH/84x9lMplsn2FRUZHuu+8+9erVS3/7298UGhqqlJQUTZ06VStXrtQ333wjk8lkO+a///1v/e1vf9Ntt92mcePGad26dfrHP/6hoqIijR8//qL1f/fdd5o+fbpGjx6twMDAGvtcxo8fr+zsbN1+++1q06aN0tLSNHv2bC1evFhbt25VeHi4re/WrVt10003qUOHDnr11Vd19OhRvfzyy9q3b5++/vprW78XXnhB3377rW6//XZ17txZGRkZmj17tq6++mpt2LBBcXFxl/X5AQBqmQEAQC2ZOnWqIck4depUpds7depk9O/f3/Y6PT3dkGS89NJLtrbExERDkjFp0qQqvefgwYMNs9lszJ8/v9LtP/zwgzF37tyLvuc5Bw8eNNq2bWu4u7sbW7dutbWvWrXKkGR89tlndv3feOMNQ5Lx/PPPX7TGG264wejUqdMlz8VisRilpaWX7OdIp06dMlJTU89rv++++wxJxr59+2xtR48eNdzc3IzExERbm9VqNa677jqjefPmRnl5uWEYhlFaWmp8++235x1z+vTphiQjOTnZ1lZUVGQEBwcbw4cPt+t71113GT4+PkZ2dvZF63/ppZcMSUZ6enqVzreq1qxZY1RUVJzXVtn36tChQ41mzZoZubm5trZ33nnHkGQsW7bM1vbtt9+e9/e9d+9ew8PDw7jrrrtsbdX5/AAAtY/bywEA9cYjjzyif/7zn5owYYKeffbZS/ZPSUnRsmXL9OCDD2rUqFGV9unWrZvuuuuuKr1/y5Yt9f7776usrEwvvvjiJfvfeOONkqT09PRKt5+7lX3VqlXasWOH7fb01atX291aP2vWLLVq1UoeHh7auXOnpLOj49ddd518fHwUGBioW265Rbt27bI7/rnb+ffu3au7775bAQEBatq0qZ5++mkZhqEjR47olltukb+/v8LDw/XKK69c8pxCQkLUqVOn89pvvfVWSbKrYdGiRbJYLHr44YdtbSaTSQ899JCOHj2qlJQUSZK7u7v69OlTpWOuWrVKWVlZdseUpMTERBUWFuqrr766YO3Tpk3TE088IUmKiYmxfd4HDx6UdPbZ+hkzZtg+6+joaE2cOFGlpaUX/UwkqV+/fjKbzee1BQUF2dWfl5en5ORk3X333fL397e133PPPfL19dWn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Tik0NFRr1qxRv379lJubq6ZNm2revHn63e9+J0navXu3OnTooJSUFPXq1avS4yQmJmrXrl365ptvJEnjx4/XV199pdTUVFufO+64Qzk5OVq6dKnjTwwAUOc46prza9dff726dOmiWbNmOfJUAAD1QG1cd87p1KmT/vCHP2jKlCkOOZfqYqS7DsnNzZUkBQUFSTr7mx2LxaIBAwbY+rRv314tWrRQSkrKRY9z7hiSlJKSYncMSRo8ePBFjwEAaNgcdc0BAKAytXXdsVqtys/Pr1PXJldnF4CzrFarxo4dq759+youLk6SlJGRIXd3dwUGBtr1DQsLU0ZGRqXH+e677/S///1PX331la0tIyNDYWFh5x0jLy9PxcXF8vLyqtmTAQDUaY685gAA8Fu1ed15+eWXVVBQoN///vc1Vv+VInTXEYmJiUpNTdX69esv+xipqam65ZZbNHXqVA0aNKgGqwMANCRccwAAtam2rjvz5s3T9OnTtWjRIoWGhl72e9U0bi+vA8aMGaPFixdr1apVat68ua09PDxcZWVlysnJseufmZmp8PBwu7adO3fqpptu0oMPPqjJkyfbbQsPD7ebBfDcMfz9/RnlBoBGxtHXHAAAfq22rjuffPKJHnjgAX366afnPVrrbIRuJzIMQ2PGjNHChQv1zTffKCYmxm77NddcIzc3N61cudLWtmfPHh0+fFi9e/e2te3YsUM33HCD7r33Xj333HPnvU/v3r3tjiFJycnJdscAADRstXXNAQBAqt3rzscff6z77rtPH3/8sYYPH+6YE7oCzF7uRA8//LDmzZunRYsWqV27drb2gIAA2wj0Qw89pCVLluj999+Xv7+//v73v0s6+zyDdPY2ixtvvFGDBw/WSy+9ZDuGi4uLmjZtKunskmFxcXFKTEzUn//8Z33zzTf6xz/+oa+++kqDBw+urdMFADhRbV1zJGnr1q2SpAceeEDt2rXTE088IXd3d3Xs2NHRpwkAqCNq67ozb9483XvvvXr99dftlhvz8vJSQECAw8+zSgw4jaRKv9577z1bn+LiYuPhhx82mjRpYnh7exu33nqrceLECdv2qVOnVnqMli1b2r3XqlWrjC5duhju7u5GbGys3XsAABq+2rzmVKUPAKBhq63rTv/+/Svtc++999beyV4CI90AAAAAADgIz3QDAAAAAOAghG4AAAAAAByE0A0AAAAAgIMQugEAAAAAcBBCNwAAAAAADkLoBgAAAADAQQjdAAAAAAA4CKEbAAAAAAAHIXQDAAAAAOAghG4AABoRwzA0YMAADR48+Lxt//rXvxQYGKijR486oTIAABomQjcAAI2IyWTSe++9p40bN+rf//63rT09PV1PPvmk3nzzTTVv3rxG39NisdTo8QAAqE8I3QAANDJRUVF6/fXX9fjjjys9PV2GYej+++/XoEGD1LVrVw0dOlS+vr4KCwvTn/70J50+fdq279KlS3XttdcqMDBQwcHBuvnmm3XgwAHb9oMHD8pkMul///uf+vfvL09PT3300UfOOE0AAOoEk2EYhrOLAAAAtS8hIUG5ubkaNWqUZsyYoR07dqhTp0564IEHdM8996i4uFjjx49XeXm5vvnmG0nS/PnzZTKZ1LlzZxUUFGjKlCk6ePCgtm7dKrPZrIMHDyomJkbR0dF65ZVX1LVrV3l6eqpZs2ZOPlsAAJyD0A0AQCN18uRJderUSdnZ2Zo/f75SU1O1bt06LVu2zNbn6NGjioqK0p49e9S2bdvzjnH69Gk1bdpU27dvV1xcnC10z5o1S4888khtng4AAHUSt5cDANBIhYaG6q9//as6dOighIQE/fTTT1q1apV8fX1tX+3bt5ck2y3k+/bt05133qnY2Fj5+/srOjpaknT48GG7Y3fr1q1WzwUAgLrK1dkFAAAA53F1dZWr69kfBwoKCjRixAi98MIL5/U7d3v4iBEj1LJlS73zzjuKiIiQ1WpVXFycysrK7Pr7+Pg4vngAAOoBQjcAAJAkXX311Zo/f76io6NtQfzXsrKytGfPHr3zzju67rrrJEnr16+v7TIBAKhXuL0cAABIkhITE5Wdna0777xTP/zwgw4cOKBly5bpvvvuU0VFhZo0aaLg4GC9/fbb2r9/v7755huNGzfO2WUDAFCnEboBAIAkKSIiQt9++60qKio0aNAgxcfHa+zYsQoMDJTZbJbZbNYnn3yizZs3Ky4uTo8++qheeuklZ5cNAECdxuzlAAAAAAA4CCPdAAAAAAA4CKEbAAAAAAAHIXQDAAAAAOAghG4AAAAAAByE0A0AAAAAgIMQugEAAAAAcBBCNwAAAAAADkLoBgAAAADAQQjdAAAAAAA4CKEbAAAAAAAHIXQDAAAAAOAghG4AAAAAABzk/wNSUAV/Mj3WhwAAAABJRU5ErkJggg==", + "image/png": "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3bF48eKIiFi4cGFMmzYtGhsbIyJi7ty5cd9998X5558fVVVV8frrr8edd94Zc+fO7Y8jAACAsZJ3FNXV1cW+ffti+fLl0d7eHjNnzozm5ub+D1/YvXv3gDtDd9xxRxQUFMQdd9wRb731VnziE5+IuXPnxk9+8pPhexUAAABDVJCNg/ewdXV1RVlZWXR2dkZpaelYbwcAABgjI9EGI/7pcwAAAB9loggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASNqQoqipqSmmT58eJSUlUVVVFZs2bfrA+e+8804sWbIkpkyZErlcLs4888zYsGHDkDYMAAAwnCbku2DdunVRX18fq1evjqqqqli1alXU1tbGjh07YvLkyYfN7+npia9+9asxefLkeOKJJ2LatGnx5ptvximnnDIc+wcAADgmBVmWZfksqKqqigsuuCDuv//+iIjo6+uLysrKuPHGG2Pp0qWHzV+9enX8/Oc/j+3bt8cJJ5wwpE12dXVFWVlZdHZ2Rmlp6ZCeAwAAGP9Gog3yevtcT09PbN68OWpqav77BIWFUVNTE62trYOueeaZZ6K6ujqWLFkS5eXlcc4558SKFSuit7f3iNc5ePBgdHV1DXgAAACMhLyiaP/+/dHb2xvl5eUDxsvLy6O9vX3QNTt37ownnngient7Y8OGDXHnnXfGvffeGz/+8Y+PeJ3GxsYoKyvrf1RWVuazTQAAgKM24p8+19fXF5MnT44HH3wwZs2aFXV1dXH77bfH6tWrj7hm2bJl0dnZ2f/Ys2fPSG8TAABIVF4ftDBp0qQoKiqKjo6OAeMdHR1RUVEx6JopU6bECSecEEVFRf1jn/vc56K9vT16enqiuLj4sDW5XC5yuVw+WwMAABiSvO4UFRcXx6xZs6KlpaV/rK+vL1paWqK6unrQNRdddFG8/vrr0dfX1z/22muvxZQpUwYNIgAAgNGU99vn6uvrY82aNfHrX/86tm3bFt/5zneiu7s7Fi9eHBERCxcujGXLlvXP/853vhN///vf46abborXXnst1q9fHytWrIglS5YM36sAAAAYory/p6iuri727dsXy5cvj/b29pg5c2Y0Nzf3f/jC7t27o7Dwv61VWVkZzz33XNxyyy1x3nnnxbRp0+Kmm26KW2+9dfheBQAAwBDl/T1FY8H3FAEAABEfge8pAgAAON6IIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaUOKoqamppg+fXqUlJREVVVVbNq06ajWrV27NgoKCmLevHlDuSwAAMCwyzuK1q1bF/X19dHQ0BBbtmyJGTNmRG1tbezdu/cD1+3atSu+973vxcUXXzzkzQIAAAy3vKPovvvui29961uxePHi+PznPx+rV6+Ok046KR555JEjrunt7Y2rr7467rrrrjj99NOPacMAAADDKa8o6unpic2bN0dNTc1/n6CwMGpqaqK1tfWI6370ox/F5MmT45prrjmq6xw8eDC6uroGPAAAAEZCXlG0f//+6O3tjfLy8gHj5eXl0d7ePuiaP/zhD/Hwww/HmjVrjvo6jY2NUVZW1v+orKzMZ5sAAABHbUQ/fe7AgQOxYMGCWLNmTUyaNOmo1y1btiw6Ozv7H3v27BnBXQIAACmbkM/kSZMmRVFRUXR0dAwY7+joiIqKisPm//Wvf41du3bF3Llz+8f6+vr+c+EJE2LHjh1xxhlnHLYul8tFLpfLZ2sAAABDktedouLi4pg1a1a0tLT0j/X19UVLS0tUV1cfNv/ss8+OV155Jdra2vofV1xxRVx66aXR1tbmbXEAAMCYy+tOUUREfX19LFq0KGbPnh1z5syJVatWRXd3dyxevDgiIhYuXBjTpk2LxsbGKCkpiXPOOWfA+lNOOSUi4rBxAACAsZB3FNXV1cW+ffti+fLl0d7eHjNnzozm5ub+D1/YvXt3FBaO6K8qAQAADJuCLMuysd7Eh+nq6oqysrLo7OyM0tLSsd4OAAAwRkaiDdzSAQAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASJooAgAAkiaKAACApIkiAAAgaaIIAABImigCAACSJooAAICkDSmKmpqaYvr06VFSUhJVVVWxadOmI85ds2ZNXHzxxTFx4sSYOHFi1NTUfOB8AACA0ZR3FK1bty7q6+ujoaEhtmzZEjNmzIja2trYu3fvoPM3btwY8+fPjxdffDFaW1ujsrIyLrvssnjrrbeOefMAAADHqiDLsiyfBVVVVXHBBRfE/fff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8ZnPfCYeffTRvK/7kYiidevWRX19fTQ0NMSWLVtixowZUVtbG3v37h10/ssvvxzz58+Pa665JrZu3Rrz5s2LefPmxauvvjrKO2e8yfesbdy4MebPnx8vvvhitLa2RmVlZVx22WXx1ltvjfLOGW/yPWvv27VrV3zve9+Liy++eJR2yniX71nr6emJr371q7Fr16544oknYseOHbFmzZqYNm3aKO+c8Sbfs/bYY4/F0qVLo6GhIbZt2xYPP/xwrFu3Lm677bZR3jnjSXd3d8yYMSOampqOav4bb7wRl19+eVx66aXR1tYWN998c1x77bXx3HPP5Xfh7CNgzpw52ZIlS/p/7u3tzaZOnZo1NjYOOv/KK6/MLr/88gFjVVVV2be//e0R3SfjX75n7X8dOnQoO/nkk7Nf//rXI7VFjhNDOWuHDh3KLrzwwuyhhx7KFi1alH39618fhZ0y3uV71n75y19mp59+etbT0zNaW+Q4ke9ZW7JkSfaVr3xlwFh9fX120UUXjeg+OX5ERPbUU0994Jwf/OAH2Re+8IUBY3V1dVltbW1e1xrzO0U9PT2xefPmqKmp6R8rLCyMmpqaaG1tHXRNa2vrgPkREbW1tUecDxFDO2v/691334333nsvTj311JHaJseBoZ61H/3oRzF58uS45pprRmObHAeGctaeeeaZqK6ujiVLlkR5eXmcc845sWLFiujt7R2tbTMODeWsXXjhhbF58+b+t9jt3LkzNmzYEF/72tdGZc+kYbi6YMJwbmoo9u/fH729vVFeXj5gvLy8PLZv3z7omvb29kHnt7e3j9g+Gf+Gctb+16233hpTp0497B8++P+Gctb+8Ic/xMMPPxxtbW2jsEOOF0M5azt37ozf//73cfXVV8eGDRvi9ddfjxtuuCHee++9aGhoGI1tMw4N5axdddVVsX///vjyl78cWZbFoUOH4vrrr/f2OYbVkbqgq6sr/vWvf8WJJ554VM8z5neKYLxYuXJlrF27Np566qkoKSkZ6+1wHDlw4EAsWLAg1qxZE5MmTRrr7XCc6+vri8mTJ8eDDz4Ys2bNirq6urj99ttj9erVY701jjMbN26MFStWxAMPPBBbtmyJJ598MtavXx933333WG8NDjPmd4omTZoURUVF0dHRMWC8o6MjKioqBl1TUVGR13yIGNpZe98999wTK1eujBdeeCHOO++8kdwmx4F8z9pf//rX2LVrV8ydO7d/rK+vLyIiJkyYEDt27IgzzjhjZDfNuDSUP9emTJkSJ5xwQhQVFfWPfe5zn4v29vbo6emJ4uLiEd0z49NQztqdd94ZCxYsiGuvvTYiIs4999zo7u6O6667Lm6//fYoLPT/5jl2R+qC0tLSo75LFPERuFNUXFwcs2bNipaWlv6xvr6+aGlpierq6kHXVFdXD5gfEfH8888fcT5EDO2sRUT87Gc/i7vvvjuam5tj9uzZo7FVxrl8z9rZZ58dr7zySrS1tfU/rrjiiv5P0qmsrBzN7TOODOXPtYsuuihef/31/vCOiHjttddiypQpgogjGspZe/fddw8Ln/dj/D+/Qw/Hbti6IL/PgBgZa9euzXK5XPboo49mf/nLX7LrrrsuO+WUU7L29vYsy7JswYIF2dKlS/vn//GPf8wmTJiQ3XPPPdm2bduyhoaG7IQTTsheeeWVsXoJjBP5nrWVK1dmxcXF2RNPPJG9/fbb/Y8DBw6M1UtgnMj3rP0vnz7H0cr3rO3evTs7+eSTs+9+97vZjh07smeffTabPHly9uMf/3isXgLjRL5nraGhITv55JOz3/zmN9nOnTuz3/3ud9kZZ5yRXXnllWP1EhgHDhw4kG3dujXbunVrFhHZfffdl23dujV78803syzLsqVLl2YLFizon79z587spJNOyr7//e9n27Zty5qamrKioqKsubk5r+t+JKIoy7LsF7/4RXbaaadlxcXF2Zw5c7I//elP/X/tkksuyRYtWjRg/uOPP56deeaZWXFxcfaFL3whW79+/SjvmPEqn7P2qU99KouIwx4NDQ2jv3HGnXz/XPv/RBH5yPesvfzyy1lVVVWWy+Wy008/PfvJT36SHTp0aJR3zXiUz1l77733sh/+8IfZGWeckZWUlGSVlZXZDTfckP3jH/8Y/Y0zbrz44ouD/rfX+2dr0aJF2SWXXHLYmpkzZ2bFxcXZ6aefnv3qV7/K+7oFWeb+JQAAkK4x/50iAACAsSSKAACApIkiAAAgaaIIAABImigCAACSJooAAICkiSIAACBpoggAAEiaKAIAAJImigAAgKSJIgAAIGmiCAAASNr/AUOP/hLIsQ49AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", "text/plain": [ "
" ] @@ -457,19 +470,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'call_tool': {'messages': [FunctionMessage(content=\"python_repl response: Succesfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# Define the data\\nyears = [2020, 2021, 2022]\\ngdp_values = [1.99, 2.14, 2.2] # Trillion GBP\\n\\n# Create the line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(years, gdp_values, marker='o')\\n\\n# Labeling the graph\\nplt.title('UK GDP from 2020 to 2022')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Trillion GBP)')\\nplt.grid(True)\\nplt.xticks(years)\\nplt.tight_layout()\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: \", name='python_repl')]}}\n", + "{'chart_generator': {'messages': [AIMessage(content=\"Unfortunately, I made an error in the provided code. There was a mismatch in the dimensions of the 'Year' and 'GDP (Billion USD)' data arrays, which caused a ValueError. Additionally, I mistakenly included placeholders for the years 2022 and 2023 without having the actual GDP data for those years.\\n\\nLet's correct this and generate a line graph with the data we have up to 2021. I will revise the code to only include the years for which we have data, and then we can create the graph accordingly.\", additional_kwargs={'tool_calls': [{'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX', 'function': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\\\n# No data for 2022 and 2023 are available\\\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\\\ngdp_data = {\\\\n \\'Year\\': [2018, 2019, 2020, 2021],\\\\n \\'GDP (Billion USD)\\': [2851.41, 2851.41, 2697.81, 3141.51]\\\\n}\\\\n\\\\n# Plot a line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(gdp_data[\\'Year\\'], gdp_data[\\'GDP (Billion USD)\\'], marker=\\'o\\')\\\\n\\\\n# Title and labels\\\\nplt.title(\\'UK GDP from 2018 to 2021\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Billion USD)\\')\\\\n\\\\n# Show grid\\\\nplt.grid(True)\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 359, 'prompt_tokens': 12796, 'total_tokens': 13155}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None}, name='chart_generator', id='run-0d4a67d2-696a-4955-990b-9a9d775b7635-0', tool_calls=[{'name': 'python_repl', 'args': {'code': \"import matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\"}, 'id': 'call_1zGQMGouC0oFQJRUkNPvs9zX'}])], 'sender': 'chart_generator'}}\n", "----\n", - "{'Chart Generator': {'messages': [HumanMessage(content=\"Here is the line graph representing the UK's GDP from 2020 to 2022:\\n\\n[Please see the graph above]\\n\\nNote that the data for 2018 and 2019 is missing, and the graph only shows the years for which we have data. If you acquire the missing figures, you can manually add them to the graph to complete the five-year view.\", name='Chart Generator')], 'sender': 'Chart Generator'}}\n", + "{'call_tool': {'messages': [ToolMessage(content=\"Succesfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# UK GDP data obtained from search results for 2018-2021 (in billion USD)\\n# No data for 2022 and 2023 are available\\n# Note: 2021 data is used as a placeholder and should be updated when actual figures are available\\ngdp_data = {\\n 'Year': [2018, 2019, 2020, 2021],\\n 'GDP (Billion USD)': [2851.41, 2851.41, 2697.81, 3141.51]\\n}\\n\\n# Plot a line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(gdp_data['Year'], gdp_data['GDP (Billion USD)'], marker='o')\\n\\n# Title and labels\\nplt.title('UK GDP from 2018 to 2021')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Billion USD)')\\n\\n# Show grid\\nplt.grid(True)\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: \\n\\nIf you have completed all tasks, respond with FINAL ANSWER.\", name='python_repl', tool_call_id='call_1zGQMGouC0oFQJRUkNPvs9zX')]}}\n", "----\n", - "{'Researcher': {'messages': [HumanMessage(content=\"FINAL ANSWER:\\n\\nHere is the line graph representing the UK's GDP from 2020 to 2022:\\n\\n[Please see the graph above]\\n\\nNote that the data for 2018 and 2019 is missing, and the graph only shows the years for which we have data. If you acquire the missing figures, you can manually add them to the graph to complete the five-year view.\", name='Researcher')], 'sender': 'Researcher'}}\n", - "----\n", - "{'__end__': {'messages': [HumanMessage(content=\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it. Once you code it up, finish.\"), HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"UK GDP data for the past 5 years\"}', 'name': 'tavily_search_results_json'}}, name='Researcher'), FunctionMessage(content=\"tavily_search_results_json response: [{'url': 'https://www.statista.com/topics/3795/gdp-of-the-uk/', 'content': 'Monthly GDP of the UK 2019-2023 Monthly GDP growth of the UK 2019-2023 Quarterly GDP growth of the UK 2019-2023 Quarterly GDP per capita in the UK 2019-2023Monthly index of gross domestic product in the United Kingdom from January 2019 to November 2023 (2019=100) GVA of the UK 2022, by sector GVA of the UK 2022, by sector Gross value added...'}, {'url': 'https://www.statista.com/topics/6500/the-british-economy/', 'content': 'Monthly GDP growth of the UK 2020-2023 Quarterly GDP growth of the UK 2015-2023 Monthly growth of gross domestic product in the United Kingdom from January 2020 to September 2023 Monthly GDP of the UK 1997-2023Economy The UK economy - Statistics & Facts United Kingdom The gross domestic product of the British economy was 2.23 trillion British pounds in 2022 and was the fifth- largest global...'}, {'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/november2023', 'content': 'data from January 2022 to September 2023, as published in our\\\\xa0GDP quarterly national accounts, UK: July to September Figure 1: UK GDP is estimated to have grown by 0.3% in November 2023 GDP monthly estimate, UK: November 2023 Source: Monthly GDP estimate from Office for National Statistics The main reasons for revisions in October 2023 are:Monthly GDP is estimated to have grown by 0.3% in November 2023, following an unrevised fall of 0.3% in October 2023. This release provides data for November 2023 and has revisions from January 2022 to September 2023, consistent with our Quarterly national accounts bulletin, published on 22 December 2023. October 2023 is also open for revision.'}, {'url': 'https://www.statista.com/statistics/375195/gdp-growth-forecast-uk/', 'content': 'Forecasted annual growth of gross domestic product in the United Kingdom from 2000 to 2028 Additional Information GDP growth forecast for the UK 2000-2028 Weak economic growth throughout 2023 United Kingdom 2000 to 2028 *Forecast data Other statistics on the topicThe UK economy Economy Economy Average annual earnings for full-time employees in the UK 1999-2023 Economy Inflation rate in the UK 1989-2023In 2022 the gross domestic product (GDP) of the United Kingdom grew by four percent but is expected to grow by just 0.6 percent in 2023, and 0.7 percent in 2024. More robust growth is...'}, {'url': 'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/october2023', 'content': 'GDP monthly estimate, UK: October 2023 Figure 1: UK GDP is estimated to have fallen by 0.3% in October 2023 Monthly estimate of gross domestic product (GDP) containing constant price gross value added (GVA) data for the UK. domestic product (GDP) in October 2023.1. Main points Monthly real gross domestic product (GDP) is estimated to have shown no growth in the three months to October 2023, compared with the three months to July 2023. Monthly GDP...'}]\", name='tavily_search_results_json'), HumanMessage(content=\"The search results provide information on the UK's GDP, including monthly and quarterly data from various years, but they do not provide a clear year-on-year GDP figure for each of the past five years. To graph the UK's GDP over the past five years, we need annual GDP figures for each year.\\n\\nI will attempt to extract the annual GDP figures mentioned in the search results and then graph them. However, please note that the information provided in the search results may be incomplete or not formatted in a way that allows for a straightforward extraction of annual GDP figures for each of the last five years. If necessary, I may need to perform additional searches to fill in any gaps.\\n\\nFrom the search results, we have the following relevant information regarding the UK's GDP:\\n\\n- The gross domestic product of the British economy was 2.23 trillion British pounds in 2022.\\n- In 2022, the gross domestic product (GDP) of the United Kingdom grew by four percent.\\n\\nUnfortunately, the search results do not provide explicit annual GDP figures for 2018, 2019, 2020, or 2021. Therefore, I will need to perform an additional search to find the missing GDP figures for these years. Let's proceed with that search.\", additional_kwargs={'function_call': {'arguments': '{\"query\":\"UK GDP 2018 2019 2020 2021\"}', 'name': 'tavily_search_results_json'}}, name='Researcher'), FunctionMessage(content='tavily_search_results_json response: [{\\'url\\': \\'https://www.statista.com/topics/3795/gdp-of-the-uk/\\', \\'content\\': \"GDP of the UK 2021, by country Gross domestic product of the United Kingdom in 2021, by country (in million GBP) Monthly GDP of the UK 2019-2023 United Kingdom\\'s share of global gross domestic product (GDP) 2028 Quarterly GDP per capita in the UK 2019-2023In 2022, the gross domestic product of the United Kingdom amounted to approximately 2.2 trillion British pounds, compared with 2.14 trillion pounds in 2021, and 1.99 trillion in 2020. Although the ...\"}, {\\'url\\': \\'https://www.beta.ons.gov.uk/economy/grossdomesticproductgdp/compendium/unitedkingdomnationalaccountsthebluebook/2022/nationalaccountsataglance\\', \\'content\\': \"of UK GDP over 2020 and 2021 Figure 1: The UK economy increased by 7.5% in 2021, having seen the largest fall in over 300 years in 2020 accounts at a glance, UK National Accounts, The Blue Book: 2022 Figure 3: In 2021, nominal GDP in most of the G20 countries had recovered to its 2019 levelsIn 2021, the UK\\'s implied GDP deflator rose by 0.4%, the lowest among the developed countries shown (Figure 4). However, these comparisons are likely to reflect some of the measurement challenges over the coronavirus pandemic. Movements in the implied GDP deflator in 2020 and 2021 have been largely affected by the government consumption deflator.\"}, {\\'url\\': \\'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/december2021\\', \\'content\\': \\'GDP monthly estimate, UK : December 2021 gross domestic product (GDP), UK. data from January 2021 to November 2021. This is consistent with GDP first quarterly estimate, UK: October to December on gross domestic product (GDP) in December 2021, with output of these services increasing by 51% and 19% respectively2. Monthly GDP. Monthly real gross domestic product (GDP) is estimated to have fallen by 0.2% in December 2021, compared with a 0.7% growth in November 2021 (revised down from 0.9% growth). This release includes revisions to the monthly data back to January 2021, consistent with the first quarterly estimate of GDP.\\'}, {\\'url\\': \\'https://www.beta.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/september2021\\', \\'content\\': \\'GDP monthly estimate, UK: September 2021 2021, while detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021. More detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021. while detail on the quarterly path can also be found in GDP first quarterly estimate, UK: July to September 2021.Source: Office for National Statistics - GDP monthly estimate. Download this chart. Image .csv .xls. Monthly real gross domestic product (GDP) grew by 0.6% in September 2021, and follows a revised 0.2% growth in August 2021 (down from 0.4% growth) and a revised 0.2% fall in July 2021 (down from a 0.1% fall).\\'}, {\\'url\\': \\'https://www.ons.gov.uk/economy/grossdomesticproductgdp/bulletins/gdpmonthlyestimateuk/october2021\\', \\'content\\': \\'GDP monthly estimate, UK : October 2021 Figure 1: UK GDP is estimated to have grown by 0.1% in October 2021, but remains 0.5% below its pre-pandemic level Monthly estimate of gross domestic product (GDP) containing constant price gross value added (GVA) data for the UK. Source: Office for National Statistics – GDP monthly estimate NotesTable 1: UK GDP in October 2021 was 0.5% below its pre-pandemic level, however services has now returned to its pre-pandemic levelChange in output, percentage change, February 2020 to October 2021, UK. Source: Office for National Statistics - GDP monthly estimate. This table uses data from the output measure of GDP.\\'}]', name='tavily_search_results_json'), HumanMessage(content=\"Based on the search results, we can now extract some annual GDP figures for the UK:\\n\\n- In 2022, the gross domestic product of the United Kingdom amounted to approximately 2.2 trillion British pounds.\\n- In 2021, the gross domestic product of the United Kingdom was about 2.14 trillion British pounds.\\n- In 2020, the gross domestic product of the United Kingdom was about 1.99 trillion British pounds.\\n\\nUnfortunately, the search results still do not provide explicit figures for 2018 and 2019. To complete the dataset, we would need to perform another search or access historical economic data from a reliable source such as the Office for National Statistics (ONS) or an economic database. If additional searches are not possible, we may need to use these three data points to give a partial visualization of the trend. \\n\\nSince we cannot perform additional searches, I will provide the data points we have, and you will need to add the missing years' data manually if required:\\n\\n- 2020: 1.99 trillion GBP\\n- 2021: 2.14 trillion GBP\\n- 2022: 2.2 trillion GBP\\n\\nPlease note that the data for 2018 and 2019 is missing, and you would need to find these figures to complete the five-year view. With the data at hand, the line graph would only represent the years 2020 to 2022.\", name='Researcher'), HumanMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"code\":\"import matplotlib.pyplot as plt\\\\n\\\\n# Define the data\\\\nyears = [2020, 2021, 2022]\\\\ngdp_values = [1.99, 2.14, 2.2] # Trillion GBP\\\\n\\\\n# Create the line graph\\\\nplt.figure(figsize=(10, 5))\\\\nplt.plot(years, gdp_values, marker=\\'o\\')\\\\n\\\\n# Labeling the graph\\\\nplt.title(\\'UK GDP from 2020 to 2022\\')\\\\nplt.xlabel(\\'Year\\')\\\\nplt.ylabel(\\'GDP (Trillion GBP)\\')\\\\nplt.grid(True)\\\\nplt.xticks(years)\\\\nplt.tight_layout()\\\\n\\\\n# Display the graph\\\\nplt.show()\"}', 'name': 'python_repl'}}, name='Chart Generator'), FunctionMessage(content=\"python_repl response: Succesfully executed:\\n```python\\nimport matplotlib.pyplot as plt\\n\\n# Define the data\\nyears = [2020, 2021, 2022]\\ngdp_values = [1.99, 2.14, 2.2] # Trillion GBP\\n\\n# Create the line graph\\nplt.figure(figsize=(10, 5))\\nplt.plot(years, gdp_values, marker='o')\\n\\n# Labeling the graph\\nplt.title('UK GDP from 2020 to 2022')\\nplt.xlabel('Year')\\nplt.ylabel('GDP (Trillion GBP)')\\nplt.grid(True)\\nplt.xticks(years)\\nplt.tight_layout()\\n\\n# Display the graph\\nplt.show()\\n```\\nStdout: \", name='python_repl'), HumanMessage(content=\"Here is the line graph representing the UK's GDP from 2020 to 2022:\\n\\n[Please see the graph above]\\n\\nNote that the data for 2018 and 2019 is missing, and the graph only shows the years for which we have data. If you acquire the missing figures, you can manually add them to the graph to complete the five-year view.\", name='Chart Generator'), HumanMessage(content=\"FINAL ANSWER:\\n\\nHere is the line graph representing the UK's GDP from 2020 to 2022:\\n\\n[Please see the graph above]\\n\\nNote that the data for 2018 and 2019 is missing, and the graph only shows the years for which we have data. If you acquire the missing figures, you can manually add them to the graph to complete the five-year view.\", name='Researcher')], 'sender': 'Researcher'}}\n", + "{'chart_generator': {'messages': [AIMessage(content=\"FINAL ANSWER\\n\\nI have generated a line graph for the UK's GDP from 2018 to 2021 using the available data. Unfortunately, due to the lack of data for 2022 and 2023, the graph only includes figures up to 2021. Here is the graph:\\n\\n[Graph Image]\\n\\nPlease note that the data for 2022 and 2023 should be added to this graph once it becomes available to complete the analysis for the past five years.\", response_metadata={'token_usage': {'completion_tokens': 99, 'prompt_tokens': 13412, 'total_tokens': 13511}, 'model_name': 'gpt-4-1106-preview', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, name='chart_generator', id='run-3474a61c-0773-4e44-bd6e-2e88cf56bb90-0')], 'sender': 'chart_generator'}}\n", "----\n" ] } ], "source": [ - "for s in graph.stream(\n", + "events = graph.stream(\n", " {\n", " \"messages\": [\n", " HumanMessage(\n", @@ -481,7 +492,8 @@ " },\n", " # Maximum number of steps to take in the graph\n", " {\"recursion_limit\": 150},\n", - "):\n", + ")\n", + "for s in events:\n", " print(s)\n", " print(\"----\")" ] @@ -511,7 +523,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/persistence_postgres.ipynb b/examples/persistence_postgres.ipynb index 12f88307e..54f365ea4 100644 --- a/examples/persistence_postgres.ipynb +++ b/examples/persistence_postgres.ipynb @@ -352,9 +352,7 @@ "outputs": [], "source": [ "from psycopg_pool import ConnectionPool\n", - "from langchain_postgres import (\n", - " PostgresSaver, PickleCheckpointSerializer\n", - ")\n", + "from langchain_postgres import PostgresSaver, PickleCheckpointSerializer\n", "\n", "pool = ConnectionPool(\n", " # Example configuration\n", diff --git a/examples/rag/langgraph_adaptive_rag.ipynb b/examples/rag/langgraph_adaptive_rag.ipynb index 55cb61f4c..3fc4baa5c 100644 --- a/examples/rag/langgraph_adaptive_rag.ipynb +++ b/examples/rag/langgraph_adaptive_rag.ipynb @@ -109,6 +109,7 @@ "from langchain_community.document_loaders import WebBaseLoader\n", "from langchain_community.vectorstores import Chroma\n", "from langchain_openai import OpenAIEmbeddings\n", + "\n", "### from langchain_cohere import CohereEmbeddings\n", "\n", "# Set embeddings\n", @@ -172,6 +173,7 @@ "from langchain_core.pydantic_v1 import BaseModel, Field\n", "from langchain_openai import ChatOpenAI\n", "\n", + "\n", "# Data model\n", "class RouteQuery(BaseModel):\n", " \"\"\"Route a user query to the most relevant datasource.\"\"\"\n", @@ -181,11 +183,12 @@ " description=\"Given a user question choose to route it to web search or a vectorstore.\",\n", " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_router = llm.with_structured_output(RouteQuery)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are an expert at routing a user question to a vectorstore or web search.\n", "The vectorstore contains documents related to agents, prompt engineering, and adversarial attacks.\n", "Use the vectorstore for questions on these topics. Otherwise, use web-search.\"\"\"\n", @@ -197,7 +200,11 @@ ")\n", "\n", "question_router = route_prompt | structured_llm_router\n", - "print(question_router.invoke({\"question\": \"Who will the Bears draft first in the NFL draft?\"}))\n", + "print(\n", + " question_router.invoke(\n", + " {\"question\": \"Who will the Bears draft first in the NFL draft?\"}\n", + " )\n", + ")\n", "print(question_router.invoke({\"question\": \"What are the types of agent memory?\"}))" ] }, @@ -216,19 +223,23 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", + "\n", "\n", "# Data model\n", "class GradeDocuments(BaseModel):\n", " \"\"\"Binary score for relevance check on retrieved documents.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Documents are relevant to the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Documents are relevant to the question, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeDocuments)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n", " If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \\n\n", " It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \\n\n", @@ -273,10 +284,12 @@ "# LLM\n", "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -303,19 +316,23 @@ } ], "source": [ - "### Hallucination Grader \n", + "### Hallucination Grader\n", + "\n", "\n", "# Data model\n", "class GradeHallucinations(BaseModel):\n", " \"\"\"Binary score for hallucination present in generation answer.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer is grounded in the facts, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer is grounded in the facts, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeHallucinations)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. \\n \n", " Give a binary score 'yes' or 'no'. 'Yes' means that the answer is grounded in / supported by the set of facts.\"\"\"\n", "hallucination_prompt = ChatPromptTemplate.from_messages(\n", @@ -347,19 +364,23 @@ } ], "source": [ - "### Answer Grader \n", + "### Answer Grader\n", + "\n", "\n", "# Data model\n", "class GradeAnswer(BaseModel):\n", " \"\"\"Binary score to assess answer addresses question.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer addresses the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer addresses the question, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeAnswer)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing whether an answer addresses / resolves a question \\n \n", " Give a binary score 'yes' or 'no'. Yes' means that the answer resolves the question.\"\"\"\n", "answer_prompt = ChatPromptTemplate.from_messages(\n", @@ -370,7 +391,7 @@ ")\n", "\n", "answer_grader = answer_prompt | structured_llm_grader\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -393,16 +414,19 @@ "source": [ "### Question Re-writer\n", "\n", - "# LLM \n", + "# LLM\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for vectorstore retrieval. Look at the input and try to reason about the underlying sematic intent / meaning.\"\"\"\n", "re_write_prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", system),\n", - " (\"human\", \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\"),\n", + " (\n", + " \"human\",\n", + " \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\",\n", + " ),\n", " ]\n", ")\n", "\n", @@ -428,6 +452,7 @@ "### Search\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults(k=3)" ] }, @@ -453,6 +478,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -460,11 +486,12 @@ " Attributes:\n", " question: question\n", " generation: LLM generation\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " documents: List[str]" ] }, { @@ -484,6 +511,7 @@ "source": [ "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -501,6 +529,7 @@ " documents = retriever.invoke(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -514,11 +543,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -533,11 +563,13 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", @@ -547,6 +579,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -566,6 +599,7 @@ " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", @@ -587,8 +621,10 @@ "\n", " return {\"documents\": web_results, \"question\": question}\n", "\n", + "\n", "### Edges ###\n", "\n", + "\n", "def route_question(state):\n", " \"\"\"\n", " Route question to web search or RAG.\n", @@ -602,14 +638,15 @@ "\n", " print(\"---ROUTE QUESTION---\")\n", " question = state[\"question\"]\n", - " source = question_router.invoke({\"question\": question}) \n", - " if source.datasource == 'web_search':\n", + " source = question_router.invoke({\"question\": question})\n", + " if source.datasource == \"web_search\":\n", " print(\"---ROUTE QUESTION TO WEB SEARCH---\")\n", " return \"web_search\"\n", - " elif source.datasource == 'vectorstore':\n", + " elif source.datasource == \"vectorstore\":\n", " print(\"---ROUTE QUESTION TO RAG---\")\n", " return \"vectorstore\"\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -628,13 +665,16 @@ " if not filtered_documents:\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -651,7 +691,9 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", " grade = score.binary_score\n", "\n", " # Check hallucination\n", @@ -659,7 +701,7 @@ " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", @@ -692,11 +734,11 @@ "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"web_search\", web_search) # web search\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # generatae\n", - "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", + "workflow.add_node(\"web_search\", web_search) # web search\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # generatae\n", + "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", "\n", "# Build graph\n", "workflow.set_conditional_entry_point(\n", @@ -764,8 +806,10 @@ "source": [ "from pprint import pprint\n", "\n", - "# Run \n", - "inputs = {\"question\": \"What player at the Bears expected to draft first in the 2024 NFL draft?\"}\n", + "# Run\n", + "inputs = {\n", + " \"question\": \"What player at the Bears expected to draft first in the 2024 NFL draft?\"\n", + "}\n", "for output in app.stream(inputs):\n", " for key, value in output.items():\n", " # Node\n", @@ -839,7 +883,7 @@ " pprint(\"\\n---\\n\")\n", "\n", "# Final generation\n", - "pprint(value [\"generation\"])" + "pprint(value[\"generation\"])" ] }, { diff --git a/examples/rag/langgraph_adaptive_rag_cohere.ipynb b/examples/rag/langgraph_adaptive_rag_cohere.ipynb index 4d0ee4394..241db3f6a 100644 --- a/examples/rag/langgraph_adaptive_rag_cohere.ipynb +++ b/examples/rag/langgraph_adaptive_rag_cohere.ipynb @@ -193,19 +193,24 @@ "from langchain_core.pydantic_v1 import BaseModel, Field\n", "from langchain_cohere import ChatCohere\n", "\n", + "\n", "# Data model\n", "class web_search(BaseModel):\n", " \"\"\"\n", " The internet. Use web_search for questions that are related to anything else than agents, prompt engineering, and adversarial attacks.\n", " \"\"\"\n", + "\n", " query: str = Field(description=\"The query to use when searching the internet.\")\n", "\n", + "\n", "class vectorstore(BaseModel):\n", " \"\"\"\n", " A vectorstore containing documents related to agents, prompt engineering, and adversarial attacks. Use the vectorstore for questions on these topics.\n", " \"\"\"\n", + "\n", " query: str = Field(description=\"The query to use when searching the vectorstore.\")\n", "\n", + "\n", "# Preamble\n", "preamble = \"\"\"You are an expert at routing a user question to a vectorstore or web search.\n", "The vectorstore contains documents related to agents, prompt engineering, and adversarial attacks.\n", @@ -213,7 +218,9 @@ "\n", "# LLM with tool use and preamble\n", "llm = ChatCohere(model=\"command-r\", temperature=0)\n", - "structured_llm_router = llm.bind_tools(tools=[web_search, vectorstore], preamble=preamble)\n", + "structured_llm_router = llm.bind_tools(\n", + " tools=[web_search, vectorstore], preamble=preamble\n", + ")\n", "\n", "# Prompt\n", "route_prompt = ChatPromptTemplate.from_messages(\n", @@ -223,12 +230,14 @@ ")\n", "\n", "question_router = route_prompt | structured_llm_router\n", - "response = question_router.invoke({\"question\": \"Who will the Bears draft first in the NFL draft?\"})\n", - "print(response.response_metadata['tool_calls'])\n", + "response = question_router.invoke(\n", + " {\"question\": \"Who will the Bears draft first in the NFL draft?\"}\n", + ")\n", + "print(response.response_metadata[\"tool_calls\"])\n", "response = question_router.invoke({\"question\": \"What are the types of agent memory?\"})\n", - "print(response.response_metadata['tool_calls'])\n", + "print(response.response_metadata[\"tool_calls\"])\n", "response = question_router.invoke({\"question\": \"Hi how are you?\"})\n", - "print('tool_calls' in response.response_metadata)" + "print(\"tool_calls\" in response.response_metadata)" ] }, { @@ -254,11 +263,15 @@ "source": [ "### Retrieval Grader\n", "\n", + "\n", "# Data model\n", "class GradeDocuments(BaseModel):\n", " \"\"\"Binary score for relevance check on retrieved documents.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Documents are relevant to the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Documents are relevant to the question, 'yes' or 'no'\"\n", + " )\n", + "\n", "\n", "# Prompt\n", "preamble = \"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n\n", @@ -279,7 +292,7 @@ "question = \"types of agent memory\"\n", "docs = retriever.invoke(question)\n", "doc_txt = docs[1].page_content\n", - "response = retrieval_grader.invoke({\"question\": question, \"document\": doc_txt})\n", + "response = retrieval_grader.invoke({\"question\": question, \"document\": doc_txt})\n", "print(response)" ] }, @@ -377,11 +390,7 @@ "\n", "# Prompt\n", "prompt = lambda x: ChatPromptTemplate.from_messages(\n", - " [\n", - " HumanMessage(\n", - " f\"Question: {x['question']} \\nAnswer: \"\n", - " )\n", - " ]\n", + " [HumanMessage(f\"Question: {x['question']} \\nAnswer: \")]\n", ")\n", "\n", "# Chain\n", @@ -419,11 +428,15 @@ "source": [ "### Hallucination Grader\n", "\n", + "\n", "# Data model\n", "class GradeHallucinations(BaseModel):\n", " \"\"\"Binary score for hallucination present in generation answer.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer is grounded in the facts, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer is grounded in the facts, 'yes' or 'no'\"\n", + " )\n", + "\n", "\n", "# Preamble\n", "preamble = \"\"\"You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. \\n\n", @@ -431,7 +444,9 @@ "\n", "# LLM with function call\n", "llm = ChatCohere(model=\"command-r\", temperature=0)\n", - "structured_llm_grader = llm.with_structured_output(GradeHallucinations, preamble=preamble)\n", + "structured_llm_grader = llm.with_structured_output(\n", + " GradeHallucinations, preamble=preamble\n", + ")\n", "\n", "# Prompt\n", "hallucination_prompt = ChatPromptTemplate.from_messages(\n", @@ -471,11 +486,15 @@ "source": [ "### Answer Grader\n", "\n", + "\n", "# Data model\n", "class GradeAnswer(BaseModel):\n", " \"\"\"Binary score to assess answer addresses question.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer addresses the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer addresses the question, 'yes' or 'no'\"\n", + " )\n", + "\n", "\n", "# Preamble\n", "preamble = \"\"\"You are a grader assessing whether an answer addresses / resolves a question \\n\n", @@ -493,7 +512,7 @@ ")\n", "\n", "answer_grader = answer_prompt | structured_llm_grader\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -519,6 +538,7 @@ "# os.environ['TAVILY_API_KEY'] = \n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults()" ] }, @@ -548,6 +568,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"|\n", " Represents the state of our graph.\n", @@ -557,9 +578,10 @@ " generation: LLM generation\n", " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " documents: List[str]" ] }, { @@ -583,6 +605,7 @@ "source": [ "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -600,6 +623,7 @@ " documents = retriever.invoke(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def llm_fallback(state):\n", " \"\"\"\n", " Generate answer using the LLM w/o vectorstore\n", @@ -615,6 +639,7 @@ " generation = llm_chain.invoke({\"question\": question})\n", " return {\"question\": question, \"generation\": generation}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer using the vectorstore\n", @@ -629,12 +654,13 @@ " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", " if not isinstance(documents, list):\n", - " documents = [documents]\n", + " documents = [documents]\n", "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"documents\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -653,7 +679,9 @@ " # Score each doc\n", " filtered_docs = []\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", @@ -663,6 +691,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question}\n", "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", @@ -684,8 +713,10 @@ "\n", " return {\"documents\": web_results, \"question\": question}\n", "\n", + "\n", "### Edges ###\n", "\n", + "\n", "def route_question(state):\n", " \"\"\"\n", " Route question to web search or RAG.\n", @@ -700,26 +731,27 @@ " print(\"---ROUTE QUESTION---\")\n", " question = state[\"question\"]\n", " source = question_router.invoke({\"question\": question})\n", - " \n", + "\n", " # Fallback to LLM or raise error if no decision\n", " if \"tool_calls\" not in source.additional_kwargs:\n", " print(\"---ROUTE QUESTION TO LLM---\")\n", - " return \"llm_fallback\" \n", + " return \"llm_fallback\"\n", " if len(source.additional_kwargs[\"tool_calls\"]) == 0:\n", - " raise \"Router could not decide source\"\n", + " raise \"Router could not decide source\"\n", "\n", " # Choose datasource\n", " datasource = source.additional_kwargs[\"tool_calls\"][0][\"function\"][\"name\"]\n", - " if datasource == 'web_search':\n", + " if datasource == \"web_search\":\n", " print(\"---ROUTE QUESTION TO WEB SEARCH---\")\n", " return \"web_search\"\n", - " elif datasource == 'vectorstore':\n", + " elif datasource == \"vectorstore\":\n", " print(\"---ROUTE QUESTION TO RAG---\")\n", " return \"vectorstore\"\n", - " else: \n", + " else:\n", " print(\"---ROUTE QUESTION TO LLM---\")\n", " return \"vectorstore\"\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -745,6 +777,7 @@ " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -761,7 +794,9 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", " grade = score.binary_score\n", "\n", " # Check hallucination\n", @@ -769,7 +804,7 @@ " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", @@ -808,11 +843,11 @@ "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"web_search\", web_search) # web search\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # rag\n", - "workflow.add_node(\"llm_fallback\", llm_fallback) # llm\n", + "workflow.add_node(\"web_search\", web_search) # web search\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # rag\n", + "workflow.add_node(\"llm_fallback\", llm_fallback) # llm\n", "\n", "# Build graph\n", "workflow.set_conditional_entry_point(\n", @@ -837,8 +872,8 @@ " \"generate\",\n", " grade_generation_v_documents_and_question,\n", " {\n", - " \"not supported\": \"generate\", # Hallucinations: re-generate \n", - " \"not useful\": \"web_search\", # Fails to answer question: fall-back to web-search \n", + " \"not supported\": \"generate\", # Hallucinations: re-generate\n", + " \"not useful\": \"web_search\", # Fails to answer question: fall-back to web-search\n", " \"useful\": END,\n", " },\n", ")\n", @@ -882,7 +917,9 @@ ], "source": [ "# Run\n", - "inputs = {\"question\": \"What player are the Bears expected to draft first in the 2024 NFL draft?\"}\n", + "inputs = {\n", + " \"question\": \"What player are the Bears expected to draft first in the 2024 NFL draft?\"\n", + "}\n", "for output in app.stream(inputs):\n", " for key, value in output.items():\n", " # Node\n", @@ -959,7 +996,7 @@ " pprint.pprint(\"\\n---\\n\")\n", "\n", "# Final generation\n", - "pprint.pprint(value [\"generation\"])" + "pprint.pprint(value[\"generation\"])" ] }, { @@ -1008,7 +1045,7 @@ " pprint.pprint(\"\\n---\\n\")\n", "\n", "# Final generation\n", - "pprint.pprint(value [\"generation\"])" + "pprint.pprint(value[\"generation\"])" ] }, { diff --git a/examples/rag/langgraph_adaptive_rag_local.ipynb b/examples/rag/langgraph_adaptive_rag_local.ipynb index bc89d1d90..18523169c 100644 --- a/examples/rag/langgraph_adaptive_rag_local.ipynb +++ b/examples/rag/langgraph_adaptive_rag_local.ipynb @@ -214,7 +214,7 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from langchain.prompts import PromptTemplate\n", "from langchain_community.chat_models import ChatOllama\n", @@ -268,10 +268,12 @@ "# LLM\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -299,7 +301,7 @@ } ], "source": [ - "### Hallucination Grader \n", + "### Hallucination Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -339,7 +341,7 @@ } ], "source": [ - "### Answer Grader \n", + "### Answer Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -358,7 +360,7 @@ ")\n", "\n", "answer_grader = prompt | llm | JsonOutputParser()\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -384,7 +386,7 @@ "# LLM\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", - "# Prompt \n", + "# Prompt\n", "re_write_prompt = PromptTemplate(\n", " template=\"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for vectorstore retrieval. Look at the initial and formulate an improved question. \\n\n", @@ -414,6 +416,7 @@ "### Search\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults(k=3)" ] }, @@ -439,6 +442,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -446,11 +450,12 @@ " Attributes:\n", " question: question\n", " generation: LLM generation\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " documents: List[str]" ] }, { @@ -464,6 +469,7 @@ "\n", "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -481,6 +487,7 @@ " documents = retriever.get_relevant_documents(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -494,11 +501,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -513,12 +521,14 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", - " grade = score['score']\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", " filtered_docs.append(d)\n", @@ -527,6 +537,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -546,6 +557,7 @@ " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", @@ -567,8 +579,10 @@ "\n", " return {\"documents\": web_results, \"question\": question}\n", "\n", + "\n", "### Edges ###\n", "\n", + "\n", "def route_question(state):\n", " \"\"\"\n", " Route question to web search or RAG.\n", @@ -583,16 +597,17 @@ " print(\"---ROUTE QUESTION---\")\n", " question = state[\"question\"]\n", " print(question)\n", - " source = question_router.invoke({\"question\": question}) \n", + " source = question_router.invoke({\"question\": question})\n", " print(source)\n", - " print(source['datasource'])\n", - " if source['datasource'] == 'web_search':\n", + " print(source[\"datasource\"])\n", + " if source[\"datasource\"] == \"web_search\":\n", " print(\"---ROUTE QUESTION TO WEB SEARCH---\")\n", " return \"web_search\"\n", - " elif source['datasource'] == 'vectorstore':\n", + " elif source[\"datasource\"] == \"vectorstore\":\n", " print(\"---ROUTE QUESTION TO RAG---\")\n", " return \"vectorstore\"\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -611,13 +626,16 @@ " if not filtered_documents:\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -634,16 +652,18 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", - " grade = score['score']\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", + " grade = score[\"score\"]\n", "\n", " # Check hallucination\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", - " grade = score['score']\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", " return \"useful\"\n", @@ -675,11 +695,11 @@ "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"web_search\", web_search) # web search\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # generatae\n", - "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", + "workflow.add_node(\"web_search\", web_search) # web search\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # generatae\n", + "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", "\n", "# Build graph\n", "workflow.set_conditional_entry_point(\n", @@ -753,7 +773,7 @@ "source": [ "from pprint import pprint\n", "\n", - "# Run \n", + "# Run\n", "inputs = {\"question\": \"What is the AlphaCodium paper about?\"}\n", "for output in app.stream(inputs):\n", " for key, value in output.items():\n", diff --git a/examples/rag/langgraph_agentic_rag.ipynb b/examples/rag/langgraph_agentic_rag.ipynb index 40f6c3da3..8ba5ac26d 100644 --- a/examples/rag/langgraph_agentic_rag.ipynb +++ b/examples/rag/langgraph_agentic_rag.ipynb @@ -36,10 +36,12 @@ "import os\n", "import getpass\n", "\n", + "\n", "def _set_env(key: str):\n", " if key not in os.environ:\n", " os.environ[key] = getpass.getpass(f\"{key}:\")\n", "\n", + "\n", "_set_env(\"OPENAI_API_KEY\")\n", "\n", "# (Optional) For tracing\n", @@ -211,6 +213,7 @@ "\n", "### Edges\n", "\n", + "\n", "def grade_documents(state) -> Literal[\"generate\", \"rewrite\"]:\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -254,12 +257,9 @@ "\n", " question = messages[0].content\n", " docs = last_message.content\n", - " \n", - " scored_result = chain.invoke(\n", - " {\"question\": question, \n", - " \"context\": docs}\n", - " )\n", - " \n", + "\n", + " scored_result = chain.invoke({\"question\": question, \"context\": docs})\n", + "\n", " score = scored_result.binary_score\n", "\n", " if score == \"yes\":\n", @@ -294,36 +294,40 @@ " # We return a list, because this will get added to the existing list\n", " return {\"messages\": [response]}\n", "\n", + "\n", "def rewrite(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", - " \n", + "\n", " Args:\n", " state (messages): The current state\n", - " \n", + "\n", " Returns:\n", " dict: The updated state with re-phrased question\n", " \"\"\"\n", - " \n", + "\n", " print(\"---TRANSFORM QUERY---\")\n", " messages = state[\"messages\"]\n", " question = messages[0].content\n", "\n", - " msg = [HumanMessage(\n", - " content=f\"\"\" \\n \n", + " msg = [\n", + " HumanMessage(\n", + " content=f\"\"\" \\n \n", " Look at the input and try to reason about the underlying semantic intent / meaning. \\n \n", " Here is the initial question:\n", " \\n ------- \\n\n", " {question} \n", " \\n ------- \\n\n", " Formulate an improved question: \"\"\",\n", - " )]\n", + " )\n", + " ]\n", "\n", " # Grader\n", " model = ChatOpenAI(temperature=0, model=\"gpt-4-0125-preview\", streaming=True)\n", " response = model.invoke(msg)\n", " return {\"messages\": [response]}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -360,8 +364,8 @@ " return {\"messages\": [response]}\n", "\n", "\n", - "print(\"*\"*20 + \"Prompt[rlm/rag-prompt]\" + \"*\"*20)\n", - "prompt = hub.pull(\"rlm/rag-prompt\").pretty_print() # Show what the prompt looks like" + "print(\"*\" * 20 + \"Prompt[rlm/rag-prompt]\" + \"*\" * 20)\n", + "prompt = hub.pull(\"rlm/rag-prompt\").pretty_print() # Show what the prompt looks like" ] }, { @@ -395,7 +399,9 @@ "retrieve = ToolNode([retriever_tool])\n", "workflow.add_node(\"retrieve\", retrieve) # retrieval\n", "workflow.add_node(\"rewrite\", rewrite) # Re-writing the question\n", - "workflow.add_node(\"generate\", generate) # Generating a response after we know the documents are relevant\n", + "workflow.add_node(\n", + " \"generate\", generate\n", + ") # Generating a response after we know the documents are relevant\n", "# Call agent node to decide to retrieve or not\n", "workflow.set_entry_point(\"agent\")\n", "\n", diff --git a/examples/rag/langgraph_crag.ipynb b/examples/rag/langgraph_crag.ipynb index c9dbce965..2b100f892 100644 --- a/examples/rag/langgraph_crag.ipynb +++ b/examples/rag/langgraph_crag.ipynb @@ -180,23 +180,27 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from langchain_openai import ChatOpenAI\n", "from langchain_core.prompts import ChatPromptTemplate\n", "from langchain_core.pydantic_v1 import BaseModel, Field\n", "\n", + "\n", "# Data model\n", "class GradeDocuments(BaseModel):\n", " \"\"\"Binary score for relevance check on retrieved documents.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Documents are relevant to the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Documents are relevant to the question, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeDocuments)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n", " If the document contains keyword(s) or semantic meaning related to the question, grade it as relevant. \\n\n", " Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.\"\"\"\n", @@ -240,10 +244,12 @@ "# LLM\n", "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -272,16 +278,19 @@ "source": [ "### Question Re-writer\n", "\n", - "# LLM \n", + "# LLM\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for web search. Look at the input and try to reason about the underlying sematic intent / meaning.\"\"\"\n", "re_write_prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", system),\n", - " (\"human\", \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\"),\n", + " (\n", + " \"human\",\n", + " \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\",\n", + " ),\n", " ]\n", ")\n", "\n", @@ -307,6 +316,7 @@ "### Search\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults(k=3)" ] }, @@ -332,6 +342,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -340,12 +351,13 @@ " question: question\n", " generation: LLM generation\n", " web_search: whether to add search\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " web_search : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " web_search: str\n", + " documents: List[str]" ] }, { @@ -357,6 +369,7 @@ "source": [ "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -374,6 +387,7 @@ " documents = retriever.get_relevant_documents(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -387,11 +401,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -406,12 +421,14 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " web_search = \"No\"\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", @@ -422,6 +439,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question, \"web_search\": web_search}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -440,7 +458,8 @@ " # Re-write question\n", " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", - " \n", + "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", @@ -464,8 +483,10 @@ "\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "### Edges\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -485,7 +506,9 @@ " if web_search == \"Yes\":\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", diff --git a/examples/rag/langgraph_crag_local.ipynb b/examples/rag/langgraph_crag_local.ipynb index 90d3b487d..6934b5910 100644 --- a/examples/rag/langgraph_crag_local.ipynb +++ b/examples/rag/langgraph_crag_local.ipynb @@ -162,7 +162,7 @@ "metadata": {}, "outputs": [], "source": [ - "run_local = 'Yes'\n", + "run_local = \"Yes\"\n", "local_llm = \"mistral:latest\"" ] }, @@ -239,7 +239,7 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from langchain.prompts import PromptTemplate\n", "from langchain_community.chat_models import ChatOllama\n", @@ -303,10 +303,12 @@ " model=\"mistral-medium\", temperature=0, mistral_api_key=mistral_api_key\n", " )\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -343,7 +345,7 @@ " model=\"mistral-medium\", temperature=0, mistral_api_key=mistral_api_key\n", " )\n", "\n", - "# Prompt \n", + "# Prompt\n", "re_write_prompt = PromptTemplate(\n", " template=\"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for vectorstore retrieval. Look at the initial and formulate an improved question. \\n\n", @@ -373,6 +375,7 @@ "### Search\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults(k=3)" ] }, @@ -398,6 +401,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -406,12 +410,13 @@ " question: question\n", " generation: LLM generation\n", " web_search: whether to add search\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " web_search : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " web_search: str\n", + " documents: List[str]" ] }, { @@ -423,6 +428,7 @@ "source": [ "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -440,6 +446,7 @@ " documents = retriever.get_relevant_documents(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -453,11 +460,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -472,13 +480,15 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " web_search = \"No\"\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", - " grade = score['score']\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", " filtered_docs.append(d)\n", @@ -488,6 +498,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question, \"web_search\": web_search}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -506,7 +517,8 @@ " # Re-write question\n", " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", - " \n", + "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based on the re-phrased question.\n", @@ -530,8 +542,10 @@ "\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "### Edges\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -551,7 +565,9 @@ " if web_search == \"Yes\":\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", diff --git a/examples/rag/langgraph_rag_agent_llama3_local.ipynb b/examples/rag/langgraph_rag_agent_llama3_local.ipynb index 0b5c83e1b..7b94db4ef 100644 --- a/examples/rag/langgraph_rag_agent_llama3_local.ipynb +++ b/examples/rag/langgraph_rag_agent_llama3_local.ipynb @@ -71,7 +71,7 @@ "source": [ "### LLM\n", "\n", - "local_llm = 'llama3'" + "local_llm = \"llama3\"" ] }, { @@ -126,7 +126,7 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from langchain.prompts import PromptTemplate\n", "from langchain_community.chat_models import ChatOllama\n", @@ -189,10 +189,12 @@ "\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -221,7 +223,7 @@ } ], "source": [ - "### Hallucination Grader \n", + "### Hallucination Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -262,7 +264,7 @@ } ], "source": [ - "### Answer Grader \n", + "### Answer Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -281,7 +283,7 @@ ")\n", "\n", "answer_grader = prompt | llm | JsonOutputParser()\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -335,6 +337,7 @@ "### Search\n", "\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "\n", "web_search_tool = TavilySearchResults(k=3)" ] }, @@ -358,6 +361,7 @@ "\n", "### State\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -366,17 +370,20 @@ " question: question\n", " generation: LLM generation\n", " web_search: whether to add search\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " web_search : str\n", - " documents : List[str]\n", + "\n", + " question: str\n", + " generation: str\n", + " web_search: str\n", + " documents: List[str]\n", + "\n", "\n", "from langchain.schema import Document\n", "\n", "### Nodes\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents from vectorstore\n", @@ -394,6 +401,7 @@ " documents = retriever.invoke(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer using RAG on retrieved documents\n", @@ -407,11 +415,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question\n", @@ -427,13 +436,15 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " web_search = \"No\"\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", - " grade = score['score']\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", + " grade = score[\"score\"]\n", " # Document relevant\n", " if grade.lower() == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", @@ -446,7 +457,8 @@ " web_search = \"Yes\"\n", " continue\n", " return {\"documents\": filtered_docs, \"question\": question, \"web_search\": web_search}\n", - " \n", + "\n", + "\n", "def web_search(state):\n", " \"\"\"\n", " Web search based based on the question\n", @@ -472,8 +484,10 @@ " documents = [web_results]\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "### Conditional edge\n", "\n", + "\n", "def route_question(state):\n", " \"\"\"\n", " Route question to web search or RAG.\n", @@ -488,16 +502,17 @@ " print(\"---ROUTE QUESTION---\")\n", " question = state[\"question\"]\n", " print(question)\n", - " source = question_router.invoke({\"question\": question}) \n", + " source = question_router.invoke({\"question\": question})\n", " print(source)\n", - " print(source['datasource'])\n", - " if source['datasource'] == 'web_search':\n", + " print(source[\"datasource\"])\n", + " if source[\"datasource\"] == \"web_search\":\n", " print(\"---ROUTE QUESTION TO WEB SEARCH---\")\n", " return \"websearch\"\n", - " elif source['datasource'] == 'vectorstore':\n", + " elif source[\"datasource\"] == \"vectorstore\":\n", " print(\"---ROUTE QUESTION TO RAG---\")\n", " return \"vectorstore\"\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or add web search\n", @@ -517,15 +532,19 @@ " if web_search == \"Yes\":\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, INCLUDE WEB SEARCH---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, INCLUDE WEB SEARCH---\"\n", + " )\n", " return \"websearch\"\n", " else:\n", " # We have relevant documents, so generate answer\n", " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "### Conditional edge\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -542,16 +561,18 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", - " grade = score['score']\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", + " grade = score[\"score\"]\n", "\n", " # Check hallucination\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", - " grade = score['score']\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", " return \"useful\"\n", @@ -562,14 +583,16 @@ " pprint(\"---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---\")\n", " return \"not supported\"\n", "\n", + "\n", "from langgraph.graph import END, StateGraph\n", + "\n", "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"websearch\", web_search) # web search\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # generatae" + "workflow.add_node(\"websearch\", web_search) # web search\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # generatae" ] }, { @@ -668,6 +691,7 @@ "\n", "# Test\n", "from pprint import pprint\n", + "\n", "inputs = {\"question\": \"What are the types of agent memory?\"}\n", "for output in app.stream(inputs):\n", " for key, value in output.items():\n", @@ -719,6 +743,7 @@ "\n", "# Test\n", "from pprint import pprint\n", + "\n", "inputs = {\"question\": \"Who are the Bears expected to draft first in the NFL draft?\"}\n", "for output in app.stream(inputs):\n", " for key, value in output.items():\n", diff --git a/examples/rag/langgraph_self_rag.ipynb b/examples/rag/langgraph_self_rag.ipynb index b1bca4217..7cc96aa16 100644 --- a/examples/rag/langgraph_self_rag.ipynb +++ b/examples/rag/langgraph_self_rag.ipynb @@ -172,7 +172,7 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from typing import Literal\n", "\n", @@ -185,13 +185,16 @@ "class GradeDocuments(BaseModel):\n", " \"\"\"Binary score for relevance check on retrieved documents.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Documents are relevant to the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Documents are relevant to the question, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeDocuments)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n", " It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \\n\n", " If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \\n\n", @@ -236,10 +239,12 @@ "# LLM\n", "llm = ChatOpenAI(model_name=\"gpt-3.5-turbo\", temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -266,19 +271,23 @@ } ], "source": [ - "### Hallucination Grader \n", + "### Hallucination Grader\n", + "\n", "\n", "# Data model\n", "class GradeHallucinations(BaseModel):\n", " \"\"\"Binary score for hallucination present in generation answer.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer is grounded in the facts, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer is grounded in the facts, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeHallucinations)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing whether an LLM generation is grounded in / supported by a set of retrieved facts. \\n \n", " Give a binary score 'yes' or 'no'. 'Yes' means that the answer is grounded in / supported by the set of facts.\"\"\"\n", "hallucination_prompt = ChatPromptTemplate.from_messages(\n", @@ -310,19 +319,23 @@ } ], "source": [ - "### Answer Grader \n", + "### Answer Grader\n", + "\n", "\n", "# Data model\n", "class GradeAnswer(BaseModel):\n", " \"\"\"Binary score to assess answer addresses question.\"\"\"\n", "\n", - " binary_score: str = Field(description=\"Answer addresses the question, 'yes' or 'no'\")\n", + " binary_score: str = Field(\n", + " description=\"Answer addresses the question, 'yes' or 'no'\"\n", + " )\n", "\n", - "# LLM with function call \n", + "\n", + "# LLM with function call\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "structured_llm_grader = llm.with_structured_output(GradeAnswer)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You are a grader assessing whether an answer addresses / resolves a question \\n \n", " Give a binary score 'yes' or 'no'. Yes' means that the answer resolves the question.\"\"\"\n", "answer_prompt = ChatPromptTemplate.from_messages(\n", @@ -333,7 +346,7 @@ ")\n", "\n", "answer_grader = answer_prompt | structured_llm_grader\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -356,16 +369,19 @@ "source": [ "### Question Re-writer\n", "\n", - "# LLM \n", + "# LLM\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "\n", - "# Prompt \n", + "# Prompt\n", "system = \"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for vectorstore retrieval. Look at the input and try to reason about the underlying sematic intent / meaning.\"\"\"\n", "re_write_prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", system),\n", - " (\"human\", \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\"),\n", + " (\n", + " \"human\",\n", + " \"Here is the initial question: \\n\\n {question} \\n Formulate an improved question.\",\n", + " ),\n", " ]\n", ")\n", "\n", @@ -395,6 +411,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -402,11 +419,12 @@ " Attributes:\n", " question: question\n", " generation: LLM generation\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " documents: List[str]" ] }, { @@ -420,6 +438,7 @@ "\n", "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -437,6 +456,7 @@ " documents = retriever.get_relevant_documents(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -450,11 +470,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -469,11 +490,13 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", @@ -483,6 +506,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -502,8 +526,10 @@ " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", "\n", + "\n", "### Edges\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -522,13 +548,16 @@ " if not filtered_documents:\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -545,7 +574,9 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", " grade = score.binary_score\n", "\n", " # Check hallucination\n", @@ -553,7 +584,7 @@ " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", " grade = score.binary_score\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", @@ -588,10 +619,10 @@ "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # generatae\n", - "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # generatae\n", + "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", "\n", "# Build graph\n", "workflow.set_entry_point(\"retrieve\")\n", diff --git a/examples/rag/langgraph_self_rag_local.ipynb b/examples/rag/langgraph_self_rag_local.ipynb index 7a0e66082..9418fc29b 100644 --- a/examples/rag/langgraph_self_rag_local.ipynb +++ b/examples/rag/langgraph_self_rag_local.ipynb @@ -188,7 +188,7 @@ } ], "source": [ - "### Retrieval Grader \n", + "### Retrieval Grader\n", "\n", "from langchain.prompts import PromptTemplate\n", "from langchain_community.chat_models import ChatOllama\n", @@ -241,10 +241,12 @@ "# LLM\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", + "\n", "# Post-processing\n", "def format_docs(docs):\n", " return \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", + "\n", "# Chain\n", "rag_chain = prompt | llm | StrOutputParser()\n", "\n", @@ -271,7 +273,7 @@ } ], "source": [ - "### Hallucination Grader \n", + "### Hallucination Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -311,7 +313,7 @@ } ], "source": [ - "### Answer Grader \n", + "### Answer Grader\n", "\n", "# LLM\n", "llm = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", @@ -330,7 +332,7 @@ ")\n", "\n", "answer_grader = prompt | llm | JsonOutputParser()\n", - "answer_grader.invoke({\"question\": question,\"generation\": generation})" + "answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { @@ -356,7 +358,7 @@ "# LLM\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", - "# Prompt \n", + "# Prompt\n", "re_write_prompt = PromptTemplate(\n", " template=\"\"\"You a question re-writer that converts an input question to a better version that is optimized \\n \n", " for vectorstore retrieval. Look at the initial and formulate an improved question. \\n\n", @@ -390,6 +392,7 @@ "from typing_extensions import TypedDict\n", "from typing import List\n", "\n", + "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", @@ -397,11 +400,12 @@ " Attributes:\n", " question: question\n", " generation: LLM generation\n", - " documents: list of documents \n", + " documents: list of documents\n", " \"\"\"\n", - " question : str\n", - " generation : str\n", - " documents : List[str]" + "\n", + " question: str\n", + " generation: str\n", + " documents: List[str]" ] }, { @@ -415,6 +419,7 @@ "\n", "from langchain.schema import Document\n", "\n", + "\n", "def retrieve(state):\n", " \"\"\"\n", " Retrieve documents\n", @@ -432,6 +437,7 @@ " documents = retriever.get_relevant_documents(question)\n", " return {\"documents\": documents, \"question\": question}\n", "\n", + "\n", "def generate(state):\n", " \"\"\"\n", " Generate answer\n", @@ -445,11 +451,12 @@ " print(\"---GENERATE---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # RAG generation\n", " generation = rag_chain.invoke({\"context\": documents, \"question\": question})\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", + "\n", "def grade_documents(state):\n", " \"\"\"\n", " Determines whether the retrieved documents are relevant to the question.\n", @@ -464,12 +471,14 @@ " print(\"---CHECK DOCUMENT RELEVANCE TO QUESTION---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", - " \n", + "\n", " # Score each doc\n", " filtered_docs = []\n", " for d in documents:\n", - " score = retrieval_grader.invoke({\"question\": question, \"document\": d.page_content})\n", - " grade = score['score']\n", + " score = retrieval_grader.invoke(\n", + " {\"question\": question, \"document\": d.page_content}\n", + " )\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---GRADE: DOCUMENT RELEVANT---\")\n", " filtered_docs.append(d)\n", @@ -478,6 +487,7 @@ " continue\n", " return {\"documents\": filtered_docs, \"question\": question}\n", "\n", + "\n", "def transform_query(state):\n", " \"\"\"\n", " Transform the query to produce a better question.\n", @@ -497,8 +507,10 @@ " better_question = question_rewriter.invoke({\"question\": question})\n", " return {\"documents\": documents, \"question\": better_question}\n", "\n", + "\n", "### Edges\n", "\n", + "\n", "def decide_to_generate(state):\n", " \"\"\"\n", " Determines whether to generate an answer, or re-generate a question.\n", @@ -517,13 +529,16 @@ " if not filtered_documents:\n", " # All documents have been filtered check_relevance\n", " # We will re-generate a new query\n", - " print(\"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\")\n", + " print(\n", + " \"---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, TRANSFORM QUERY---\"\n", + " )\n", " return \"transform_query\"\n", " else:\n", " # We have relevant documents, so generate answer\n", " print(\"---DECISION: GENERATE---\")\n", " return \"generate\"\n", "\n", + "\n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", @@ -540,16 +555,18 @@ " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", - " score = hallucination_grader.invoke({\"documents\": documents, \"generation\": generation})\n", - " grade = score['score']\n", + " score = hallucination_grader.invoke(\n", + " {\"documents\": documents, \"generation\": generation}\n", + " )\n", + " grade = score[\"score\"]\n", "\n", " # Check hallucination\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", - " score = answer_grader.invoke({\"question\": question,\"generation\": generation})\n", - " grade = score['score']\n", + " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", + " grade = score[\"score\"]\n", " if grade == \"yes\":\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", " return \"useful\"\n", @@ -583,10 +600,10 @@ "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", - "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", - "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", - "workflow.add_node(\"generate\", generate) # generatae\n", - "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", + "workflow.add_node(\"retrieve\", retrieve) # retrieve\n", + "workflow.add_node(\"grade_documents\", grade_documents) # grade documents\n", + "workflow.add_node(\"generate\", generate) # generatae\n", + "workflow.add_node(\"transform_query\", transform_query) # transform_query\n", "\n", "# Build graph\n", "workflow.set_entry_point(\"retrieve\")\n", diff --git a/examples/state-model.ipynb b/examples/state-model.ipynb index db129d5ab..d97108736 100644 --- a/examples/state-model.ipynb +++ b/examples/state-model.ipynb @@ -182,21 +182,9 @@ "execution_count": 4, "id": "cd3cbae5-d92c-4559-a4aa-44721b80d107", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/nuno/dev/langgraph/.venv/lib/python3.11/site-packages/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `format_tool_to_openai_function` was deprecated in LangChain 0.1.16 and will be removed in 0.2.0. Use langchain_core.utils.function_calling.convert_to_openai_function() instead.\n", - " warn_deprecated(\n" - ] - } - ], + "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -229,6 +217,7 @@ "from langchain_core.messages import BaseMessage\n", "from langchain_core.pydantic_v1 import BaseModel\n", "\n", + "\n", "class AgentState(BaseModel):\n", " messages: Annotated[Sequence[BaseMessage], operator.add]" ] @@ -271,10 +260,8 @@ "metadata": {}, "outputs": [], "source": [ - "import json\n", - "\n", "from langgraph.prebuilt import ToolInvocation\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", @@ -282,7 +269,7 @@ " messages = state.messages\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -304,16 +291,17 @@ " # we know the last message involves a function call\n", " last_message = messages[-1]\n", " # We construct an ToolInvocation from the function_call\n", + " tool_call = last_message.tool_calls[0]\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", " response = tool_executor.invoke(action)\n", " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " function_message = ToolMessage(\n", + " content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n", + " )\n", " # We return a list, because this will get added to the existing list\n", " return {\"messages\": [function_message]}" ] @@ -379,6 +367,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e09aaa63", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "547c3931-3dae-4281-ad4e-4b51305594d4", @@ -392,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "8edb04b9-40b6-46f1-a7a8-4b2d8aba7752", "metadata": {}, "outputs": [ @@ -400,9 +415,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}})]}}\n", - "{'action': {'messages': [FunctionMessage(content=\"[{'url': 'https://forecast.weather.gov/zipcity.php?inputstring=San francisco,CA', 'content': 'NOAA National Weather Service National Weather Service. Toggle navigation. HOME; FORECAST . Local; Graphical; Aviation; Marine; Rivers and Lakes; Hurricanes; Severe Weather; Fire Weather; ... San Francisco CA 37.77°N 122.41°W (Elev. 131 ft) Last Update: 1:27 am PDT Apr 1, 2024. Forecast Valid: 8am PDT Apr 1, 2024-6pm PDT Apr 7, 2024 .'}]\", name='tavily_search_results_json')]}}\n", - "{'agent': {'messages': [AIMessage(content='You can check the weather in San Francisco by visiting the [NOAA National Weather Service website](https://forecast.weather.gov/zipcity.php?inputstring=San%20francisco,CA).')]}}\n" + "{'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_RDyIGqQrd5OfH9QDnSnhOIMg', 'function': {'arguments': '{\"query\":\"weather in San Francisco\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls'}, id='run-207417c6-2a5a-4e04-be44-2d1f7fbad005-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in San Francisco'}, 'id': 'call_RDyIGqQrd5OfH9QDnSnhOIMg'}])]}}\n" ] } ], @@ -439,7 +452,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/storm/storm.ipynb b/examples/storm/storm.ipynb index f329bfd12..fc6db2c9f 100644 --- a/examples/storm/storm.ipynb +++ b/examples/storm/storm.ipynb @@ -707,9 +707,10 @@ " return [{\"content\": r[\"content\"], \"url\": r[\"url\"]} for r in results]\n", "'''\n", "\n", - "# DDG \n", + "# DDG\n", "search_engine = DuckDuckGoSearchAPIWrapper()\n", "\n", + "\n", "@tool\n", "async def search_engine(query: str):\n", " \"\"\"Search engine to the internet.\"\"\"\n", @@ -1228,7 +1229,7 @@ " (\n", " \"user\",\n", " 'Write the complete Wiki article using markdown format. Organize citations using footnotes like \"[1]\",'\n", - " ' avoiding duplicates in the footer. Include URLs in the footer.',\n", + " \" avoiding duplicates in the footer. Include URLs in the footer.\",\n", " ),\n", " ]\n", ")\n", diff --git a/examples/streaming-tokens.ipynb b/examples/streaming-tokens.ipynb index 9647570ee..5ba64afb5 100644 --- a/examples/streaming-tokens.ipynb +++ b/examples/streaming-tokens.ipynb @@ -61,7 +61,7 @@ "metadata": {}, "outputs": [ { - "name": "stdin", + "name": "stdout", "output_type": "stream", "text": [ "OpenAI API Key: ········\n", @@ -193,10 +193,7 @@ "metadata": {}, "outputs": [], "source": [ - "from langchain.tools.render import format_tool_to_openai_function\n", - "\n", - "functions = [format_tool_to_openai_function(t) for t in tools]\n", - "model = model.bind_functions(functions)" + "model = model.bind_tools(tools)" ] }, { @@ -232,21 +229,20 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 12, "id": "3b541bb9-900c-40d0-964d-7b5dfee30667", "metadata": {}, "outputs": [], "source": [ "from langgraph.prebuilt import ToolInvocation\n", - "import json\n", - "from langchain_core.messages import FunctionMessage\n", + "from langchain_core.messages import ToolMessage\n", "\n", "\n", "# Define the function that determines whether to continue or not\n", "def should_continue(messages):\n", " last_message = messages[-1]\n", " # If there is no function call, then we finish\n", - " if \"function_call\" not in last_message.additional_kwargs:\n", + " if not last_message.tool_calls:\n", " return \"end\"\n", " # Otherwise if there is, we continue\n", " else:\n", @@ -265,17 +261,18 @@ " # Based on the continue condition\n", " # we know the last message involves a function call\n", " last_message = messages[-1]\n", + " tool_call = last_message.tool_calls[0]\n", " # We construct an ToolInvocation from the function_call\n", " action = ToolInvocation(\n", - " tool=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\n", + " tool=tool_call[\"name\"],\n", + " tool_input=tool_call[\"args\"],\n", " )\n", " # We call the tool_executor and get back a response\n", " response = await tool_executor.ainvoke(action)\n", " # We use the response to create a FunctionMessage\n", - " function_message = FunctionMessage(content=str(response), name=action.tool)\n", + " function_message = ToolMessage(\n", + " content=str(response), name=action.tool, tool_call_id=tool_call[\"id\"]\n", + " )\n", " # We return a list, because this will get added to the existing list\n", " return function_message" ] @@ -292,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 13, "id": "813ae66c-3b58-4283-a02a-36da72a2ab90", "metadata": {}, "outputs": [], @@ -341,6 +338,33 @@ "app = workflow.compile()" ] }, + { + "cell_type": "code", + "execution_count": 14, + "id": "72785b66", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADtAOgDASIAAhEBAxEB/8QAHQABAAIDAAMBAAAAAAAAAAAAAAYHBAUIAgMJAf/EAFYQAAEDBAADAgcICg8FCQAAAAECAwQABQYRBxIhEzEIFBUiQVXRFjJRVnOTlJUXI1NUYXGBorThJDQ1Njc4QkNSdZGhsbKzCXSCktQYJTNiZYOGlsH/xAAbAQEAAgMBAQAAAAAAAAAAAAAAAgMBBAUGB//EADsRAAIBAgIFCAcIAgMAAAAAAAABAgMRBCEVMUFRkQUSExRSYaHRIlNicZKx8DIzNEKBwdLhI6JjcsL/2gAMAwEAAhEDEQA/APqnSlKAUpSgFKUoBSlKAw5t5t9tcSiXOjRVqHMEvPJQSPh0TWP7qrL64gfSUe2oDmUCLP4luCTGZkBNoY5Q6gK19ue7t16fc9a/VsP5hHsrTxONo4Wp0cotuyezarnTo4LpYKfO1lie6qy+uIH0lHtp7qrL64gfSUe2q79z1r9Ww/mEeynuetfq2H8wj2Vq6Vw/YlxRdo72vAsT3VWX1xA+ko9tPdVZfXED6Sj21Xfuetfq2H8wj2U9z1r9Ww/mEeymlcP2JcUNHe14Fie6qy+uIH0lHtp7qrL64gfSUe2q79z1r9Ww/mEeynuetfq2H8wj2U0rh+xLiho72vAsT3VWX1xA+ko9tPdVZfXED6Sj21Xfuetfq2H8wj2U9z1r9Ww/mEeymlcP2JcUNHe14FjsZFaZTyGmbnDddWdJQiQhSlH4AAa2NUxcLRBh3PH3GIUdhwXaKAttpKT7/wCECrnrpUqsK9JVYJpNtZ9xoYij0ElG9xSlKsNUUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoCtMo/hMe/qhj/Werzrwyj+Ex7+qGP9Z6vOvMcrfin7o/JHp8J9zEVHc24g2Dh3b48y/wA/xJqS8I7CG2XH3XnCCeVDbaVLUdAnoDoCpFVYcebdbpllsz8uBkzs2HO7aBcsTiqkS7c92ax2pQkHaCCUFJSoHm6j0jlU0pSSZszbUW0YeTeEbj+P5VhlubZmzrdkUSRNE6Nb5bpbbQAEBLaGVKUVKJ2OhQEgqACgakV8424VjWVDHbnevE7p2rTCkuRXuxQ44AW0rfCOzSVBSdBSh3iqq8pZrHc4N5vluOXSdOgR7nGu7NpgF6S0X0oDDi47eynmS0CsJ6JUrXSonxvgZdm0DiFBm2nNrhclOtrx632pp1u1+JJQ05zuFJCHHuYO7Q4VL5gkIT3VuKjCTSf6599vruNV1ZpN/t3HQ9z4wYnactdxd+5Orv7RYC4EeDIfcSHujajyNkcndte+VOxzEbG9Nwi4423izNv0ONDmwpFtnyYyEvQpKEOMtOBAcLjjSUpWonZa3zp9I6E1j8P7VKHHHiTenbdKjwp8GzCLKkxltB0Jbf50pKgOqSpPMnvBI3qsLgs/OxjJ83xm52O7RX5ORXC7xrgqEswXo7yw4gpfHmc2laKd7BB6VS4QUXbXZbeJYpSclfVn/RcNKUrVNk1d4/b2P/1tF/z1blVHeP29j/8AW0X/AD1blex5O/Bx97/Y8/yh94vcKUpW+cwUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoCtMo/hMe/qhj/WeqO5Tw1xPOJLMnIcatV7kMo7Np24Q23lITvegVA6G6sfIcAgZHdk3F2VOiSgwI5VDf7MKQFKUARo+lRrX/Yqg+uL39N/VXPxWB6zV6WNTm5JansVjr0cXThTUJK5V54A8NCgIOA44UAkhPkxnQJ1s+9/AP7K3uK8PMXwVclWO49bLGqSEh82+KhkuhO+Xm5QN62db+E1M/sVQfXF7+m/qp9iqD64vf039Vaj5Lm1Z1vmXLG0FmomtpWy+xVB9cXv6b+qqi4jRZuM+ELwfw+Fe7omzZOi8KuCFyOZajGjJca5Va83zid/DUND/APKuDJ6QpbmWXWsyHGrTltrctt7tsW7W9wpUuLNZS62og7BKVAjoetSX7FUH1xe/pv6qfYqg+uL39N/VWVyQ1mqq4Mx1+k8mmVengBw0QdpwHHAdEbFrZ7j3/wAms2x8HMExm6sXO0YdY7ZcY5JalRLe0262SCDyqCdjYJH5asP7FUH1xe/pv6qfYqg+uL39N/VU3yXN5Ot8yPXaC/L4IjN4/b2P/wBbRf8APVuVDY3C22sTokpdwuspUV5L7bciVzI50nYJGutTKurQorD0VS517NvjY52KrRrTUoilKVaaYpSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAK534zfxwfB0+SyP9CRXRFc78Zv44Pg6fJZH+hIoDoilKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAK534zfxwfB0+SyP9CRXRFc78Zv44Pg6fJZH+hIoDoilKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApWhyPMoOOONx1pemXB1PM3Cip5nCneuZXcEJ2D5yiB0IGz0qLO53k8ghTFptcNB7kyJbjqx+PlQB/YTVqpSau7Jd7L4UalRXiibZHYIWV49dLJc2vGLbcorsKU1vXO04goWnf4UqIr4UcX+E114R8Vr5g8xtcmbAl9gwtCCTJbVpTK0gf00KSdDu3rvFfZ73Z5d97WT/AJnqqPP+DJ4jcZMP4j3OJafK+ODzGEFzspRSSpgudN7bWSoa7zoHoNVLol2lxLOqVtxY3gocFUcBuCVjxx1ATd3k+P3VQIO5bgTzjY6HkAQ2D6Q2D6auCq192eXfe1k/5nq/UZtliCCqDZnx6Uh91v8Av5Vf4U6L2lxHVK24smlRGx8Q48+UzCucN2zTnSEthxXaMOq7uVDo6b+BKglR9ANS6q5QlDWa8oSg7SVhSlKgQFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFaXMMgONWF+W02l6WopZisrOg48s8qAT6Bs7J9ABrdVAuJ61G4Yq0f8AwjOdWd+lQjucv+JP5KupJOeey74K5bSipzUXtNJBg+KB1xx1UmZIX2siU5795fwn4ABoADokAAaAFZVK5p4zcYMmxO/ZFdsYvl0uttx1+OifbWbNGNtjk9n2jL0pag6pwhfN9q3yc6QR3mtaUnN3es9JKUaUdWR0tSuZOM/FfKoF34ls2zMmMTexiLF8l2kxGHXbst5oL59uAqO1qLSA33KT133Vl3/iTxCybN8hs2NtX+IxjqI0ZSrXAtslT8lyOl1RkGS82Qkc4SEtJTvSjzDYAjYg60U7W+vpHSFfinEoUlKlBJWdJBPedb0PyA/2VQtuyHiRmmewccl3o4LKOIxbtOixIceUtqcqQ80sJU4FjkPKnY87okaKTsmHIu2RcWbrwCvjmRybBdLgxdEOu26NHWlDzbC0uOoS62sefykaOwAegB60DqrYvq6X7nU8qKzOjuMPtpdZcHKpCh0IqR8P76/IEyzTnlPyoAQpl9xXMt+OoEJUo+lQUlaSfTypUeqq0CQQkAnmIHefTTHVqa4kWwI/nbbLS4Nd6Q5HIP5Cdf8AEa2aPpXpvc3+qV/krFOLgpUnJ60WfSlKrPPilKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQCovxEsz90sKX4ba351ueTNZZQdKd5QQtsfhUhS0j8JFSilThLmSUiUZOLUlsKsiympsZuQw4l1lxIUhae4iquyvwcsfy13I237xf4Vqv7pkz7RBmpaiuSORKe31yFQV5iDrm5CUjaTVgZzerJj3E+z4vaphbyjIEOzRZvFnXGFoRzFb6nEJIjlRBBUdpUrvAJKqynXLzEITKxi6JX6THDT6D+IpXv+0Cs9C5Z03dfpf69x3o4ilWj6Rz1xa4aZpM4gP3jGbdfpNzTDZZg3pu62zsG3UI0FONSGC42nmJKgydK2SACasGdwPbvlxbvz2RXrHcmmwWI16k41LEdq4LbRrmUlSFaI2oJUnlUEnW6sHyhP+Ll6+ifrrVz86iWu926zTYkuJd7kFmDb3w2iRK5BtfZNlfMvlHU8oOh306vV3Ek6N23LX3njBwGBAzY5SiRMcuJtLVmKHXApsstuLcCjscxWVLOyVaPTpvrUVPg+2JrEsXscO63q2u4286/bbtDkNomNFznDgKi2UFKg4oEFHdqrA8oT/i5evon66/Uy7m6QlrGby4o+gsob/vWsD++nV6u75E3OjtaMmKwYsVlkuuPltAQXXSCtehraiNdT3mtpw8gquN4n30g+LIb8Rhq3sODYU64PwFQQn/2j6CKjWUh7F8SueTZeh6245bme3lQbY2uXMdRsAglv3qevnBO+mzzpANWdid6tWR4vabpYnEO2SZFbfhLbaLSFMKSCgpQQCkcutAgdKkkqSavdvw/X6yOfisTGcejgbalKVScoUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlYkq6xIUqNFeksomSgvxeMp1KXH+UcyghJI5tDqdd3poDLqrLnmFy4n3PiBgViZyDDZ1sjNsNZe5BT4v4w4nmIY5z5/KkoOxroo6KSEqOEzjFy8IXDLe5nljvWACHefHWbPCvPK7KZaO2fGVM60CrS+RKtgoSQqrgoDS4jjfuVxu02py4TL0/b4qIpudyWHJUgJABU4sAbUdAk+k1uqUoDxddQy2txxaW20AqUtR0Egd5Jr4weEb4TV14g+Es5nlhmKYj2CU2zYVpPRLLCyUrI6b7RXMsg+hfKegr7E5jjEbNsRvmOzHpEaJd4L9veeiLCXm0OtqQpSCQQFAKJBII3roa+e3ED/Z/wDD3FePXCnCYl5yZy1ZYi7KnPPyo5fbMWOl1vsiGAkbUo83MlWx3a76A7x4RcS7bxh4bWDMLV5sS6xg6WubZZcBKXGifSULCkk/gqYVXXAvgXYPB8xKbjmNS7nJtci4O3BKLm+l5TClpQkttlKU6QOQEA7OyokndWLQCoXd+HD1w4i49lUTJrxambVGciO2KK6nyfMbUDrnbKTpSVcpCh10kDpU0pQED4Z8SLjmNouEjJMUn4HOiXNdtES7OtlMg7HZrZcSdOJUFJAI6c3MAVa3U8qK8SuF+M8XsXcx/K7Yi6WxTqX0oK1IW06nfK4haSFJUNnqD3EjuJFap685bi+cXuTeG7HH4Ww7T40zPQ64mbFcaSC6HUEFKkFPMQU9wR6SdUBP6VosJziw8R8Zg5DjVzYu9mmp52ZUc7SrR0QQeqVAggpIBBBBANb2gFKUoBSlKAUpSgFKUoBWP5Qi/fLPzgrIrntjjhbrnlMiz2fH8hvseJO8my7zboSVwY8gEJWhS1LCjyE6UUJUE9dnoaAvzyhF++WfnBTyhF++WfnBXOs/wjsbt91mNKt16dscG4C1zMmaiJNtjyecIKFuc/PoLUEFYQUAnRVXrvfhIWGxSshD1jyF63Y9O8Qu11YhoVFhq0g86lFwKUjTiSeRKlJHVSQCCQOjvKEX75Z+cFPKEX75Z+cFc4M8X7yvj9c8IGMz5Vmj26JIRPjIZ0hTq3Ap5xSngex0gJHKgq5kr2NcpOsx3j7Ft2N3a83prIJfaZY5YY9tctjCZUR0pTyRwhl1QcSDsBe+YlfUaG6AvzPslvEPFL4cOjQbplMeMHIUS4uqYjOuKJABd1o6AUeUEfyQSkKCq0lj4d2a53/Gc5y+HaJXEe22pMBy4RHVKYZWQS6WUqPTalLAURzcqiN6NV0rwg7HFsd/n3C0Xq1y7FMhQp9plsNCU2qU422wscrhbUhRcB2FnolXTY1W/wAh4sWDFMmnWa6uPQjCsi7/ACJriR4u3GQ52ahsHmK99dBPUenfSgLpbmR3VhKH21qPclKwSa91Udw4402/I84tdkl2DIMZm3Fp563eXISWUzUoRzL7MpWrSgk83IvlVrZ10NXjQClKUArnbjN/HC8HT5LI/wBCRXRNc7cZv44Xg6fJZH+hIoDomlKUApSlAK/O+v2lAV/l/Da6zJWJLw7JV4RDs1wMmXbIMJpUW4sLVt1paNDlUdrIUO5SySCdEZeJcUI+UZVlVhesl4scmwSENKkXWL2UeahfNyOx3NkLSeRXwEa6gVNaiHFXLbbg2HSL1dnVtQoziAQ22XHHFqPIhtCB1UtSlJSEjvJFASfyhF++WfnBTyhF++WfnBXOq/CIssCBfnrxYchx6baLU7el225xG0PyYjfRa2eVxSFaJSCkrBBUNgbrZY3xstGQZCm0SLZd8feegLukN+8x0MNTIqCkLcQQslPLzoJS4EKAUCU0BfKZ0ZaglMhpSidABY2a99cl/wDaIfynOuGLGNW2+26xXrIRHVdp9vbREucUMPK00pRKwCpKFAlKCoAkbG660oBSlKAUpSgFcp8N7bnfCd6XibWHJv8AY3LzIlxsgZujLKURpEhTqu2bX9sLiO0UNJSQrQ6jvrqytf5Bg/cPz1e2gONrtwtzz7HuRcJomPMu2S7XWQ61lZntBpiG/KMhfOyT2peSFKQAE8pPKeYCt5fOFuTS+GfHe0s2ztJ+TXOXItLRkNfsltcOO2hXMVaRtbax55B6b7iK6t8gwfuH56vbTyDB+4fnq9tAc4rx/KsW41x8ig48q+We62OFaJbrMxlpdvcZfcUpxSXFDnRyvE+Zs7QRrqDUYVwoyopcHkvqeKKcjH7Ia/c8FP27334D5nvv/LXWvkGD9w/PV7ahHEjK7NwufsUq42+6z4l7ukazIEJsONQ3HCrTzmiFhJ7ifOHROgNkkChuJHCLKMou3Fp+3wmiLs1j79qU9IQlMp2E8p5xs6JKN6SnagB5w9AOtVn3C7NON2R5Q7Px04hAuOHqtERyZOYkLEoS0PpDqWlK0k8veObzQd9Ty12L5Bg/cPz1e2nkGD9w/PV7aA5z4AYExb83t82bwVseCT4cZz/viG9EcUp8p5FJYDQKwhSVOecspOtDR2ddOVhsWmJGdS601yrT3HmJ/wD2sygFKVr7/f7di1knXi7zGrfbILKpEmU+rlQ02kbKifxUBpeJ/Eyw8IcIueVZJLES2QUcx11ceWfettp/lLUegH9ugCaqDgLw6yLOcxXxp4lRlQ8imMqZx3HlklNhgLHcQf59xJ8862ASOmylOg4cWG5eFjn8HijlcN2Fw3srxXh2Oyk6M1wHXlGQn0719rSe7vHQEudUUApSlAKUpQClKUAqoPCo4bzOKPCZ6025ER+4MT4twjxZ/wC1pSmXA52LvQ+asAp/KN9Kt+vTJiNTGwh5HOkHetkdfyUBxvK4UOXrhrn8W08GLXgV9nWJ+BCVHkwlPy3HEKCm+ZrzUo2G9FShv0gaqT5/wqvGZZJhzaWSxbmsYvFnnzA4jcZySzHbb83m5ldUL6p2By9SNiul/IMH7h+er21DOH2L5RGumXKy52FKgu3ZxdiTEJCmoPKORDmgPP3zd+/x0Bz7i2N8RrnP4NWW74Qi2sYddo5m3Vi6R3WH2mojrCXGmwoL5VFSTogKGwNEbI7FrBbssNpxK0s6Ukgg8yuh/trOoBSlKAUpSgFKUoBSlKAVj3BqQ/AktxXxFlLbUlp9SOcNrIPKop9Ojo69NZFQPjhh+V5zw2u1owrK38OyJ5siPcGUIIVtJSptaihSmwoKOnGuVxCglQPQpUBA7l4RVs8Hzhhavs1ZRaFZ4iMVSLfYz20iYr7YW1IZCUlPOG+XnUENBexzAEVddkvMPIrNAu1ue8Yt8+O3KjvcpTztrSFIVpQBGwQdEA18IeMXDvNOGmd3K3Z5DmMZA84qS9KmOF0zCtRJeDuz2vMdkq2eu99Qa+2/BQa4N4GP/QIH6O3QE0pSlAeLjiWkKWtQQhIJUpR0APhNcnTXZPhu5+uBGcdZ4FY1MAlvtkp9081s77NJHfGQdEke+7x1KSjN4uZbdvCY4gTODWDT3YOLW8gZtk0Q+8Rv9z2FdxcXohfwaIOwlaT0fiWJ2jBcat2P2GC1bbRb2UsRorI81CR/eSTsknqSSTsmgNlGjMwozUeO0hhhpAbbaaSEpQkDQSAOgAHTVe2lKAUpSgFKUoBSlKAUpSgI9l3EXFOH6YqsoyezY2mWVCObvcGooe5dc3J2ihza5k713bHw1TnC3irwUxC9Z1Kt/F2xzHb1fHLhKRc7ww0hl1SUgoYKikLa6DSklQ7+tbPwyOBo48cD7vaorPaX+3DylaiBtSn20nbQ+USVI13bKSe6vmX4FvAhXHLjjbIE6L2uO2g+UbsFp8xTaCOVk+g86+VJHfy85HdQH2lpSlAKUpQClKUApSlAKUrDu90j2O0zbjLUURYjK33VAbISlJUdfkFZScnZAwMlyuLjTbSVtuy5r+/F4ccbcc1rZ2dBKRsbUogdQO8gGGv5Plk9RWJFutLZ0Qy0wqSsfDtxSkg/kQPy1g21MmQXblcAPKk7TkjRJDfTzWk7/koB0O7Z5lEbUd5tWSqKm+bBJ9+vhst4/I7tHBwjG882QLi1wqa43455EzGXGukVCudlzxBCHo6/6TbiSFJPdvR0e4gipVZWcgx2zQLVb763HgQY7cWOz4ilXI2hIShO1KJOgANkk1s6w5d5gQLhAgyZjDE2epaYsdxwBx8oSVr5E950kEnXdWOsT3L4Y+RsdXo9k93lPLPjG39Xt+2sO8HKrzaZsBWVuRUymVsl+JDQ282FAgqQsHaVDfQjuPWvFOTW1eTuY8JBN4bhpnqj9mvowpZQFc2uX3ySNb307tVtKdYnuXwx8jHV6PZIfwpwiTwQxNjHsTet6ba0tTy25kNRckOq98446lwEqOu8g6AAA0ABa2N5w3d5Yt8+Kq13MglDalhbUgDqS0sa3odSlQSrvOtDdRmsefBbuEfsnOZJCgttxB0ttYO0rSfQoHqDRVlPKol70rW4WTKqmEpyXoqzLWpUfwa/u5DjzT0ooNwYWqLL7MaSXkHSlAegK6KA+BQqQVGUXCTi9hwWnF2YpSlRMClKUBCOIN8u9vu9jg2uaiCJaZC3XFMB0nkCNAA93vjWl8fyv4yN/V7ftrYcRP33Yv8AIzP8Gq9Fa2LxNShzI07Zrcnte9Hl+UsZXoV+ZTlZW7jG8fyv4yN/V7ftp4/lfxkb+r2/bWTStHSGI3r4Y+RytJYvt+C8jG8fyv4yN/V7ftqE8O+FCeFd0ye441cGoEvI5xuFwc8RQrncOzpOz5qAVLISOgK1aqf0ppDEb18MfIaSxfb8F5GN4/lfxkb+r2/bTx/K/jI39Xt+2sK95Va8cmWeLcZXi793l+IwkdmtXavci3OXaQQnzW1natDp37IrbU6/iN6+GPkZ0ji1nz/BeRjeP5X8ZG/q9v21hXq/Zba7NPmoyFpao0dx4JNvb0SlJOu/8FbatRl/7071/uT/APpqq6hjq8qsYtqza/LHf7idPlHFOcU57dy8i14DypEGM6vXO42lR18JANK8LT+5UL5FH+UUrpy1s9yZdKUqIFRLitz+4G6cm/5rn1/Q7VHP+bupbWJd7XHvlqm26Wkriy2VsOpB0ShSSk/3GraUlCpGT2NEouzTK8qluNbszJsztOJ2FzIV31FvduLjdqvxtEZpgrDaXXnUoWpaucEJQEke+KhrVW5bVSYxdttwI8qQeVuRoEBwa811O/5KwNjv0eZO9pOtBmPCrFs+nxJt8tnjUuK2plt5uQ6wotKIKm1ltSedBIG0K2n8FUTi4ScWeml/kh6O0onCc0yPi1B4SY9dsin2hm7WOZcrjOtb/i0m4vR3ENJaS6nRR0UXFcmidegVueI3DKGnilwdtD1+ySQ2V3VnxtV7kIk6EdTg+2oUlXN15eb3xSkAk1ZsvgVgs3GYGPuWBCbVb5DkmE0zIeaXEcWoqWWXErC2wSo+ahQHo1qvObwRwu4Yxb8ffsxNst8hUuIES30PMvKKipaXkrDnMorVs83XZ3uoFXRStZ56vCxUXGDMb7w0zniVNst0uDhawqNcmIsuU4/GiyFSnGC820olKeVCEqIA0Skk72a881TdeEV8gWy15ffr4xfsbvDsrypcFyVtPR4yXG5bKidtEqUU6QQnzk6AI3V4fY7x03F6c5bEPyX7UiyOl9xbqXISSpQaUlRKVDa17JGzvqTWlx7gTg2LIuKbdY+zM+Gq3PLelvvLEZQ0WW1OLUW0dfeoKR0HwCsmXTk3rKzwh2747kXBaarJb3dVZfbnhdmbnOU+y4sQRIStDZ81ohSSPMA2D12etdE1HmuH9gZcxhaIHKrGWy1aT2zn7GSWexI995/2s8vn7+Hv61uZ85u3sdo5zKKlBDbaBtbiydJQkelRPQCiTk7LWWwjzE7/AFkbvhfz+NZV39l5SRy7/peKsb1+Du/Lup3WgwewO49jzTMkIE99apUvsztPbLO1JB9IT0SD8CRW/raqtOeWyy4Kx5urJTm5IUpSqSoUpSgK+4ifvuxf5GZ/g1Xor38RP33Yv8jM/wAGqjeVRslksMDG7jarc8FHtlXWA7LSpOugSEPNcp36STXN5Q+1T/6/+pHjOV1fEpX2L9ze1V/hIZld8I4XyJlkdTFnyp0S3iYtwNpioefQ2pwrKVBGgogKKVcpIOjrR2QtnFHkIOS4jz7Gj7npWtdd9PHvxV7o2KZLkEebbM4mY1kGPzI6mXoESzPMKWSRrmU5JdBGt9OXe9EEarmxtFpt3OVBRhJSk00tmfkUrkVh4m8PsE4g3KRcpFvsreMTFtpcyh+6y2pqRtt5p1xhpbQ5ecEBRG+UgDVba6Xm88Jcqs0uJe7xkKLtid1ucqFdpipCHJUVth1C20no1zdopJS2Ep0R5o1Vm2ngRg9ksd7tEWzueIXmL4lOQ/PkvLdY0oBsLW4VJSAtWgkjWzqpG9hVlkXmz3VyEFz7RGeiQnS4vTTToQHE8u9K2G0dVAka6a2d2dJE2HiIPJq6z2a8str2nOEbGZQd4EZZPyy9ZHcr5eGZcoS5pXDC3YEhzbLPvWgnqkcuuhO9nu6rqtLb4O2B47cI1ystiRAuUF9cu3lUqSuPFfUlSeZDPahCU+edoSAD+MAjL8l8U/jLiB/+Oyv+uqM2p6mQrTjWas9W9W291ywK1GX/AL071/uT/wDpqqLeS+Kfxmw//wCuyv8ArqlOW79yV633+Ivd3yaqnQVq0M9q+ZVCKVSNnfNFqWn9yoXyKP8AKKUtP7lQvkUf5RSvQy+0z6QZdKUqIFKUoDSZLikTJW2lOLdizWObxeZHOnGt62OuwpJ0NpUCDoHWwCIa/jGWQFFKY9uu7YIAeafVGWR6dtqSoD8iz+SrNpVqqZc2STXf/WZfTr1KWUWVX5Myv4uN/WDfsp5Myv4uN/WDfsq1KVLnw9Wv9vMv67VKr8mZX8XG/rBv2U8mZX8XG/rBv2ValKc+Hq1/t5jrtUq9uw5dKVyptUCED/OypxVy/wDChB3+LY/HUoxvB27PKE+dKVdLmAQh1SAhpgHoQ0gb1sdCpRUrqRvR1UopWHUytGKXu/u7KqmIqVFaTyFKUqk1hSlKAUpSgIRxBsd2uF3sc61wkThETIQ62p8NEc4Rognv96a0viGV/FxH1g37KtGlJxp1EukgnbLbvvsa3mjWwVDES59SN372Vd4hlfxcR9YN+yniGV/FxH1g37KtGlQ6HD+qXGX8ijReE7Hi/Mq7xDK/i4j6wb9lPEMr+LiPrBv2VaNKdDh/VLjL+Q0XhOx4vzKu8Qyv4uI+sG/ZTxDK/i4j6wb9lWjSnQ4f1S4y/kNF4TseL8yrvEMr+LiPrBv2Vh3qw5ZdLNPhIx5tCpMdxkKM9vQKkkb7vw1btKlGnQhJSVNXXfL+RlcmYWLTUfF+ZjwGVR4MZpeudttKTr4QAKVkUqTd3c6h/9k=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "try:\n", + " display(Image(app.get_graph(xray=True).draw_mermaid_png()))\n", + "except:\n", + " # This requires some extra dependencies and is optional\n", + " pass" + ] + }, { "cell_type": "markdown", "id": "2a1b56c5-bd61-4192-8bdb-458a1e9f0159", @@ -355,28 +379,25 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 15, "id": "cfd140f0-a5a6-4697-8115-322242f197b5", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/harrisonchase/workplace/langchain/libs/core/langchain_core/_api/beta_decorator.py:86: LangChainBetaWarning: This API is in beta and may change in the future.\n", - " warn_beta(\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "--\n", - "Starting tool: tavily_search_results_json with inputs: {'query': 'weather in San Francisco'}\n", - "Done tool: tavily_search_results_json\n", - "Tool output was: [{'url': 'https://www.whereandwhen.net/when/north-america/california/san-francisco-ca/january/', 'content': 'Best time to go to San Francisco? Weather in San Francisco in january 2024 How was the weather last january? Here is the day by day recorded weather in San Francisco in january 2023: Seasonal average climate and temperature of San Francisco in january 8% 46% 29% 12% 8% Evolution of daily average temperature and precipitation in San Francisco in januaryWeather in San Francisco in january 2024. The weather in San Francisco in january comes from statistical datas on the past years. You can view the weather statistics the entire month, but also by using the tabs for the beginning, the middle and the end of the month. ... 23-01-2023 47°F to 61°F. 24-01-2023 43°F to 58°F. 25-01-2023 47°F to ...'}]\n", - "--\n", - "I|'m| sorry|,| but| I| couldn|'t| find| the| current| weather| in| San| Francisco|.| However|,| you| can| check| the| weather| in| San| Francisco| for| the| month| of| January| on| this| website|:| [|San| Francisco| Weather| in| January|](|https|://|www|.where|and|when|.net|/|when|/n|orth|-|amer|ica|/cal|ifornia|/s|an|-fr|anc|isco|-ca|/j|an|uary|/|).|" + "The| current| weather| in| San| Francisco| is| as| follows|:\n", + "|-| Temperature|:| |55|.|0|°F| (|12|.|8|°C|)\n", + "|-| Condition|:| Over|cast|\n", + "|-| Wind|:| |11|.|9| mph| (|19|.|1| k|ph|)| from| W|SW|\n", + "|-| Hum|idity|:| |96|%\n", + "|-| Cloud| Cover|:| |100|%\n", + "|-| Fe|els| like|:| |52|.|4|°F| (|11|.|4|°C|)\n", + "|-| Visibility|:| |9|.|0| miles| (|16|.|0| km|)\n", + "|-| UV| Index|:| |1|.|0|\n", + "\n", + "|For| more| details|,| you| can| visit| [|Weather| API|](|https|://|www|.weather|api|.com|/|).|" ] } ], @@ -429,7 +450,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.1" + "version": "3.11.2" } }, "nbformat": 4, diff --git a/examples/time-travel.ipynb b/examples/time-travel.ipynb index 52c28756f..0d5d0f36c 100644 --- a/examples/time-travel.ipynb +++ b/examples/time-travel.ipynb @@ -381,7 +381,7 @@ "source": [ "from langchain_core.messages import HumanMessage\n", "\n", - "thread = {\"configurable\": {\"thread_id\": '3'}}\n", + "thread = {\"configurable\": {\"thread_id\": \"3\"}}\n", "for event in app.stream(\"hi! I'm bob\", thread):\n", " for v in event.values():\n", " print(v)" @@ -551,7 +551,7 @@ } ], "source": [ - "thread = {\"configurable\": {\"thread_id\": '4'}}\n", + "thread = {\"configurable\": {\"thread_id\": \"4\"}}\n", "for event in app_w_interrupt.stream(\"what is the weather in sf currently\", thread):\n", " for v in event.values():\n", " print(v)" @@ -642,7 +642,9 @@ "metadata": {}, "outputs": [], "source": [ - "current_values.values[-1].tool_calls[0]['args']['query'] = \"weather in San Francisco today\"" + "current_values.values[-1].tool_calls[0][\"args\"][\n", + " \"query\"\n", + "] = \"weather in San Francisco today\"" ] }, { @@ -799,7 +801,7 @@ "source": [ "for state in app_w_interrupt.get_state_history(thread):\n", " print(state)\n", - " print('--')\n", + " print(\"--\")\n", " if len(state.values) == 2:\n", " to_replay = state" ] @@ -911,7 +913,9 @@ "metadata": {}, "outputs": [], "source": [ - "branch_config = app_w_interrupt.update_state(to_replay.config, AIMessage(content='All done here!', id=to_replay.values[-1].id))" + "branch_config = app_w_interrupt.update_state(\n", + " to_replay.config, AIMessage(content=\"All done here!\", id=to_replay.values[-1].id)\n", + ")" ] }, { diff --git a/examples/usaco/usaco.ipynb b/examples/usaco/usaco.ipynb index 1f55d7de0..dc864cbc8 100644 --- a/examples/usaco/usaco.ipynb +++ b/examples/usaco/usaco.ipynb @@ -1323,10 +1323,14 @@ "builder.add_edge(\"draft\", \"retrieve\")\n", "builder.add_edge(\"retrieve\", \"solve\")\n", "builder.add_edge(\"solve\", \"evaluate\")\n", + "\n", + "\n", "def control_edge(state: State):\n", " if state.get(\"status\") == \"success\":\n", " return END\n", " return \"solve\"\n", + "\n", + "\n", "builder.add_conditional_edges(\"evaluate\", control_edge, {END: END, \"solve\": \"solve\"})\n", "checkpointer = SqliteSaver.from_conn_string(\":memory:\")" ] diff --git a/examples/visualization.ipynb b/examples/visualization.ipynb index 234cbbc57..9ae41008f 100644 --- a/examples/visualization.ipynb +++ b/examples/visualization.ipynb @@ -214,7 +214,7 @@ { "cell_type": "code", "execution_count": 6, - "id": "df39af17", + "id": "6b7dc713", "metadata": { "ExecuteTime": { "end_time": "2024-04-19T11:25:40.358604Z", @@ -227,8 +227,9 @@ "from IPython.display import display, HTML\n", "import base64\n", "\n", + "\n", "def display_image(image_bytes: bytes, width=300):\n", - " decoded_img_bytes = base64.b64encode(image_bytes).decode('utf-8')\n", + " decoded_img_bytes = base64.b64encode(image_bytes).decode(\"utf-8\")\n", " html = f''\n", " display(HTML(html))" ] @@ -271,7 +272,7 @@ } ], "source": [ - "#%%capture --no-stderr\n", + "%%capture --no-stderr\n", "%pip install pygraphviz" ] }, @@ -374,10 +375,9 @@ } ], "source": [ - "# %%capture --no-stderr\n", - "%pip install pyppeteer\n", - "# %%capture --no-stderr\n", - "%pip install nest_asyncio" + "%%capture --no-stderr\n", + "%pip install --quiet pyppeteer\n", + "%pip install --quiet nest_asyncio" ] }, { @@ -418,17 +418,19 @@ "import nest_asyncio\n", "from langchain_core.runnables.graph import CurveStyle, NodeColors, MermaidDrawMethod\n", "\n", - "nest_asyncio.apply() # Required for Jupyter Notebook to run async functions\n", + "nest_asyncio.apply() # Required for Jupyter Notebook to run async functions\n", "\n", - "display_image(app.get_graph().draw_mermaid_png(\n", - " curve_style=CurveStyle.LINEAR,\n", - " node_colors=NodeColors(start=\"#ffdfba\", end=\"#baffc9\", other=\"#fad7de\"),\n", - " wrap_label_n_words=9,\n", - " output_file_path=None,\n", - " draw_method=MermaidDrawMethod.PYPPETEER,\n", - " background_color=\"white\",\n", - " padding=10\n", - "))" + "display_image(\n", + " app.get_graph().draw_mermaid_png(\n", + " curve_style=CurveStyle.LINEAR,\n", + " node_colors=NodeColors(start=\"#ffdfba\", end=\"#baffc9\", other=\"#fad7de\"),\n", + " wrap_label_n_words=9,\n", + " output_file_path=None,\n", + " draw_method=MermaidDrawMethod.PYPPETEER,\n", + " background_color=\"white\",\n", + " padding=10,\n", + " )\n", + ")" ] }, { @@ -469,9 +471,11 @@ } ], "source": [ - "display_image(app.get_graph().draw_mermaid_png(\n", - " draw_method=MermaidDrawMethod.API,\n", - "))" + "display_image(\n", + " app.get_graph().draw_mermaid_png(\n", + " draw_method=MermaidDrawMethod.API,\n", + " )\n", + ")" ] } ], diff --git a/langgraph/checkpoint/sqlite.py b/langgraph/checkpoint/sqlite.py index c685a5842..e4c3be2a3 100644 --- a/langgraph/checkpoint/sqlite.py +++ b/langgraph/checkpoint/sqlite.py @@ -154,7 +154,7 @@ class SqliteSaver(BaseCheckpointSaver, AbstractContextManager): self.is_setup = True @contextmanager - def cursor(self, transaction: bool = True): + def cursor(self, transaction: bool = True) -> Iterator[sqlite3.Cursor]: """Get a cursor for the SQLite database. This method returns a cursor for the SQLite database. It is used internally