From 215a1b2bfcefad0603c10c1234a5e73a822a0d77 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 19:24:09 -0800 Subject: [PATCH 01/16] Simple example --- .../multi-agent-collaboration.ipynb | 475 ++++++++++++++++++ examples/advanced_agents/multi-agent/plot.png | Bin 0 -> 21953 bytes 2 files changed, 475 insertions(+) create mode 100644 examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb create mode 100644 examples/advanced_agents/multi-agent/plot.png diff --git a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb new file mode 100644 index 000000000..ade3e1609 --- /dev/null +++ b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb @@ -0,0 +1,475 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334", + "metadata": {}, + "source": [ + "# Multi-agent Collaboration Intro\n", + "\n", + "A single agent can usually operate effectively using a handful of tools and a scoped domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. One way to improve your system's performance is through multi-agent collaboration. Each agent can specialize in a task or domain, and LangGraph can effectively orchestrate them to accomplish a larger goal. \n", + "\n", + "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d7b6dcc-c985-46e2-8457-7e6b0298b950", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install -U langchain langchain_openai langsmith pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_if_undefined(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass(f\"Please provide your {var}\")\n", + "\n", + "\n", + "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "\n", + "# Optional, add tracing in LangSmith\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" + ] + }, + { + "cell_type": "markdown", + "id": "7d5fc0f3-5d9e-4e72-a281-177f101c2a7d", + "metadata": {}, + "source": [ + "## Example 1: 2 Agents\n", + "\n", + "Below is an example of 2 agents collaborating to accomplish a single task." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "075c91c3-c249-471d-b259-41975faa83fb", + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "from typing import List, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from langchain_core.tools import tool\n", + "\n", + "USER_DATA = [\n", + " {\n", + " \"id\": 1,\n", + " \"name\": \"Alice\",\n", + " \"email\": \"alice@gmail.com\",\n", + " \"favorite_color\": \"red\",\n", + " \"favorite_foods\": [1, 2, 3],\n", + " },\n", + " {\n", + " \"id\": 21,\n", + " \"name\": \"Bob\",\n", + " \"email\": \"bob@hotmail.com\",\n", + " \"favorite_color\": \"orange\",\n", + " \"favorite_foods\": [4, 5, 6],\n", + " },\n", + " {\n", + " \"id\": 35,\n", + " \"name\": \"Charlie\",\n", + " \"email\": \"charlie@yahoo.com\",\n", + " \"favorite_color\": \"yellow\",\n", + " \"favorite_foods\": [3, 7, 2],\n", + " },\n", + " {\n", + " \"id\": 41,\n", + " \"name\": \"Donna\",\n", + " \"email\": \"donna@example.com\",\n", + " \"favorite_color\": \"green\",\n", + " \"favorite_foods\": [6, 1, 4],\n", + " },\n", + " {\n", + " \"id\": 42,\n", + " \"name\": \"Eve\",\n", + " \"email\": \"eve@example.org\",\n", + " \"favorite_color\": \"blue\",\n", + " \"favorite_foods\": [5, 7, 4],\n", + " },\n", + " {\n", + " \"id\": 43,\n", + " \"name\": \"Frank The Cat\",\n", + " \"email\": \"frank.the.cat@langchain.dev\",\n", + " \"favorite_color\": \"yellow\",\n", + " \"favorite_foods\": [3],\n", + " },\n", + "]\n", + "\n", + "FOOD_DATA = [\n", + " {\n", + " \"id\": 1,\n", + " \"name\": \"Pizza\",\n", + " \"calories\": 285, # Calories per serving\n", + " \"allergic_ingredients\": [\"Gluten\", \"Dairy\"],\n", + " },\n", + " {\n", + " \"id\": 2,\n", + " \"name\": \"Chocolate\",\n", + " \"calories\": 50, # Calories per serving\n", + " \"allergic_ingredients\": [\"Milk\", \"Soy\"],\n", + " },\n", + " {\n", + " \"id\": 3,\n", + " \"name\": \"Sushi\",\n", + " \"calories\": 300, # Calories per serving\n", + " \"allergic_ingredients\": [\"Fish\", \"Soy\"],\n", + " },\n", + " {\n", + " \"id\": 4,\n", + " \"name\": \"Burger\",\n", + " \"calories\": 350, # Calories per serving\n", + " \"allergic_ingredients\": [\"Gluten\", \"Dairy\"],\n", + " },\n", + " {\n", + " \"id\": 5,\n", + " \"name\": \"Ice Cream\",\n", + " \"calories\": 200, # Calories per serving\n", + " \"allergic_ingredients\": [\"Dairy\"],\n", + " },\n", + " {\n", + " \"id\": 6,\n", + " \"name\": \"Pasta\",\n", + " \"calories\": 180, # Calories per serving\n", + " \"allergic_ingredients\": [\"Gluten\"],\n", + " },\n", + " {\n", + " \"id\": 7,\n", + " \"name\": \"Salad\",\n", + " \"calories\": 50, # Calories per serving\n", + " \"allergic_ingredients\": [],\n", + " },\n", + "]\n", + "\n", + "\n", + "@tool\n", + "def list_users() -> List[str]:\n", + " \"\"\"Return a list of all the user names.\"\"\"\n", + " return [user_info[\"name\"] for user_info in USER_DATA]\n", + "\n", + "\n", + "@tool\n", + "def get_user_favorite_food_ids(user_id: int) -> List[int]:\n", + " \"\"\"Get the favorite foods of the user with the provided ID.\"\"\"\n", + " for user_info in USER_DATA:\n", + " if user_info[\"id\"] == user_id:\n", + " return user_info[\"favorite_foods\"]\n", + " raise ValueError(f\"Id: {user_id} not found.\")\n", + "\n", + "\n", + "@tool\n", + "def get_food_name(food_id: int) -> str:\n", + " \"\"\"Get the food name.\"\"\"\n", + " for food_info in FOOD_DATA:\n", + " if food_info[\"id\"] == food_id:\n", + " return food_info[\"name\"]\n", + " raise ValueError(f\"Id: {food_id} not found.\")\n", + "\n", + "\n", + "@tool\n", + "def get_user_id(user_name: str) -> int:\n", + " \"\"\"Return the ID of the provided user name.\"\"\"\n", + " for user_info in USER_DATA:\n", + " if user_info[\"name\"] == user_name:\n", + " return user_info[\"id\"]\n", + " raise ValueError(f\"Name: {user_name} not found.\")\n", + "\n", + "\n", + "@tool\n", + "def create_barplot(\n", + " data: Union[List[float], List[int]],\n", + " labels: Union[List[str], None] = None,\n", + " title: str = \"Bar Plot\",\n", + " xlabel: str = \"X\",\n", + " ylabel: str = \"Y\",\n", + " color: Union[str, List[str]] = \"blue\",\n", + ") -> Tuple[plt.Figure, plt.Axes]:\n", + " \"\"\"\n", + " Generates a bar plot from the provided data and returns the figure and axis objects.\n", + "\n", + " :param data: A list of numerical values for the bar heights.\n", + " :param labels: A list of strings for the bar labels. Default is None.\n", + " :param title: Title of the plot. Default is 'Bar Plot'.\n", + " :param xlabel: Label for the X-axis. Default is 'X'.\n", + " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", + " :param color: Color of the bars. Can be a single color or a list of colors. Default is 'blue'.\n", + " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", + " :return: Tuple containing the figure and axes objects.\n", + " \"\"\"\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + " x_positions = range(len(data))\n", + "\n", + " if labels and len(labels) == len(data):\n", + " plt.xticks(x_positions, labels)\n", + "\n", + " ax.bar(x_positions, data, color=color)\n", + " ax.set_title(title)\n", + " ax.set_xlabel(xlabel)\n", + " ax.set_ylabel(ylabel)\n", + " plt.savefig(\"./plot.png\")\n", + " return \"./plot.png\"" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b0ca7d80-31e4-4394-bfce-ffac314bac7d", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import operator\n", + "from typing import Annotated, Sequence, TypedDict\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.messages import (\n", + " AIMessage,\n", + " BaseMessage,\n", + " FunctionMessage,\n", + " HumanMessage,\n", + ")\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_openai import ChatOpenAI\n", + "from typing_extensions import TypedDict\n", + "\n", + "from langgraph.graph import END, StateGraph\n", + "from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation\n", + "\n", + "\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]\n", + " sender: str" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", + "metadata": {}, + "outputs": [], + "source": [ + "workflow = StateGraph(AgentState)\n", + "\n", + "# This a helper class we have that is useful for running tools\n", + "# It takes in an agent action and calls that tool and returns the result\n", + "tools = [\n", + " list_users,\n", + " get_user_favorite_food_ids,\n", + " get_food_name,\n", + " get_user_id,\n", + " create_barplot,\n", + "]\n", + "tool_executor = ToolExecutor(tools)\n", + "\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", + " \" Use the provided tools to progress towards answering the question.\"\n", + " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", + " \" will help where you left off. Execute what you can to \"\n", + " \" If you have the final answer, prefix your response with FINAL ANSWER:\",\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " ]\n", + ")\n", + "\n", + "\n", + "def add_agent_node(name: str, llm, tools, prompt):\n", + " functions = [format_tool_to_openai_function(t) for t in tools]\n", + " chain = (\n", + " (lambda x: {**x, \"intermediate_steps\": []})\n", + " | prompt\n", + " | llm.bind_functions(functions)\n", + " | (lambda x: {\"messages\": [x], \"sender\": name})\n", + " )\n", + " workflow.add_node(name, chain)\n", + "\n", + "\n", + "def call_tool(state):\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", + " 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", + " )\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 return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}\n", + "\n", + "\n", + "def should_continue(state):\n", + " # This is the router\n", + " messages = state[\"messages\"]\n", + " last_message = messages[-1]\n", + " if \"function_call\" in last_message.additional_kwargs:\n", + " return \"call_tool\"\n", + " if \"FINAL ANSWER\" in last_message.content:\n", + " return \"end\"\n", + " return \"continue\"\n", + "\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4\")\n", + "add_agent_node(\n", + " \"user_info\",\n", + " llm,\n", + " [\n", + " list_users,\n", + " get_user_favorite_food_ids,\n", + " get_food_name,\n", + " get_user_id,\n", + " ],\n", + " prompt,\n", + ")\n", + "add_agent_node(\"plot_bars\", llm, [create_barplot], prompt)\n", + "workflow.add_node(\"call_tool\", call_tool)\n", + "workflow.add_conditional_edges(\n", + " \"user_info\",\n", + " should_continue,\n", + " {\"continue\": \"plot_bars\", \"call_tool\": \"call_tool\", \"end\": END},\n", + ")\n", + "workflow.add_conditional_edges(\n", + " \"plot_bars\",\n", + " should_continue,\n", + " {\"continue\": \"user_info\", \"call_tool\": \"call_tool\", \"end\": END},\n", + ")\n", + "# We will let the user_info tool decide when to respond.\n", + "workflow.add_edge(\"call_tool\", \"user_info\")\n", + "workflow.set_entry_point(\"user_info\")\n", + "graph = workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "176a99b0-b457-45cf-8901-90facaa852da", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'messages': [HumanMessage(content='Draw a bar plot of the favorite foods of Eve and Donna'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_name\": \"Eve\"\\n}', 'name': 'get_user_id'}}),\n", + " FunctionMessage(content='42', name='get_user_id'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_id\": 42\\n}', 'name': 'get_user_favorite_food_ids'}}),\n", + " FunctionMessage(content='[5, 7, 4]', name='get_user_favorite_food_ids'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 5\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Ice Cream', name='get_food_name'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 7\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Salad', name='get_food_name'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 4\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Burger', name='get_food_name'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_name\": \"Donna\"\\n}', 'name': 'get_user_id'}}),\n", + " FunctionMessage(content='41', name='get_user_id'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_id\": 41\\n}', 'name': 'get_user_favorite_food_ids'}}),\n", + " FunctionMessage(content='[6, 1, 4]', name='get_user_favorite_food_ids'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 6\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Pasta', name='get_food_name'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 1\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Pizza', name='get_food_name'),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 4\\n}', 'name': 'get_food_name'}}),\n", + " FunctionMessage(content='Burger', name='get_food_name'),\n", + " AIMessage(content=\"Based on the data obtained, we can make the following observations about the favorite foods of Eve and Donna:\\n\\n- Eve's favorite foods are Ice Cream, Salad, and Burger.\\n- Donna's favorite foods are Pasta, Pizza, and Burger.\\n\\nTo draw a bar plot, we need a tool with graphical capabilities which is currently not available in this environment. However, if we were to represent this data on a bar plot, we would have 'Food Items' on the x-axis and 'Count' on the y-axis. Each user would have a different color bar. For each food item, there would be a bar representing the count of users who have that food as their favorite. \\n\\nFor example, 'Burger' would have a count of 2 because both Eve and Donna have it as their favorite food. Other food items like 'Ice Cream', 'Salad', 'Pasta', and 'Pizza' would have a count of 1 as they are the favorite of only one user. \\n\\nPlease use a tool with graphical capabilities like Python's matplotlib library or Excel to create this bar plot.\"),\n", + " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", + " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", + " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", + " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", + " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"data\": [1, 1, 2, 1, 1],\\n \"labels\": [\"Ice Cream\", \"Salad\", \"Burger\", \"Pasta\", \"Pizza\"],\\n \"title\": \"Favorite Foods of Eve and Donna\",\\n \"xlabel\": \"Food Items\",\\n \"ylabel\": \"Count\",\\n \"color\": [\"blue\", \"blue\", \"blue\", \"orange\", \"orange\"]\\n}', 'name': 'create_barplot'}}),\n", + " FunctionMessage(content='./plot.png', name='create_barplot'),\n", + " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", + " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", + " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", + " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", + " AIMessage(content=\"FINAL ANSWER:\\n\\nUnfortunately, I'm unable to draw a bar plot in this environment. However, I have provided the data and the Python code needed to create the bar plot in your local environment using the matplotlib library.\\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following Python code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\")],\n", + " 'sender': 'user_info'}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAIjCAYAAADvBuGTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/OQEPoAAAACXBIWXMAAA9hAAAPYQGoP6dpAABSDUlEQVR4nO3deVhV1eL/8c8B4aAoODMUgYo5pZJjmqYWiqZerW6pDaJXrSwzozLplnNZlmaDSlqOZXlNs66VQ5iahpoDdS3zqheHFJwSEExQWL8/+rG/ngAF23pA36/n2U+dtddee+3DYns+7L3XcRhjjAAAAAAAf4mHuzsAAAAAAFcDwhUAAAAA2IBwBQAAAAA2IFwBAAAAgA0IVwAAAABgA8IVAAAAANiAcAUAAAAANiBcAQAAAIANCFcAAAAAYAPCFQCUQnPmzJHD4dC+ffvc3ZUrqn379mrfvv0V3WdGRoYGDhyowMBAORwODRs27IruvzTo16+fwsLC3N0NAHA7whWAa15eUCloGTFihLu7V2TTpk3TnDlzbG+3ffv2hb4/v/zyi+37K2lefvllzZkzR4MHD9b8+fP10EMPFVo3LCys0Peqc+fOV7DXJdP5Y8nDw0N+fn6qU6eOHnroIa1atcrd3QOAv6yMuzsAACXF2LFjVaNGDZeym266yU29ubCHHnpIvXv3ltPptMqmTZumqlWrql+/frbv7/rrr9eECRPylQcHB9u+r5Jm9erVuuWWWzRq1Kgi1Y+IiNDTTz+dr/xaeK+K4vyxlJmZqT179mjJkiX64IMPdN999+mDDz6Ql5eXm3sJAJeGcAUA/1+XLl3UrFkzd3fjgjIzM+Xr6ytPT095enpesf36+/vrwQcfvGL7K0mOHj2q+vXrF7n+ddddd82+V0VR0Fh65ZVXNHToUE2bNk1hYWF69dVX3dQ7APhruC0QAC5i//79euyxx1SnTh2VLVtWVapU0b333uvyvNOWLVvkcDg0d+7cfNuvWLFCDodDy5Yts8q2b9+uLl26yM/PT+XLl9cdd9yhjRs3umyXd7vi2rVr9dhjj6l69eq6/vrrXdbl9SEsLEw//fST1q5da912df6zSampqRo2bJhCQkLkdDoVHh6uV199Vbm5uba8R+fOndO4ceNUq1YtOZ1OhYWF6fnnn1dWVla+utOmTVODBg3kdDoVHBysxx9/XKmpqfnqzZgxQ7Vq1VLZsmXVokULffvttwXu++2331aDBg1Urlw5VapUSc2aNdOCBQsu2uejR49qwIABCggIkI+Pjxo3buzy81uzZo0cDoeSkpL0xRdfWO/rX33O7fXXX5fD4dD+/fvzrYuNjZW3t7dOnjxplW3atEmdO3eWv7+/ypUrp3bt2mnDhg0X3U92drZGjhyppk2byt/fX76+vmrbtq2++eYbl3r79u2Tw+HQ66+/br3nTqdTzZs31/fff5+v3aVLl+qmm26Sj4+PbrrpJn366aeX8C648vT01FtvvaX69evrnXfeUVpamrWuqGMrLCxM3bp10/r169WiRQv5+PioZs2amjdvnku9vN+dDRs2KCYmRtWqVZOvr6/uuusuHTt2zKXuZ599pq5duyo4OFhOp1O1atXSuHHjlJOT85ePGcBVygDANW727NlGkvn666/NsWPHXBZjjFm0aJFp3LixGTlypJkxY4Z5/vnnTaVKlUxoaKjJzMy02qlZs6a5884787Xfv39/U6lSJZOdnW2MMWbHjh3G19fXBAUFmXHjxplXXnnF1KhRwzidTrNx48Z8/apfv75p166defvtt80rr7zisi4pKckYY8ynn35qrr/+elO3bl0zf/58M3/+fLNy5UpjjDGZmZmmUaNGpkqVKub55583cXFxpm/fvsbhcJgnn3zyou9Pu3btTN26dfO9N6dOnbLqREdHG0nm73//u5k6darp27evkWR69uzp0taoUaOMJBMZGWnefvttM2TIEOPp6WmaN29uvT/GGPPee+8ZSaZ169bmrbfeMsOGDTMVK1Y0NWvWNO3atbPqzZgxw9rvu+++a958800zYMAAM3To0Ase0+nTp029evWMl5eXeeqpp8xbb71l2rZtaySZKVOmGGOMSUlJMfPnzzdVq1Y1ERER1vuakZFRaLuhoaGmU6dO+d6rY8eOmdOnTxtjjNm/f79xOBxm4sSJ+bavWbOm6dq1q/U6Pj7eeHt7m1atWplJkyaZN954wzRq1Mh4e3ubTZs2XfAYjx07ZoKCgkxMTIyZPn26mThxoqlTp47x8vIy27dvt+olJSUZSebmm2824eHh5tVXXzUTJ040VatWNddff73Lz2XFihXGw8PD3HTTTWby5Mnmn//8p/H39zcNGjQwoaGhF+yPMX+MpQYNGhS6fty4cUaSWbZs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+qpXr17q1auXsrOzdffdd+ull15SbGysfHx8Cu3zjz/+qNzcXJerV3m3euX14XLq1auXHnvsMe3atUsLFy5UuXLl1L17d2t9rVq1JEl+fn6X9LP75JNPVLNmTS1ZssRlbBT1+7r+7Pyfy58VNJ6LKycnRwsWLFC5cuXUpk0ba59FGVuXw5o1a3TixAktWbJEt912m1WelJR02fYJoPTjmSsAuAhPT898f/l+++23C5yOuV69emrYsKEWLlyohQsXKigoyOWDmaenpzp16qTPPvvMZUrvI0eOaMGCBWrTpo11W1hx+fr6Fhj47rvvPiUkJGjFihX51qWmpurcuXOXtL88d955pyRpypQpLuWTJ0+WJHXt2lWSFBkZKW9vb7311lsu7+f777+vtLQ0q16zZs1UrVo1xcXFKTs726o3Z86cfMd34sQJl9fe3t6qX7++jDEut8EV1OeUlBQtXLjQKjt37pzefvttlS9fXu3atSvi0V+6e+65R56envroo4+0aNEidevWTb6+vtb6pk2bqlatWnr99det2+XO9+dpw/8s78rM+e/1pk2blJCQcEn9DQoKUkREhObOnetyS9yqVav0888/X1KbeXJycjR06FDt3LlTQ4cOtX4Hijq2LoeC3r/s7GxNmzbtsu0TQOnHlSsAuIhu3bpp/vz58vf3V/369ZWQkKCvv/5aVapUKbB+r169NHLkSPn4+GjAgAH5nusZP368Vq1apTZt2uixxx5TmTJl9O677yorK0sTJ0685H42bdpU06dP1/jx4xUeHq7q1avr9ttv17PPPqvPP/9c3bp1U79+/dS0aVNlZmbqP//5jz755BPt27dPVatWveT9Nm7cWNHR0ZoxY4Z1K9XmzZs1d+5c9ezZUx06dJD0x1W72NhYjRkzRp07d9bf/vY37dq1S9OmTVPz5s2tL5b18vLS+PHj9cgjj+j2229Xr169lJSUpNmzZ+d75qpTp04KDAzUrbfeqoCAAO3cuVPvvPOOunbtaj23U5CHH35Y7777rvr166etW7cqLCxMn3zyiTZs2KApU6ZccNuLOXTokD744IN85eXLl1fPnj2t19WrV1eHDh00efJknTp1ypr0JI+Hh4fee+89denSRQ0aNFD//v113XXX6dChQ/rmm2/k5+enf//734X2o1u3blqyZInuuusude3aVUlJSYqLi1P9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TZqkH374QfPnzy9wm+zs7CvcS7jbPffco+3bt2vu3Ln673//q88//1zt27fXiRMn3N01lHCdO3dWcnKykpOTFR8frzJlyqhbt26X3F5OTo5yc3Nt7OH/4dxW+uSNr927d+vpp5/W6NGj9dprr7m7WyilChtP/v7+ttx9AfchXKFAubm5mjhxosLDw+V0OnXDDTfopZdestYfPHhQ9913nypWrKjKlSurR48e2rdvX6Htbd68WS+//LImTZqk1157Ta1bt1ZYWJg6duyoxYsXKzo6WpI0evRoRURE6L333lONGjWs7yZLTU3VwIEDVa1aNfn5+en222/XDz/8YLW/d+9e9ejRQwEBASpfvryaN2+ur7/+2qUPYWFhGj9+vPr27avy5csrNDRUn3/+uY4dO6YePXpYV9K2bNli4zuJ4khNTdW3336rV199VR06dFBoaKhatGih2NhY/e1vf5MkTZ48WQ0bNpSvr69CQkL02GOPKSMjo9A2izI2jh49qu7du6ts2bKqUaOGPvzww8t6nLg88q50BgYGKiIiQiNGjNDBgwd17NixAq8QJCYmyuFwWOeuvFtKP//8c9WvX19Op1MHDhxQcnKyunbtao2PBQsW5Ltt9GLnqMLObSg98sZXaGioBg8erMjISH3++ecXPSft379f3bt3V6VKleTr66sGDRroyy+/1L59+9ShQwdJUqVKleRwONSvXz9J0vLly9WmTRtVrFhRVapUUbdu3bR37153HDYuk8LG0/l3Eu3bt8/linze0r59e0lS+/btC1yfd04r7r+XsAfhCgWKjY3VK6+8ohdffFE///yzFixYYH2x89mzZxUVFaUKFSro22+/1YYNG1S+fHl17ty50L/Gfvjhhypfvrwee+yxAtef/1eaPXv2aPHixVqyZIkSExMlSffee6+OHj2qr776Slu3blWTJk10xx13WLcTZmRk6M4771R8fLy2b9+uzp07q3v37jpw4IDLft544w3deuut2r59u7p27aqHHnpIffv21YMPPqht27apVq1a6tu3r/j6N/coX768ypcvr6VLlyorK6vAOh4eHnrrrbf0008/ae7cuVq9erWGDx9eaJtFGRv9+vXTwYMH9c033+iTTz7RtGnTdPToUduPD1dORkaGPvjgA4WHh6tKlSpF3u706dN69dVX9d577+mnn35S9erV1bdvXx0+fFhr1qzR4sWLNWPGjHzj42LnKKngcxtKr7Jlyyo7O/ui56THH39cWVlZWrdunf7zn//o1VdfVfny5RUSEqLFixdLknbt2qXk5GS9+eabkqTMzEzFxMRoy5Ytio+Pl4eHh+66667LdiUV7pc3ns4XEhJiXY1PTk7W9u3bVaVKFd12222SpCVLlrisv/vuu1WnTh3r81px/72ETQxgjImOjjY9evQwxhiTnp5unE6nmTlzZoF158+fb+rUqWNyc3OtsqysLFO2bFmzYsWKArfp0qWLadSo0UX7MWrUKOPl5WWOHj1qlX377bfGz8/PnDlzxqVurVq1zLvvvltoWw0aNDBvv/229To0NNQ8+OCD1uvk5GQjybz44otWWUJCgpFkkpOTL9pXXB6ffPKJqVSpkvHx8TGtW7c2sbGx5ocffii0/qJFi0yVKlWs17Nnzzb+/v4X3Mf5Y2PXrl1Gktm8ebO1fufOnUaSeeONN/7SseDKiY6ONp6ensbX19f4+voaSSYoKMhs3brVGGPMN998YySZkydPWtts377dSDJJSUnGmD/GjiSTmJho1ckbC99//71Vtnv3bpfxUZRzVEHnNpQe5/8bmZuba1atWmWcTqd55pln8tX98zmpYcOGZvTo0QW2W9C4LMixY8eMJPOf//znko8BJceFxtP56873+++/m5YtW5pu3bqZnJycfOsnT55sKlasaHbt2lXofv88NnF5cOUK+ezcuVNZWVm64447Clz/ww8/aM+ePapQoYJ1paFy5co6c+ZMobctmGJcCQoNDVW1atVc9peRkaEqVapY+ytfvrySkpKs/WVkZOiZZ55RvXr1VLFiRZUvX147d+7Md+WqUaNG1v/n/WWnYcOG+cq4auE+99xzjw4fPqzPP/9cnTt31po1a9SkSRPrAd+vv/5ad9xxh6677jpVqFBBDz30kE6cOKHTp08X2N7FxsbOnTtVpkwZNW3a1Nqmbt263PNeCnXo0EGJiYlKTEzU5s2bFRUVpS5dumj//v1FbsPb29vlPLFr1y6VKVNGTZo0scrCw8NVqVIl63VRzlFS/nMbSpdly5apfPny8vHxUZcuXdSrVy+NHj36ouekoUOHavz48br11ls1atSoIk2ysnv3bvXp00c1a9aUn5+fwsLCJCnfv2kovQobT4X5xz/+oVOnTmnBggXy8HD9+P7V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UOI99thjOnjwoJ544gn98ssv+uyzzzRq1CjFxMTIw8NDvr6+Gjx4sJ599lktX75cP//8swYNGqTTp09rwIABkqRHH31Uu3fv1rPPPqtdu3ZpwYIFmjNnTrH7EhYWpvj4eKWkpOjkyZOSpJEjR2revHkaM2aMfvrpJ+3cuVMff/yxXnjhhUs+5t9//11DhgzRmjVrtH//fm3YsEHff/+96tWrd8ltAgAuL8IVAKDEu+666/Tll19q8+bNaty4sR599FENGDDAJby88soruueee/TQQw+pSZMm2rNnj1asWKFKlSpJkm644QYtXrxYS5cuVePGjRUXF6eXX3652H2ZNGmSVq1apZCQEN18882SpKioKC1btkwrV65U8+bNdcstt+iNN95QaGjoJR+zp6enTpw4ob59++rGG2/Ufffdpy5dumjMmDGX3CYA4PJymPPvsQAAAAAAXBKuXAEAAACADQhXAAAAAGADwhUAAAAA2IBwBQAAAAA2IFwBAAAAgA0IVwAAAABgA8IVAAAAANiAcAUAAAAANiBcAQAAAIANCFcAAAAAYAPCFQAAAADY4P8Bw6j3d18E1YkAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "result = graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(\n", + " content=\"Draw a bar plot of the favorite foods of Eve and Donna\"\n", + " )\n", + " ]\n", + " },\n", + " # Maximum number of steps to take in the graph\n", + " {\"recursion_limit\": 150},\n", + ")\n", + "result[\"messages\"][-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e88f835-2c94-4fe1-90d6-c9edac105195", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/advanced_agents/multi-agent/plot.png b/examples/advanced_agents/multi-agent/plot.png new file mode 100644 index 0000000000000000000000000000000000000000..33adddc8c350a9a41788234e3886ac4a49953e2b GIT binary patch literal 21953 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 literal 0 HcmV?d00001 From 84a1915896349918797222a44162ac74e10c18e1 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:18:54 -0800 Subject: [PATCH 02/16] del image --- examples/advanced_agents/multi-agent/plot.png | Bin 21953 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/advanced_agents/multi-agent/plot.png diff --git a/examples/advanced_agents/multi-agent/plot.png b/examples/advanced_agents/multi-agent/plot.png deleted file mode 100644 index 33adddc8c350a9a41788234e3886ac4a49953e2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21953 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 From a6e3d8d29ce72048cd909134285d01edc7f06ea1 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:26:28 -0800 Subject: [PATCH 03/16] Add collab --- .../multi-agent/agent_supervisor.ipynb | 328 ++++++++++++++++++ .../multi-agent-collaboration.ipynb | 285 ++++----------- 2 files changed, 386 insertions(+), 227 deletions(-) create mode 100644 examples/advanced_agents/multi-agent/agent_supervisor.ipynb diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb new file mode 100644 index 000000000..940055f33 --- /dev/null +++ b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb @@ -0,0 +1,328 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", + "metadata": {}, + "source": [ + "## Example 2: Agent Team Supervisor\n", + "\n", + "The prevoius example routed messages automatically based on the output of the initial researcher agent.\n", + "\n", + "We can also choose to use an LLM to orchestrate the different agents.\n", + "\n", + "Below, we will create an agent group, with an agent supervisor to help delegate tasks.\n", + "\n", + "To simplify each agent node, we will use the AgentExecutor class from LangChain." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", + "metadata": {}, + "outputs": [], + "source": [ + "# %%capture --no-stderr\n", + "# %pip install -U langchain langchain_openai langchain_experimental langsmith pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_if_undefined(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass(f\"Please provide your {var}\")\n", + "\n", + "\n", + "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "_set_if_undefined(\"TAVILY_API_KEY\")\n", + "\n", + "# Optional, add tracing in LangSmith\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f04c6778-403b-4b49-9b93-678e910d5cec", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.tools import tool\n", + "\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", + "\n", + "\n", + "@tool\n", + "def create_plot(\n", + " data: Union[List[float], List[int]],\n", + " labels: Union[List[str], None] = None,\n", + " title: str = \"Plot\",\n", + " xlabel: str = \"X\",\n", + " ylabel: str = \"Y\",\n", + " color: Union[str, List[str]] = \"blue\",\n", + " plot_type: str = \"bar\",\n", + ") -> Tuple[plt.Figure, plt.Axes]:\n", + " \"\"\"\n", + " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", + "\n", + " :param data: A list of numerical values for the bar heights or line points.\n", + " :param labels: A list of strings for the bar or point labels. Default is None.\n", + " :param title: Title of the plot. Default is 'Plot'.\n", + " :param xlabel: Label for the X-axis. Default is 'X'.\n", + " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", + " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", + " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", + " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", + " :return: Tuple containing the figure and axes objects.\n", + " \"\"\"\n", + " if plot_type not in [\"bar\", \"line\"]:\n", + " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", + "\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + " x_positions = range(len(data))\n", + "\n", + " if labels and len(labels) == len(data):\n", + " plt.xticks(x_positions, labels)\n", + "\n", + " if plot_type == \"bar\":\n", + " ax.bar(x_positions, data, color=color)\n", + " elif plot_type == \"line\":\n", + " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", + "\n", + " ax.set_title(title)\n", + " ax.set_xlabel(xlabel)\n", + " ax.set_ylabel(ylabel)\n", + "\n", + " return fig, ax" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict\n", + "\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain_core.messages import BaseMessage, HumanMessage\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.tools import BaseTool\n", + "from langchain_experimental.tools import PythonREPLTool\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "from langgraph.graph import END, StateGraph\n", + "\n", + "\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]\n", + " next: str\n", + "\n", + "\n", + "workflow = StateGraph(AgentState)\n", + "\n", + "\n", + "def create_agent_node(name: str, llm: ChatOpenAI, tools: list, system_prompt: str):\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " system_prompt,\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", + " ]\n", + " )\n", + " agent = create_openai_functions_agent(llm, tools, prompt)\n", + " executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n", + "\n", + " def _update_state(ai_message) -> dict:\n", + " if isinstance(ai_message, FunctionMessage):\n", + " result = ai_message\n", + " else:\n", + " message = ai_message.dict(exclude={\"type\"})\n", + " message[\"name\"] = name\n", + " result = HumanMessage(**message)\n", + " return {\n", + " \"messages\": [result],\n", + " \"sender\": name,\n", + " }\n", + "\n", + " chain = executor | _update_state\n", + " workflow.add_node(name, chain)\n", + "\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4\")\n", + "\n", + "create_agent_node(\"Researcher\", llm, [tavily_tool], \"You are a web researcher.\")\n", + "create_agent_node(\"Chart Generator\", llm, [create_plot], \"You are a chart generator.\")\n", + "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION\n", + "create_agent_node(\n", + " \"Data Analyst\",\n", + " llm,\n", + " [PythonREPLTool()],\n", + " \"You may generate safe python code to analyze data.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d6374825-912f-40c9-910d-afa267b401bf", + "metadata": {}, + "source": [ + "Almost done, now we need to create the team supervisor." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "17c108a0-6dc3-46fd-a5e6-a1fcfad5458a", + "metadata": {}, + "outputs": [], + "source": [ + "# So the team supervisor is an LLM node. It just picks the next t\n", + "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", + "\n", + "\n", + "def create_agent_supervisor(members: List[str], llm: ChatOpenAI, system_prompt: str):\n", + " options = [\"FINISH\"] + members\n", + " function_def = {\n", + " \"name\": \"route\",\n", + " \"description\": \"Select the next role.\",\n", + " \"parameters\": {\n", + " \"title\": \"routeSchema\",\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"next\": {\n", + " \"title\": \"Next\",\n", + " \"anyOf\": [\n", + " {\"enum\": options},\n", + " ],\n", + " }\n", + " },\n", + " \"required\": [\"next\"],\n", + " },\n", + " }\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " (\n", + " \"system\",\n", + " \"Given the conversation above, who should act next?\"\n", + " \" Or should we FINISH? Select one of: {options}\",\n", + " ),\n", + " ]\n", + " ).partial(options=str(options))\n", + " chain = (\n", + " prompt\n", + " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", + " | JsonOutputFunctionsParser()\n", + " )\n", + " workflow.add_node(\"supervisor\", chain)\n", + " conditional_map = {k: k for k in members}\n", + " conditional_map[\"FINISH\"] = END\n", + "\n", + " for member in members:\n", + " workflow.add_edge(member, \"supervisor\")\n", + " workflow.add_conditional_edges(\n", + " \"supervisor\", lambda x: x[\"next\"], conditional_map\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", + "metadata": {}, + "outputs": [], + "source": [ + "create_agent_supervisor(\n", + " [\"Researcher\", \"Chart Generator\", \"Data Analyst\"],\n", + " llm,\n", + " \"You are an agent supervisor tasked with managing work order.\"\n", + " \" Respond with only the role will optimally help us accomplish the user's task or question.\"\n", + " \" When finished, respond with FINISH.\",\n", + ")\n", + "\n", + "# Finally, add entrypoint\n", + "workflow.set_entry_point(\"supervisor\")\n", + "\n", + "\n", + "def enter(text: str) -> dict:\n", + " return {\"messages\": [HumanMessage(content=text)]}\n", + "\n", + "\n", + "graph = enter | workflow.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", + "metadata": {}, + "outputs": [ + { + "ename": "InvalidUpdateError", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mInvalidUpdateError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mgraph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mCode hello world and print it to the terminal\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1780\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 1778\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1779\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 1780\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1781\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1782\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 1783\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1784\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1785\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1786\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1787\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 1788\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:521\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 512\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 513\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 519\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 520\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 521\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 522\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 523\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 524\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 525\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 526\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 527\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 528\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 529\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:557\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 548\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 549\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 550\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 556\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 557\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 558\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 559\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 560\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 561\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 562\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 563\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 564\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 565\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1232\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1230\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1231\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1232\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m \u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43miterator\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1233\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:335\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m _interrupt_or_proceed(done, inflight, step)\n\u001b[1;32m 334\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[0;32m--> 335\u001b[0m \u001b[43m_apply_writes\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcheckpoint\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mchannels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpending_writes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 337\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdebug:\n\u001b[1;32m 338\u001b[0m print_checkpoint(step, channels)\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:687\u001b[0m, in \u001b[0;36m_apply_writes\u001b[0;34m(checkpoint, channels, pending_writes, config, for_step)\u001b[0m\n\u001b[1;32m 685\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m chan, vals \u001b[38;5;129;01min\u001b[39;00m pending_writes_by_channel\u001b[38;5;241m.\u001b[39mitems():\n\u001b[1;32m 686\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m chan \u001b[38;5;129;01min\u001b[39;00m channels:\n\u001b[0;32m--> 687\u001b[0m \u001b[43mchannels\u001b[49m\u001b[43m[\u001b[49m\u001b[43mchan\u001b[49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mupdate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvals\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 688\u001b[0m checkpoint[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mchannel_versions\u001b[39m\u001b[38;5;124m\"\u001b[39m][chan] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 689\u001b[0m updated_channels\u001b[38;5;241m.\u001b[39madd(chan)\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/channels/last_value.py:47\u001b[0m, in \u001b[0;36mLastValue.update\u001b[0;34m(self, values)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 46\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(values) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m---> 47\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m InvalidUpdateError()\n\u001b[1;32m 49\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m values[\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", + "\u001b[0;31mInvalidUpdateError\u001b[0m: " + ] + } + ], + "source": [ + "graph.invoke(\"Code hello world and print it to the terminal\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb index ade3e1609..30d666a9a 100644 --- a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb +++ b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc", "metadata": {}, "outputs": [], @@ -40,6 +40,7 @@ "\n", "_set_if_undefined(\"OPENAI_API_KEY\")\n", "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "_set_if_undefined(\"TAVILY_API_KEY\")\n", "\n", "# Optional, add tracing in LangSmith\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", @@ -58,179 +59,67 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "075c91c3-c249-471d-b259-41975faa83fb", "metadata": {}, "outputs": [], "source": [ - "import uuid\n", "from typing import List, Tuple, Union\n", "\n", "import matplotlib.pyplot as plt\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", "from langchain_core.tools import tool\n", "\n", - "USER_DATA = [\n", - " {\n", - " \"id\": 1,\n", - " \"name\": \"Alice\",\n", - " \"email\": \"alice@gmail.com\",\n", - " \"favorite_color\": \"red\",\n", - " \"favorite_foods\": [1, 2, 3],\n", - " },\n", - " {\n", - " \"id\": 21,\n", - " \"name\": \"Bob\",\n", - " \"email\": \"bob@hotmail.com\",\n", - " \"favorite_color\": \"orange\",\n", - " \"favorite_foods\": [4, 5, 6],\n", - " },\n", - " {\n", - " \"id\": 35,\n", - " \"name\": \"Charlie\",\n", - " \"email\": \"charlie@yahoo.com\",\n", - " \"favorite_color\": \"yellow\",\n", - " \"favorite_foods\": [3, 7, 2],\n", - " },\n", - " {\n", - " \"id\": 41,\n", - " \"name\": \"Donna\",\n", - " \"email\": \"donna@example.com\",\n", - " \"favorite_color\": \"green\",\n", - " \"favorite_foods\": [6, 1, 4],\n", - " },\n", - " {\n", - " \"id\": 42,\n", - " \"name\": \"Eve\",\n", - " \"email\": \"eve@example.org\",\n", - " \"favorite_color\": \"blue\",\n", - " \"favorite_foods\": [5, 7, 4],\n", - " },\n", - " {\n", - " \"id\": 43,\n", - " \"name\": \"Frank The Cat\",\n", - " \"email\": \"frank.the.cat@langchain.dev\",\n", - " \"favorite_color\": \"yellow\",\n", - " \"favorite_foods\": [3],\n", - " },\n", - "]\n", - "\n", - "FOOD_DATA = [\n", - " {\n", - " \"id\": 1,\n", - " \"name\": \"Pizza\",\n", - " \"calories\": 285, # Calories per serving\n", - " \"allergic_ingredients\": [\"Gluten\", \"Dairy\"],\n", - " },\n", - " {\n", - " \"id\": 2,\n", - " \"name\": \"Chocolate\",\n", - " \"calories\": 50, # Calories per serving\n", - " \"allergic_ingredients\": [\"Milk\", \"Soy\"],\n", - " },\n", - " {\n", - " \"id\": 3,\n", - " \"name\": \"Sushi\",\n", - " \"calories\": 300, # Calories per serving\n", - " \"allergic_ingredients\": [\"Fish\", \"Soy\"],\n", - " },\n", - " {\n", - " \"id\": 4,\n", - " \"name\": \"Burger\",\n", - " \"calories\": 350, # Calories per serving\n", - " \"allergic_ingredients\": [\"Gluten\", \"Dairy\"],\n", - " },\n", - " {\n", - " \"id\": 5,\n", - " \"name\": \"Ice Cream\",\n", - " \"calories\": 200, # Calories per serving\n", - " \"allergic_ingredients\": [\"Dairy\"],\n", - " },\n", - " {\n", - " \"id\": 6,\n", - " \"name\": \"Pasta\",\n", - " \"calories\": 180, # Calories per serving\n", - " \"allergic_ingredients\": [\"Gluten\"],\n", - " },\n", - " {\n", - " \"id\": 7,\n", - " \"name\": \"Salad\",\n", - " \"calories\": 50, # Calories per serving\n", - " \"allergic_ingredients\": [],\n", - " },\n", - "]\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", "\n", "\n", "@tool\n", - "def list_users() -> List[str]:\n", - " \"\"\"Return a list of all the user names.\"\"\"\n", - " return [user_info[\"name\"] for user_info in USER_DATA]\n", - "\n", - "\n", - "@tool\n", - "def get_user_favorite_food_ids(user_id: int) -> List[int]:\n", - " \"\"\"Get the favorite foods of the user with the provided ID.\"\"\"\n", - " for user_info in USER_DATA:\n", - " if user_info[\"id\"] == user_id:\n", - " return user_info[\"favorite_foods\"]\n", - " raise ValueError(f\"Id: {user_id} not found.\")\n", - "\n", - "\n", - "@tool\n", - "def get_food_name(food_id: int) -> str:\n", - " \"\"\"Get the food name.\"\"\"\n", - " for food_info in FOOD_DATA:\n", - " if food_info[\"id\"] == food_id:\n", - " return food_info[\"name\"]\n", - " raise ValueError(f\"Id: {food_id} not found.\")\n", - "\n", - "\n", - "@tool\n", - "def get_user_id(user_name: str) -> int:\n", - " \"\"\"Return the ID of the provided user name.\"\"\"\n", - " for user_info in USER_DATA:\n", - " if user_info[\"name\"] == user_name:\n", - " return user_info[\"id\"]\n", - " raise ValueError(f\"Name: {user_name} not found.\")\n", - "\n", - "\n", - "@tool\n", - "def create_barplot(\n", + "def create_plot(\n", " data: Union[List[float], List[int]],\n", " labels: Union[List[str], None] = None,\n", - " title: str = \"Bar Plot\",\n", + " title: str = \"Plot\",\n", " xlabel: str = \"X\",\n", " ylabel: str = \"Y\",\n", " color: Union[str, List[str]] = \"blue\",\n", + " plot_type: str = \"bar\",\n", ") -> Tuple[plt.Figure, plt.Axes]:\n", " \"\"\"\n", - " Generates a bar plot from the provided data and returns the figure and axis objects.\n", + " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", "\n", - " :param data: A list of numerical values for the bar heights.\n", - " :param labels: A list of strings for the bar labels. Default is None.\n", - " :param title: Title of the plot. Default is 'Bar Plot'.\n", + " :param data: A list of numerical values for the bar heights or line points.\n", + " :param labels: A list of strings for the bar or point labels. Default is None.\n", + " :param title: Title of the plot. Default is 'Plot'.\n", " :param xlabel: Label for the X-axis. Default is 'X'.\n", " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", - " :param color: Color of the bars. Can be a single color or a list of colors. Default is 'blue'.\n", + " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", + " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", " :return: Tuple containing the figure and axes objects.\n", " \"\"\"\n", + " if plot_type not in [\"bar\", \"line\"]:\n", + " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", + "\n", " fig, ax = plt.subplots(figsize=(10, 6))\n", " x_positions = range(len(data))\n", "\n", " if labels and len(labels) == len(data):\n", " plt.xticks(x_positions, labels)\n", "\n", - " ax.bar(x_positions, data, color=color)\n", + " if plot_type == \"bar\":\n", + " ax.bar(x_positions, data, color=color)\n", + " elif plot_type == \"line\":\n", + " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", + "\n", " ax.set_title(title)\n", " ax.set_xlabel(xlabel)\n", " ax.set_ylabel(ylabel)\n", - " plt.savefig(\"./plot.png\")\n", - " return \"./plot.png\"" + "\n", + " return fig, ax" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "b0ca7d80-31e4-4394-bfce-ffac314bac7d", "metadata": {}, "outputs": [], @@ -244,6 +133,7 @@ "from langchain_core.messages import (\n", " AIMessage,\n", " BaseMessage,\n", + " ChatMessage,\n", " FunctionMessage,\n", " HumanMessage,\n", ")\n", @@ -262,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", "metadata": {}, "outputs": [], @@ -271,13 +161,7 @@ "\n", "# This a helper class we have that is useful for running tools\n", "# It takes in an agent action and calls that tool and returns the result\n", - "tools = [\n", - " list_users,\n", - " get_user_favorite_food_ids,\n", - " get_food_name,\n", - " get_user_id,\n", - " create_barplot,\n", - "]\n", + "tools = [tavily_tool, create_plot]\n", "tool_executor = ToolExecutor(tools)\n", "\n", "prompt = ChatPromptTemplate.from_messages(\n", @@ -287,8 +171,9 @@ " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", " \" Use the provided tools to progress towards answering the question.\"\n", " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", - " \" will help where you left off. Execute what you can to \"\n", - " \" If you have the final answer, prefix your response with FINAL ANSWER:\",\n", + " \" will help where you left off. Execute what you can to make progress.\"\n", + " \" If you have the final answer, prefix your response with FINAL ANSWER.\"\n", + " \" You have access to the following tools: {tool_names}.\",\n", " ),\n", " MessagesPlaceholder(variable_name=\"messages\"),\n", " ]\n", @@ -297,11 +182,24 @@ "\n", "def add_agent_node(name: str, llm, tools, prompt):\n", " functions = [format_tool_to_openai_function(t) for t in tools]\n", + "\n", + " def _update_state(ai_message) -> dict:\n", + " if isinstance(ai_message, FunctionMessage):\n", + " result = ai_message\n", + " else:\n", + " message = ai_message.dict(exclude={\"type\"})\n", + " message[\"name\"] = name\n", + " result = HumanMessage(**message)\n", + " return {\n", + " \"messages\": [result],\n", + " \"sender\": name,\n", + " }\n", + "\n", " chain = (\n", " (lambda x: {**x, \"intermediate_steps\": []})\n", - " | prompt\n", + " | prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", " | llm.bind_functions(functions)\n", - " | (lambda x: {\"messages\": [x], \"sender\": name})\n", + " | _update_state\n", " )\n", " workflow.add_node(name, chain)\n", "\n", @@ -339,100 +237,41 @@ "\n", "llm = ChatOpenAI(model=\"gpt-4\")\n", "add_agent_node(\n", - " \"user_info\",\n", + " \"Researcher\",\n", " llm,\n", - " [\n", - " list_users,\n", - " get_user_favorite_food_ids,\n", - " get_food_name,\n", - " get_user_id,\n", - " ],\n", + " [tavily_tool],\n", " prompt,\n", ")\n", - "add_agent_node(\"plot_bars\", llm, [create_barplot], prompt)\n", + "add_agent_node(\"Chart Generator\", llm, [create_plot], prompt)\n", "workflow.add_node(\"call_tool\", call_tool)\n", "workflow.add_conditional_edges(\n", - " \"user_info\",\n", + " \"Researcher\",\n", " should_continue,\n", - " {\"continue\": \"plot_bars\", \"call_tool\": \"call_tool\", \"end\": END},\n", + " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", ")\n", "workflow.add_conditional_edges(\n", - " \"plot_bars\",\n", + " \"Chart Generator\",\n", " should_continue,\n", - " {\"continue\": \"user_info\", \"call_tool\": \"call_tool\", \"end\": END},\n", + " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"end\": END},\n", ")\n", "# We will let the user_info tool decide when to respond.\n", - "workflow.add_edge(\"call_tool\", \"user_info\")\n", - "workflow.set_entry_point(\"user_info\")\n", + "workflow.add_edge(\"call_tool\", \"Researcher\")\n", + "workflow.set_entry_point(\"Researcher\")\n", "graph = workflow.compile()" ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "176a99b0-b457-45cf-8901-90facaa852da", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'messages': [HumanMessage(content='Draw a bar plot of the favorite foods of Eve and Donna'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_name\": \"Eve\"\\n}', 'name': 'get_user_id'}}),\n", - " FunctionMessage(content='42', name='get_user_id'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_id\": 42\\n}', 'name': 'get_user_favorite_food_ids'}}),\n", - " FunctionMessage(content='[5, 7, 4]', name='get_user_favorite_food_ids'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 5\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Ice Cream', name='get_food_name'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 7\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Salad', name='get_food_name'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 4\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Burger', name='get_food_name'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_name\": \"Donna\"\\n}', 'name': 'get_user_id'}}),\n", - " FunctionMessage(content='41', name='get_user_id'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"user_id\": 41\\n}', 'name': 'get_user_favorite_food_ids'}}),\n", - " FunctionMessage(content='[6, 1, 4]', name='get_user_favorite_food_ids'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 6\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Pasta', name='get_food_name'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 1\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Pizza', name='get_food_name'),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"food_id\": 4\\n}', 'name': 'get_food_name'}}),\n", - " FunctionMessage(content='Burger', name='get_food_name'),\n", - " AIMessage(content=\"Based on the data obtained, we can make the following observations about the favorite foods of Eve and Donna:\\n\\n- Eve's favorite foods are Ice Cream, Salad, and Burger.\\n- Donna's favorite foods are Pasta, Pizza, and Burger.\\n\\nTo draw a bar plot, we need a tool with graphical capabilities which is currently not available in this environment. However, if we were to represent this data on a bar plot, we would have 'Food Items' on the x-axis and 'Count' on the y-axis. Each user would have a different color bar. For each food item, there would be a bar representing the count of users who have that food as their favorite. \\n\\nFor example, 'Burger' would have a count of 2 because both Eve and Donna have it as their favorite food. Other food items like 'Ice Cream', 'Salad', 'Pasta', and 'Pizza' would have a count of 1 as they are the favorite of only one user. \\n\\nPlease use a tool with graphical capabilities like Python's matplotlib library or Excel to create this bar plot.\"),\n", - " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", - " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", - " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", - " AIMessage(content=\"The current environment doesn't support graphical outputs. However, I can provide you with the data and the steps to create the bar plot on your local machine using Python's matplotlib library. \\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\\n\\nThis will create a bar plot with food items on the x-axis and the count on the y-axis. The two sets of bars represent Eve's and Donna's favorite food respectively.\"),\n", - " AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\\n \"data\": [1, 1, 2, 1, 1],\\n \"labels\": [\"Ice Cream\", \"Salad\", \"Burger\", \"Pasta\", \"Pizza\"],\\n \"title\": \"Favorite Foods of Eve and Donna\",\\n \"xlabel\": \"Food Items\",\\n \"ylabel\": \"Count\",\\n \"color\": [\"blue\", \"blue\", \"blue\", \"orange\", \"orange\"]\\n}', 'name': 'create_barplot'}}),\n", - " FunctionMessage(content='./plot.png', name='create_barplot'),\n", - " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", - " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", - " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", - " AIMessage(content=\"I'm sorry, I made a mistake. This environment doesn't support the creation or display of plots. You can use the data and instructions provided earlier to create the plot in a local Python environment or any other tool that supports bar plots.\"),\n", - " AIMessage(content=\"FINAL ANSWER:\\n\\nUnfortunately, I'm unable to draw a bar plot in this environment. However, I have provided the data and the Python code needed to create the bar plot in your local environment using the matplotlib library.\\n\\nHere is the data:\\n\\n```python\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n```\\n\\nYou can use the following Python code to create the bar plot:\\n\\n```python\\nimport matplotlib.pyplot as plt\\nimport numpy as np\\n\\nfood_items = ['Ice Cream', 'Salad', 'Burger', 'Pasta', 'Pizza']\\neve_favorites = [1, 1, 1, 0, 0]\\ndonna_favorites = [0, 0, 1, 1, 1]\\n\\nx = np.arange(len(food_items)) # the label locations\\nwidth = 0.35 # the width of the bars\\n\\nfig, ax = plt.subplots()\\nrects1 = ax.bar(x - width/2, eve_favorites, width, label='Eve')\\nrects2 = ax.bar(x + width/2, donna_favorites, width, label='Donna')\\n\\n# Add some text for labels, title and custom x-axis tick labels, etc.\\nax.set_ylabel('Count')\\nax.set_title('Favorite foods of Eve and Donna')\\nax.set_xticks(x)\\nax.set_xticklabels(food_items)\\nax.legend()\\n\\nfig.tight_layout()\\n\\nplt.show()\\n```\")],\n", - " 'sender': 'user_info'}" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAIjCAYAAADvBuGTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/OQEPoAAAACXBIWXMAAA9hAAAPYQGoP6dpAABSDUlEQVR4nO3deVhV1eL/8c8B4aAoODMUgYo5pZJjmqYWiqZerW6pDaJXrSwzozLplnNZlmaDSlqOZXlNs66VQ5iahpoDdS3zqheHFJwSEExQWL8/+rG/ngAF23pA36/n2U+dtddee+3DYns+7L3XcRhjjAAAAAAAf4mHuzsAAAAAAFcDwhUAAAAA2IBwBQAAAAA2IFwBAAAAgA0IVwAAAABgA8IVAAAAANiAcAUAAAAANiBcAQAAAIANCFcAAAAAYAPCFQCUQnPmzJHD4dC+ffvc3ZUrqn379mrfvv0V3WdGRoYGDhyowMBAORwODRs27IruvzTo16+fwsLC3N0NAHA7whWAa15eUCloGTFihLu7V2TTpk3TnDlzbG+3ffv2hb4/v/zyi+37K2lefvllzZkzR4MHD9b8+fP10EMPFVo3LCys0Peqc+fOV7DXJdP5Y8nDw0N+fn6qU6eOHnroIa1atcrd3QOAv6yMuzsAACXF2LFjVaNGDZeym266yU29ubCHHnpIvXv3ltPptMqmTZumqlWrql+/frbv7/rrr9eECRPylQcHB9u+r5Jm9erVuuWWWzRq1Kgi1Y+IiNDTTz+dr/xaeK+K4vyxlJmZqT179mjJkiX64IMPdN999+mDDz6Ql5eXm3sJAJeGcAUA/1+XLl3UrFkzd3fjgjIzM+Xr6ytPT095enpesf36+/vrwQcfvGL7K0mOHj2q+vXrF7n+ddddd82+V0VR0Fh65ZVXNHToUE2bNk1hYWF69dVX3dQ7APhruC0QAC5i//79euyxx1SnTh2VLVtWVapU0b333uvyvNOWLVvkcDg0d+7cfNuvWLFCDodDy5Yts8q2b9+uLl26yM/PT+XLl9cdd9yhjRs3umyXd7vi2rVr9dhjj6l69eq6/vrrXdbl9SEsLEw//fST1q5da912df6zSampqRo2bJhCQkLkdDoVHh6uV199Vbm5uba8R+fOndO4ceNUq1YtOZ1OhYWF6fnnn1dWVla+utOmTVODBg3kdDoVHBysxx9/XKmpqfnqzZgxQ7Vq1VLZsmXVokULffvttwXu++2331aDBg1Urlw5VapUSc2aNdOCBQsu2uejR49qwIABCggIkI+Pjxo3buzy81uzZo0cDoeSkpL0xRdfWO/rX33O7fXXX5fD4dD+/fvzrYuNjZW3t7dOnjxplW3atEmdO3eWv7+/ypUrp3bt2mnDhg0X3U92drZGjhyppk2byt/fX76+vmrbtq2++eYbl3r79u2Tw+HQ66+/br3nTqdTzZs31/fff5+v3aVLl+qmm26Sj4+PbrrpJn366aeX8C648vT01FtvvaX69evrnXfeUVpamrWuqGMrLCxM3bp10/r169WiRQv5+PioZs2amjdvnku9vN+dDRs2KCYmRtWqVZOvr6/uuusuHTt2zKXuZ599pq5duyo4OFhOp1O1atXSuHHjlJOT85ePGcBVygDANW727NlGkvn666/NsWPHXBZjjFm0aJFp3LixGTlypJkxY4Z5/vnnTaVKlUxoaKjJzMy02qlZs6a5884787Xfv39/U6lSJZOdnW2MMWbHjh3G19fXBAUFmXHjxplXXnnF1KhRwzidTrNx48Z8/apfv75p166defvtt80rr7zisi4pKckYY8ynn35qrr/+elO3bl0zf/58M3/+fLNy5UpjjDGZmZmmUaNGpkqVKub55583cXFxpm/fvsbhcJgnn3zyou9Pu3btTN26dfO9N6dOnbLqREdHG0nm73//u5k6darp27evkWR69uzp0taoUaOMJBMZGWnefvttM2TIEOPp6WmaN29uvT/GGPPee+8ZSaZ169bmrbfeMsOGDTMVK1Y0NWvWNO3atbPqzZgxw9rvu+++a958800zYMAAM3To0Ase0+nTp029evWMl5eXeeqpp8xbb71l2rZtaySZKVOmGGOMSUlJMfPnzzdVq1Y1ERER1vuakZFRaLuhoaGmU6dO+d6rY8eOmdOnTxtjjNm/f79xOBxm4sSJ+bavWbOm6dq1q/U6Pj7eeHt7m1atWplJkyaZN954wzRq1Mh4e3ubTZs2XfAYjx07ZoKCgkxMTIyZPn26mThxoqlTp47x8vIy27dvt+olJSUZSebmm2824eHh5tVXXzUTJ040VatWNddff73Lz2XFihXGw8PD3HTTTWby5Mnmn//8p/H39zcNGjQwoaGhF+yPMX+MpQYNGhS6fty4cUaSWbZs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+qpXr17q1auXsrOzdffdd+ull15SbGysfHx8Cu3zjz/+qNzcXJerV3m3euX14XLq1auXHnvsMe3atUsLFy5UuXLl1L17d2t9rVq1JEl+fn6X9LP75JNPVLNmTS1ZssRlbBT1+7r+7Pyfy58VNJ6LKycnRwsWLFC5cuXUpk0ba59FGVuXw5o1a3TixAktWbJEt912m1WelJR02fYJoPTjmSsAuAhPT898f/l+++23C5yOuV69emrYsKEWLlyohQsXKigoyOWDmaenpzp16qTPPvvMZUrvI0eOaMGCBWrTpo11W1hx+fr6Fhj47rvvPiUkJGjFihX51qWmpurcuXOXtL88d955pyRpypQpLuWTJ0+WJHXt2lWSFBkZKW9vb7311lsu7+f777+vtLQ0q16zZs1UrVo1xcXFKTs726o3Z86cfMd34sQJl9fe3t6qX7++jDEut8EV1OeUlBQtXLjQKjt37pzefvttlS9fXu3atSvi0V+6e+65R56envroo4+0aNEidevWTb6+vtb6pk2bqlatWnr99det2+XO9+dpw/8s78rM+e/1pk2blJCQcEn9DQoKUkREhObOnetyS9yqVav0888/X1KbeXJycjR06FDt3LlTQ4cOtX4Hijq2LoeC3r/s7GxNmzbtsu0TQOnHlSsAuIhu3bpp/vz58vf3V/369ZWQkKCvv/5aVapUKbB+r169NHLkSPn4+GjAgAH5nusZP368Vq1apTZt2uixxx5TmTJl9O677yorK0sTJ0685H42bdpU06dP1/jx4xUeHq7q1avr9ttv17PPPqvPP/9c3bp1U79+/dS0aVNlZmbqP//5jz755BPt27dPVatWveT9Nm7cWNHR0ZoxY4Z1K9XmzZs1d+5c9ezZUx06dJD0x1W72NhYjRkzRp07d9bf/vY37dq1S9OmTVPz5s2tL5b18vLS+PHj9cgjj+j2229Xr169lJSUpNmzZ+d75qpTp04KDAzUrbfeqoCAAO3cuVPvvPOOunbtaj23U5CHH35Y7777rvr166etW7cqLCxMn3zyiTZs2KApU6ZccNuLOXTokD744IN85eXLl1fPnj2t19WrV1eHDh00efJknTp1ypr0JI+Hh4fee+89denSRQ0aNFD//v113XXX6dChQ/rmm2/k5+enf//734X2o1u3blqyZInuuusude3aVUlJSYqLi1P9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TZqkH374QfPnzy9wm+zs7CvcS7jbPffco+3bt2vu3Ln673//q88//1zt27fXiRMn3N01lHCdO3dWcnKykpOTFR8frzJlyqhbt26X3F5OTo5yc3Nt7OH/4dxW+uSNr927d+vpp5/W6NGj9dprr7m7WyilChtP/v7+ttx9AfchXKFAubm5mjhxosLDw+V0OnXDDTfopZdestYfPHhQ9913nypWrKjKlSurR48e2rdvX6Htbd68WS+//LImTZqk1157Ta1bt1ZYWJg6duyoxYsXKzo6WpI0evRoRURE6L333lONGjWs7yZLTU3VwIEDVa1aNfn5+en222/XDz/8YLW/d+9e9ejRQwEBASpfvryaN2+ur7/+2qUPYWFhGj9+vPr27avy5csrNDRUn3/+uY4dO6YePXpYV9K2bNli4zuJ4khNTdW3336rV199VR06dFBoaKhatGih2NhY/e1vf5MkTZ48WQ0bNpSvr69CQkL02GOPKSMjo9A2izI2jh49qu7du6ts2bKqUaOGPvzww8t6nLg88q50BgYGKiIiQiNGjNDBgwd17NixAq8QJCYmyuFwWOeuvFtKP//8c9WvX19Op1MHDhxQcnKyunbtao2PBQsW5Ltt9GLnqMLObSg98sZXaGioBg8erMjISH3++ecXPSft379f3bt3V6VKleTr66sGDRroyy+/1L59+9ShQwdJUqVKleRwONSvXz9J0vLly9WmTRtVrFhRVapUUbdu3bR37153HDYuk8LG0/l3Eu3bt8/linze0r59e0lS+/btC1yfd04r7r+XsAfhCgWKjY3VK6+8ohdffFE///yzFixYYH2x89mzZxUVFaUKFSro22+/1YYNG1S+fHl17ty50L/Gfvjhhypfvrwee+yxAtef/1eaPXv2aPHixVqyZIkSExMlSffee6+OHj2qr776Slu3blWTJk10xx13WLcTZmRk6M4771R8fLy2b9+uzp07q3v37jpw4IDLft544w3deuut2r59u7p27aqHHnpIffv21YMPPqht27apVq1a6tu3r/j6N/coX768ypcvr6VLlyorK6vAOh4eHnrrrbf0008/ae7cuVq9erWGDx9eaJtFGRv9+vXTwYMH9c033+iTTz7RtGnTdPToUduPD1dORkaGPvjgA4WHh6tKlSpF3u706dN69dVX9d577+mnn35S9erV1bdvXx0+fFhr1qzR4sWLNWPGjHzj42LnKKngcxtKr7Jlyyo7O/ui56THH39cWVlZWrdunf7zn//o1VdfVfny5RUSEqLFixdLknbt2qXk5GS9+eabkqTMzEzFxMRoy5Ytio+Pl4eHh+66667LdiUV7pc3ns4XEhJiXY1PTk7W9u3bVaVKFd12222SpCVLlrisv/vuu1WnTh3r81px/72ETQxgjImOjjY9evQwxhiTnp5unE6nmTlzZoF158+fb+rUqWNyc3OtsqysLFO2bFmzYsWKArfp0qWLadSo0UX7MWrUKOPl5WWOHj1qlX377bfGz8/PnDlzxqVurVq1zLvvvltoWw0aNDBvv/229To0NNQ8+OCD1uvk5GQjybz44otWWUJCgpFkkpOTL9pXXB6ffPKJqVSpkvHx8TGtW7c2sbGx5ocffii0/qJFi0yVKlWs17Nnzzb+/v4X3Mf5Y2PXrl1Gktm8ebO1fufOnUaSeeONN/7SseDKiY6ONp6ensbX19f4+voaSSYoKMhs3brVGGPMN998YySZkydPWtts377dSDJJSUnGmD/GjiSTmJho1ckbC99//71Vtnv3bpfxUZRzVEHnNpQe5/8bmZuba1atWmWcTqd55pln8tX98zmpYcOGZvTo0QW2W9C4LMixY8eMJPOf//znko8BJceFxtP56873+++/m5YtW5pu3bqZnJycfOsnT55sKlasaHbt2lXofv88NnF5cOUK+ezcuVNZWVm64447Clz/ww8/aM+ePapQoYJ1paFy5co6c+ZMobctmGJcCQoNDVW1atVc9peRkaEqVapY+ytfvrySkpKs/WVkZOiZZ55RvXr1VLFiRZUvX147d+7Md+WqUaNG1v/n/WWnYcOG+cq4auE+99xzjw4fPqzPP/9cnTt31po1a9SkSRPrAd+vv/5ad9xxh6677jpVqFBBDz30kE6cOKHTp08X2N7FxsbOnTtVpkwZNW3a1Nqmbt263PNeCnXo0EGJiYlKTEzU5s2bFRUVpS5dumj//v1FbsPb29vlPLFr1y6VKVNGTZo0scrCw8NVqVIl63VRzlFS/nMbSpdly5apfPny8vHxUZcuXdSrVy+NHj36ouekoUOHavz48br11ls1atSoIk2ysnv3bvXp00c1a9aUn5+fwsLCJCnfv2kovQobT4X5xz/+oVOnTmnBggXy8HD9+P7V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UOI99thjOnjwoJ544gn98ssv+uyzzzRq1CjFxMTIw8NDvr6+Gjx4sJ599lktX75cP//8swYNGqTTp09rwIABkqRHH31Uu3fv1rPPPqtdu3ZpwYIFmjNnTrH7EhYWpvj4eKWkpOjkyZOSpJEjR2revHkaM2aMfvrpJ+3cuVMff/yxXnjhhUs+5t9//11DhgzRmjVrtH//fm3YsEHff/+96tWrd8ltAgAuL8IVAKDEu+666/Tll19q8+bNaty4sR599FENGDDAJby88soruueee/TQQw+pSZMm2rNnj1asWKFKlSpJkm644QYtXrxYS5cuVePGjRUXF6eXX3652H2ZNGmSVq1apZCQEN18882SpKioKC1btkwrV65U8+bNdcstt+iNN95QaGjoJR+zp6enTpw4ob59++rGG2/Ufffdpy5dumjMmDGX3CYA4PJymPPvsQAAAAAAXBKuXAEAAACADQhXAAAAAGADwhUAAAAA2IBwBQAAAAA2IFwBAAAAgA0IVwAAAABgA8IVAAAAANiAcAUAAAAANiBcAQAAAIANCFcAAAAAYAPCFQAAAADY4P8Bw6j3d18E1YkAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "result = graph.invoke(\n", " {\n", " \"messages\": [\n", " HumanMessage(\n", - " content=\"Draw a bar plot of the favorite foods of Eve and Donna\"\n", + " content=\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it.\"\n", " )\n", " ]\n", " },\n", @@ -441,14 +280,6 @@ ")\n", "result[\"messages\"][-1]" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9e88f835-2c94-4fe1-90d6-c9edac105195", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 9aefcc20afbbc59a7302bbfff7c51380f1c2fd47 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Wed, 17 Jan 2024 23:59:19 -0800 Subject: [PATCH 04/16] update collab --- .../multi-agent/agent_supervisor.ipynb | 191 +++++++++++------- 1 file changed, 115 insertions(+), 76 deletions(-) diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb index 940055f33..de47650b6 100644 --- a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb +++ b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb @@ -5,7 +5,7 @@ "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", "metadata": {}, "source": [ - "## Example 2: Agent Team Supervisor\n", + "## Multi-agent Example 2: Agent Team Supervisor\n", "\n", "The prevoius example routed messages automatically based on the output of the initial researcher agent.\n", "\n", @@ -30,6 +30,24 @@ { "cell_type": "code", "execution_count": 2, + "id": "f5b02e36-3b2e-485e-ac55-c3523c42ec58", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "env: LANGCHAIN_API_KEY=ls__4f8a0a0114d145a08a0c3c7f5631289c\n" + ] + } + ], + "source": [ + "%env LANGCHAIN_API_KEY=ls__4f8a0a0114d145a08a0c3c7f5631289c" + ] + }, + { + "cell_type": "code", + "execution_count": 3, "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", "metadata": {}, "outputs": [], @@ -54,12 +72,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "f04c6778-403b-4b49-9b93-678e910d5cec", "metadata": {}, "outputs": [], "source": [ - "from typing import List, Tuple, Union\n", + "from typing import Annotated, List, Tuple, Union\n", "\n", "import matplotlib.pyplot as plt\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", @@ -70,27 +88,21 @@ "\n", "@tool\n", "def create_plot(\n", - " data: Union[List[float], List[int]],\n", - " labels: Union[List[str], None] = None,\n", - " title: str = \"Plot\",\n", - " xlabel: str = \"X\",\n", - " ylabel: str = \"Y\",\n", - " color: Union[str, List[str]] = \"blue\",\n", - " plot_type: str = \"bar\",\n", - ") -> Tuple[plt.Figure, plt.Axes]:\n", - " \"\"\"\n", - " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", - "\n", - " :param data: A list of numerical values for the bar heights or line points.\n", - " :param labels: A list of strings for the bar or point labels. Default is None.\n", - " :param title: Title of the plot. Default is 'Plot'.\n", - " :param xlabel: Label for the X-axis. Default is 'X'.\n", - " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", - " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", - " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", - " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", - " :return: Tuple containing the figure and axes objects.\n", - " \"\"\"\n", + " data: Annotated[\n", + " Union[List[float], List[int]],\n", + " \"Numerical values for bar heights or line points.\",\n", + " ],\n", + " file_name: Annotated[str, \"File path to save the figure.\"],\n", + " labels: Annotated[\n", + " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", + " ] = None,\n", + " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", + " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", + " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", + " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", + " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", + ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", + " \"\"\"Create a line or bar chart.\"\"\"\n", " if plot_type not in [\"bar\", \"line\"]:\n", " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", "\n", @@ -108,13 +120,14 @@ " ax.set_title(title)\n", " ax.set_xlabel(xlabel)\n", " ax.set_ylabel(ylabel)\n", - "\n", - " return fig, ax" + " fig.savefig(file_name)\n", + " plt.close(fig)\n", + " return f'Saved \"{title}\" plot to {file_name}'" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", "metadata": {}, "outputs": [], @@ -140,7 +153,7 @@ "workflow = StateGraph(AgentState)\n", "\n", "\n", - "def create_agent_node(name: str, llm: ChatOpenAI, tools: list, system_prompt: str):\n", + "def create_worker_node(name: str, llm: ChatOpenAI, tools: list, system_prompt: str):\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", @@ -152,31 +165,21 @@ " ]\n", " )\n", " agent = create_openai_functions_agent(llm, tools, prompt)\n", - " executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n", - "\n", - " def _update_state(ai_message) -> dict:\n", - " if isinstance(ai_message, FunctionMessage):\n", - " result = ai_message\n", - " else:\n", - " message = ai_message.dict(exclude={\"type\"})\n", - " message[\"name\"] = name\n", - " result = HumanMessage(**message)\n", - " return {\n", - " \"messages\": [result],\n", - " \"sender\": name,\n", - " }\n", - "\n", - " chain = executor | _update_state\n", + " executor = AgentExecutor(agent=agent, tools=tools)\n", + " chain = executor | (\n", + " lambda x: {\"messages\": [HumanMessage(content=x[\"output\"], name=name)]}\n", + " )\n", " workflow.add_node(name, chain)\n", "\n", "\n", - "llm = ChatOpenAI(model=\"gpt-4\")\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", "\n", - "create_agent_node(\"Researcher\", llm, [tavily_tool], \"You are a web researcher.\")\n", - "create_agent_node(\"Chart Generator\", llm, [create_plot], \"You are a chart generator.\")\n", + "# Note: these worker nodes don't _have_ to be agents. They can be any DAG, tool, or function\n", + "create_worker_node(\"Researcher\", llm, [tavily_tool], \"You are a web researcher.\")\n", + "create_worker_node(\"Chart Generator\", llm, [create_plot], \"You are a chart generator.\")\n", "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION\n", - "create_agent_node(\n", - " \"Data Analyst\",\n", + "create_worker_node(\n", + " \"Coder\",\n", " llm,\n", " [PythonREPLTool()],\n", " \"You may generate safe python code to analyze data.\",\n", @@ -193,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "17c108a0-6dc3-46fd-a5e6-a1fcfad5458a", "metadata": {}, "outputs": [], @@ -202,7 +205,7 @@ "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", "\n", "\n", - "def create_agent_supervisor(members: List[str], llm: ChatOpenAI, system_prompt: str):\n", + "def create_supervisor(members: List[str], llm: ChatOpenAI, system_prompt: str):\n", " options = [\"FINISH\"] + members\n", " function_def = {\n", " \"name\": \"route\",\n", @@ -232,6 +235,8 @@ " ),\n", " ]\n", " ).partial(options=str(options))\n", + " if \"members\" in prompt.input_variables:\n", + " prompt = prompt.partial(members=\", \".join(members))\n", " chain = (\n", " prompt\n", " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", @@ -243,24 +248,24 @@ "\n", " for member in members:\n", " workflow.add_edge(member, \"supervisor\")\n", - " workflow.add_conditional_edges(\n", - " \"supervisor\", lambda x: x[\"next\"], conditional_map\n", - " )" + " workflow.add_conditional_edges(\"supervisor\", lambda x: x[\"next\"], conditional_map)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", "metadata": {}, "outputs": [], "source": [ - "create_agent_supervisor(\n", - " [\"Researcher\", \"Chart Generator\", \"Data Analyst\"],\n", + "create_supervisor(\n", + " [\"Researcher\", \"Chart Generator\", \"Coder\"],\n", " llm,\n", - " \"You are an agent supervisor tasked with managing work order.\"\n", - " \" Respond with only the role will optimally help us accomplish the user's task or question.\"\n", - " \" When finished, respond with FINISH.\",\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following workers: {members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", ")\n", "\n", "# Finally, add entrypoint\n", @@ -276,32 +281,66 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", "metadata": {}, "outputs": [ { - "ename": "InvalidUpdateError", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mInvalidUpdateError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[7], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mgraph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mCode hello world and print it to the terminal\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1780\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 1778\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1779\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 1780\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1781\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1782\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 1783\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1784\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1785\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1786\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1787\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 1788\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:521\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 512\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 513\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 519\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 520\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 521\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 522\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 523\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 524\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 525\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 526\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 527\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 528\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 529\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:557\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 548\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 549\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 550\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 556\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 557\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 558\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 559\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 560\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 561\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 562\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 563\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 564\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 565\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1232\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1230\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1231\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1232\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m \u001b[43mcontext\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mnext\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43miterator\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1233\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:335\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m _interrupt_or_proceed(done, inflight, step)\n\u001b[1;32m 334\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[0;32m--> 335\u001b[0m \u001b[43m_apply_writes\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcheckpoint\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mchannels\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpending_writes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 337\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdebug:\n\u001b[1;32m 338\u001b[0m print_checkpoint(step, channels)\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:687\u001b[0m, in \u001b[0;36m_apply_writes\u001b[0;34m(checkpoint, channels, pending_writes, config, for_step)\u001b[0m\n\u001b[1;32m 685\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m chan, vals \u001b[38;5;129;01min\u001b[39;00m pending_writes_by_channel\u001b[38;5;241m.\u001b[39mitems():\n\u001b[1;32m 686\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m chan \u001b[38;5;129;01min\u001b[39;00m channels:\n\u001b[0;32m--> 687\u001b[0m \u001b[43mchannels\u001b[49m\u001b[43m[\u001b[49m\u001b[43mchan\u001b[49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mupdate\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvals\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 688\u001b[0m checkpoint[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mchannel_versions\u001b[39m\u001b[38;5;124m\"\u001b[39m][chan] \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 689\u001b[0m updated_channels\u001b[38;5;241m.\u001b[39madd(chan)\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/channels/last_value.py:47\u001b[0m, in \u001b[0;36mLastValue.update\u001b[0;34m(self, values)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 46\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(values) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;241m1\u001b[39m:\n\u001b[0;32m---> 47\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m InvalidUpdateError()\n\u001b[1;32m 49\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mvalue \u001b[38;5;241m=\u001b[39m values[\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", - "\u001b[0;31mInvalidUpdateError\u001b[0m: " + "name": "stderr", + "output_type": "stream", + "text": [ + "Rate limit exceeded for https://api.smith.langchain.com/runs/e906b9ec-0dc1-4f78-b5d1-d6da08bac093. HTTPError('429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/e906b9ec-0dc1-4f78-b5d1-d6da08bac093', '{\"detail\":\"Hourly usage limit exceeded\"}')\n", + "Rate limit exceeded for https://api.smith.langchain.com/runs. HTTPError('429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs', '{\"detail\":\"Hourly usage limit exceeded\"}')\n", + "Python REPL can execute arbitrary code. Use with caution.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "The code `print('Hello, World!')` has been executed, and it printed `Hello, World!` to the terminal.\n" ] } ], "source": [ - "graph.invoke(\"Code hello world and print it to the terminal\")" + "results = graph.invoke(\"Code hello world and print it to the terminal\")\n", + "results[\"messages\"][-1].pretty_print()" ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "The chart summarizing the data on the 2023 California wildfires has been created successfully. You can see the representation of the statistics mentioned in the summary including the total number of fires, total acres burned, comparison with the five-year average, the size of the largest wildfire, and the number of fatalities.\n", + "\n", + "![2023 California Wildfires Overview](sandbox:/ca_wildfires_2023_chart.png)\n" + ] + } + ], + "source": [ + "results = graph.invoke(\n", + " \"Write a research summary of CA wildfires in 2023. Include a chart.\"\n", + ")\n", + "results[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d363d2c-e0da-4cce-ba47-ad2aa9df0fef", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From d6c1c58f1980b7acf5e5ea37553f41fe7fa1f2a1 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 18 Jan 2024 08:34:50 -0800 Subject: [PATCH 05/16] Add image --- .../multi-agent/agent_supervisor.ipynb | 47 +++--------------- .../multi-agent/img/supervisor-diagram.png | Bin 0 -> 74993 bytes 2 files changed, 6 insertions(+), 41 deletions(-) create mode 100644 examples/advanced_agents/multi-agent/img/supervisor-diagram.png diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb index de47650b6..1723198b4 100644 --- a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb +++ b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb @@ -13,6 +13,8 @@ "\n", "Below, we will create an agent group, with an agent supervisor to help delegate tasks.\n", "\n", + "![diagram](./img/supervisor-diagram.png)\n", + "\n", "To simplify each agent node, we will use the AgentExecutor class from LangChain." ] }, @@ -23,26 +25,8 @@ "metadata": {}, "outputs": [], "source": [ - "# %%capture --no-stderr\n", - "# %pip install -U langchain langchain_openai langchain_experimental langsmith pandas" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f5b02e36-3b2e-485e-ac55-c3523c42ec58", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "env: LANGCHAIN_API_KEY=ls__4f8a0a0114d145a08a0c3c7f5631289c\n" - ] - } - ], - "source": [ - "%env LANGCHAIN_API_KEY=ls__4f8a0a0114d145a08a0c3c7f5631289c" + "%%capture --no-stderr\n", + "%pip install -U langchain langchain_openai langchain_experimental langsmith pandas" ] }, { @@ -281,29 +265,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Rate limit exceeded for https://api.smith.langchain.com/runs/e906b9ec-0dc1-4f78-b5d1-d6da08bac093. HTTPError('429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs/e906b9ec-0dc1-4f78-b5d1-d6da08bac093', '{\"detail\":\"Hourly usage limit exceeded\"}')\n", - "Rate limit exceeded for https://api.smith.langchain.com/runs. HTTPError('429 Client Error: Too Many Requests for url: https://api.smith.langchain.com/runs', '{\"detail\":\"Hourly usage limit exceeded\"}')\n", - "Python REPL can execute arbitrary code. Use with caution.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "================================\u001b[1m Human Message \u001b[0m=================================\n", - "\n", - "The code `print('Hello, World!')` has been executed, and it printed `Hello, World!` to the terminal.\n" - ] - } - ], + "outputs": [], "source": [ "results = graph.invoke(\"Code hello world and print it to the terminal\")\n", "results[\"messages\"][-1].pretty_print()" diff --git a/examples/advanced_agents/multi-agent/img/supervisor-diagram.png b/examples/advanced_agents/multi-agent/img/supervisor-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..84497aa4cfc7ee7ae2c778cf7a4d8da4aea455d1 GIT binary patch literal 74993 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 n{9}p#IMM%_ Date: Thu, 18 Jan 2024 15:51:42 -0800 Subject: [PATCH 06/16] Add hierarchical example --- .../multi-agent/agent_supervisor.ipynb | 4 +- .../hierarchical_agent_teams.ipynb | 637 ++++++++++++++++++ .../multi-agent/img/hierarchical-diagram.png | Bin 0 -> 110777 bytes 3 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb create mode 100644 examples/advanced_agents/multi-agent/img/hierarchical-diagram.png diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb index 1723198b4..21acbcbc5 100644 --- a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb +++ b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb @@ -5,9 +5,9 @@ "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", "metadata": {}, "source": [ - "## Multi-agent Example 2: Agent Team Supervisor\n", + "## Multi-agent Example 3: Agent Team Supervisor\n", "\n", - "The prevoius example routed messages automatically based on the output of the initial researcher agent.\n", + "The previous example routed messages automatically based on the output of the initial researcher agent.\n", "\n", "We can also choose to use an LLM to orchestrate the different agents.\n", "\n", diff --git a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb new file mode 100644 index 000000000..64942f416 --- /dev/null +++ b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb @@ -0,0 +1,637 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", + "metadata": {}, + "source": [ + "## Agent Teams\n", + "\n", + "In our previous example ([Agent Supervisor](./agent_supervisor.ipynb)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n", + "\n", + "But what if the job for a single worker becomes too complex? Or what if the number of workers becomes too large?\n", + "\n", + "For some applications, the system may be more effective if work is distributed hierarchically.\n", + "\n", + "You can do this by composing different subgraphs and creating a top-level supervisor, along with mid-level supervisors.\n", + "\n", + "\n", + "To do this, let's build a simple research assistant! The graph will look something like the following:\n", + "\n", + "![diagram](./img/hierarchical-diagram.png)\n", + "\n", + "\n", + "\n", + "In the rest of this notebook, you will:\n", + "1. Define some utilities to help create the graph and their relations\n", + "2. Write the tools and agent implementations.\n", + "3. Generate each team's sub-graph.\n", + "4. Compose everything together.\n", + "\n", + "But before all of that, some setup:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langchain langchain_openai langsmith pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "import uuid\n", + "\n", + "\n", + "def _set_if_undefined(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass(f\"Please provide your {var}\")\n", + "\n", + "\n", + "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "_set_if_undefined(\"TAVILY_API_KEY\")\n", + "\n", + "# Optional, add tracing in LangSmith\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" + ] + }, + { + "cell_type": "markdown", + "id": "504ee1c6-2b6a-439d-9046-df54e1e15698", + "metadata": {}, + "source": [ + "## Define Utilities\n", + "\n", + "We are going to create a few utility functions to help us:\n", + "\n", + "1. Create an agent and add it to a graph.\n", + "2. Create a supervisor for the sub-graph.\n", + "\n", + "These will simplify the graph compositional code at the end for us so it's easier to see what's going on." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e09fb60f-1aac-455b-b67d-8d2e4ccfd747", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any, Callable, List, Optional, TypedDict, Union\n", + "\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables import Runnable\n", + "from langchain_core.tools import BaseTool\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "from langgraph.graph import END, StateGraph\n", + "\n", + "\n", + "class WorkerAgent(TypedDict):\n", + " name: str\n", + " description: str\n", + "\n", + "\n", + "def create_worker_agent(\n", + " graph_builder: StateGraph,\n", + " name: str,\n", + " llm: ChatOpenAI,\n", + " tools: list,\n", + " system_prompt: str,\n", + " prelude: Optional[Union[Runnable, Callable]] = None, # Optional required steps\n", + ") -> str:\n", + " \"\"\"Create a function-calling agent and add it to the graph.\"\"\"\n", + " system_prompt += \"\\nYou are one of the following team members: {team_members}\"\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " system_prompt,\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", + " ]\n", + " )\n", + " agent = create_openai_functions_agent(llm, tools, prompt)\n", + " executor = AgentExecutor(agent=agent, tools=tools)\n", + " chain = executor | (\n", + " lambda x: {\"messages\": [HumanMessage(content=x[\"output\"], name=name)]}\n", + " )\n", + " if prelude is not None:\n", + " chain = prelude | chain\n", + " graph_builder.add_node(name, chain)\n", + " return name\n", + "\n", + "\n", + "def create_team_supervisor(\n", + " graph_builder: StateGraph, llm: ChatOpenAI, system_prompt: str\n", + ") -> str:\n", + " \"\"\"An LLM-based router.\"\"\"\n", + " supervisor_id = uuid.uuid4().hex[:4]\n", + " supervisor_name = f\"supervisor - {supervisor_id}\"\n", + " members = list(graph_builder.nodes)\n", + " options = [\"FINISH\"] + members\n", + " function_def = {\n", + " \"name\": \"route\",\n", + " \"description\": \"Select the next role.\",\n", + " \"parameters\": {\n", + " \"title\": \"routeSchema\",\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"next\": {\n", + " \"title\": \"Next\",\n", + " \"anyOf\": [\n", + " {\"enum\": options},\n", + " ],\n", + " }\n", + " },\n", + " \"required\": [\"next\"],\n", + " },\n", + " }\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " (\n", + " \"system\",\n", + " \"Given the conversation above, who should act next?\"\n", + " \" Or should we FINISH? Select one of: {options}\",\n", + " ),\n", + " ]\n", + " ).partial(options=str(options), team_members=\", \".join(members))\n", + " chain = (\n", + " prompt\n", + " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", + " | JsonOutputFunctionsParser()\n", + " )\n", + " graph_builder.add_node(supervisor_name, chain)\n", + " conditional_map = {k: k for k in members}\n", + " conditional_map[\"FINISH\"] = END\n", + "\n", + " for member in members:\n", + " graph_builder.add_edge(member, supervisor_name)\n", + " graph_builder.add_conditional_edges(\n", + " supervisor_name, lambda x: x[\"next\"], conditional_map\n", + " )\n", + " return supervisor_name" + ] + }, + { + "cell_type": "markdown", + "id": "00282b1f-bb4d-4ee7-9bae-e8e6f586f12e", + "metadata": {}, + "source": [ + "## Define agents + tools\n", + "\n", + "### Research Team\n", + "\n", + "The research team can use a search engine and web scraper to find information on the web." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f04c6778-403b-4b49-9b93-678e910d5cec", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, List, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.tools import tool\n", + "from langsmith import trace\n", + "\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", + "\n", + "\n", + "@tool\n", + "def scrape_webpages(urls: List[str]) -> str:\n", + " \"\"\"Use requests and bs4 to scrape the provided web pages for detailed information.\"\"\"\n", + " loader = WebBaseLoader(urls)\n", + " docs = loader.load()\n", + " return \"\\n\\n\".join(\n", + " [\n", + " f'\\n{doc.page_content}\\n'\n", + " for doc in docs\n", + " ]\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "53db0c78-e357-48ba-ae5f-3fc04735a3b7", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "\n", + "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "\n", + "# Research team graph\n", + "class State(TypedDict):\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " team_members: List[str]\n", + " final_response: AIMessage\n", + " next: str\n", + "\n", + "\n", + "research_graph = StateGraph(State)\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "create_worker_agent(\n", + " research_graph,\n", + " \"Search\",\n", + " llm,\n", + " [tavily_tool],\n", + " \"You are a research assistant who can search for things using a search engine.\",\n", + ")\n", + "create_worker_agent(\n", + " research_graph,\n", + " \"Web Scraper\",\n", + " llm,\n", + " [tavily_tool],\n", + " \"You are a research assistant who can scrape specified urls for more detailed information.\",\n", + ")\n", + "supervisor_node = create_team_supervisor(\n", + " research_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following workers: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "research_graph.set_entry_point(supervisor_node)\n", + "\n", + "\n", + "def return_final_response(state):\n", + " return {\"final_response\": state[\"messages\"][-1]}\n", + "\n", + "\n", + "research_chain = research_graph.compile() | return_final_response" + ] + }, + { + "cell_type": "markdown", + "id": "749b99ab-f6f0-4c5d-a90b-10102465d186", + "metadata": {}, + "source": [ + "## Document Writing Team\n", + "\n", + "We will construct a graph in a similar fashion. This time using different tools.\n", + "\n", + "Note that we are giving file-system access to our agent here, which is not safe in all cases." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "202806d6-80bf-4153-ac16-ed6059236f2a", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from tempfile import TemporaryDirectory\n", + "from typing import Dict\n", + "\n", + "_TEMP_DIRECTORY = TemporaryDirectory()\n", + "WORKING_DIRECTORY = Path(_TEMP_DIRECTORY.name)\n", + "\n", + "\n", + "@tool\n", + "def create_outline(\n", + " points: Annotated[List[str], \"List of main points or sections.\"],\n", + " subpoints: Annotated[\n", + " List[List[str]],\n", + " \"List of lists, each containing subpoints for the corresponding main point.\",\n", + " ],\n", + " file_name: Annotated[str, \"File path to save the outline.\"],\n", + ") -> Annotated[str, \"Path of the saved outline file.\"]:\n", + " \"\"\"Create and save an outline.\"\"\"\n", + " if len(points) != len(subpoints):\n", + " raise ValueError(\"Each main point must have a corresponding list of subpoints.\")\n", + "\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " for i, point in enumerate(points):\n", + " file.write(f\"{i + 1}. {point}\\n\")\n", + " for j, subpoint in enumerate(subpoints[i]):\n", + " file.write(f\"\\t{j + 1}. {subpoint}\\n\")\n", + " return f\"Outline saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def read_document(\n", + " file_name: Annotated[str, \"File path to save the document.\"],\n", + " start: Annotated[Optional[int], \"The start line. Default is 0\"] = None,\n", + " end: Annotated[Optional[int], \"The end line. Default is None\"] = None,\n", + ") -> str:\n", + " \"\"\"Read the specified document.\"\"\"\n", + " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", + " lines = file.readlines()\n", + " if start is not None:\n", + " start = 0\n", + " return \"\\n\".join(lines[start:end])\n", + "\n", + "\n", + "@tool\n", + "def write_document(\n", + " content: Annotated[str, \"Text content to be written into the document.\"],\n", + " file_name: Annotated[str, \"File path to save the document.\"],\n", + ") -> Annotated[str, \"Path of the saved document file.\"]:\n", + " \"\"\"Create and save a text document.\"\"\"\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " file.write(content)\n", + " return f\"Document saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def edit_document(\n", + " file_name: Annotated[str, \"Path of the document to be edited.\"],\n", + " inserts: Annotated[\n", + " Dict[int, str],\n", + " \"Dictionary where key is the line number (1-indexed) and value is the text to be inserted at that line.\",\n", + " ],\n", + ") -> Annotated[str, \"Path of the edited document file.\"]:\n", + " \"\"\"Edit a document by inserting text at specific line numbers.\"\"\"\n", + " # Read the contents of the file\n", + "\n", + " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", + " lines = file.readlines()\n", + "\n", + " # Adjust the line numbers for 0-indexing and sort\n", + " sorted_inserts = sorted(inserts.items())\n", + "\n", + " # Perform the insertions\n", + " for line_number, text in sorted_inserts:\n", + " if 1 <= line_number <= len(lines) + 1:\n", + " # Insert the text at the specified line number\n", + " lines.insert(line_number - 1, text + \"\\n\")\n", + " else:\n", + " return f\"Error: Line number {line_number} is out of range.\"\n", + "\n", + " # Write the modified content back to the file\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " file.writelines(lines)\n", + "\n", + " return f\"Document edited and saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def create_plot(\n", + " data: Annotated[\n", + " Union[List[float], List[int]],\n", + " \"Numerical values for bar heights or line points.\",\n", + " ],\n", + " file_name: Annotated[str, \"File path to save the figure.\"],\n", + " labels: Annotated[\n", + " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", + " ] = None,\n", + " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", + " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", + " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", + " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", + " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", + ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", + " \"\"\"Create a line or bar chart.\"\"\"\n", + " if plot_type not in [\"bar\", \"line\"]:\n", + " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", + "\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + " x_positions = range(len(data))\n", + "\n", + " if labels and len(labels) == len(data):\n", + " plt.xticks(x_positions, labels)\n", + "\n", + " if plot_type == \"bar\":\n", + " ax.bar(x_positions, data, color=color)\n", + " elif plot_type == \"line\":\n", + " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", + "\n", + " ax.set_title(title)\n", + " ax.set_xlabel(xlabel)\n", + " ax.set_ylabel(ylabel)\n", + " fig.savefig(str(WORKING_DIRECTORY / file_name))\n", + " plt.close(fig)\n", + " return f'Saved \"{title}\" plot to {file_name}'" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1bcdbf44-9481-430c-8429-fa142ed8a626", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from pathlib import Path\n", + "\n", + "\n", + "# Research team graph\n", + "class AuthoringState(TypedDict):\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " team_members: str\n", + " final_response: AIMessage\n", + " next: str\n", + " current_files: str\n", + "\n", + "\n", + "authoring_graph = StateGraph(AuthoringState)\n", + "\n", + "\n", + "def prelude(state):\n", + " written_files = []\n", + " if not WORKING_DIRECTORY.exists():\n", + " WORKING_DIRECTORY.mkdir()\n", + " try:\n", + " written_files = [\n", + " f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n", + " ]\n", + " except:\n", + " pass\n", + " if not written_files:\n", + " return {**state, \"current_files\": \"No files written.\"}\n", + " return {\n", + " **state,\n", + " \"current_files\": \"\\nBelow are files your team has written to the directory:\\n\"\n", + " + \"\\n\".join([f\" - {f}\" for f in written_files]),\n", + " }\n", + "\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Author Docs\",\n", + " llm,\n", + " [write_document, edit_document, read_document],\n", + " \"You are an expert writing a research document.\\n\"\n", + " \"Below are files currently in your directory:\\n{current_files}\",\n", + " prelude=prelude,\n", + ")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Outline + Notetaker\",\n", + " llm,\n", + " [create_outline, read_document],\n", + " \"You are an expert senior researcher tasked with writing a paper outline and\"\n", + " \" taking notes to craft a perfect paper.{current_files}\",\n", + " prelude=prelude,\n", + ")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Generate Charts\",\n", + " llm,\n", + " [read_document, create_plot],\n", + " \"You are a data viz expert tasked with generating charts for a research project.\"\n", + " \"{current_files}\",\n", + ")\n", + "\n", + "supervisor_node = create_team_supervisor(\n", + " authoring_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following workers: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "authoring_graph.set_entry_point(supervisor_node)\n", + "\n", + "\n", + "def enter_chain(message: str):\n", + " return {\n", + " \"messages\": [HumanMessage(content=\"Write a short report on pikas\")],\n", + " \"team_members\": \"\\n\".join(sorted(authoring_graph.nodes)),\n", + " }\n", + "\n", + "\n", + "def return_final_response(state):\n", + " return {\"final_response\": state[\"messages\"][-1]}\n", + "\n", + "\n", + "authoring_chain = enter_chain | authoring_graph.compile() | return_final_response" + ] + }, + { + "cell_type": "markdown", + "id": "f4b5b08d-9a9a-474a-94b4-f7aaa8ff19e6", + "metadata": {}, + "source": [ + "## Adding Hierarchy\n", + "\n", + "We've created two graphs already. Now let's put them together.\n", + "\n", + "We'll create a third graph to orchestrate the previous two, and add some connectors to define how state is shared between the different graphs." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "\n", + "# Research team graph\n", + "class State(TypedDict):\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " next: str\n", + "\n", + "\n", + "def join_graph(response):\n", + " return {\"messages\": [response[\"final_response\"]]}\n", + "\n", + "\n", + "super_graph = StateGraph(State)\n", + "super_graph.add_node(\"Research team\", research_chain | join_graph)\n", + "super_graph.add_node(\"Paper writing team\", authoring_chain | join_graph)\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "supervisor_node = create_team_supervisor(\n", + " super_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following teams: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "super_graph.set_entry_point(supervisor_node)\n", + "super_graph = super_graph.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", + "metadata": {}, + "outputs": [], + "source": [ + "results = super_graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(\n", + " content=\"Research and write a report about the climate impacts\"\n", + " \" on crop yields in Bangladesh in 2023. Write the paper and include plots.\",\n", + " )\n", + " ]\n", + " },\n", + " # {\"recursion_limit\": 150},\n", + ")\n", + "results[\"messages\"][-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6ffcc7f-7b78-4ca5-8e0a-7c0ac08300fc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/advanced_agents/multi-agent/img/hierarchical-diagram.png b/examples/advanced_agents/multi-agent/img/hierarchical-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..b3fa81275dadd96bfd203b9fb92ae01173a94252 GIT binary patch literal 110777 zcmeFZcQ}>*A2@90ph9Job)q7&$~@K)QeG5k{XT#EuIu^Zd7kTAS68?De$UsQf|L|wPLW(7!NI{fb?3H}3Jwlo5)Kaj z4&(%Q^YOmuh}sw^4z2dyczL zZchyD?>S6}n>NSkoWNOqp_?a2-Gu{Hyax>;B{qC|=Z;B4DK6gk#{_dohUjRnhc(qw zIA%{KEBimkw9@wR5xxE?zP+DmzC^ewf)ha#*s-};CK5=7_gG`8`Z*p>;PoL7S)``_ zef+*Nub|bWCW5L+b`wE8rSIV!qBTsMDE6KPH^c z^W@tTo85-PkgZ{(6n+Ra9!al(yV4uis5Ze&9=KXTiTSq?! z!R=3JnW6Q(Rj@bq{-E{vU6SbgYJu+3)5>k+k0f>^zDI9MK2<4r_cn;cyE7uX#h(wq zlw5f66Y@7X?M=FC^`XH^OVj7S8lJAU^-EOsBa9^oT!rehekTjo*s^?iH(0gy=Io72 zmM@=+w?us(tv3>ln7EW5xs_W$K$jwxsva2?agvA9DCFjSm2=e= zl23SEY8TYnqav>(mb!>b;+e%Qd4?`+g{!H7F_x=HNbY-(%E&IE7V+EQEkh2G91(9p z>bYQewJcpH#aMU{O)RMaInBFQ!aq64FW0g=iUpG5jhr*K%McW}H60#9Y%lqs?0j|3 zrr%w0i^T6|x#}nUYgm`QL?a35@b`yG|U zLSu_0cz}m%t91Pd^Yd|0CYPr~(!Ue~383C)x{u2=a7ZrhI|i!J(Zl`Aij~Beh(!DZ z$$X#ax^P5=pE)Hhho=Z--jP9YT8g}~;k3-(^KK!_A3jg@fGP4Rf}2Uwv-Jgy)X7W! zXPyKVmEisfaUZ4lJ4F_Aqq$m?XmW8d;csNCf{Rp%#MGsMc-Ct#cOp$LP5Ewpsn>qD zuSq&UVo~}Vw}k9oKz#|PH4dGv22Ft9_LuM05hm=*FV*}OU#ty3{QGTCX-helmDPl% zCc$WV`8J<0U-3mst!(eCaHj5+w{8UiZeC$6dvCWqTu$%zp4ADYuWG)Z_JOoVISZHj z!&yz{pTYDme!uNnm8vK`kzDg*`3DmA)w`OutRP=@LbywCRnSv;g%XllKh*YE^p1!; zQ)45W;fG(;5>>wfUBZ}YniZOJaK2Y?1$TGHXl^>=w^{8rH*MqNB%H;^$Cs&axX*&q zGl|Q%|7}=^_R0<-9mjT$TaoSRJ=>Bw_+uMl$BlQ>_{N5(Eq{5$B8C5+*n>iRk!BG% zDpdrpJgIai&u|kq;y6eUv*I^YS$try#MAkr@PTNK=+rM(LBeJqxnCpzu3x)42=yO} z{XOk*i`>_@l^m87{tEgsP;}%eH-*6^NDzD^;@qWEKcu7r&%H)AO4dhEes_C7suDUU zd73#bg7mhqJ*{it3N-hbBCCH!QeQfSg7zqYzZllNt?iQySbD)rzfV>L~Vp9Blbd|78_Ys5yR zV#99O5|#K!E=t7)DAt<(4q05?P$*-t`I7saY*B277I};NQqS2k2;Enjn^*3Wy!BO5 z=ur@q62wnP;ruGh^ekQO;}y3mQy#xp^05lKcL(5E%<-3s&N80ezH{lh*pK)z$>1w? zcL?8|X@|92w-dLksZdYjYx!B;bh<+HLiGoeJ2=1P!qhFLv+2E^pE?UWwP()Hh|bVbQc~WaRE*N3d|OW!RmkS9 z)|@GFZ!)^f44w?0w7VzEHqa-M{rs^Bci8s#BTt zPL4h9<-gtb-0;d(%3f2P64vti-)P$z2rZY{!O*4&uVpN-6Pn@5~Y>a8z zzIOK-6URf%PR)V5cbeBZ3a)i)#us?lm{>(w7FK0^$>`T^d1I{iK+o8-#sA@`>5bmh z`iWAGwwLBH#-G2e1=CwhK8!ZexbNJ{Q0QDt-DmT`hEam%x6n=ABnzp4CmHw+Z(HBi z{(hUBW9)03XZ&Qe;TaXPsthNitM!-wi$I!gnw5gHjCm*tt(r)NdFLZjVbgdAzhy`2 zH*y~uETeBlYviou;B|-Q6c0>UKeHwscr#d0>Ruu@@MzFo$4UYek|T4+yK0xg7Tgly)5{7RS{L~RYlP3#OlOV z2{NB1pBqme`E1`DxS1Ldd-Afce*h=h{4W|imskr0gI6*=jUQgxcKzM>yZSqvX^!a< zh5R$K2byE`-`$m8Dkr7;n~^sK>7Vx$uZ2CdZ+xa{WO>(8dak2H+3ve49T}-7DLaYy-8*mW4KpV0wRQe9cj};$cUM2ByV8&FfZ4b$pX@mAU@8*(dThPz00P~O~kJY95@}Vn+JwN;(|5KRmMe&J~ z4yQxX(=&Oqbnk6?;d_7U3f-f9Byiq}t$)S5z;-#C!Xh@tI;OYtK>+^Zrl6 zuPW7S1bXy~c4tqwg*C_73F~BtW@I{_|D&z_Gu0Z{hFGV^OHe_jpHNHmCyYP0eu7gGhFVv)@38EDepZI+l!fA8j5

{vpA^;w2Y$UC& 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≷P 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 Date: Thu, 18 Jan 2024 15:54:28 -0800 Subject: [PATCH 07/16] note --- .../hierarchical_agent_teams.ipynb | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb index 64942f416..98e445b4b 100644 --- a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb +++ b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb @@ -24,9 +24,8 @@ "\n", "In the rest of this notebook, you will:\n", "1. Define some utilities to help create the graph and their relations\n", - "2. Write the tools and agent implementations.\n", - "3. Generate each team's sub-graph.\n", - "4. Compose everything together.\n", + "2. Write the tools and agent implementations for each team\n", + "3. Compose everything together.\n", "\n", "But before all of that, some setup:" ] @@ -63,7 +62,8 @@ "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", "_set_if_undefined(\"TAVILY_API_KEY\")\n", "\n", - "# Optional, add tracing in LangSmith\n", + "# Optional, add tracing in LangSmith. \n", + "# This will help you visualize and debug the control flow\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" ] @@ -585,10 +585,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "GraphRecursionError", + "evalue": "Recursion limit of 25 reachedwithout hitting a stop condition. You can increase the limitby setting the `recursion_limit` config key.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mGraphRecursionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43msuper_graph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmessages\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43mHumanMessage\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43mcontent\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mResearch and write a report about the climate impacts\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 6\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m on crop yields in Bangladesh in 2023. Write the paper and include plots.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 7\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 8\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 9\u001b[0m \u001b[43m \u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 10\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# {\"recursion_limit\": 150},\u001b[39;49;00m\n\u001b[1;32m 11\u001b[0m \u001b[43m)\u001b[49m\n\u001b[1;32m 12\u001b[0m results[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessages\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:521\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 512\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 513\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 519\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 520\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 521\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 522\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 523\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 524\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 525\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 526\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 527\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 528\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 529\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:557\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 548\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 549\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 550\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 556\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 557\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 558\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 559\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 560\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 561\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 562\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 563\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 564\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 565\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1232\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1230\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1231\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1232\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1233\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:289\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 287\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n\u001b[1;32m 288\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m step \u001b[38;5;241m==\u001b[39m config[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrecursion_limit\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[0;32m--> 289\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m GraphRecursionError(\n\u001b[1;32m 290\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRecursion limit of \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mconfig[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrecursion_limit\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m reached\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 291\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwithout hitting a stop condition. You can increase the limit\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 292\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mby setting the `recursion_limit` config key.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 293\u001b[0m )\n\u001b[1;32m 295\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdebug:\n\u001b[1;32m 296\u001b[0m print_step_start(step, next_tasks)\n", + "\u001b[0;31mGraphRecursionError\u001b[0m: Recursion limit of 25 reachedwithout hitting a stop condition. You can increase the limitby setting the `recursion_limit` config key." + ] + } + ], "source": [ "results = super_graph.invoke(\n", " {\n", From 48e635b9f3afc8bbfefef96617ebd22a8aff7695 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:58:54 -0800 Subject: [PATCH 08/16] UPdate --- .../multi-agent/agent_supervisor.ipynb | 6 +-- .../hierarchical_agent_teams.ipynb | 5 +-- .../multi-agent-collaboration.ipynb | 43 +++++++++++++------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb index 21acbcbc5..07c85533f 100644 --- a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb +++ b/examples/advanced_agents/multi-agent/agent_supervisor.ipynb @@ -5,9 +5,9 @@ "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", "metadata": {}, "source": [ - "## Multi-agent Example 3: Agent Team Supervisor\n", + "## Agent Supervisor\n", "\n", - "The previous example routed messages automatically based on the output of the initial researcher agent.\n", + "The [previous example](multi-agent-collaboration.ipynb) routed messages automatically based on the output of the initial researcher agent.\n", "\n", "We can also choose to use an LLM to orchestrate the different agents.\n", "\n", @@ -15,7 +15,7 @@ "\n", "![diagram](./img/supervisor-diagram.png)\n", "\n", - "To simplify each agent node, we will use the AgentExecutor class from LangChain." + "To simplify the code in each agent node, we will use the AgentExecutor class from LangChain. This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." ] }, { diff --git a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb index 98e445b4b..e3bfcd053 100644 --- a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb +++ b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb @@ -5,7 +5,7 @@ "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", "metadata": {}, "source": [ - "## Agent Teams\n", + "## Hierarchical Agent Teams\n", "\n", "In our previous example ([Agent Supervisor](./agent_supervisor.ipynb)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n", "\n", @@ -21,7 +21,6 @@ "![diagram](./img/hierarchical-diagram.png)\n", "\n", "\n", - "\n", "In the rest of this notebook, you will:\n", "1. Define some utilities to help create the graph and their relations\n", "2. Write the tools and agent implementations for each team\n", @@ -62,7 +61,7 @@ "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", "_set_if_undefined(\"TAVILY_API_KEY\")\n", "\n", - "# Optional, add tracing in LangSmith. \n", + "# Optional, add tracing in LangSmith.\n", "# This will help you visualize and debug the control flow\n", "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" diff --git a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb index 30d666a9a..9b3c8f0d3 100644 --- a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb +++ b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb @@ -5,11 +5,13 @@ "id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334", "metadata": {}, "source": [ - "# Multi-agent Collaboration Intro\n", + "# Basic Multi-agent Collaboration\n", "\n", "A single agent can usually operate effectively using a handful of tools and a scoped domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. One way to improve your system's performance is through multi-agent collaboration. Each agent can specialize in a task or domain, and LangGraph can effectively orchestrate them to accomplish a larger goal. \n", "\n", - "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al." + "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.\n", + "\n", + "This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." ] }, { @@ -52,7 +54,7 @@ "id": "7d5fc0f3-5d9e-4e72-a281-177f101c2a7d", "metadata": {}, "source": [ - "## Example 1: 2 Agents\n", + "## Create tool\n", "\n", "Below is an example of 2 agents collaborating to accomplish a single task." ] @@ -117,10 +119,20 @@ " return fig, ax" ] }, + { + "cell_type": "markdown", + "id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb", + "metadata": {}, + "source": [ + "## Create the graph\n", + "\n", + "We will create the individual agents below and place them within the graph." + ] + }, { "cell_type": "code", "execution_count": null, - "id": "b0ca7d80-31e4-4394-bfce-ffac314bac7d", + "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", "metadata": {}, "outputs": [], "source": [ @@ -147,16 +159,9 @@ "\n", "class AgentState(TypedDict):\n", " messages: Annotated[Sequence[BaseMessage], operator.add]\n", - " sender: str" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", - "metadata": {}, - "outputs": [], - "source": [ + " sender: str\n", + "\n", + "\n", "workflow = StateGraph(AgentState)\n", "\n", "# This a helper class we have that is useful for running tools\n", @@ -260,6 +265,16 @@ "graph = workflow.compile()" ] }, + { + "cell_type": "markdown", + "id": "8c9447e7-9ab6-43eb-8ae6-9b52f8ba8425", + "metadata": {}, + "source": [ + "## Invoke\n", + "\n", + "With the graph created, you can invoke it!" + ] + }, { "cell_type": "code", "execution_count": null, From 2e871982eea878e5890220dd23ffeb743cfeb9a7 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:28:40 -0800 Subject: [PATCH 09/16] Update readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index ad8725a89..72ce4ff84 100644 --- a/README.md +++ b/README.md @@ -451,6 +451,14 @@ We also have a lot of examples highlighting how to slightly modify the base chat - [Force calling a tool first](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/force-calling-a-tool-first.ipynb): How to always call a specific tool first - [Managing agent steps](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/managing-agent-steps.ipynb): How to more explicitly manage intermediate steps that an agent takes + +### Advanced + Multi-agent Examples + +- [Multi-agent collaboration](examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb): how to create two agents that work together to accomplish a task +- [Multi-agent with supervisor](examples/advanced_agents/multi-agent/agent_supervisor.ipynb): how to orchestrate individual agents by using an LLM as a "supervisor" to distribute work +- [Hierarchical agent teams](examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb): how to orchestrate "teams" of agents as nested graphs that can collaborate to solve a problem +- [Chat bot evaluation as multi-agent simulation](examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb): How to simulate a dialogue between a "virtual user" and your chat bot + ### Async If you are running LangGraph in async workflows, you may want to create the nodes to be async by default. From 6dabca8b77b9f96ef622fe9a9fceaa9dc05259a2 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:34:12 -0800 Subject: [PATCH 10/16] Update simulation --- .../agent-simulation-evaluation.ipynb | 470 ++++++++++-------- 1 file changed, 249 insertions(+), 221 deletions(-) diff --git a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb b/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb index 71ddb19a1..57298515b 100644 --- a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb +++ b/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb @@ -59,58 +59,20 @@ }, { "cell_type": "markdown", - "id": "6ef4528d-6b2a-47c7-98b5-50f14984a304", + "id": "6b69031e-7bf8-4401-941e-4dccd59ea870", "metadata": {}, "source": [ - "## 1. Define Chat Bot\n", + "## 1. Define the virtual user\n", "\n", - "Next, we will define our chat bot. For this notebook, we assume the bot's API accepts a list of messages and responds with a message. If you want to update this, all you'll have to change is this section and the \"get_messages_for_agent\" function in \n", - "the simulator below.\n", + "The virtual user needs an LLM to reason and instructions for how it's supposed to behave (or what it's trying to accomplish).\n", "\n", - "The implementation within `my_chat_bot` is configurable and can even be run on another system (e.g., if your system isn't running in python)." + "Below, create an agent and instruct it to role-play a 'simulated' user. By including the `{system_prompt}` placeholder in the prompt and a state variable with the same name `Environment`, you can customize the user behavior each time you simulate a dialogue." ] }, { "cell_type": "code", "execution_count": 3, - "id": "828479af-cf9c-4888-a365-599643a96b55", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List\n", - "\n", - "import openai\n", - "\n", - "\n", - "# This is flexible, but you can define your agent here, or call your agent API here.\n", - "def my_chat_bot(messages: List[dict]) -> dict:\n", - " completion = openai.chat.completions.create(\n", - " messages=messages, model=\"gpt-3.5-turbo\"\n", - " )\n", - " return completion.choices[0].message.model_dump()" - ] - }, - { - "cell_type": "markdown", - "id": "321312b4-a1f0-4454-a481-fdac4e37cb7d", - "metadata": {}, - "source": [ - "## 2. Define the Agent Simulation\n", - "\n", - "The code below creates a LangGraph workflow to run the simulation. The main components are:\n", - "\n", - "1. Simulation state: the inputs to each node in the graph, containing the messages (stateful) and the details about the simulated user.\n", - "2. Functions to interoperate between the simulation state and your chat bot's API\n", - "3. The simulated user definition.\n", - "4. The graph itself, with a conditional stopping criterion.\n", - "\n", - "Read the comments in the code below for more information." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "cc93d114-fa65-4021-a67e-b1e6d4edc88a", + "id": "0f4c63d8-15ef-4ee6-8bb6-654465baae04", "metadata": {}, "outputs": [], "source": [ @@ -128,25 +90,182 @@ "SIMULATED_USER_NAME = \"simulated\"\n", "\n", "\n", - "# This is just an example, we can\n", - "# configure additional parameters if\n", - "# you want more control\n", - "class SimulatedUserConfig(TypedDict):\n", - " system_prompt: str\n", - "\n", - "\n", "# This is the input to every node in the simulation graph\n", "# It tracks the graph state over time. Our only \"state\"\n", "# is the conversation messages, while the user config\n", "# is provided to make the virtual user more unique or realistic\n", "class Environment(TypedDict):\n", " messages: Annotated[List[BaseMessage], operator.add]\n", - " simulated_user_config: SimulatedUserConfig\n", + " # The system prompt will be fed into the prompt template below\n", + " # For more control, try adding different parameters to provide the\n", + " # prompt template below\n", + " system_prompt: str\n", "\n", "\n", - "# We currently let the virtual user decide if the conversation can end\n", - "# we could also track max conversation turns, add a conversation \"supervisor\"\n", - "# or use other heuristics to control the dialogue flow\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are role-playing a human character: '{name}'. \"\n", + " \"You are not an AI assistant and you are not supposed to help or assist.\"\n", + " \" You must behave as this human would throughout the conversation below.\\n\\n\"\n", + " \"Your messages will bear the name 'simulated', but DO NOT under any circumstances\"\n", + " \"say that you are 'simulated'. You will be evaluated based on how realistic your\"\n", + " \"impersonation of this character is. This must feel real! Here are the details for your character:\"\n", + " \"\\n\"\n", + " # This system_prompt is specified in the Environment above\n", + " \"{system_prompt}\"\n", + " # The stopping criteria of FINISHED is used in the function hould_continue in a later\n", + " # section. This tells the graph to stop the simulation.\n", + " '\\n\\nWhen you are finished with the conversation, respond with a single word \"FINISHED\"',\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " ]\n", + ").partial(name=SIMULATED_USER_NAME)\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "\n", + "\n", + "def rename_message(message: AIMessage):\n", + " # If we use an AIMessage, the simulated user may forget to continue role playing.\n", + " # It will also confuse YOUR chat bot, since IT is supposed to be the AI in this scenario.\n", + " # We instead convert them to 'Human' messages with the 'simulated' name\n", + " # Your chat bot will then receive all the user's messages and think they\n", + " # are human ones\n", + " return {\n", + " \"messages\": [HumanMessage(content=message.content, name=SIMULATED_USER_NAME)]\n", + " }" + ] + }, + { + "cell_type": "markdown", + "id": "d8d5a3a7-72b6-4063-89e6-3ecf2bce3340", + "metadata": {}, + "source": [ + "Now we can compose these pieces using LCEL. The `|` syntax pipelines the data flow." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b486511b-57f3-4a7c-be0d-691a7d1006da", + "metadata": {}, + "outputs": [], + "source": [ + "virtual_user = prompt | llm | rename_message" + ] + }, + { + "cell_type": "markdown", + "id": "6ef4528d-6b2a-47c7-98b5-50f14984a304", + "metadata": {}, + "source": [ + "## 2. Define your chat bot\n", + "\n", + "Next, define the chat bot. For this notebook, we assume the bot's API accepts a list of messages and responds with a message. If you want to update this, you can change this section and the \"get_messages_for_agent\" function in the simulator below (as well as the environment state if it requires additional inputs).\n", + "\n", + "The actual implementation within `my_chat_bot` is configurable and can even be run on another system (e.g., if your system isn't running in python)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "828479af-cf9c-4888-a365-599643a96b55", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "\n", + "import openai\n", + "\n", + "\n", + "# This is flexible, but you can define your agent here, or call your agent API here.\n", + "def my_chat_bot(messages: List[dict], model=\"gpt-3.5-turbo\") -> dict:\n", + " completion = openai.chat.completions.create(\n", + " messages=messages, model=\"gpt-3.5-turbo\"\n", + " )\n", + " return completion.choices[0].message.model_dump()" + ] + }, + { + "cell_type": "markdown", + "id": "c7669a7e-1602-4af7-a730-84fa19f363f2", + "metadata": {}, + "source": [ + "Every node in the simulation is pased a state object of the `Environment` type above.\n", + "\n", + "The two functions below define the API between the `Environment` state and your chat bot." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "4d495ccc-0f5f-4194-89e5-15dccbbb7412", + "metadata": {}, + "outputs": [], + "source": [ + "@chain\n", + "def get_messages_for_agent(state: Environment):\n", + " \"\"\"Convert the simulation state to the input\n", + "\n", + " for your agent you want to evaluate.\"\"\"\n", + " messages = []\n", + " for message in state[\"messages\"]:\n", + " messages.append(convert_message_to_dict(message))\n", + " if getattr(message, \"name\", None) != SIMULATED_USER_NAME:\n", + " # Ensure YOUR chat bot still sees its messages\n", + " # as assistant messages\n", + " messages[-1][\"role\"] = \"assistant\"\n", + " return messages\n", + "\n", + "\n", + "def get_response_message_from_agent(agent_output):\n", + " \"\"\"Get the response from the agent you are evaluting,\n", + " and use it to update the simulation state.\"\"\"\n", + " # If we directly return an AI message from your chat bot, our\n", + " # virtual user will likely forget it's role playing. To cover this up\n", + " # we will convert it to a Human message.\n", + " return {\"messages\": [HumanMessage(content=agent_output[\"content\"])]}" + ] + }, + { + "cell_type": "markdown", + "id": "321312b4-a1f0-4454-a481-fdac4e37cb7d", + "metadata": {}, + "source": [ + "## 3. Define simulation graph\n", + "\n", + "The dialogue simulation is almost ready. It's time to put everything together!\n", + "\n", + "Below, create a graph using the `Environment` state defined above. Wire together the\n", + "virtual user and your chat bot, including a conditional `should_continue` edge to\n", + "handle the stopping behavior." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "6d4caf50-4108-4a68-9c0a-775fa97fccd7", + "metadata": {}, + "outputs": [], + "source": [ + "graph_builder = StateGraph(Environment)\n", + "graph_builder.add_node(\"user\", virtual_user)\n", + "graph_builder.add_node(\n", + " \"chat_bot\",\n", + " # The \"|\" syntax composes these steps in the pipeline to map between\n", + " # the simulation state and your chat bot's API\n", + " get_messages_for_agent | my_chat_bot | get_response_message_from_agent,\n", + ")\n", + "# Every response from your chat bot will automatically go to the\n", + "# simulated user\n", + "graph_builder.add_edge(\"chat_bot\", \"user\")\n", + "\n", + "\n", + "# Recall that we instructed the simulated user to respond \"FINISHED\" when\n", + "# it is done with the conversation. This function\n", + "# parses that output and tells the graph to cease execution.\n", + "# You can add other heuristics here for more control.\n", "def should_continue(state: Environment):\n", " \"\"\"Determine if the simulation should continue.\"\"\"\n", " if state[\"messages\"][-1].content.strip().endswith(\"FINISHED\"):\n", @@ -154,102 +273,22 @@ " return \"continue\"\n", "\n", "\n", - "## The next two functions define the API between the simulation\n", - "# and the chat bot you wish to test.\n", - "# We are assuming your chat bot accepts a list of OAI messages\n", - "@chain\n", - "def get_messages_for_agent(state: Environment):\n", - " \"\"\"Convert the simulation state to the input\n", - "\n", - " for your agent you want to evaluate.\"\"\"\n", - " return [convert_message_to_dict(message) for message in state[\"messages\"]]\n", - "\n", - "\n", - "# This takes the output of your chat bot\n", - "# and adds it to the simulation state\n", - "def get_response_message_from_agent(agent_output):\n", - " \"\"\"Get the response from the agent you are evaluting,\n", - " and use it to update the simulation state.\"\"\"\n", - " # If we do an ai message here, the user proxy llm\n", - " # will usually forget it's acting.\n", - " return {\"messages\": [HumanMessage(content=agent_output[\"content\"])]}\n", - "\n", - "\n", - "# This is run once at the beginning of the simulation.\n", - "# It's more convenient to just write an input string\n", - "# than to pass in a full message, but this could be removed below\n", - "def enter(inputs: dict):\n", - " \"\"\"Start the simulation. This makes it less verbose to invoke.\"\"\"\n", - " inputs[\"messages\"] = [\n", - " HumanMessage(content=inputs[\"input\"], name=SIMULATED_USER_NAME)\n", - " ]\n", - " return inputs\n", - "\n", - "\n", - "def create_simulation(chat_bot: Callable[[List[Dict]], Dict], simulated_user_llm=None):\n", - " \"\"\"Create a chat bot simulation graph.\n", - "\n", - " Args:\n", - " - chat_bot: the agent you are evaluating. Accepts a list of openai messages\n", - " and returns an openai assistant message\n", - " - simulated_user_llm: the LLM to power your virtual user.\n", - " Defaults to gpt-4-1106-preview\n", - " Returns:\n", - " - simulation: an runnable object formed from compiling the state graph\n", - " \"\"\"\n", - " # This defines the virtual user proxy\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " \"You are role-playing a human character: '{name}'. \"\n", - " \"You are not an AI assistant and you are not supposed to help or assist.\"\n", - " \" You must behave as this human would throughout the conversation below.\\n\\n\"\n", - " \"Your messages will bear the name 'simulated', but DO NOT under any circumstances\"\n", - " \"say that you are 'simulated'. You will be evaluated based on how realistic your\"\n", - " \"impersonation of this character is. This must feel real! Here are the details for your character:\"\n", - " \"\\n\"\n", - " \"{system_prompt}\" # This is the value you provide to characterize the user\n", - " '\\n\\nWhen you are finished with the conversation, respond with a single word \"FINISHED\"',\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " ]\n", - " ).partial(name=SIMULATED_USER_NAME)\n", - " simulated_user_llm = simulated_user_llm or ChatOpenAI(model=\"gpt-4-1106-preview\")\n", - " user_proxy = (\n", - " (lambda x: {**x, **x[\"simulated_user_config\"]})\n", - " | prompt\n", - " | simulated_user_llm\n", - " | (\n", - " lambda x: {\n", - " \"messages\": [HumanMessage(content=x.content, name=SIMULATED_USER_NAME)]\n", - " }\n", - " )\n", - " )\n", - " graph_builder = StateGraph(Environment)\n", - " graph_builder.add_node(\"user\", user_proxy)\n", - " graph_builder.add_node(\n", - " # The \"|\" syntax composes these steps in the pipeline to map between\n", - " # the simulation state and your chat bot's API\n", - " \"chat_bot\",\n", - " get_messages_for_agent | chat_bot | get_response_message_from_agent,\n", - " )\n", - " # Every response from your chat bot will automatically go to the\n", - " # simulated user\n", - " graph_builder.add_edge(\"chat_bot\", \"user\")\n", - " graph_builder.add_conditional_edges(\n", - " \"user\",\n", - " should_continue,\n", - " # If the finish criteria are met, we will stop the simulation,\n", - " # otherwise, the virtual user's message will be sent to your chat bot\n", - " {\n", - " \"end\": END,\n", - " \"continue\": \"chat_bot\",\n", - " },\n", - " )\n", - " # The input will first go to your chat bot\n", - " graph_builder.set_entry_point(\"chat_bot\")\n", - " return (enter | graph_builder.compile()).with_config(run_name=\"Agent Simulation\")" + "graph_builder.add_conditional_edges(\n", + " # Every time the \"user\" node completes ...\n", + " \"user\",\n", + " # Call this function ...\n", + " should_continue,\n", + " # And based on the outputs of should_continue ...\n", + " {\n", + " # End the simulation OR\n", + " \"end\": END,\n", + " # continue to the chat_bot node\n", + " \"continue\": \"chat_bot\",\n", + " },\n", + ")\n", + "# The input will first go to your chat bot\n", + "graph_builder.set_entry_point(\"chat_bot\")\n", + "simulation = graph_builder.compile()" ] }, { @@ -265,45 +304,79 @@ }, { "cell_type": "code", - "execution_count": 13, - "id": "4d495ccc-0f5f-4194-89e5-15dccbbb7412", + "execution_count": 8, + "id": "9a7cc134-4fbf-4240-8976-7605a6263578", "metadata": {}, "outputs": [], "source": [ - "simulation = create_simulation(my_chat_bot)" + "# The tracing context manager lets us easily fetch the trace URL in-context.\n", + "# You can turn this off if you don't want to trace the execution.\n", + "result = simulation.invoke(\n", + " {\n", + " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", + " \" They all like the beach, except for Aunt Lily, who prefers the mountains.\",\n", + " \"messages\": [\n", + " HumanMessage(\n", + " content=\"help me plan my family vacation\", name=SIMULATED_USER_NAME\n", + " )\n", + " ],\n", + " }\n", + ")" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 9, "id": "fa91e733-3493-43c2-ba21-171cf78deef8", "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel input which has no readers\n" + "ename": "SyntaxError", + "evalue": "invalid syntax (1300180531.py, line 5)", + "output_type": "error", + "traceback": [ + "\u001b[0;36m Cell \u001b[0;32mIn[9], line 5\u001b[0;36m\u001b[0m\n\u001b[0;31m \"messages\": [\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], "source": [ - "from langchain_core.tracers.context import tracing_v2_enabled\n", - "\n", - "# The tracing context manager lets us easily fetch the trace URL in-context.\n", - "# You can turn this off if you don't want to trace the execution.\n", - "with tracing_v2_enabled() as tracer:\n", - " result = simulation.invoke(\n", - " {\n", - " \"simulated_user_config\": {\n", - " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", - " \" They all like the beach, except for Aunt Lily, who prefers the mountains.\"\n", - " },\n", - " \"input\": \"help me plan my family vacation\",\n", - " }\n", - " )\n", - " # You can go to this run to review the entire simulation trace\n", - " url = tracer.get_run_url()" + "result = simulation.invoke(\n", + " {\n", + " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", + " \" They all like the beach, except for Aunt Lily, who prefers the mountains.\"\n", + " \"messages\": [\n", + " HumanMessage(content=\"help me plan my family vacation\", name=SIMULATED_USER_NAME)\n", + " ],\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0de1664f-1625-42e7-9dad-eb2c061b88d4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[HumanMessage(content='help me plan my family vacation', name='simulated'),\n", + " HumanMessage(content=\"Of course! I would be happy to help you plan your family vacation. Please provide me with some more details:\\n\\n1. Destination: Where are you thinking of going? Do you have any specific preferences or requirements?\\n2. Duration: How long would you like your vacation to be?\\n3. Budget: What is your approximate budget for the trip?\\n4. Number of people: How many people will be traveling with you?\\n5. Interests: What are the interests or activities your family members enjoy?\\n6. Age of children: If you have children, please provide their ages.\\n\\nOnce I have this information, I will be able to provide you with a customized vacation plan that suits your family's needs and preferences.\"),\n", + " HumanMessage(content=\"Well, planning a family vacation can be a bit of a balancing act, especially when everyone has different preferences. First off, since we're on a budget, we've got to consider somewhere that's not going to break the bank. And since everyone likes the beach—except Aunt Lily, who's more into the mountains—we might want to look for a place that offers a bit of both.\\n\\nMaybe we could find a coastal area that's near some mountains or hills. That way, most of the family can enjoy the beach while Aunt Lily has the option to explore the mountains or go on a hike. The duration of the trip will probably depend on how much time we can take off work and what our budget can handle.\\n\\nSpeaking of budget, we've got to keep costs in mind for accommodations, food, travel, and any activities we plan to do. Since there are a few of us traveling, maybe renting a house or apartment through a site like Airbnb could be more cost-effective than booking multiple hotel rooms.\\n\\nAs for the number of people, I'd have to get a headcount, but let's assume it's the usual gang. That way, we can start looking into group discounts or family rates for activities and travel.\\n\\nWe also need to think about what kind of activities we want access to. Besides the beach and hiking, do we want to be near a town or city for dining out and entertainment? Or would we prefer a more secluded spot where we can cook our meals and have some quiet time?\\n\\nLastly, we don't have to worry about children's ages for this trip, which simplifies things a bit.\\n\\nSo, what do you think? Is there a place you know of that could fit this mix of interests and constraints?\", name='simulated'),\n", + " HumanMessage(content=\"Based on your preferences and requirements, I can suggest a few possible destinations that offer a mix of beach and mountain activities:\\n\\n1. California, USA: Consider areas like Santa Cruz or Half Moon Bay, which offer beautiful coastal landscapes and are near the Santa Cruz Mountains for hiking and exploring.\\n\\n2. Costa Rica: This country has stunning beaches along the Pacific and Caribbean coasts, as well as rainforests and mountains for hiking and wildlife encounters.\\n\\n3. Barcelona, Spain: The city offers a vibrant beachfront, while being in close proximity to the mountains of Montserrat for hiking and scenic views.\\n\\n4. Bali, Indonesia: With its gorgeous beaches and lush, mountainous landscapes, Bali provides a diverse range of activities for everyone.\\n\\n5. Cape Town, South Africa: Known for its stunning coastal scenery, Cape Town also offers Table Mountain and nearby hiking trails for exploring the mountains.\\n\\n6. Thailand: Places like Phuket or Krabi provide beautiful beaches for relaxation and activities, while being close to mountainous regions like Khao Sok National Park or Elephant Hills.\\n\\nOnce you've chosen a destination, I can help you with specific recommendations for accommodations, activities, and budgeting. Let me know which option interests you the most, or if you have any other preferences!\"),\n", + " HumanMessage(content=\"Oh, those are some fantastic suggestions! But you know, going international might be a stretch for our budget. California sounds like a good middle ground, though. I've heard Santa Cruz has some nice beaches, and being close to the mountains could be perfect for Aunt Lily.\\n\\nI'm thinking we could make it a road trip if it's within a reasonable distance. That way, we could save on flights and have the flexibility to explore. We'd just have to work out the logistics of car rentals and gas prices, but that could be part of the adventure.\\n\\nRenting a house or a larger apartment might work out well for us, especially if we can find a place with a kitchen. It'll save us a lot on eating out, and I know a couple of us enjoy cooking, so it could be fun to prepare meals together.\\n\\nFor activities, it's a mix. Some will want to lounge on the beach, others might want to try surfing or stand-up paddleboarding, and I'm sure Aunt Lily will want to hit the trails. It'll be a challenge to schedule everything, but maybe we can have some group activities and also allow for some time when everyone can do their own thing.\\n\\nSo, I think I'll start looking into Santa Cruz and see what kind of deals I can find. I'll have to get everyone's input, of course, but it's a solid starting point. Thanks for the brainstorming help!\", name='simulated'),\n", + " HumanMessage(content=\"You're very welcome! Santa Cruz sounds like a great choice for a family road trip, combining the beach and mountain activities you're looking for. Renting a house or larger apartment will give you the flexibility and cost-saving advantages you mentioned.\\n\\nWhen it comes to activities, Santa Cruz offers a range of options to suit everyone's interests. The beach is perfect for relaxing, swimming, and trying out water sports like surfing or stand-up paddleboarding. For Aunt Lily, there are beautiful hiking trails in nearby Santa Cruz Mountains, such as Henry Cowell Redwoods State Park or Big Basin Redwoods State Park.\\n\\nDo check for any specific guidelines or restrictions in the area regarding COVID-19 before finalizing your plans.\\n\\nTake your time researching accommodations, car rentals, and budget-friendly deals in Santa Cruz. It's always great to involve everyone in the decision-making process to ensure a memorable and enjoyable vacation for the whole family.\\n\\nIf you need any further assistance or information during your planning process, feel free to reach out. Have a fantastic family vacation in Santa Cruz!\"),\n", + " HumanMessage(content='FINISHED', name='simulated')]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# These are the message from the final simulation state\n", + "result[\"messages\"]" ] }, { @@ -313,7 +386,8 @@ "source": [ "## (Optional) Review Results\n", "\n", - "If you've traced the run, you can see the full simulation trace in the UI by clicking on the url.\n", + "If you've traced the run, you can see the full simulation trace in the LangSmith UI by going to the `Agent Simulation Evaluation` project.\n", + "\n", "Select the last 'ChatOpenAI' call in the trace to see the full conversation in a single view.\n", "\n", "![full-conversation](./img/virtual_user_full_convo.png)\n", @@ -324,44 +398,6 @@ "![annotate](./img/virtual_user_annotate.png)" ] }, - { - "cell_type": "code", - "execution_count": null, - "id": "702d008c-dac5-478f-8239-ede3050573c6", - "metadata": {}, - "outputs": [], - "source": [ - "url" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "0de1664f-1625-42e7-9dad-eb2c061b88d4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[HumanMessage(content='help me plan my family vacation', name='simulated'),\n", - " HumanMessage(content=\"Sure! I'd be happy to help you plan your family vacation. Can you provide more details about your preferences, such as the destination, budget, duration of the trip, and any specific activities or attractions you have in mind?\"),\n", - " HumanMessage(content=\"Oh, planning family vacations is always a bit of a juggling act, isn't it? We've got a variety of tastes in my family too, so I totally get where you're coming from. We're on a budget, so we usually look for places that won't break the bank. Everyone loves the beach—it's just Aunt Lily who's the odd one out, preferring the mountains.\\n\\nHere's a thought, maybe you can find a coastal area that's near some mountains? That way, the majority of the family gets to enjoy the sand and surf while Aunt Lily isn't too far from a mountain getaway. Depending on where you live, there might be some places not too far away that offer both. \\n\\nFor instance, places like the Central Coast of California have beaches and they're not too far from mountains. Or you could look into a spot like the South of France, if you're up for international travel and can find some deals. I've also heard that places like Costa Rica have both, but I've never been there myself.\\n\\nAs for the budget, I'm always on the lookout for off-season deals or vacation rentals that can accommodate the whole family. It can be way more cost-effective than booking multiple hotel rooms, and you can save a bit by cooking meals at the rental rather than eating out all the time.\\n\\nHave you thought about any specific destinations yet?\", name='simulated'),\n", - " HumanMessage(content=\"Those are great suggestions! Finding a destination that offers both beach and mountain options can be a great compromise for your family. Here are a few more specific destination ideas that might fit your preferences:\\n\\n1. The Oregon Coast, USA: Known for its stunning coastline and nearby mountain ranges like the Cascade Range, the Oregon Coast offers a mix of beautiful beaches, charming coastal towns, and opportunities for hiking in the mountains.\\n\\n2. Bali, Indonesia: This tropical island destination offers gorgeous beaches as well as volcanic mountains like Mount Batur. You can relax on the beach, explore temples, try water sports, and even trek through rice terraces and lush forests.\\n\\n3. Split, Croatia: Located on the stunning Dalmatian Coast, Split offers a mix of beach relaxation and nearby mountain hiking opportunities in places like the Biokovo nature park. Plus, you can explore the historic Old Town and nearby islands like Hvar.\\n\\n4. Cape Town, South Africa: With its iconic Table Mountain and beautiful Atlantic beaches like Camps Bay, Cape Town provides the best of both worlds. You can take a cable car up Table Mountain, visit the penguins at Boulders Beach, and even go on a wine tour in the nearby Cape Winelands.\\n\\nWhen it comes to budget-friendly options, consider booking vacation rentals, researching affordable or all-inclusive resorts, and keeping an eye out for discounts on flights and attractions. It's also advisable to be flexible with your travel dates, as traveling during the offseason can often result in more affordable prices.\\n\\nLet me know if you need any more information or help with planning specific activities or accommodations in any of these destinations!\"),\n", - " HumanMessage(content=\"Oh, those are some fantastic ideas, really! Each of those spots has something unique to offer. I'll definitely have to look into the Oregon Coast. It has that rugged charm, and I've heard it's not as pricey as California. Bali sounds like a dream, honestly, but I have to admit, international travel might be a bit much for the budget this time around. \\n\\nCroatia is one of those places I've always wanted to visit, with all that beautiful coastline and history, but again, might be a stretch budget-wise. Cape Town would be an adventure for sure, but South Africa is a big trip. It's probably out of our range for now.\\n\\nI really appreciate the suggestions about being flexible with travel dates and looking at vacation rentals. That's the kind of approach we usually take. We try to avoid the peak seasons to save some money and find those hidden deals.\\n\\nIt sounds like you've done a fair bit of traveling yourself, or you're just really good at sniffing out the cool spots to visit. Do you travel a lot?\", name='simulated'),\n", - " HumanMessage(content=\"I'm glad you found the suggestions helpful! The Oregon Coast is definitely a more budget-friendly option compared to some other coastal destinations. It offers stunning landscapes, charming towns, and the opportunity to explore both the beach and the mountains.\\n\\nBali is indeed a dream destination, but it's understandable that international travel might not fit within the budget this time. It's always good to keep it in mind for future trips though, as it offers a unique cultural experience along with beautiful beaches and mountains.\\n\\nCroatia is known for its stunning coastline and historic cities like Split and Dubrovnik, but it can sometimes be on the more expensive side. It's always worth checking for deals and considering different accommodation options to make it more affordable.\\n\\nAnd yes, South Africa and Cape Town are definitely big trips. If it's not feasible for the current vacation, you can always keep it on your bucket list for future adventures when the budget allows.\\n\\nAs for your question, I do love to travel and explore different places whenever I get the chance. I'm also always researching and learning about new destinations to be able to offer suggestions and help others plan their trips. It's a passion of mine! If you ever need more help or have any specific questions about the destinations or planning, feel free to ask.\"),\n", - " HumanMessage(content='FINISHED', name='simulated')]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gch # These are the message from the final simulation state\n", - "result[\"messages\"]" - ] - }, { "cell_type": "markdown", "id": "23db8891-5db3-4a98-a283-53d45cd28c60", @@ -375,14 +411,6 @@ "\n", "LangGraph gives you full control over the simulation so you can manually change the simulated user and the conversation dynamics." ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dde4f2b5-cfe8-4ff0-99ea-fe2c5fed70c0", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 230845164096c08b0516cb9625725b49bd1eda37 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Fri, 19 Jan 2024 17:36:52 -0800 Subject: [PATCH 11/16] mv directories --- .../hierarchical_agent_teams.ipynb | 652 ----------------- .../multi-agent-collaboration.ipynb | 321 -------- .../agent-simulation-evaluation.ipynb | 4 +- .../agent_supervisor.ipynb | 0 .../hierarchical_agent_teams.ipynb | 686 ++++++++++++++++++ .../img/hierarchical-diagram.png | Bin .../img/supervisor-diagram.png | Bin .../img/virtual_user_annotate.png | Bin .../img/virtual_user_diagram.png | Bin .../img/virtual_user_full_convo.png | Bin .../multi-agent-collaboration.ipynb | 373 ++++++++++ 11 files changed, 1062 insertions(+), 974 deletions(-) delete mode 100644 examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb delete mode 100644 examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb rename examples/{advanced_agents/multi-agent => multi_agent}/agent-simulation-evaluation.ipynb (99%) rename examples/{advanced_agents/multi-agent => multi_agent}/agent_supervisor.ipynb (100%) create mode 100644 examples/multi_agent/hierarchical_agent_teams.ipynb rename examples/{advanced_agents/multi-agent => multi_agent}/img/hierarchical-diagram.png (100%) rename examples/{advanced_agents/multi-agent => multi_agent}/img/supervisor-diagram.png (100%) rename examples/{advanced_agents/multi-agent => multi_agent}/img/virtual_user_annotate.png (100%) rename examples/{advanced_agents/multi-agent => multi_agent}/img/virtual_user_diagram.png (100%) rename examples/{advanced_agents/multi-agent => multi_agent}/img/virtual_user_full_convo.png (100%) create mode 100644 examples/multi_agent/multi-agent-collaboration.ipynb diff --git a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb b/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb deleted file mode 100644 index e3bfcd053..000000000 --- a/examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb +++ /dev/null @@ -1,652 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", - "metadata": {}, - "source": [ - "## Hierarchical Agent Teams\n", - "\n", - "In our previous example ([Agent Supervisor](./agent_supervisor.ipynb)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n", - "\n", - "But what if the job for a single worker becomes too complex? Or what if the number of workers becomes too large?\n", - "\n", - "For some applications, the system may be more effective if work is distributed hierarchically.\n", - "\n", - "You can do this by composing different subgraphs and creating a top-level supervisor, along with mid-level supervisors.\n", - "\n", - "\n", - "To do this, let's build a simple research assistant! The graph will look something like the following:\n", - "\n", - "![diagram](./img/hierarchical-diagram.png)\n", - "\n", - "\n", - "In the rest of this notebook, you will:\n", - "1. Define some utilities to help create the graph and their relations\n", - "2. Write the tools and agent implementations for each team\n", - "3. Compose everything together.\n", - "\n", - "But before all of that, some setup:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install -U langchain langchain_openai langsmith pandas" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", - "metadata": {}, - "outputs": [], - "source": [ - "import getpass\n", - "import os\n", - "import uuid\n", - "\n", - "\n", - "def _set_if_undefined(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass(f\"Please provide your {var}\")\n", - "\n", - "\n", - "_set_if_undefined(\"OPENAI_API_KEY\")\n", - "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", - "_set_if_undefined(\"TAVILY_API_KEY\")\n", - "\n", - "# Optional, add tracing in LangSmith.\n", - "# This will help you visualize and debug the control flow\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" - ] - }, - { - "cell_type": "markdown", - "id": "504ee1c6-2b6a-439d-9046-df54e1e15698", - "metadata": {}, - "source": [ - "## Define Utilities\n", - "\n", - "We are going to create a few utility functions to help us:\n", - "\n", - "1. Create an agent and add it to a graph.\n", - "2. Create a supervisor for the sub-graph.\n", - "\n", - "These will simplify the graph compositional code at the end for us so it's easier to see what's going on." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "e09fb60f-1aac-455b-b67d-8d2e4ccfd747", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Any, Callable, List, Optional, TypedDict, Union\n", - "\n", - "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", - "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_core.runnables import Runnable\n", - "from langchain_core.tools import BaseTool\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "from langgraph.graph import END, StateGraph\n", - "\n", - "\n", - "class WorkerAgent(TypedDict):\n", - " name: str\n", - " description: str\n", - "\n", - "\n", - "def create_worker_agent(\n", - " graph_builder: StateGraph,\n", - " name: str,\n", - " llm: ChatOpenAI,\n", - " tools: list,\n", - " system_prompt: str,\n", - " prelude: Optional[Union[Runnable, Callable]] = None, # Optional required steps\n", - ") -> str:\n", - " \"\"\"Create a function-calling agent and add it to the graph.\"\"\"\n", - " system_prompt += \"\\nYou are one of the following team members: {team_members}\"\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " system_prompt,\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", - " ]\n", - " )\n", - " agent = create_openai_functions_agent(llm, tools, prompt)\n", - " executor = AgentExecutor(agent=agent, tools=tools)\n", - " chain = executor | (\n", - " lambda x: {\"messages\": [HumanMessage(content=x[\"output\"], name=name)]}\n", - " )\n", - " if prelude is not None:\n", - " chain = prelude | chain\n", - " graph_builder.add_node(name, chain)\n", - " return name\n", - "\n", - "\n", - "def create_team_supervisor(\n", - " graph_builder: StateGraph, llm: ChatOpenAI, system_prompt: str\n", - ") -> str:\n", - " \"\"\"An LLM-based router.\"\"\"\n", - " supervisor_id = uuid.uuid4().hex[:4]\n", - " supervisor_name = f\"supervisor - {supervisor_id}\"\n", - " members = list(graph_builder.nodes)\n", - " options = [\"FINISH\"] + members\n", - " function_def = {\n", - " \"name\": \"route\",\n", - " \"description\": \"Select the next role.\",\n", - " \"parameters\": {\n", - " \"title\": \"routeSchema\",\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"next\": {\n", - " \"title\": \"Next\",\n", - " \"anyOf\": [\n", - " {\"enum\": options},\n", - " ],\n", - " }\n", - " },\n", - " \"required\": [\"next\"],\n", - " },\n", - " }\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", system_prompt),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " (\n", - " \"system\",\n", - " \"Given the conversation above, who should act next?\"\n", - " \" Or should we FINISH? Select one of: {options}\",\n", - " ),\n", - " ]\n", - " ).partial(options=str(options), team_members=\", \".join(members))\n", - " chain = (\n", - " prompt\n", - " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", - " | JsonOutputFunctionsParser()\n", - " )\n", - " graph_builder.add_node(supervisor_name, chain)\n", - " conditional_map = {k: k for k in members}\n", - " conditional_map[\"FINISH\"] = END\n", - "\n", - " for member in members:\n", - " graph_builder.add_edge(member, supervisor_name)\n", - " graph_builder.add_conditional_edges(\n", - " supervisor_name, lambda x: x[\"next\"], conditional_map\n", - " )\n", - " return supervisor_name" - ] - }, - { - "cell_type": "markdown", - "id": "00282b1f-bb4d-4ee7-9bae-e8e6f586f12e", - "metadata": {}, - "source": [ - "## Define agents + tools\n", - "\n", - "### Research Team\n", - "\n", - "The research team can use a search engine and web scraper to find information on the web." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "f04c6778-403b-4b49-9b93-678e910d5cec", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Annotated, List, Tuple, Union\n", - "\n", - "import matplotlib.pyplot as plt\n", - "from langchain_community.document_loaders import WebBaseLoader\n", - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_core.tools import tool\n", - "from langsmith import trace\n", - "\n", - "tavily_tool = TavilySearchResults(max_results=5)\n", - "\n", - "\n", - "@tool\n", - "def scrape_webpages(urls: List[str]) -> str:\n", - " \"\"\"Use requests and bs4 to scrape the provided web pages for detailed information.\"\"\"\n", - " loader = WebBaseLoader(urls)\n", - " docs = loader.load()\n", - " return \"\\n\\n\".join(\n", - " [\n", - " f'\\n{doc.page_content}\\n'\n", - " for doc in docs\n", - " ]\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "53db0c78-e357-48ba-ae5f-3fc04735a3b7", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "\n", - "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", - "from langchain_openai.chat_models import ChatOpenAI\n", - "\n", - "\n", - "# Research team graph\n", - "class State(TypedDict):\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " team_members: List[str]\n", - " final_response: AIMessage\n", - " next: str\n", - "\n", - "\n", - "research_graph = StateGraph(State)\n", - "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", - "create_worker_agent(\n", - " research_graph,\n", - " \"Search\",\n", - " llm,\n", - " [tavily_tool],\n", - " \"You are a research assistant who can search for things using a search engine.\",\n", - ")\n", - "create_worker_agent(\n", - " research_graph,\n", - " \"Web Scraper\",\n", - " llm,\n", - " [tavily_tool],\n", - " \"You are a research assistant who can scrape specified urls for more detailed information.\",\n", - ")\n", - "supervisor_node = create_team_supervisor(\n", - " research_graph,\n", - " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following workers: {team_members}. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - ")\n", - "\n", - "research_graph.set_entry_point(supervisor_node)\n", - "\n", - "\n", - "def return_final_response(state):\n", - " return {\"final_response\": state[\"messages\"][-1]}\n", - "\n", - "\n", - "research_chain = research_graph.compile() | return_final_response" - ] - }, - { - "cell_type": "markdown", - "id": "749b99ab-f6f0-4c5d-a90b-10102465d186", - "metadata": {}, - "source": [ - "## Document Writing Team\n", - "\n", - "We will construct a graph in a similar fashion. This time using different tools.\n", - "\n", - "Note that we are giving file-system access to our agent here, which is not safe in all cases." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "202806d6-80bf-4153-ac16-ed6059236f2a", - "metadata": {}, - "outputs": [], - "source": [ - "from pathlib import Path\n", - "from tempfile import TemporaryDirectory\n", - "from typing import Dict\n", - "\n", - "_TEMP_DIRECTORY = TemporaryDirectory()\n", - "WORKING_DIRECTORY = Path(_TEMP_DIRECTORY.name)\n", - "\n", - "\n", - "@tool\n", - "def create_outline(\n", - " points: Annotated[List[str], \"List of main points or sections.\"],\n", - " subpoints: Annotated[\n", - " List[List[str]],\n", - " \"List of lists, each containing subpoints for the corresponding main point.\",\n", - " ],\n", - " file_name: Annotated[str, \"File path to save the outline.\"],\n", - ") -> Annotated[str, \"Path of the saved outline file.\"]:\n", - " \"\"\"Create and save an outline.\"\"\"\n", - " if len(points) != len(subpoints):\n", - " raise ValueError(\"Each main point must have a corresponding list of subpoints.\")\n", - "\n", - " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", - " for i, point in enumerate(points):\n", - " file.write(f\"{i + 1}. {point}\\n\")\n", - " for j, subpoint in enumerate(subpoints[i]):\n", - " file.write(f\"\\t{j + 1}. {subpoint}\\n\")\n", - " return f\"Outline saved to {file_name}\"\n", - "\n", - "\n", - "@tool\n", - "def read_document(\n", - " file_name: Annotated[str, \"File path to save the document.\"],\n", - " start: Annotated[Optional[int], \"The start line. Default is 0\"] = None,\n", - " end: Annotated[Optional[int], \"The end line. Default is None\"] = None,\n", - ") -> str:\n", - " \"\"\"Read the specified document.\"\"\"\n", - " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", - " lines = file.readlines()\n", - " if start is not None:\n", - " start = 0\n", - " return \"\\n\".join(lines[start:end])\n", - "\n", - "\n", - "@tool\n", - "def write_document(\n", - " content: Annotated[str, \"Text content to be written into the document.\"],\n", - " file_name: Annotated[str, \"File path to save the document.\"],\n", - ") -> Annotated[str, \"Path of the saved document file.\"]:\n", - " \"\"\"Create and save a text document.\"\"\"\n", - " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", - " file.write(content)\n", - " return f\"Document saved to {file_name}\"\n", - "\n", - "\n", - "@tool\n", - "def edit_document(\n", - " file_name: Annotated[str, \"Path of the document to be edited.\"],\n", - " inserts: Annotated[\n", - " Dict[int, str],\n", - " \"Dictionary where key is the line number (1-indexed) and value is the text to be inserted at that line.\",\n", - " ],\n", - ") -> Annotated[str, \"Path of the edited document file.\"]:\n", - " \"\"\"Edit a document by inserting text at specific line numbers.\"\"\"\n", - " # Read the contents of the file\n", - "\n", - " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", - " lines = file.readlines()\n", - "\n", - " # Adjust the line numbers for 0-indexing and sort\n", - " sorted_inserts = sorted(inserts.items())\n", - "\n", - " # Perform the insertions\n", - " for line_number, text in sorted_inserts:\n", - " if 1 <= line_number <= len(lines) + 1:\n", - " # Insert the text at the specified line number\n", - " lines.insert(line_number - 1, text + \"\\n\")\n", - " else:\n", - " return f\"Error: Line number {line_number} is out of range.\"\n", - "\n", - " # Write the modified content back to the file\n", - " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", - " file.writelines(lines)\n", - "\n", - " return f\"Document edited and saved to {file_name}\"\n", - "\n", - "\n", - "@tool\n", - "def create_plot(\n", - " data: Annotated[\n", - " Union[List[float], List[int]],\n", - " \"Numerical values for bar heights or line points.\",\n", - " ],\n", - " file_name: Annotated[str, \"File path to save the figure.\"],\n", - " labels: Annotated[\n", - " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", - " ] = None,\n", - " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", - " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", - " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", - " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", - " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", - ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", - " \"\"\"Create a line or bar chart.\"\"\"\n", - " if plot_type not in [\"bar\", \"line\"]:\n", - " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", - "\n", - " fig, ax = plt.subplots(figsize=(10, 6))\n", - " x_positions = range(len(data))\n", - "\n", - " if labels and len(labels) == len(data):\n", - " plt.xticks(x_positions, labels)\n", - "\n", - " if plot_type == \"bar\":\n", - " ax.bar(x_positions, data, color=color)\n", - " elif plot_type == \"line\":\n", - " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", - "\n", - " ax.set_title(title)\n", - " ax.set_xlabel(xlabel)\n", - " ax.set_ylabel(ylabel)\n", - " fig.savefig(str(WORKING_DIRECTORY / file_name))\n", - " plt.close(fig)\n", - " return f'Saved \"{title}\" plot to {file_name}'" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "1bcdbf44-9481-430c-8429-fa142ed8a626", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "from pathlib import Path\n", - "\n", - "\n", - "# Research team graph\n", - "class AuthoringState(TypedDict):\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " team_members: str\n", - " final_response: AIMessage\n", - " next: str\n", - " current_files: str\n", - "\n", - "\n", - "authoring_graph = StateGraph(AuthoringState)\n", - "\n", - "\n", - "def prelude(state):\n", - " written_files = []\n", - " if not WORKING_DIRECTORY.exists():\n", - " WORKING_DIRECTORY.mkdir()\n", - " try:\n", - " written_files = [\n", - " f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n", - " ]\n", - " except:\n", - " pass\n", - " if not written_files:\n", - " return {**state, \"current_files\": \"No files written.\"}\n", - " return {\n", - " **state,\n", - " \"current_files\": \"\\nBelow are files your team has written to the directory:\\n\"\n", - " + \"\\n\".join([f\" - {f}\" for f in written_files]),\n", - " }\n", - "\n", - "\n", - "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", - "create_worker_agent(\n", - " authoring_graph,\n", - " \"Author Docs\",\n", - " llm,\n", - " [write_document, edit_document, read_document],\n", - " \"You are an expert writing a research document.\\n\"\n", - " \"Below are files currently in your directory:\\n{current_files}\",\n", - " prelude=prelude,\n", - ")\n", - "create_worker_agent(\n", - " authoring_graph,\n", - " \"Outline + Notetaker\",\n", - " llm,\n", - " [create_outline, read_document],\n", - " \"You are an expert senior researcher tasked with writing a paper outline and\"\n", - " \" taking notes to craft a perfect paper.{current_files}\",\n", - " prelude=prelude,\n", - ")\n", - "create_worker_agent(\n", - " authoring_graph,\n", - " \"Generate Charts\",\n", - " llm,\n", - " [read_document, create_plot],\n", - " \"You are a data viz expert tasked with generating charts for a research project.\"\n", - " \"{current_files}\",\n", - ")\n", - "\n", - "supervisor_node = create_team_supervisor(\n", - " authoring_graph,\n", - " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following workers: {team_members}. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - ")\n", - "\n", - "authoring_graph.set_entry_point(supervisor_node)\n", - "\n", - "\n", - "def enter_chain(message: str):\n", - " return {\n", - " \"messages\": [HumanMessage(content=\"Write a short report on pikas\")],\n", - " \"team_members\": \"\\n\".join(sorted(authoring_graph.nodes)),\n", - " }\n", - "\n", - "\n", - "def return_final_response(state):\n", - " return {\"final_response\": state[\"messages\"][-1]}\n", - "\n", - "\n", - "authoring_chain = enter_chain | authoring_graph.compile() | return_final_response" - ] - }, - { - "cell_type": "markdown", - "id": "f4b5b08d-9a9a-474a-94b4-f7aaa8ff19e6", - "metadata": {}, - "source": [ - "## Adding Hierarchy\n", - "\n", - "We've created two graphs already. Now let's put them together.\n", - "\n", - "We'll create a third graph to orchestrate the previous two, and add some connectors to define how state is shared between the different graphs." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", - "from langchain_openai.chat_models import ChatOpenAI\n", - "\n", - "\n", - "# Research team graph\n", - "class State(TypedDict):\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " next: str\n", - "\n", - "\n", - "def join_graph(response):\n", - " return {\"messages\": [response[\"final_response\"]]}\n", - "\n", - "\n", - "super_graph = StateGraph(State)\n", - "super_graph.add_node(\"Research team\", research_chain | join_graph)\n", - "super_graph.add_node(\"Paper writing team\", authoring_chain | join_graph)\n", - "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", - "supervisor_node = create_team_supervisor(\n", - " super_graph,\n", - " llm,\n", - " \"You are a supervisor tasked with managing a conversation between the\"\n", - " \" following teams: {team_members}. Given the following user request,\"\n", - " \" respond with the worker to act next. Each worker will perform a\"\n", - " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", - ")\n", - "\n", - "super_graph.set_entry_point(supervisor_node)\n", - "super_graph = super_graph.compile()" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", - "metadata": {}, - "outputs": [ - { - "ename": "GraphRecursionError", - "evalue": "Recursion limit of 25 reachedwithout hitting a stop condition. You can increase the limitby setting the `recursion_limit` config key.", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mGraphRecursionError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[15], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43msuper_graph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmessages\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43mHumanMessage\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43mcontent\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mResearch and write a report about the climate impacts\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 6\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m on crop yields in Bangladesh in 2023. Write the paper and include plots.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 7\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 8\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 9\u001b[0m \u001b[43m \u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 10\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# {\"recursion_limit\": 150},\u001b[39;49;00m\n\u001b[1;32m 11\u001b[0m \u001b[43m)\u001b[49m\n\u001b[1;32m 12\u001b[0m results[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessages\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:521\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 511\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 512\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 513\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 518\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 519\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 520\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 521\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 522\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 523\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 524\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 525\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 526\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 527\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 528\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 529\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:557\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 548\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 549\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 550\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 555\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 556\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 557\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 558\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 559\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 560\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 561\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 562\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 563\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 564\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 565\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1232\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1230\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1231\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1232\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1233\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1234\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:289\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 287\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n\u001b[1;32m 288\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m step \u001b[38;5;241m==\u001b[39m config[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrecursion_limit\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[0;32m--> 289\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m GraphRecursionError(\n\u001b[1;32m 290\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRecursion limit of \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mconfig[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mrecursion_limit\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m reached\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 291\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mwithout hitting a stop condition. You can increase the limit\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 292\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mby setting the `recursion_limit` config key.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 293\u001b[0m )\n\u001b[1;32m 295\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdebug:\n\u001b[1;32m 296\u001b[0m print_step_start(step, next_tasks)\n", - "\u001b[0;31mGraphRecursionError\u001b[0m: Recursion limit of 25 reachedwithout hitting a stop condition. You can increase the limitby setting the `recursion_limit` config key." - ] - } - ], - "source": [ - "results = super_graph.invoke(\n", - " {\n", - " \"messages\": [\n", - " HumanMessage(\n", - " content=\"Research and write a report about the climate impacts\"\n", - " \" on crop yields in Bangladesh in 2023. Write the paper and include plots.\",\n", - " )\n", - " ]\n", - " },\n", - " # {\"recursion_limit\": 150},\n", - ")\n", - "results[\"messages\"][-1]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6ffcc7f-7b78-4ca5-8e0a-7c0ac08300fc", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb b/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb deleted file mode 100644 index 9b3c8f0d3..000000000 --- a/examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb +++ /dev/null @@ -1,321 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334", - "metadata": {}, - "source": [ - "# Basic Multi-agent Collaboration\n", - "\n", - "A single agent can usually operate effectively using a handful of tools and a scoped domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. One way to improve your system's performance is through multi-agent collaboration. Each agent can specialize in a task or domain, and LangGraph can effectively orchestrate them to accomplish a larger goal. \n", - "\n", - "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.\n", - "\n", - "This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0d7b6dcc-c985-46e2-8457-7e6b0298b950", - "metadata": {}, - "outputs": [], - "source": [ - "%pip install -U langchain langchain_openai langsmith pandas" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc", - "metadata": {}, - "outputs": [], - "source": [ - "import getpass\n", - "import os\n", - "\n", - "\n", - "def _set_if_undefined(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass(f\"Please provide your {var}\")\n", - "\n", - "\n", - "_set_if_undefined(\"OPENAI_API_KEY\")\n", - "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", - "_set_if_undefined(\"TAVILY_API_KEY\")\n", - "\n", - "# Optional, add tracing in LangSmith\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" - ] - }, - { - "cell_type": "markdown", - "id": "7d5fc0f3-5d9e-4e72-a281-177f101c2a7d", - "metadata": {}, - "source": [ - "## Create tool\n", - "\n", - "Below is an example of 2 agents collaborating to accomplish a single task." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "075c91c3-c249-471d-b259-41975faa83fb", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List, Tuple, Union\n", - "\n", - "import matplotlib.pyplot as plt\n", - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_core.tools import tool\n", - "\n", - "tavily_tool = TavilySearchResults(max_results=5)\n", - "\n", - "\n", - "@tool\n", - "def create_plot(\n", - " data: Union[List[float], List[int]],\n", - " labels: Union[List[str], None] = None,\n", - " title: str = \"Plot\",\n", - " xlabel: str = \"X\",\n", - " ylabel: str = \"Y\",\n", - " color: Union[str, List[str]] = \"blue\",\n", - " plot_type: str = \"bar\",\n", - ") -> Tuple[plt.Figure, plt.Axes]:\n", - " \"\"\"\n", - " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", - "\n", - " :param data: A list of numerical values for the bar heights or line points.\n", - " :param labels: A list of strings for the bar or point labels. Default is None.\n", - " :param title: Title of the plot. Default is 'Plot'.\n", - " :param xlabel: Label for the X-axis. Default is 'X'.\n", - " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", - " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", - " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", - " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", - " :return: Tuple containing the figure and axes objects.\n", - " \"\"\"\n", - " if plot_type not in [\"bar\", \"line\"]:\n", - " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", - "\n", - " fig, ax = plt.subplots(figsize=(10, 6))\n", - " x_positions = range(len(data))\n", - "\n", - " if labels and len(labels) == len(data):\n", - " plt.xticks(x_positions, labels)\n", - "\n", - " if plot_type == \"bar\":\n", - " ax.bar(x_positions, data, color=color)\n", - " elif plot_type == \"line\":\n", - " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", - "\n", - " ax.set_title(title)\n", - " ax.set_xlabel(xlabel)\n", - " ax.set_ylabel(ylabel)\n", - "\n", - " return fig, ax" - ] - }, - { - "cell_type": "markdown", - "id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb", - "metadata": {}, - "source": [ - "## Create the graph\n", - "\n", - "We will create the individual agents below and place them within the graph." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "import operator\n", - "from typing import Annotated, Sequence, TypedDict\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.messages import (\n", - " AIMessage,\n", - " BaseMessage,\n", - " ChatMessage,\n", - " FunctionMessage,\n", - " HumanMessage,\n", - ")\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_openai import ChatOpenAI\n", - "from typing_extensions import TypedDict\n", - "\n", - "from langgraph.graph import END, StateGraph\n", - "from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation\n", - "\n", - "\n", - "class AgentState(TypedDict):\n", - " messages: Annotated[Sequence[BaseMessage], operator.add]\n", - " sender: str\n", - "\n", - "\n", - "workflow = StateGraph(AgentState)\n", - "\n", - "# This a helper class we have that is useful for running tools\n", - "# It takes in an agent action and calls that tool and returns the result\n", - "tools = [tavily_tool, create_plot]\n", - "tool_executor = ToolExecutor(tools)\n", - "\n", - "prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", - " \" Use the provided tools to progress towards answering the question.\"\n", - " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", - " \" will help where you left off. Execute what you can to make progress.\"\n", - " \" If you have the final answer, prefix your response with FINAL ANSWER.\"\n", - " \" You have access to the following tools: {tool_names}.\",\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " ]\n", - ")\n", - "\n", - "\n", - "def add_agent_node(name: str, llm, tools, prompt):\n", - " functions = [format_tool_to_openai_function(t) for t in tools]\n", - "\n", - " def _update_state(ai_message) -> dict:\n", - " if isinstance(ai_message, FunctionMessage):\n", - " result = ai_message\n", - " else:\n", - " message = ai_message.dict(exclude={\"type\"})\n", - " message[\"name\"] = name\n", - " result = HumanMessage(**message)\n", - " return {\n", - " \"messages\": [result],\n", - " \"sender\": name,\n", - " }\n", - "\n", - " chain = (\n", - " (lambda x: {**x, \"intermediate_steps\": []})\n", - " | prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", - " | llm.bind_functions(functions)\n", - " | _update_state\n", - " )\n", - " workflow.add_node(name, chain)\n", - "\n", - "\n", - "def call_tool(state):\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", - " 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", - " )\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 return a list, because this will get added to the existing list\n", - " return {\"messages\": [function_message]}\n", - "\n", - "\n", - "def should_continue(state):\n", - " # This is the router\n", - " messages = state[\"messages\"]\n", - " last_message = messages[-1]\n", - " if \"function_call\" in last_message.additional_kwargs:\n", - " return \"call_tool\"\n", - " if \"FINAL ANSWER\" in last_message.content:\n", - " return \"end\"\n", - " return \"continue\"\n", - "\n", - "\n", - "llm = ChatOpenAI(model=\"gpt-4\")\n", - "add_agent_node(\n", - " \"Researcher\",\n", - " llm,\n", - " [tavily_tool],\n", - " prompt,\n", - ")\n", - "add_agent_node(\"Chart Generator\", llm, [create_plot], prompt)\n", - "workflow.add_node(\"call_tool\", call_tool)\n", - "workflow.add_conditional_edges(\n", - " \"Researcher\",\n", - " should_continue,\n", - " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", - ")\n", - "workflow.add_conditional_edges(\n", - " \"Chart Generator\",\n", - " should_continue,\n", - " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"end\": END},\n", - ")\n", - "# We will let the user_info tool decide when to respond.\n", - "workflow.add_edge(\"call_tool\", \"Researcher\")\n", - "workflow.set_entry_point(\"Researcher\")\n", - "graph = workflow.compile()" - ] - }, - { - "cell_type": "markdown", - "id": "8c9447e7-9ab6-43eb-8ae6-9b52f8ba8425", - "metadata": {}, - "source": [ - "## Invoke\n", - "\n", - "With the graph created, you can invoke it!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "176a99b0-b457-45cf-8901-90facaa852da", - "metadata": {}, - "outputs": [], - "source": [ - "result = graph.invoke(\n", - " {\n", - " \"messages\": [\n", - " HumanMessage(\n", - " content=\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it.\"\n", - " )\n", - " ]\n", - " },\n", - " # Maximum number of steps to take in the graph\n", - " {\"recursion_limit\": 150},\n", - ")\n", - "result[\"messages\"][-1]" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb b/examples/multi_agent/agent-simulation-evaluation.ipynb similarity index 99% rename from examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb rename to examples/multi_agent/agent-simulation-evaluation.ipynb index 57298515b..0df5b1566 100644 --- a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb +++ b/examples/multi_agent/agent-simulation-evaluation.ipynb @@ -382,7 +382,9 @@ { "cell_type": "markdown", "id": "73ff30e4-1992-4bc9-834d-f4c08b281d20", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "## (Optional) Review Results\n", "\n", diff --git a/examples/advanced_agents/multi-agent/agent_supervisor.ipynb b/examples/multi_agent/agent_supervisor.ipynb similarity index 100% rename from examples/advanced_agents/multi-agent/agent_supervisor.ipynb rename to examples/multi_agent/agent_supervisor.ipynb diff --git a/examples/multi_agent/hierarchical_agent_teams.ipynb b/examples/multi_agent/hierarchical_agent_teams.ipynb new file mode 100644 index 000000000..7494dede1 --- /dev/null +++ b/examples/multi_agent/hierarchical_agent_teams.ipynb @@ -0,0 +1,686 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", + "metadata": {}, + "source": [ + "## Hierarchical Agent Teams\n", + "\n", + "In our previous example ([Agent Supervisor](./agent_supervisor.ipynb)), we introduced the concept of a single supervisor node to route work between different worker nodes.\n", + "\n", + "But what if the job for a single worker becomes too complex? What if the number of workers becomes too large?\n", + "\n", + "For some applications, the system may be more effective if work is distributed _hierarchically_.\n", + "\n", + "You can do this by composing different subgraphs and creating a top-level supervisor, along with mid-level supervisors.\n", + "\n", + "To do this, let's build a simple research assistant! The graph will look something like the following:\n", + "\n", + "![diagram](./img/hierarchical-diagram.png)\n", + "\n", + "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al. In the rest of this notebook, you will:\n", + "\n", + "1. Define some utilities to help create the graph and their relations\n", + "2. Write the tools and agent implementations for each team\n", + "3. Compose everything together.\n", + "\n", + "But before all of that, some setup:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", + "metadata": {}, + "outputs": [], + "source": [ + "# %%capture --no-stderr\n", + "# %pip install -U langgraph langchain langchain_openai langsmith" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "import uuid\n", + "\n", + "\n", + "def _set_if_undefined(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass(f\"Please provide your {var}\")\n", + "\n", + "\n", + "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "_set_if_undefined(\"TAVILY_API_KEY\")\n", + "\n", + "# Optional, add tracing in LangSmith.\n", + "# This will help you visualize and debug the control flow\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" + ] + }, + { + "cell_type": "markdown", + "id": "504ee1c6-2b6a-439d-9046-df54e1e15698", + "metadata": {}, + "source": [ + "## Define Utilities\n", + "\n", + "We are going to create a few utility functions to make it more concise when we want to:\n", + "\n", + "1. Create a worker agent and add it to a graph.\n", + "2. Create a supervisor for the sub-graph.\n", + "\n", + "These will simplify the graph compositional code at the end for us so it's easier to see what's going on." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e09fb60f-1aac-455b-b67d-8d2e4ccfd747", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any, Callable, List, Optional, TypedDict, Union\n", + "\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.runnables import Runnable\n", + "from langchain_core.tools import BaseTool\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "from langgraph.graph import END, StateGraph\n", + "\n", + "\n", + "def create_worker_agent(\n", + " graph_builder: StateGraph,\n", + " name: str,\n", + " llm: ChatOpenAI,\n", + " tools: list,\n", + " system_prompt: str,\n", + " prelude: Optional[Union[Runnable, Callable]] = None, # Optional required steps\n", + ") -> str:\n", + " \"\"\"Create a function-calling agent and add it to the graph.\"\"\"\n", + " system_prompt += \"\\nYou are one of the following team members: {team_members}\"\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " system_prompt,\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", + " ]\n", + " )\n", + " agent = create_openai_functions_agent(llm, tools, prompt)\n", + " executor = AgentExecutor(agent=agent, tools=tools)\n", + " chain = executor | (\n", + " lambda x: {\"messages\": [HumanMessage(content=x[\"output\"], name=name)]}\n", + " )\n", + " if prelude is not None:\n", + " chain = prelude | chain\n", + " graph_builder.add_node(name, chain)\n", + " return name\n", + "\n", + "\n", + "def create_team_supervisor(\n", + " graph_builder: StateGraph, llm: ChatOpenAI, system_prompt: str\n", + ") -> str:\n", + " \"\"\"An LLM-based router.\"\"\"\n", + " supervisor_id = uuid.uuid4().hex[:4]\n", + " supervisor_name = f\"supervisor - {supervisor_id}\"\n", + " members = list(graph_builder.nodes)\n", + " options = [\"FINISH\"] + members\n", + " function_def = {\n", + " \"name\": \"route\",\n", + " \"description\": \"Select the next role.\",\n", + " \"parameters\": {\n", + " \"title\": \"routeSchema\",\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"next\": {\n", + " \"title\": \"Next\",\n", + " \"anyOf\": [\n", + " {\"enum\": options},\n", + " ],\n", + " }\n", + " },\n", + " \"required\": [\"next\"],\n", + " },\n", + " }\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " (\n", + " \"system\",\n", + " \"Given the conversation above, who should act next?\"\n", + " \" Or should we FINISH? Select one of: {options}\",\n", + " ),\n", + " ]\n", + " ).partial(options=str(options), team_members=\", \".join(members))\n", + " chain = (\n", + " prompt\n", + " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", + " | JsonOutputFunctionsParser()\n", + " )\n", + " graph_builder.add_node(supervisor_name, chain)\n", + " conditional_map = {k: k for k in members}\n", + " conditional_map[\"FINISH\"] = END\n", + "\n", + " for member in members:\n", + " graph_builder.add_edge(member, supervisor_name)\n", + " graph_builder.add_conditional_edges(\n", + " supervisor_name, lambda x: x[\"next\"], conditional_map\n", + " )\n", + " return supervisor_name" + ] + }, + { + "cell_type": "markdown", + "id": "00282b1f-bb4d-4ee7-9bae-e8e6f586f12e", + "metadata": {}, + "source": [ + "## Define agents + tools\n", + "\n", + "Now we can get to define our hierachical teams. \"Choose your player!\"\n", + "\n", + "### Research Team\n", + "\n", + "The research team can use a search engine and url scraper to find information on the web. Feel free to add additional functionality below to boost the team performance!" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "f04c6778-403b-4b49-9b93-678e910d5cec", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, List, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.tools import tool\n", + "from langsmith import trace\n", + "\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", + "\n", + "\n", + "@tool\n", + "def scrape_webpages(urls: List[str]) -> str:\n", + " \"\"\"Use requests and bs4 to scrape the provided web pages for detailed information.\"\"\"\n", + " loader = WebBaseLoader(urls)\n", + " docs = loader.load()\n", + " return \"\\n\\n\".join(\n", + " [\n", + " f'\\n{doc.page_content}\\n'\n", + " for doc in docs\n", + " ]\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "53db0c78-e357-48ba-ae5f-3fc04735a3b7", + "metadata": {}, + "outputs": [], + "source": [ + "import functools\n", + "import operator\n", + "\n", + "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "\n", + "# Research team graph state\n", + "class State(TypedDict):\n", + " # A message is added after each team member finishes\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " # The team members are tracked so they are aware of\n", + " # the others' skill-sets\n", + " team_members: List[str]\n", + " # Used to route work. The supervisor calls a function\n", + " # that will update this every time it makes a decision\n", + " next: str\n", + "\n", + "\n", + "research_graph = StateGraph(State)\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "create_worker_agent(\n", + " research_graph,\n", + " \"Search\",\n", + " llm,\n", + " [tavily_tool],\n", + " \"You are a research assistant who can search for things using a search engine.\",\n", + ")\n", + "create_worker_agent(\n", + " research_graph,\n", + " \"Web Scraper\",\n", + " llm,\n", + " [tavily_tool],\n", + " \"You are a research assistant who can scrape specified urls for more detailed information.\",\n", + ")\n", + "supervisor_node = create_team_supervisor(\n", + " research_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following workers: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "research_graph.set_entry_point(supervisor_node)\n", + "\n", + "\n", + "# The following functions interoperate between the top level graph state\n", + "# and the state of the research sub-graph\n", + "# this makes it so that the states of each graph don't get intermixed\n", + "def enter_chain(message: str, members: Optional[list] = None):\n", + " results = {\n", + " \"messages\": [HumanMessage(content=message)],\n", + " }\n", + " if members:\n", + " results[\"team_members\"] = \"\\n\".join(sorted(members))\n", + " return results\n", + "\n", + "\n", + "def return_final_response(state):\n", + " return {\"final_response\": state[\"messages\"][-1]}\n", + "\n", + "\n", + "research_chain = (\n", + " functools.partial(enter_chain, members=research_graph.nodes)\n", + " | research_graph.compile()\n", + " | return_final_response\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "749b99ab-f6f0-4c5d-a90b-10102465d186", + "metadata": {}, + "source": [ + "## Document Writing Team\n", + "\n", + "We will construct a graph in a similar fashion. This time using different tools.\n", + "\n", + "Note that we are giving file-system access to our agent here, which is not safe in all cases." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "202806d6-80bf-4153-ac16-ed6059236f2a", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "from tempfile import TemporaryDirectory\n", + "from typing import Dict\n", + "\n", + "_TEMP_DIRECTORY = TemporaryDirectory()\n", + "WORKING_DIRECTORY = Path(_TEMP_DIRECTORY.name)\n", + "\n", + "\n", + "@tool\n", + "def create_outline(\n", + " points: Annotated[List[str], \"List of main points or sections.\"],\n", + " file_name: Annotated[str, \"File path to save the outline.\"],\n", + ") -> Annotated[str, \"Path of the saved outline file.\"]:\n", + " \"\"\"Create and save an outline.\"\"\"\n", + " if len(points) != len(subpoints):\n", + " raise ValueError(\"Each main point must have a corresponding list of subpoints.\")\n", + "\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " for i, point in enumerate(points):\n", + " file.write(f\"{i + 1}. {point}\\n\")\n", + " return f\"Outline saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def read_document(\n", + " file_name: Annotated[str, \"File path to save the document.\"],\n", + " start: Annotated[Optional[int], \"The start line. Default is 0\"] = None,\n", + " end: Annotated[Optional[int], \"The end line. Default is None\"] = None,\n", + ") -> str:\n", + " \"\"\"Read the specified document.\"\"\"\n", + " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", + " lines = file.readlines()\n", + " if start is not None:\n", + " start = 0\n", + " return \"\\n\".join(lines[start:end])\n", + "\n", + "\n", + "@tool\n", + "def write_document(\n", + " content: Annotated[str, \"Text content to be written into the document.\"],\n", + " file_name: Annotated[str, \"File path to save the document.\"],\n", + ") -> Annotated[str, \"Path of the saved document file.\"]:\n", + " \"\"\"Create and save a text document.\"\"\"\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " file.write(content)\n", + " return f\"Document saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def edit_document(\n", + " file_name: Annotated[str, \"Path of the document to be edited.\"],\n", + " inserts: Annotated[\n", + " Dict[int, str],\n", + " \"Dictionary where key is the line number (1-indexed) and value is the text to be inserted at that line.\",\n", + " ],\n", + ") -> Annotated[str, \"Path of the edited document file.\"]:\n", + " \"\"\"Edit a document by inserting text at specific line numbers.\"\"\"\n", + " # Read the contents of the file\n", + "\n", + " with (WORKING_DIRECTORY / file_name).open(\"r\") as file:\n", + " lines = file.readlines()\n", + "\n", + " # Adjust the line numbers for 0-indexing and sort\n", + " sorted_inserts = sorted(inserts.items())\n", + "\n", + " # Perform the insertions\n", + " for line_number, text in sorted_inserts:\n", + " if 1 <= line_number <= len(lines) + 1:\n", + " # Insert the text at the specified line number\n", + " lines.insert(line_number - 1, text + \"\\n\")\n", + " else:\n", + " return f\"Error: Line number {line_number} is out of range.\"\n", + "\n", + " # Write the modified content back to the file\n", + " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", + " file.writelines(lines)\n", + "\n", + " return f\"Document edited and saved to {file_name}\"\n", + "\n", + "\n", + "@tool\n", + "def create_plot(\n", + " data: Annotated[\n", + " Union[List[float], List[int]],\n", + " \"Numerical values for bar heights or line points.\",\n", + " ],\n", + " file_name: Annotated[str, \"File path to save the figure.\"],\n", + " labels: Annotated[\n", + " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", + " ] = None,\n", + " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", + " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", + " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", + " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", + " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", + ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", + " \"\"\"Create a line or bar chart.\"\"\"\n", + " if plot_type not in [\"bar\", \"line\"]:\n", + " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", + "\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + " x_positions = range(len(data))\n", + "\n", + " if labels and len(labels) == len(data):\n", + " plt.xticks(x_positions, labels)\n", + "\n", + " if plot_type == \"bar\":\n", + " ax.bar(x_positions, data, color=color)\n", + " elif plot_type == \"line\":\n", + " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", + "\n", + " ax.set_title(title)\n", + " ax.set_xlabel(xlabel)\n", + " ax.set_ylabel(ylabel)\n", + " fig.savefig(str(WORKING_DIRECTORY / file_name))\n", + " plt.close(fig)\n", + " return f'Saved \"{title}\" plot to {file_name}'" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "1bcdbf44-9481-430c-8429-fa142ed8a626", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from pathlib import Path\n", + "\n", + "\n", + "# Document writing team graph state\n", + "class AuthoringState(TypedDict):\n", + " # This tracks the team's conversation internally\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " # This provides each worker with context on the others' skill sets\n", + " team_members: str\n", + " # This is how the supervisor tells langgraph who to work next\n", + " next: str\n", + " # This tracks the shared directory state\n", + " current_files: str\n", + "\n", + "\n", + "# This will be run before each worker agent begins work\n", + "# It makes it so they are more aware of the current state\n", + "# of the working directory.\n", + "def prelude(state):\n", + " written_files = []\n", + " if not WORKING_DIRECTORY.exists():\n", + " WORKING_DIRECTORY.mkdir()\n", + " try:\n", + " written_files = [\n", + " f.relative_to(WORKING_DIRECTORY) for f in WORKING_DIRECTORY.rglob(\"*\")\n", + " ]\n", + " except:\n", + " pass\n", + " if not written_files:\n", + " return {**state, \"current_files\": \"No files written.\"}\n", + " return {\n", + " **state,\n", + " \"current_files\": \"\\nBelow are files your team has written to the directory:\\n\"\n", + " + \"\\n\".join([f\" - {f}\" for f in written_files]),\n", + " }\n", + "\n", + "\n", + "# Create the graph here:\n", + "authoring_graph = StateGraph(AuthoringState)\n", + "\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Author Docs\",\n", + " llm,\n", + " [write_document, edit_document, read_document],\n", + " \"You are an expert writing a research document.\\n\"\n", + " # The {current_files} value is populated automatically by the graph state\n", + " \"Below are files currently in your directory:\\n{current_files}\",\n", + " prelude=prelude,\n", + ")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Outline + Notetaker\",\n", + " llm,\n", + " [create_outline, read_document],\n", + " \"You are an expert senior researcher tasked with writing a paper outline and\"\n", + " \" taking notes to craft a perfect paper.{current_files}\",\n", + " prelude=prelude,\n", + ")\n", + "create_worker_agent(\n", + " authoring_graph,\n", + " \"Generate Charts\",\n", + " llm,\n", + " [read_document, create_plot],\n", + " \"You are a data viz expert tasked with generating charts for a research project.\"\n", + " \"{current_files}\",\n", + ")\n", + "\n", + "supervisor_node = create_team_supervisor(\n", + " authoring_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following workers: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "authoring_graph.set_entry_point(supervisor_node)\n", + "\n", + "# We re-use the enter/exit functions to wrap the graph\n", + "authoring_chain = (\n", + " functools.partial(enter_chain, members=authoring_graph.nodes)\n", + " | authoring_graph.compile()\n", + " | return_final_response\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "f4b5b08d-9a9a-474a-94b4-f7aaa8ff19e6", + "metadata": {}, + "source": [ + "## Add Layers\n", + "\n", + "In this design, we are enforcing a top-down planning policy. We've created two graphs already, but we have to decide how to route work between the two.\n", + "\n", + "We'll create a _third_ graph to orchestrate the previous two, and add some connectors to define how this top-level state is shared between the different graphs." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", + "from langchain_openai.chat_models import ChatOpenAI\n", + "\n", + "\n", + "# Research team graph\n", + "class State(TypedDict):\n", + " messages: Annotated[List[BaseMessage], operator.add]\n", + " next: str\n", + "\n", + "\n", + "def get_last_message(state: State) -> str:\n", + " return state[\"messages\"][-1].content\n", + "\n", + "\n", + "def join_graph(response: dict):\n", + " return {\"messages\": [response[\"final_response\"]]}\n", + "\n", + "\n", + "super_graph = StateGraph(State)\n", + "super_graph.add_node(\"Research team\", get_last_message | research_chain | join_graph)\n", + "super_graph.add_node(\n", + " \"Paper writing team\", get_last_message | authoring_chain | join_graph\n", + ")\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "supervisor_node = create_team_supervisor(\n", + " super_graph,\n", + " llm,\n", + " \"You are a supervisor tasked with managing a conversation between the\"\n", + " \" following teams: {team_members}. Given the following user request,\"\n", + " \" respond with the worker to act next. Each worker will perform a\"\n", + " \" task and respond with their results and status. When finished,\"\n", + " \" respond with FINISH.\",\n", + ")\n", + "\n", + "super_graph.set_entry_point(supervisor_node)\n", + "super_graph = enter_chain | super_graph.compile()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'subpoints' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43msuper_graph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mResearch and write a report about the climate impacts\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m on crop yields in Bangladesh in 2023. Write the paper and include plots.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrecursion_limit\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m150\u001b[39;49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m)\u001b[49m\n\u001b[1;32m 6\u001b[0m results[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessages\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:531\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 521\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 522\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 523\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 529\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 530\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 531\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 532\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 533\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 534\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 535\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 536\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 537\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 538\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 539\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:567\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 559\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 560\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 566\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 567\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 568\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 569\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 570\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 571\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 572\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 573\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 574\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 575\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1486\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1484\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1485\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1486\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1487\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1488\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:342\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m done, inflight \u001b[38;5;241m=\u001b[39m concurrent\u001b[38;5;241m.\u001b[39mfutures\u001b[38;5;241m.\u001b[39mwait(\n\u001b[1;32m 333\u001b[0m [\n\u001b[1;32m 334\u001b[0m executor\u001b[38;5;241m.\u001b[39msubmit(proc\u001b[38;5;241m.\u001b[39minvoke, \u001b[38;5;28minput\u001b[39m, config)\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 338\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstep_timeout,\n\u001b[1;32m 339\u001b[0m )\n\u001b[1;32m 341\u001b[0m \u001b[38;5;66;03m# interrupt on failure or timeout\u001b[39;00m\n\u001b[0;32m--> 342\u001b[0m \u001b[43m_interrupt_or_proceed\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdone\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minflight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 344\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[1;32m 345\u001b[0m _apply_writes(checkpoint, channels, pending_writes, config, step \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m)\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:650\u001b[0m, in \u001b[0;36m_interrupt_or_proceed\u001b[0;34m(done, inflight, step)\u001b[0m\n\u001b[1;32m 648\u001b[0m inflight\u001b[38;5;241m.\u001b[39mpop()\u001b[38;5;241m.\u001b[39mcancel()\n\u001b[1;32m 649\u001b[0m \u001b[38;5;66;03m# raise the exception\u001b[39;00m\n\u001b[0;32m--> 650\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[1;32m 651\u001b[0m \u001b[38;5;66;03m# TODO this is where retry of an entire step would happen\u001b[39;00m\n\u001b[1;32m 653\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m inflight:\n\u001b[1;32m 654\u001b[0m \u001b[38;5;66;03m# if we got here means we timed out\u001b[39;00m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58\u001b[0m, in \u001b[0;36m_WorkItem.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 58\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfuture\u001b[38;5;241m.\u001b[39mset_exception(exc)\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:3868\u001b[0m, in \u001b[0;36mRunnableBindingBase.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3862\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 3863\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 3864\u001b[0m \u001b[38;5;28minput\u001b[39m: Input,\n\u001b[1;32m 3865\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 3866\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Optional[Any],\n\u001b[1;32m 3867\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Output:\n\u001b[0;32m-> 3868\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3869\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3870\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_merge_configs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3871\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m{\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3872\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:531\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 521\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 522\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 523\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 529\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 530\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 531\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 532\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 533\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 534\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 535\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 536\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 537\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 538\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 539\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:567\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 559\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 560\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 566\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 567\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 568\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 569\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 570\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 571\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 572\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 573\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 574\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 575\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1486\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1484\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1485\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1486\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1487\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1488\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:342\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m done, inflight \u001b[38;5;241m=\u001b[39m concurrent\u001b[38;5;241m.\u001b[39mfutures\u001b[38;5;241m.\u001b[39mwait(\n\u001b[1;32m 333\u001b[0m [\n\u001b[1;32m 334\u001b[0m executor\u001b[38;5;241m.\u001b[39msubmit(proc\u001b[38;5;241m.\u001b[39minvoke, \u001b[38;5;28minput\u001b[39m, config)\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 338\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstep_timeout,\n\u001b[1;32m 339\u001b[0m )\n\u001b[1;32m 341\u001b[0m \u001b[38;5;66;03m# interrupt on failure or timeout\u001b[39;00m\n\u001b[0;32m--> 342\u001b[0m \u001b[43m_interrupt_or_proceed\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdone\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minflight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 344\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[1;32m 345\u001b[0m _apply_writes(checkpoint, channels, pending_writes, config, step \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m)\n", + "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:650\u001b[0m, in \u001b[0;36m_interrupt_or_proceed\u001b[0;34m(done, inflight, step)\u001b[0m\n\u001b[1;32m 648\u001b[0m inflight\u001b[38;5;241m.\u001b[39mpop()\u001b[38;5;241m.\u001b[39mcancel()\n\u001b[1;32m 649\u001b[0m \u001b[38;5;66;03m# raise the exception\u001b[39;00m\n\u001b[0;32m--> 650\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[1;32m 651\u001b[0m \u001b[38;5;66;03m# TODO this is where retry of an entire step would happen\u001b[39;00m\n\u001b[1;32m 653\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m inflight:\n\u001b[1;32m 654\u001b[0m \u001b[38;5;66;03m# if we got here means we timed out\u001b[39;00m\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58\u001b[0m, in \u001b[0;36m_WorkItem.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 58\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfuture\u001b[38;5;241m.\u001b[39mset_exception(exc)\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:3868\u001b[0m, in \u001b[0;36mRunnableBindingBase.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3862\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 3863\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 3864\u001b[0m \u001b[38;5;28minput\u001b[39m: Input,\n\u001b[1;32m 3865\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 3866\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Optional[Any],\n\u001b[1;32m 3867\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Output:\n\u001b[0;32m-> 3868\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3869\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3870\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_merge_configs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3871\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m{\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3872\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/chains/base.py:162\u001b[0m, in \u001b[0;36mChain.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 160\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 161\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n\u001b[0;32m--> 162\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 163\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_end(outputs)\n\u001b[1;32m 164\u001b[0m final_outputs: Dict[\u001b[38;5;28mstr\u001b[39m, Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprep_outputs(\n\u001b[1;32m 165\u001b[0m inputs, outputs, return_only_outputs\n\u001b[1;32m 166\u001b[0m )\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/chains/base.py:156\u001b[0m, in \u001b[0;36mChain.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 149\u001b[0m run_manager \u001b[38;5;241m=\u001b[39m callback_manager\u001b[38;5;241m.\u001b[39mon_chain_start(\n\u001b[1;32m 150\u001b[0m dumpd(\u001b[38;5;28mself\u001b[39m),\n\u001b[1;32m 151\u001b[0m inputs,\n\u001b[1;32m 152\u001b[0m name\u001b[38;5;241m=\u001b[39mrun_name,\n\u001b[1;32m 153\u001b[0m )\n\u001b[1;32m 154\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 155\u001b[0m outputs \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m--> 156\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call\u001b[49m\u001b[43m(\u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 157\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 158\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_call(inputs)\n\u001b[1;32m 159\u001b[0m )\n\u001b[1;32m 160\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 161\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1376\u001b[0m, in \u001b[0;36mAgentExecutor._call\u001b[0;34m(self, inputs, run_manager)\u001b[0m\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;66;03m# We now enter the agent loop (until it returns something).\u001b[39;00m\n\u001b[1;32m 1375\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_should_continue(iterations, time_elapsed):\n\u001b[0;32m-> 1376\u001b[0m next_step_output \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_take_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1377\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1378\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1379\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1380\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1381\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1382\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1383\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(next_step_output, AgentFinish):\n\u001b[1;32m 1384\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_return(\n\u001b[1;32m 1385\u001b[0m next_step_output, intermediate_steps, run_manager\u001b[38;5;241m=\u001b[39mrun_manager\n\u001b[1;32m 1386\u001b[0m )\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1102\u001b[0m, in \u001b[0;36mAgentExecutor._take_next_step\u001b[0;34m(self, name_to_tool_map, color_mapping, inputs, intermediate_steps, run_manager)\u001b[0m\n\u001b[1;32m 1093\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_take_next_step\u001b[39m(\n\u001b[1;32m 1094\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1095\u001b[0m name_to_tool_map: Dict[\u001b[38;5;28mstr\u001b[39m, BaseTool],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1099\u001b[0m run_manager: Optional[CallbackManagerForChainRun] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1100\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[AgentFinish, List[Tuple[AgentAction, \u001b[38;5;28mstr\u001b[39m]]]:\n\u001b[1;32m 1101\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_consume_next_step(\n\u001b[0;32m-> 1102\u001b[0m \u001b[43m[\u001b[49m\n\u001b[1;32m 1103\u001b[0m \u001b[43m \u001b[49m\u001b[43ma\u001b[49m\n\u001b[1;32m 1104\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_iter_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1105\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1106\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1107\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1108\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 1112\u001b[0m )\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1102\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1093\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_take_next_step\u001b[39m(\n\u001b[1;32m 1094\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1095\u001b[0m name_to_tool_map: Dict[\u001b[38;5;28mstr\u001b[39m, BaseTool],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1099\u001b[0m run_manager: Optional[CallbackManagerForChainRun] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1100\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[AgentFinish, List[Tuple[AgentAction, \u001b[38;5;28mstr\u001b[39m]]]:\n\u001b[1;32m 1101\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_consume_next_step(\n\u001b[0;32m-> 1102\u001b[0m \u001b[43m[\u001b[49m\n\u001b[1;32m 1103\u001b[0m \u001b[43m \u001b[49m\u001b[43ma\u001b[49m\n\u001b[1;32m 1104\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_iter_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1105\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1106\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1107\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1108\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 1112\u001b[0m )\n", + "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1198\u001b[0m, in \u001b[0;36mAgentExecutor._iter_next_step\u001b[0;34m(self, name_to_tool_map, color_mapping, inputs, intermediate_steps, run_manager)\u001b[0m\n\u001b[1;32m 1196\u001b[0m tool_run_kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mllm_prefix\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 1197\u001b[0m \u001b[38;5;66;03m# We then call the tool on the tool input to get an observation\u001b[39;00m\n\u001b[0;32m-> 1198\u001b[0m observation \u001b[38;5;241m=\u001b[39m \u001b[43mtool\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1199\u001b[0m \u001b[43m \u001b[49m\u001b[43magent_action\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtool_input\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1200\u001b[0m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1201\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcolor\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1202\u001b[0m \u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 1203\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_run_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1204\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1205\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1206\u001b[0m tool_run_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39magent\u001b[38;5;241m.\u001b[39mtool_run_logging_kwargs()\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:374\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 372\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (\u001b[38;5;167;01mException\u001b[39;00m, \u001b[38;5;167;01mKeyboardInterrupt\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 373\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_tool_error(e)\n\u001b[0;32m--> 374\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 375\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 376\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_tool_end(\n\u001b[1;32m 377\u001b[0m \u001b[38;5;28mstr\u001b[39m(observation), color\u001b[38;5;241m=\u001b[39mcolor, name\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs\n\u001b[1;32m 378\u001b[0m )\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:346\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 344\u001b[0m tool_args, tool_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_to_args_and_kwargs(parsed_input)\n\u001b[1;32m 345\u001b[0m observation \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m--> 346\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_run\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 347\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 348\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_run(\u001b[38;5;241m*\u001b[39mtool_args, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mtool_kwargs)\n\u001b[1;32m 349\u001b[0m )\n\u001b[1;32m 350\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ToolException \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 351\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_tool_error:\n", + "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:641\u001b[0m, in \u001b[0;36mStructuredTool._run\u001b[0;34m(self, run_manager, *args, **kwargs)\u001b[0m\n\u001b[1;32m 632\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc:\n\u001b[1;32m 633\u001b[0m new_argument_supported \u001b[38;5;241m=\u001b[39m signature(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc)\u001b[38;5;241m.\u001b[39mparameters\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcallbacks\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 634\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (\n\u001b[1;32m 635\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc(\n\u001b[1;32m 636\u001b[0m \u001b[38;5;241m*\u001b[39margs,\n\u001b[1;32m 637\u001b[0m callbacks\u001b[38;5;241m=\u001b[39mrun_manager\u001b[38;5;241m.\u001b[39mget_child() \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 638\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[1;32m 639\u001b[0m )\n\u001b[1;32m 640\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_argument_supported\n\u001b[0;32m--> 641\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 642\u001b[0m )\n\u001b[1;32m 643\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool does not support sync\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "Cell \u001b[0;32mIn[10], line 15\u001b[0m, in \u001b[0;36mcreate_outline\u001b[0;34m(points, file_name)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;129m@tool\u001b[39m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcreate_outline\u001b[39m(\n\u001b[1;32m 11\u001b[0m points: Annotated[List[\u001b[38;5;28mstr\u001b[39m], \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mList of main points or sections.\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[1;32m 12\u001b[0m file_name: Annotated[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFile path to save the outline.\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[1;32m 13\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Annotated[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPath of the saved outline file.\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[1;32m 14\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Create and save an outline.\"\"\"\u001b[39;00m\n\u001b[0;32m---> 15\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(points) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mlen\u001b[39m(\u001b[43msubpoints\u001b[49m):\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEach main point must have a corresponding list of subpoints.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m (WORKING_DIRECTORY \u001b[38;5;241m/\u001b[39m file_name)\u001b[38;5;241m.\u001b[39mopen(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m file:\n", + "\u001b[0;31mNameError\u001b[0m: name 'subpoints' is not defined" + ] + } + ], + "source": [ + "results = super_graph.invoke(\n", + " \"Research and write a report about the climate impacts\"\n", + " \" on crop yields in Bangladesh in 2023. Write the paper and include plots.\",\n", + " {\"recursion_limit\": 150},\n", + ")\n", + "results[\"messages\"][-1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6ffcc7f-7b78-4ca5-8e0a-7c0ac08300fc", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/advanced_agents/multi-agent/img/hierarchical-diagram.png b/examples/multi_agent/img/hierarchical-diagram.png similarity index 100% rename from examples/advanced_agents/multi-agent/img/hierarchical-diagram.png rename to examples/multi_agent/img/hierarchical-diagram.png diff --git a/examples/advanced_agents/multi-agent/img/supervisor-diagram.png b/examples/multi_agent/img/supervisor-diagram.png similarity index 100% rename from examples/advanced_agents/multi-agent/img/supervisor-diagram.png rename to examples/multi_agent/img/supervisor-diagram.png diff --git a/examples/advanced_agents/multi-agent/img/virtual_user_annotate.png b/examples/multi_agent/img/virtual_user_annotate.png similarity index 100% rename from examples/advanced_agents/multi-agent/img/virtual_user_annotate.png rename to examples/multi_agent/img/virtual_user_annotate.png diff --git a/examples/advanced_agents/multi-agent/img/virtual_user_diagram.png b/examples/multi_agent/img/virtual_user_diagram.png similarity index 100% rename from examples/advanced_agents/multi-agent/img/virtual_user_diagram.png rename to examples/multi_agent/img/virtual_user_diagram.png diff --git a/examples/advanced_agents/multi-agent/img/virtual_user_full_convo.png b/examples/multi_agent/img/virtual_user_full_convo.png similarity index 100% rename from examples/advanced_agents/multi-agent/img/virtual_user_full_convo.png rename to examples/multi_agent/img/virtual_user_full_convo.png diff --git a/examples/multi_agent/multi-agent-collaboration.ipynb b/examples/multi_agent/multi-agent-collaboration.ipynb new file mode 100644 index 000000000..6225774e3 --- /dev/null +++ b/examples/multi_agent/multi-agent-collaboration.ipynb @@ -0,0 +1,373 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "39fd1948-b5c3-48c4-b10e-2ae7e8c83334", + "metadata": {}, + "source": [ + "# Basic Multi-agent Collaboration\n", + "\n", + "A single agent can usually operate effectively using a handful of tools and a scoped domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. One way to improve your system's performance is through multi-agent collaboration. Each agent can specialize in a task or domain, and LangGraph can effectively orchestrate them to accomplish a larger goal. \n", + "\n", + "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.\n", + "\n", + "This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0d7b6dcc-c985-46e2-8457-7e6b0298b950", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -U langchain langchain_openai langsmith pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "743c19df-6da9-4d1e-b2d2-ea40080b9fdc", + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_if_undefined(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass(f\"Please provide your {var}\")\n", + "\n", + "\n", + "_set_if_undefined(\"OPENAI_API_KEY\")\n", + "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", + "_set_if_undefined(\"TAVILY_API_KEY\")\n", + "\n", + "# Optional, add tracing in LangSmith\n", + "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", + "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" + ] + }, + { + "cell_type": "markdown", + "id": "7d5fc0f3-5d9e-4e72-a281-177f101c2a7d", + "metadata": {}, + "source": [ + "## Create tools\n", + "\n", + "We will make 2 agents, 1 for plotting, and another for searching information on the web. Below are their tools:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "075c91c3-c249-471d-b259-41975faa83fb", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from langchain_community.tools.tavily_search import TavilySearchResults\n", + "from langchain_core.tools import tool\n", + "\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", + "\n", + "\n", + "@tool\n", + "def create_plot(\n", + " data: list,\n", + " labels: Union[List[str], None] = None,\n", + " title: str = \"Plot\",\n", + " xlabel: str = \"X\",\n", + " ylabel: str = \"Y\",\n", + " color: Union[str, List[str]] = \"blue\",\n", + " plot_type: str = \"bar\",\n", + ") -> str:\n", + " \"\"\"\n", + " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", + "\n", + " :param data: A list of numerical values for the bar heights or line points.\n", + " :param labels: A list of strings for the bar or point labels. Default is None.\n", + " :param title: Title of the plot. Default is 'Plot'.\n", + " :param xlabel: Label for the X-axis. Default is 'X'.\n", + " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", + " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", + " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", + " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", + " :return: Tuple containing the figure and axes objects.\n", + " \"\"\"\n", + " if plot_type not in [\"bar\", \"line\"]:\n", + " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", + "\n", + " fig, ax = plt.subplots(figsize=(10, 6))\n", + " x_positions = range(len(data))\n", + "\n", + " if labels and len(labels) == len(data):\n", + " plt.xticks(x_positions, labels)\n", + "\n", + " if plot_type == \"bar\":\n", + " ax.bar(x_positions, data, color=color)\n", + " elif plot_type == \"line\":\n", + " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", + "\n", + " ax.set_title(title)\n", + " ax.set_xlabel(xlabel)\n", + " ax.set_ylabel(ylabel)\n", + "\n", + " return \"Chart generated!\"" + ] + }, + { + "cell_type": "markdown", + "id": "5e4344a7-21df-4d54-90d2-9d19b3416ffb", + "metadata": {}, + "source": [ + "## Create graph utilites\n", + "\n", + "The following helper functions will simplify the code when it comes to actually constructing the graph.\n", + "\n", + "You can skip ahead if you just want to see what the graph looks like." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "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", + " HumanMessage,\n", + ")\n", + "\n", + "from langgraph.graph import END, StateGraph\n", + "from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation\n", + "\n", + "\n", + "def _update_state(ai_message, name: str) -> dict:\n", + " \"\"\"The graph routes the logic based on the message type\n", + " and the sender origin. We nee\"\"\"\n", + " if isinstance(ai_message, FunctionMessage):\n", + " result = ai_message\n", + " else:\n", + " result = HumanMessage(**ai_message.dict(exclude={\"type\", \"name\"}), name=name)\n", + " return {\n", + " \"messages\": [result],\n", + " # Since we have a strict workflow, we can\n", + " # track the sender so we know who to pass to next.\n", + " \"sender\": name,\n", + " }\n", + "\n", + "\n", + "def add_agent_node(workflow: StateGraph, name: str, llm, tools):\n", + " \"\"\"Create an agent and add it to the graph builder.\"\"\"\n", + " functions = [format_tool_to_openai_function(t) for t in tools]\n", + "\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", + " \" Use the provided tools to progress towards answering the question.\"\n", + " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", + " \" will help where you left off. Execute what you can to make progress.\"\n", + " \" If you have the final answer, prefix your response with FINAL ANSWER.\"\n", + " \" You have access to the following tools: {tool_names}.\",\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " ]\n", + " )\n", + "\n", + " chain = (\n", + " (lambda x: {**x, \"intermediate_steps\": []})\n", + " | prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", + " | llm.bind_functions(functions)\n", + " | (lambda x: _update_state(x, name=name))\n", + " )\n", + " workflow.add_node(name, chain)\n", + "\n", + "\n", + "def call_tool(state):\n", + " \"\"\"This a helper class we have that is useful for running tools\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", + " 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", + " )\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 return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}" + ] + }, + { + "cell_type": "markdown", + "id": "f1b54c0c-0b09-408b-abc5-86308929afb6", + "metadata": {}, + "source": [ + "## Create graph\n", + "\n", + "Now that we've defined our tools and made some helper functions, will create the individual agents below and tell them how to talk to each other using LangGraph." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Annotated, Sequence, TypedDict\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 langchain_openai import ChatOpenAI\n", + "from typing_extensions import TypedDict\n", + "\n", + "\n", + "# This defines the object that is passed between each node\n", + "# in the graph. We will create different nodes for each agent and tool\n", + "class AgentState(TypedDict):\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]\n", + " sender: str\n", + "\n", + "\n", + "tools = [tavily_tool, create_plot]\n", + "tool_executor = ToolExecutor(tools)\n", + "\n", + "\n", + "workflow = StateGraph(AgentState)\n", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "add_agent_node(\n", + " workflow,\n", + " \"Researcher\",\n", + " llm,\n", + " [tavily_tool],\n", + ")\n", + "add_agent_node(workflow, \"Chart Generator\", llm, [create_plot])\n", + "workflow.add_node(\"call_tool\", call_tool)\n", + "\n", + "\n", + "# Either agent can decide to end\n", + "def should_continue(state):\n", + " # This is the router\n", + " messages = state[\"messages\"]\n", + " last_message = messages[-1]\n", + " if \"function_call\" in last_message.additional_kwargs:\n", + " return \"call_tool\"\n", + " if \"FINAL ANSWER\" in last_message.content:\n", + " return \"end\"\n", + " return \"continue\"\n", + "\n", + "\n", + "workflow.add_conditional_edges(\n", + " \"Researcher\",\n", + " should_continue,\n", + " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", + ")\n", + "workflow.add_conditional_edges(\n", + " \"Chart Generator\",\n", + " should_continue,\n", + " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"end\": END},\n", + ")\n", + "# We will assume that any time a tool is called, the researcher will choose what to do next\n", + "workflow.add_edge(\"call_tool\", \"Researcher\")\n", + "workflow.set_entry_point(\"Researcher\")\n", + "graph = workflow.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "8c9447e7-9ab6-43eb-8ae6-9b52f8ba8425", + "metadata": {}, + "source": [ + "## Invoke\n", + "\n", + "With the graph created, you can invoke it!" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "176a99b0-b457-45cf-8901-90facaa852da", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HumanMessage(content=\"FINAL ANSWER:\\n\\nThe line graph of the UK's GDP over the past five years has been generated based on the estimated and provided figures:\\n\\n1. 2019: 2.238 trillion GBP\\n2. 2020: 2.018 trillion GBP\\n3. 2021: 2.14 trillion GBP\\n4. 2022: 2.2 trillion GBP\\n5. 2023: GDP growth of 0.1% in Q1 (Jan to Mar) - Note that the full-year value for 2023 is an estimate based on Q1 data and should be treated with caution.\\n\\nPlease note that the 2023 figure is not the full-year GDP but rather the growth rate for the first quarter. The actual GDP value for 2023 will be determined at the end of the year. The graph provides a visual representation of the GDP trend over the past five years, with an observable dip in 2020 due to the economic impact of the COVID-19 pandemic, followed by a recovery in subsequent years.\", name='Chart Generator')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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QUFB2rZtm9asWaOIiAjVrVtX165dizmma9eu+vbbb7VkyRJt2rRJZ86cUbNmze54rNdff10tWrSI93m+//57vfTSS+rQoYN2796tDz/8UJMmTdK0adOS7LUBnsQZ79Xt27crICBA8+bN0549e9S/f3/17ds3zvvw6NGjatiwoWrWrKmdO3eqS5cuateuna3/GADuIrnep+Hh4cqWLZsGDBig0qVLJ+trBDxBcr1XN2/erDp16mjlypXavn27atasqUaNGmnHjh3J+npjWEh258+ftyRZmzZtsizLskJCQqzUqVNbS5YsiTlm3759liRr69atd9x/8ODBVunSpe/Y36pVK+uFF16Is2/KlClW7ty5raioKOe+CCAFeNj3arSOHTtaNWvWjPm+V69eVokSJeIc06JFC6tevXpOfgWA50uq9+m/Va9e3ercubNTcwMpTXK8V6M99thj1tChQ50TPJE4c2WD0NBQSVKWLFkkmVYeERGh2rVrxxxTrFgx5c2bV1u3bk3w44aHhytt2rRx9qVLl06nTp3S8ePHnZAcSFmc9V4NDQ2NeQxJ2rp1a5zHkKR69eol6v0OwEiq9ykA50qu92pUVJSuXLli2/uZcpXMoqKi1KVLFz311FMqWbKkJCk4OFg+Pj7KlClTnGOzZ8+u4ODgBD92vXr1tHTpUq1bt05RUVE6ePCgJk6cKMlcOw4g4Zz1Xv3555/1xRdfqH379jH7goODlT179jseIywsTDdu3HDuCwE8WFK+TwE4T3K+VydMmKCrV6+qefPmTsufGKlsedYULCgoSLt379aPP/7o9Md+88039ffff+vZZ59VRESE/Pz81LlzZw0ZMkReXvRoIDGc8V7dvXu3mjRposGDB6tu3bpOTAdA4n0KuIvkeq8uWLBAQ4cO1fLlyxUQEPDAz/Uw+I07GXXq1EkrVqzQhg0blDt37pj9gYGBunXrlkJCQuIcf+7cOQUGBib48R0Oh8aOHaurV6/q+PHjCg4OVsWKFSVJBQsWdMprAFICZ7xX9+7dq1q1aql9+/YaMGBAnNsCAwPvmA303Llz8vPzU7p06Zz7YgAPldTvUwDOkVzv1UWLFqldu3ZavHjxHZfeJyfKVTKwLEudOnXSsmXLtH79ehUoUCDO7U888YRSp06tdevWxew7cOCATpw4ocqVKyf6+by9vZUrVy75+Pho4cKFqly5srJly/bQrwPwdM56r+7Zs0c1a9ZU27ZtNXLkyDuep3LlynEeQ5LWrFnzQO93IKVJrvcpgIeTnO/VhQsX6rXXXtPChQvVsGHDpHlBCWXLNBopzNtvv235+/tbGzdutM6ePRvzdf369ZhjOnToYOXNm9dav3699fvvv1uVK1e2KleuHOdxDh06ZO3YscN66623rCJFilg7duywduzYYYWHh1uWZVkXLlywpk+fbu3bt8/asWOH9e6771pp06a1fvnll2R9vYC7csZ7ddeuXVa2bNmsl19+Oc5jnD9/PuaYI0eOWOnTp7d69uxp7du3z/rggw8sb29va9WqVcn6egF3lFzvU8uyYv6dfeKJJ6zWrVtbO3bssPbs2ZNsrxVwZ8n1Xp0/f76VKlUq64MPPohzTEhISLK+3miUq2QgKd6vTz/9NOaYGzduWB07drQyZ85spU+f3nruueess2fPxnmc6tWrx/s4R48etSzLlKsnn3zS8vX1tdKnT2/VqlXL2rZtWzK+UsC9OeO9Onjw4HgfI1++fHGea8OGDVaZMmUsHx8fq2DBgnGeA8DdJef7NCHHAIhfcr1X7/b7cdu2bZPvxf6Lw7Isy5lnwgAAAAAgJWLMFQAAAAA4AeUKAAAAAJyAcgUAAAAATkC5AgAAAAAnoFwBAAAAgBNQrgAAAADACShXAAAAAOAElCsAAAAAcALKFQAAAAA4AeUKAODxLMtS7dq1Va9evTtu+/DDD5UpUyadOnXKhmQAAE9CuQIAeDyHw6FPP/1Uv/zyiz766KOY/UePHlWvXr00depU5c6d26nPGRER4dTHAwC4PsoVACBFyJMnj95//3316NFDR48elWVZeuONN1S3bl2VLVtWDRo0UIYMGZQ9e3a98sorunjxYsx9V61apaefflqZMmXSI488omeffVZ///13zO3Hjh2Tw+HQF198oerVqytt2rSaP3++HS8TAGAjh2VZlt0hAABILk2bNlVoaKiaNWum4cOHa8+ePSpRooTatWunNm3a6MaNG+rdu7du376t9evXS5K++uorORwOlSpVSlevXtWgQYN07Ngx7dy5U15eXjp27JgKFCig/Pnza+LEiSpbtqzSpk2rHDly2PxqAQDJiXIFAEhRzp8/rxIlSujy5cv66quvtHv3bm3ZskWrV6+OOebUqVPKkyePDhw4oCJFitzxGBcvXlS2bNm0a9culSxZMqZcTZ48WZ07d07OlwMAcCFcFggASFECAgL01ltvqXjx4mratKn+/PNPbdiwQRkyZIj5KlasmCTFXPp36NAhtWrVSgULFpSfn5/y588vSTpx4kScxy5fvnyyvhYAgGtJZXcAAACSW6pUqZQqlfkn8OrVq2rUqJHGjh17x3HRl/U1atRI+fLl06xZs5QzZ05FRUWpZMmSunXrVpzjfX19kz48AMBlUa4AAClauXLl9NVXXyl//vwxhevfLl26pAMHDmjWrFmqWrWqJOnHH39M7pgAADfAZYEAgBQtKChIly9fVqtWrfTbb7/p77//1urVq/Xaa68pMjJSmTNn1iOPPKKZM2fq8OHDWr9+vbp162Z3bACAC6JcAQBStJw5c+qnn35SZGSk6tatq8cff1xdunRRpkyZ5OXlJS8vLy1atEjbt29XyZIl1bVrV40fP97u2AAAF8RsgQAAAADgBJy5AgAAAAAnoFwBAAAAgBNQrgAAAADACShXAAAAAOAElCsAAAAAcALKFQAAAAA4AeUKAAAAAJyAcgUAAAAATkC5AgAAAAAnoFwBAAAAgBNQrgAAAADACf4PqQ4omJblBywAAAAASUVORK5CYII=", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "result = graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(\n", + " content=\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it.\"\n", + " )\n", + " ],\n", + " },\n", + " # Maximum number of steps to take in the graph\n", + " {\"recursion_limit\": 150},\n", + ")\n", + "result[\"messages\"][-1]" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From af4bdc6f431885852d9d7ee28e810477d637c365 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:07:13 -0800 Subject: [PATCH 12/16] Update flow --- .../agent-simulation-evaluation.ipynb | 55 +++++-------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/examples/multi_agent/agent-simulation-evaluation.ipynb b/examples/multi_agent/agent-simulation-evaluation.ipynb index 0df5b1566..c7271d5e4 100644 --- a/examples/multi_agent/agent-simulation-evaluation.ipynb +++ b/examples/multi_agent/agent-simulation-evaluation.ipynb @@ -7,16 +7,18 @@ "source": [ "# Chat Bot Evaluation as Multi-agent Simulation\n", "\n", - "When building a chat bot, such as a customer support assistant, it can be hard to properly evalute your bot's performance. It's time-consuming to have to manually interact with it intensively for each code change.\n", + "Manually evaluating a chat bot on every code change is time-consuming. One way to automate some of the work is to simulate a \"virtual user\". Then you can focus on reviewing samples of the interactions.\n", "\n", - "One way to make the evaluation process easier and more reproducible is to simulate a user interaction.\n", - "\n", - "With LangGraph, it's easy to set this up. Below is an example of how to create a \"virtual user\" to simulate a conversation.\n", - "\n", - "The overall simulation looks something like this:\n", + "In thos notebook, you will use LangGraph to create a dialogue simulation between a virtual user and your chat bot. The overall simulation looks something like this:\n", "\n", "![diagram](./img/virtual_user_diagram.png)\n", "\n", + "The main steps are:\n", + "1. Defining the virtual user\n", + "2. Connecting your chat bot\n", + "3. Constructing the dialogue simulation graph\n", + "4. Running!\n", + "\n", "First, we'll set up our environment." ] }, @@ -296,7 +298,7 @@ "id": "2e0bd26e-8c1d-471d-9fef-d95dc0163491", "metadata": {}, "source": [ - "## 3. Run Simulation\n", + "## 4. Run Simulation\n", "\n", "Now we can evaluate our chat bot! We will provide information about the simulated user (as a system prompt)\n", "as well as the initial input message from that simulated user to the chat bot." @@ -309,8 +311,6 @@ "metadata": {}, "outputs": [], "source": [ - "# The tracing context manager lets us easily fetch the trace URL in-context.\n", - "# You can turn this off if you don't want to trace the execution.\n", "result = simulation.invoke(\n", " {\n", " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", @@ -324,33 +324,6 @@ ")" ] }, - { - "cell_type": "code", - "execution_count": 9, - "id": "fa91e733-3493-43c2-ba21-171cf78deef8", - "metadata": {}, - "outputs": [ - { - "ename": "SyntaxError", - "evalue": "invalid syntax (1300180531.py, line 5)", - "output_type": "error", - "traceback": [ - "\u001b[0;36m Cell \u001b[0;32mIn[9], line 5\u001b[0;36m\u001b[0m\n\u001b[0;31m \"messages\": [\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" - ] - } - ], - "source": [ - "result = simulation.invoke(\n", - " {\n", - " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", - " \" They all like the beach, except for Aunt Lily, who prefers the mountains.\"\n", - " \"messages\": [\n", - " HumanMessage(content=\"help me plan my family vacation\", name=SIMULATED_USER_NAME)\n", - " ],\n", - " }\n", - ")" - ] - }, { "cell_type": "code", "execution_count": 10, @@ -361,11 +334,11 @@ "data": { "text/plain": [ "[HumanMessage(content='help me plan my family vacation', name='simulated'),\n", - " HumanMessage(content=\"Of course! I would be happy to help you plan your family vacation. Please provide me with some more details:\\n\\n1. Destination: Where are you thinking of going? Do you have any specific preferences or requirements?\\n2. Duration: How long would you like your vacation to be?\\n3. Budget: What is your approximate budget for the trip?\\n4. Number of people: How many people will be traveling with you?\\n5. Interests: What are the interests or activities your family members enjoy?\\n6. Age of children: If you have children, please provide their ages.\\n\\nOnce I have this information, I will be able to provide you with a customized vacation plan that suits your family's needs and preferences.\"),\n", - " HumanMessage(content=\"Well, planning a family vacation can be a bit of a balancing act, especially when everyone has different preferences. First off, since we're on a budget, we've got to consider somewhere that's not going to break the bank. And since everyone likes the beach—except Aunt Lily, who's more into the mountains—we might want to look for a place that offers a bit of both.\\n\\nMaybe we could find a coastal area that's near some mountains or hills. That way, most of the family can enjoy the beach while Aunt Lily has the option to explore the mountains or go on a hike. The duration of the trip will probably depend on how much time we can take off work and what our budget can handle.\\n\\nSpeaking of budget, we've got to keep costs in mind for accommodations, food, travel, and any activities we plan to do. Since there are a few of us traveling, maybe renting a house or apartment through a site like Airbnb could be more cost-effective than booking multiple hotel rooms.\\n\\nAs for the number of people, I'd have to get a headcount, but let's assume it's the usual gang. That way, we can start looking into group discounts or family rates for activities and travel.\\n\\nWe also need to think about what kind of activities we want access to. Besides the beach and hiking, do we want to be near a town or city for dining out and entertainment? Or would we prefer a more secluded spot where we can cook our meals and have some quiet time?\\n\\nLastly, we don't have to worry about children's ages for this trip, which simplifies things a bit.\\n\\nSo, what do you think? Is there a place you know of that could fit this mix of interests and constraints?\", name='simulated'),\n", - " HumanMessage(content=\"Based on your preferences and requirements, I can suggest a few possible destinations that offer a mix of beach and mountain activities:\\n\\n1. California, USA: Consider areas like Santa Cruz or Half Moon Bay, which offer beautiful coastal landscapes and are near the Santa Cruz Mountains for hiking and exploring.\\n\\n2. Costa Rica: This country has stunning beaches along the Pacific and Caribbean coasts, as well as rainforests and mountains for hiking and wildlife encounters.\\n\\n3. Barcelona, Spain: The city offers a vibrant beachfront, while being in close proximity to the mountains of Montserrat for hiking and scenic views.\\n\\n4. Bali, Indonesia: With its gorgeous beaches and lush, mountainous landscapes, Bali provides a diverse range of activities for everyone.\\n\\n5. Cape Town, South Africa: Known for its stunning coastal scenery, Cape Town also offers Table Mountain and nearby hiking trails for exploring the mountains.\\n\\n6. Thailand: Places like Phuket or Krabi provide beautiful beaches for relaxation and activities, while being close to mountainous regions like Khao Sok National Park or Elephant Hills.\\n\\nOnce you've chosen a destination, I can help you with specific recommendations for accommodations, activities, and budgeting. Let me know which option interests you the most, or if you have any other preferences!\"),\n", - " HumanMessage(content=\"Oh, those are some fantastic suggestions! But you know, going international might be a stretch for our budget. California sounds like a good middle ground, though. I've heard Santa Cruz has some nice beaches, and being close to the mountains could be perfect for Aunt Lily.\\n\\nI'm thinking we could make it a road trip if it's within a reasonable distance. That way, we could save on flights and have the flexibility to explore. We'd just have to work out the logistics of car rentals and gas prices, but that could be part of the adventure.\\n\\nRenting a house or a larger apartment might work out well for us, especially if we can find a place with a kitchen. It'll save us a lot on eating out, and I know a couple of us enjoy cooking, so it could be fun to prepare meals together.\\n\\nFor activities, it's a mix. Some will want to lounge on the beach, others might want to try surfing or stand-up paddleboarding, and I'm sure Aunt Lily will want to hit the trails. It'll be a challenge to schedule everything, but maybe we can have some group activities and also allow for some time when everyone can do their own thing.\\n\\nSo, I think I'll start looking into Santa Cruz and see what kind of deals I can find. I'll have to get everyone's input, of course, but it's a solid starting point. Thanks for the brainstorming help!\", name='simulated'),\n", - " HumanMessage(content=\"You're very welcome! Santa Cruz sounds like a great choice for a family road trip, combining the beach and mountain activities you're looking for. Renting a house or larger apartment will give you the flexibility and cost-saving advantages you mentioned.\\n\\nWhen it comes to activities, Santa Cruz offers a range of options to suit everyone's interests. The beach is perfect for relaxing, swimming, and trying out water sports like surfing or stand-up paddleboarding. For Aunt Lily, there are beautiful hiking trails in nearby Santa Cruz Mountains, such as Henry Cowell Redwoods State Park or Big Basin Redwoods State Park.\\n\\nDo check for any specific guidelines or restrictions in the area regarding COVID-19 before finalizing your plans.\\n\\nTake your time researching accommodations, car rentals, and budget-friendly deals in Santa Cruz. It's always great to involve everyone in the decision-making process to ensure a memorable and enjoyable vacation for the whole family.\\n\\nIf you need any further assistance or information during your planning process, feel free to reach out. Have a fantastic family vacation in Santa Cruz!\"),\n", + " HumanMessage(content=\"Of course! I'd be happy to help you plan your family vacation. To assist you better, could you please provide some information about your preferences and interests? Include details like the duration of the trip, budget, destination preferences, and any specific activities or landmarks you'd like to include in your itinerary.\"),\n", + " HumanMessage(content=\"Oh, planning a family vacation can be quite the task, right? Especially when everyone has different preferences. Well, my family is pretty similar. We're always on a budget, and it can be tough to please everyone. Usually, we end up somewhere by the beach because most of the family loves it, but then there's Aunt Lily who's always campaigning for the mountains.\\n\\nI've found that the trick is to compromise and maybe find a place that has a bit of both. You know, like a coastal area that has access to some nature trails or a scenic mountain spot not too far from the shore. This way, everyone gets a bit of what they like. \\n\\nHave you considered somewhere like that? Maybe a place where you can have a beach day but also take a day trip to the mountains? It might not be the perfect solution for Aunt Lily, but at least it's something. And staying on budget means looking for deals, maybe even renting a house instead of booking multiple hotel rooms. It can save a lot and feels more personal, you know?\", name='simulated'),\n", + " HumanMessage(content=\"Absolutely, compromising and finding a destination that offers a mix of beach and mountain activities can be a great solution! Here's a step-by-step approach to planning your family vacation:\\n\\n1. Determine your budget: Set a realistic budget for your vacation, including accommodation, transportation, food, and activities. This will help you make informed decisions throughout the planning process.\\n\\n2. Choose a destination: Look for places that offer both beach and mountain activities. Some popular destinations that might suit your needs could include places like California, Oregon, Hawaii (Big Island or Maui), or the Caribbean islands like Puerto Rico or the Dominican Republic.\\n\\n3. Research accommodation options: Look for vacation rentals or holiday homes that can accommodate your entire family. Websites like Airbnb, Vrbo, or Booking.com can provide a range of options to suit your budget and preferences.\\n\\n4. Plan activities: Research activities and attractions in the chosen destination that cater to both beach and mountain interests. For beach lovers, plan beach days, water sports like snorkeling, surfing or kayaking, and beachside relaxation. For mountain enthusiasts, look for hiking trails, scenic drives, or even a day trip to a nearby national park or mountain range.\\n\\n5. Create a flexible itinerary: Start by planning a rough itinerary that outlines which days will be devoted to beach activities and which will be dedicated to mountain adventures. Allow for some flexibility and downtime for relaxing and enjoying each other's company.\\n\\n6. Make transportation arrangements: Decide on the most convenient and cost-effective transportation option for your family. Depending on the distance, consider driving, flying, or a combination of both.\\n\\n7. Pack accordingly: Ensure everyone brings appropriate clothing and gear for both beach and mountain activities. Pack essentials such as sunscreen, hats, swimsuits, hiking shoes, and layers for changing weather conditions.\\n\\n8. Keep everyone involved: Involve family members in the planning process and gather their input on activities and attractions. This will make everyone feel invested and excited about the trip.\\n\\nRemember, the key is to find a balance and cater to everyone's interests as much as possible. By planning ahead and considering these factors, you can help ensure a memorable and enjoyable family vacation!\"),\n", + " HumanMessage(content=\"Oh, that's a solid plan you've got there! It really does cover all the bases, and I like how you've broken it down into manageable steps. That's a good reminder that I should probably start by setting the budget. That's usually the tricky part for us. Once we have that locked down, we can start looking into destinations that won't break the bank.\\n\\nCalifornia and Oregon sound appealing with their combo of beaches and mountains, but I think they might be a bit on the pricey side for us. Hawaii would be a dream come true, but again, I'm not sure it's in the cards budget-wise.\\n\\nI've heard that Puerto Rico and the Dominican Republic can be more affordable and still offer that mix of beach and mountain activities. Maybe I'll dig a little deeper into those options. Plus, the idea of a vacation rental instead of a hotel could really help us save some cash.\\n\\nI've got to make sure to find those activities you mentioned. The family would love things like snorkeling and hiking, and it's a bonus if we can find some free or low-cost activities too. A flexible itinerary is key, especially with my crew. Someone's always changing their mind about what they want to do.\\n\\nThanks for the tips on transportation and packing as well. I always tend to overpack, so it's a good reminder to just stick to the essentials.\\n\\nInvolving the family is... well, it's a process. Everyone has their own opinion, but it's important that they all get a say. Aunt Lily might need some extra convincing, but I'm sure we'll find a compromise.\\n\\nThis has been super helpful. I appreciate the guidance. I guess it's time to get down to the nitty-gritty and start making some decisions. Wish me luck!\", name='simulated'),\n", + " HumanMessage(content=\"You're very welcome! I'm glad you found the plan helpful, and it seems like you have a great approach in mind. Setting a budget and researching destinations that fit within that budget is a smart starting point.\\n\\nPuerto Rico and the Dominican Republic are indeed more affordable destinations compared to some others, and they offer a fantastic mix of beach and mountain activities. Exploring more about their attractions, availability of vacation rentals, and affordable activities will help you make an informed decision.\\n\\nRemember that planning low-cost or free activities can be a great way to save money while still enjoying the vacation. Look for local hiking trails, public beaches, parks, or cultural experiences that don't come with hefty price tags.\\n\\nKeeping the itinerary flexible will definitely help accommodate any changing preferences within your family. It's all about finding that balance and ensuring everyone gets to do at least a few things they enjoy.\\n\\nAs you involve your family in the planning process, consider creating a checklist of their preferences and priorities. This way, you can work together to find compromises and make decisions that make everyone happy.\\n\\nI'm glad I could be of assistance, and I wish you the best of luck with your planning! I hope you and your family have a fantastic vacation filled with memorable experiences. Enjoy your trip!\"),\n", " HumanMessage(content='FINISHED', name='simulated')]" ] }, From baead3d113f180d36f6fbbf54a025621cdaca0ba Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:08:11 -0800 Subject: [PATCH 13/16] again --- README.md | 8 +- .../agent-simulation-evaluation.ipynb | 412 ------------------ 2 files changed, 4 insertions(+), 416 deletions(-) delete mode 100644 examples/multi_agent/agent-simulation-evaluation.ipynb diff --git a/README.md b/README.md index 72ce4ff84..4bddc2bc9 100644 --- a/README.md +++ b/README.md @@ -454,10 +454,10 @@ We also have a lot of examples highlighting how to slightly modify the base chat ### Advanced + Multi-agent Examples -- [Multi-agent collaboration](examples/advanced_agents/multi-agent/multi-agent-collaboration.ipynb): how to create two agents that work together to accomplish a task -- [Multi-agent with supervisor](examples/advanced_agents/multi-agent/agent_supervisor.ipynb): how to orchestrate individual agents by using an LLM as a "supervisor" to distribute work -- [Hierarchical agent teams](examples/advanced_agents/multi-agent/hierarchical_agent_teams.ipynb): how to orchestrate "teams" of agents as nested graphs that can collaborate to solve a problem -- [Chat bot evaluation as multi-agent simulation](examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb): How to simulate a dialogue between a "virtual user" and your chat bot +- [Multi-agent collaboration](examples/multi_agent/multi-agent-collaboration.ipynb): how to create two agents that work together to accomplish a task +- [Multi-agent with supervisor](examples/multi_agent/agent_supervisor.ipynb): how to orchestrate individual agents by using an LLM as a "supervisor" to distribute work +- [Hierarchical agent teams](examples/multi_agent/hierarchical_agent_teams.ipynb): how to orchestrate "teams" of agents as nested graphs that can collaborate to solve a problem +- [Chat bot evaluation as multi-agent simulation](examples/multi_agent/agent-simulation-evaluation.ipynb): How to simulate a dialogue between a "virtual user" and your chat bot ### Async diff --git a/examples/multi_agent/agent-simulation-evaluation.ipynb b/examples/multi_agent/agent-simulation-evaluation.ipynb deleted file mode 100644 index c7271d5e4..000000000 --- a/examples/multi_agent/agent-simulation-evaluation.ipynb +++ /dev/null @@ -1,412 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", - "metadata": {}, - "source": [ - "# Chat Bot Evaluation as Multi-agent Simulation\n", - "\n", - "Manually evaluating a chat bot on every code change is time-consuming. One way to automate some of the work is to simulate a \"virtual user\". Then you can focus on reviewing samples of the interactions.\n", - "\n", - "In thos notebook, you will use LangGraph to create a dialogue simulation between a virtual user and your chat bot. The overall simulation looks something like this:\n", - "\n", - "![diagram](./img/virtual_user_diagram.png)\n", - "\n", - "The main steps are:\n", - "1. Defining the virtual user\n", - "2. Connecting your chat bot\n", - "3. Constructing the dialogue simulation graph\n", - "4. Running!\n", - "\n", - "First, we'll set up our environment." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", - "metadata": {}, - "outputs": [], - "source": [ - "# %%capture --no-stderr\n", - "# %pip install -U langgraph langchain langchain_openai" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", - "metadata": {}, - "outputs": [], - "source": [ - "import getpass\n", - "import os\n", - "import uuid\n", - "\n", - "\n", - "def _set_if_undefined(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass(f\"Please provide your {var}\")\n", - "\n", - "\n", - "_set_if_undefined(\"OPENAI_API_KEY\")\n", - "_set_if_undefined(\"LANGCHAIN_API_KEY\")\n", - "\n", - "# Optional, add tracing in LangSmith.\n", - "# This will help you visualize and debug the control flow\n", - "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n", - "os.environ[\"LANGCHAIN_PROJECT\"] = \"Agent Simulation Evaluation\"" - ] - }, - { - "cell_type": "markdown", - "id": "6b69031e-7bf8-4401-941e-4dccd59ea870", - "metadata": {}, - "source": [ - "## 1. Define the virtual user\n", - "\n", - "The virtual user needs an LLM to reason and instructions for how it's supposed to behave (or what it's trying to accomplish).\n", - "\n", - "Below, create an agent and instruct it to role-play a 'simulated' user. By including the `{system_prompt}` placeholder in the prompt and a state variable with the same name `Environment`, you can customize the user behavior each time you simulate a dialogue." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "0f4c63d8-15ef-4ee6-8bb6-654465baae04", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "from typing import Annotated, Callable, Dict, List, TypedDict\n", - "\n", - "from langchain.adapters.openai import convert_message_to_dict\n", - "from langchain_core.messages import AIMessage, BaseMessage, HumanMessage\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_core.runnables import chain\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "from langgraph.graph import END, StateGraph\n", - "\n", - "SIMULATED_USER_NAME = \"simulated\"\n", - "\n", - "\n", - "# This is the input to every node in the simulation graph\n", - "# It tracks the graph state over time. Our only \"state\"\n", - "# is the conversation messages, while the user config\n", - "# is provided to make the virtual user more unique or realistic\n", - "class Environment(TypedDict):\n", - " messages: Annotated[List[BaseMessage], operator.add]\n", - " # The system prompt will be fed into the prompt template below\n", - " # For more control, try adding different parameters to provide the\n", - " # prompt template below\n", - " system_prompt: str\n", - "\n", - "\n", - "prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " \"You are role-playing a human character: '{name}'. \"\n", - " \"You are not an AI assistant and you are not supposed to help or assist.\"\n", - " \" You must behave as this human would throughout the conversation below.\\n\\n\"\n", - " \"Your messages will bear the name 'simulated', but DO NOT under any circumstances\"\n", - " \"say that you are 'simulated'. You will be evaluated based on how realistic your\"\n", - " \"impersonation of this character is. This must feel real! Here are the details for your character:\"\n", - " \"\\n\"\n", - " # This system_prompt is specified in the Environment above\n", - " \"{system_prompt}\"\n", - " # The stopping criteria of FINISHED is used in the function hould_continue in a later\n", - " # section. This tells the graph to stop the simulation.\n", - " '\\n\\nWhen you are finished with the conversation, respond with a single word \"FINISHED\"',\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " ]\n", - ").partial(name=SIMULATED_USER_NAME)\n", - "\n", - "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", - "\n", - "\n", - "def rename_message(message: AIMessage):\n", - " # If we use an AIMessage, the simulated user may forget to continue role playing.\n", - " # It will also confuse YOUR chat bot, since IT is supposed to be the AI in this scenario.\n", - " # We instead convert them to 'Human' messages with the 'simulated' name\n", - " # Your chat bot will then receive all the user's messages and think they\n", - " # are human ones\n", - " return {\n", - " \"messages\": [HumanMessage(content=message.content, name=SIMULATED_USER_NAME)]\n", - " }" - ] - }, - { - "cell_type": "markdown", - "id": "d8d5a3a7-72b6-4063-89e6-3ecf2bce3340", - "metadata": {}, - "source": [ - "Now we can compose these pieces using LCEL. The `|` syntax pipelines the data flow." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b486511b-57f3-4a7c-be0d-691a7d1006da", - "metadata": {}, - "outputs": [], - "source": [ - "virtual_user = prompt | llm | rename_message" - ] - }, - { - "cell_type": "markdown", - "id": "6ef4528d-6b2a-47c7-98b5-50f14984a304", - "metadata": {}, - "source": [ - "## 2. Define your chat bot\n", - "\n", - "Next, define the chat bot. For this notebook, we assume the bot's API accepts a list of messages and responds with a message. If you want to update this, you can change this section and the \"get_messages_for_agent\" function in the simulator below (as well as the environment state if it requires additional inputs).\n", - "\n", - "The actual implementation within `my_chat_bot` is configurable and can even be run on another system (e.g., if your system isn't running in python)." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "828479af-cf9c-4888-a365-599643a96b55", - "metadata": {}, - "outputs": [], - "source": [ - "from typing import List\n", - "\n", - "import openai\n", - "\n", - "\n", - "# This is flexible, but you can define your agent here, or call your agent API here.\n", - "def my_chat_bot(messages: List[dict], model=\"gpt-3.5-turbo\") -> dict:\n", - " completion = openai.chat.completions.create(\n", - " messages=messages, model=\"gpt-3.5-turbo\"\n", - " )\n", - " return completion.choices[0].message.model_dump()" - ] - }, - { - "cell_type": "markdown", - "id": "c7669a7e-1602-4af7-a730-84fa19f363f2", - "metadata": {}, - "source": [ - "Every node in the simulation is pased a state object of the `Environment` type above.\n", - "\n", - "The two functions below define the API between the `Environment` state and your chat bot." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "4d495ccc-0f5f-4194-89e5-15dccbbb7412", - "metadata": {}, - "outputs": [], - "source": [ - "@chain\n", - "def get_messages_for_agent(state: Environment):\n", - " \"\"\"Convert the simulation state to the input\n", - "\n", - " for your agent you want to evaluate.\"\"\"\n", - " messages = []\n", - " for message in state[\"messages\"]:\n", - " messages.append(convert_message_to_dict(message))\n", - " if getattr(message, \"name\", None) != SIMULATED_USER_NAME:\n", - " # Ensure YOUR chat bot still sees its messages\n", - " # as assistant messages\n", - " messages[-1][\"role\"] = \"assistant\"\n", - " return messages\n", - "\n", - "\n", - "def get_response_message_from_agent(agent_output):\n", - " \"\"\"Get the response from the agent you are evaluting,\n", - " and use it to update the simulation state.\"\"\"\n", - " # If we directly return an AI message from your chat bot, our\n", - " # virtual user will likely forget it's role playing. To cover this up\n", - " # we will convert it to a Human message.\n", - " return {\"messages\": [HumanMessage(content=agent_output[\"content\"])]}" - ] - }, - { - "cell_type": "markdown", - "id": "321312b4-a1f0-4454-a481-fdac4e37cb7d", - "metadata": {}, - "source": [ - "## 3. Define simulation graph\n", - "\n", - "The dialogue simulation is almost ready. It's time to put everything together!\n", - "\n", - "Below, create a graph using the `Environment` state defined above. Wire together the\n", - "virtual user and your chat bot, including a conditional `should_continue` edge to\n", - "handle the stopping behavior." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "6d4caf50-4108-4a68-9c0a-775fa97fccd7", - "metadata": {}, - "outputs": [], - "source": [ - "graph_builder = StateGraph(Environment)\n", - "graph_builder.add_node(\"user\", virtual_user)\n", - "graph_builder.add_node(\n", - " \"chat_bot\",\n", - " # The \"|\" syntax composes these steps in the pipeline to map between\n", - " # the simulation state and your chat bot's API\n", - " get_messages_for_agent | my_chat_bot | get_response_message_from_agent,\n", - ")\n", - "# Every response from your chat bot will automatically go to the\n", - "# simulated user\n", - "graph_builder.add_edge(\"chat_bot\", \"user\")\n", - "\n", - "\n", - "# Recall that we instructed the simulated user to respond \"FINISHED\" when\n", - "# it is done with the conversation. This function\n", - "# parses that output and tells the graph to cease execution.\n", - "# You can add other heuristics here for more control.\n", - "def should_continue(state: Environment):\n", - " \"\"\"Determine if the simulation should continue.\"\"\"\n", - " if state[\"messages\"][-1].content.strip().endswith(\"FINISHED\"):\n", - " return \"end\"\n", - " return \"continue\"\n", - "\n", - "\n", - "graph_builder.add_conditional_edges(\n", - " # Every time the \"user\" node completes ...\n", - " \"user\",\n", - " # Call this function ...\n", - " should_continue,\n", - " # And based on the outputs of should_continue ...\n", - " {\n", - " # End the simulation OR\n", - " \"end\": END,\n", - " # continue to the chat_bot node\n", - " \"continue\": \"chat_bot\",\n", - " },\n", - ")\n", - "# The input will first go to your chat bot\n", - "graph_builder.set_entry_point(\"chat_bot\")\n", - "simulation = graph_builder.compile()" - ] - }, - { - "cell_type": "markdown", - "id": "2e0bd26e-8c1d-471d-9fef-d95dc0163491", - "metadata": {}, - "source": [ - "## 4. Run Simulation\n", - "\n", - "Now we can evaluate our chat bot! We will provide information about the simulated user (as a system prompt)\n", - "as well as the initial input message from that simulated user to the chat bot." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "9a7cc134-4fbf-4240-8976-7605a6263578", - "metadata": {}, - "outputs": [], - "source": [ - "result = simulation.invoke(\n", - " {\n", - " \"system_prompt\": \"You are on a budget. Your family is hard to please.\"\n", - " \" They all like the beach, except for Aunt Lily, who prefers the mountains.\",\n", - " \"messages\": [\n", - " HumanMessage(\n", - " content=\"help me plan my family vacation\", name=SIMULATED_USER_NAME\n", - " )\n", - " ],\n", - " }\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "0de1664f-1625-42e7-9dad-eb2c061b88d4", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[HumanMessage(content='help me plan my family vacation', name='simulated'),\n", - " HumanMessage(content=\"Of course! I'd be happy to help you plan your family vacation. To assist you better, could you please provide some information about your preferences and interests? Include details like the duration of the trip, budget, destination preferences, and any specific activities or landmarks you'd like to include in your itinerary.\"),\n", - " HumanMessage(content=\"Oh, planning a family vacation can be quite the task, right? Especially when everyone has different preferences. Well, my family is pretty similar. We're always on a budget, and it can be tough to please everyone. Usually, we end up somewhere by the beach because most of the family loves it, but then there's Aunt Lily who's always campaigning for the mountains.\\n\\nI've found that the trick is to compromise and maybe find a place that has a bit of both. You know, like a coastal area that has access to some nature trails or a scenic mountain spot not too far from the shore. This way, everyone gets a bit of what they like. \\n\\nHave you considered somewhere like that? Maybe a place where you can have a beach day but also take a day trip to the mountains? It might not be the perfect solution for Aunt Lily, but at least it's something. And staying on budget means looking for deals, maybe even renting a house instead of booking multiple hotel rooms. It can save a lot and feels more personal, you know?\", name='simulated'),\n", - " HumanMessage(content=\"Absolutely, compromising and finding a destination that offers a mix of beach and mountain activities can be a great solution! Here's a step-by-step approach to planning your family vacation:\\n\\n1. Determine your budget: Set a realistic budget for your vacation, including accommodation, transportation, food, and activities. This will help you make informed decisions throughout the planning process.\\n\\n2. Choose a destination: Look for places that offer both beach and mountain activities. Some popular destinations that might suit your needs could include places like California, Oregon, Hawaii (Big Island or Maui), or the Caribbean islands like Puerto Rico or the Dominican Republic.\\n\\n3. Research accommodation options: Look for vacation rentals or holiday homes that can accommodate your entire family. Websites like Airbnb, Vrbo, or Booking.com can provide a range of options to suit your budget and preferences.\\n\\n4. Plan activities: Research activities and attractions in the chosen destination that cater to both beach and mountain interests. For beach lovers, plan beach days, water sports like snorkeling, surfing or kayaking, and beachside relaxation. For mountain enthusiasts, look for hiking trails, scenic drives, or even a day trip to a nearby national park or mountain range.\\n\\n5. Create a flexible itinerary: Start by planning a rough itinerary that outlines which days will be devoted to beach activities and which will be dedicated to mountain adventures. Allow for some flexibility and downtime for relaxing and enjoying each other's company.\\n\\n6. Make transportation arrangements: Decide on the most convenient and cost-effective transportation option for your family. Depending on the distance, consider driving, flying, or a combination of both.\\n\\n7. Pack accordingly: Ensure everyone brings appropriate clothing and gear for both beach and mountain activities. Pack essentials such as sunscreen, hats, swimsuits, hiking shoes, and layers for changing weather conditions.\\n\\n8. Keep everyone involved: Involve family members in the planning process and gather their input on activities and attractions. This will make everyone feel invested and excited about the trip.\\n\\nRemember, the key is to find a balance and cater to everyone's interests as much as possible. By planning ahead and considering these factors, you can help ensure a memorable and enjoyable family vacation!\"),\n", - " HumanMessage(content=\"Oh, that's a solid plan you've got there! It really does cover all the bases, and I like how you've broken it down into manageable steps. That's a good reminder that I should probably start by setting the budget. That's usually the tricky part for us. Once we have that locked down, we can start looking into destinations that won't break the bank.\\n\\nCalifornia and Oregon sound appealing with their combo of beaches and mountains, but I think they might be a bit on the pricey side for us. Hawaii would be a dream come true, but again, I'm not sure it's in the cards budget-wise.\\n\\nI've heard that Puerto Rico and the Dominican Republic can be more affordable and still offer that mix of beach and mountain activities. Maybe I'll dig a little deeper into those options. Plus, the idea of a vacation rental instead of a hotel could really help us save some cash.\\n\\nI've got to make sure to find those activities you mentioned. The family would love things like snorkeling and hiking, and it's a bonus if we can find some free or low-cost activities too. A flexible itinerary is key, especially with my crew. Someone's always changing their mind about what they want to do.\\n\\nThanks for the tips on transportation and packing as well. I always tend to overpack, so it's a good reminder to just stick to the essentials.\\n\\nInvolving the family is... well, it's a process. Everyone has their own opinion, but it's important that they all get a say. Aunt Lily might need some extra convincing, but I'm sure we'll find a compromise.\\n\\nThis has been super helpful. I appreciate the guidance. I guess it's time to get down to the nitty-gritty and start making some decisions. Wish me luck!\", name='simulated'),\n", - " HumanMessage(content=\"You're very welcome! I'm glad you found the plan helpful, and it seems like you have a great approach in mind. Setting a budget and researching destinations that fit within that budget is a smart starting point.\\n\\nPuerto Rico and the Dominican Republic are indeed more affordable destinations compared to some others, and they offer a fantastic mix of beach and mountain activities. Exploring more about their attractions, availability of vacation rentals, and affordable activities will help you make an informed decision.\\n\\nRemember that planning low-cost or free activities can be a great way to save money while still enjoying the vacation. Look for local hiking trails, public beaches, parks, or cultural experiences that don't come with hefty price tags.\\n\\nKeeping the itinerary flexible will definitely help accommodate any changing preferences within your family. It's all about finding that balance and ensuring everyone gets to do at least a few things they enjoy.\\n\\nAs you involve your family in the planning process, consider creating a checklist of their preferences and priorities. This way, you can work together to find compromises and make decisions that make everyone happy.\\n\\nI'm glad I could be of assistance, and I wish you the best of luck with your planning! I hope you and your family have a fantastic vacation filled with memorable experiences. Enjoy your trip!\"),\n", - " HumanMessage(content='FINISHED', name='simulated')]" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# These are the message from the final simulation state\n", - "result[\"messages\"]" - ] - }, - { - "cell_type": "markdown", - "id": "73ff30e4-1992-4bc9-834d-f4c08b281d20", - "metadata": { - "jp-MarkdownHeadingCollapsed": true - }, - "source": [ - "## (Optional) Review Results\n", - "\n", - "If you've traced the run, you can see the full simulation trace in the LangSmith UI by going to the `Agent Simulation Evaluation` project.\n", - "\n", - "Select the last 'ChatOpenAI' call in the trace to see the full conversation in a single view.\n", - "\n", - "![full-conversation](./img/virtual_user_full_convo.png)\n", - "\n", - "\n", - "From this run, you can manually annotate it to score its quality. This feedback can be used to compare the quality of different versions of your chat bot.\n", - "\n", - "![annotate](./img/virtual_user_annotate.png)" - ] - }, - { - "cell_type": "markdown", - "id": "23db8891-5db3-4a98-a283-53d45cd28c60", - "metadata": {}, - "source": [ - "## Conclusion\n", - "\n", - "In this notebook, you set up a multi-agent simulation to review how your chat bot behaves with simulated users.\n", - "\n", - "To implement this for your chat bot, you can create a dataset of user profiles and questions your chat bot should handle and run periodically. You can use an LLM-as-judge to give the bot an initial score and then manually review to spot check. \n", - "\n", - "LangGraph gives you full control over the simulation so you can manually change the simulated user and the conversation dynamics." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 580ec6b0959b5e4367c06584b4b88a5d6738ceff Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Sat, 20 Jan 2024 09:14:57 -0800 Subject: [PATCH 14/16] coder --- examples/multi_agent/agent_supervisor.ipynb | 381 ++++++++++++-------- 1 file changed, 227 insertions(+), 154 deletions(-) diff --git a/examples/multi_agent/agent_supervisor.ipynb b/examples/multi_agent/agent_supervisor.ipynb index 07c85533f..ec3e3b52c 100644 --- a/examples/multi_agent/agent_supervisor.ipynb +++ b/examples/multi_agent/agent_supervisor.ipynb @@ -15,23 +15,25 @@ "\n", "![diagram](./img/supervisor-diagram.png)\n", "\n", - "To simplify the code in each agent node, we will use the AgentExecutor class from LangChain. This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." + "To simplify the code in each agent node, we will use the AgentExecutor class from LangChain. This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance.\n", + "\n", + "Before we build, let's configure our environment:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", + "metadata": {}, + "outputs": [], + "source": [ + "# %%capture --no-stderr\n", + "# %pip install -U langchain langchain_openai langchain_experimental langsmith pandas" ] }, { "cell_type": "code", "execution_count": 1, - "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "%pip install -U langchain langchain_openai langchain_experimental langsmith pandas" - ] - }, - { - "cell_type": "code", - "execution_count": 3, "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", "metadata": {}, "outputs": [], @@ -54,6 +56,16 @@ "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" ] }, + { + "cell_type": "markdown", + "id": "1ac25624-4d83-45a4-b9ef-a10589aacfb7", + "metadata": {}, + "source": [ + "## Create tools\n", + "\n", + "For this example, you will make an agent to do web research with a search engine, and one agent to create plots. Define the tools they'll use below:" + ] + }, { "cell_type": "code", "execution_count": 4, @@ -63,81 +75,43 @@ "source": [ "from typing import Annotated, List, Tuple, Union\n", "\n", - "import matplotlib.pyplot as plt\n", "from langchain_community.tools.tavily_search import TavilySearchResults\n", "from langchain_core.tools import tool\n", + "from langchain_experimental.tools import PythonREPLTool\n", "\n", "tavily_tool = TavilySearchResults(max_results=5)\n", "\n", + "# This executes code locally, which can be unsafe\n", + "python_repl_tool = PythonREPLTool()" + ] + }, + { + "cell_type": "markdown", + "id": "d58d1e85-22d4-4c22-9062-72a346a0d709", + "metadata": {}, + "source": [ + "## Helper Utilites\n", "\n", - "@tool\n", - "def create_plot(\n", - " data: Annotated[\n", - " Union[List[float], List[int]],\n", - " \"Numerical values for bar heights or line points.\",\n", - " ],\n", - " file_name: Annotated[str, \"File path to save the figure.\"],\n", - " labels: Annotated[\n", - " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", - " ] = None,\n", - " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", - " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", - " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", - " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", - " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", - ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", - " \"\"\"Create a line or bar chart.\"\"\"\n", - " if plot_type not in [\"bar\", \"line\"]:\n", - " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", - "\n", - " fig, ax = plt.subplots(figsize=(10, 6))\n", - " x_positions = range(len(data))\n", - "\n", - " if labels and len(labels) == len(data):\n", - " plt.xticks(x_positions, labels)\n", - "\n", - " if plot_type == \"bar\":\n", - " ax.bar(x_positions, data, color=color)\n", - " elif plot_type == \"line\":\n", - " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", - "\n", - " ax.set_title(title)\n", - " ax.set_xlabel(xlabel)\n", - " ax.set_ylabel(ylabel)\n", - " fig.savefig(file_name)\n", - " plt.close(fig)\n", - " return f'Saved \"{title}\" plot to {file_name}'" + "Define a helper function below, which make it easier to add new agent worker nodes." ] }, { "cell_type": "code", - "execution_count": 5, - "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", + "execution_count": 3, + "id": "c4823dd9-26bd-4e1a-8117-b97b2860211a", "metadata": {}, "outputs": [], "source": [ - "import operator\n", - "from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict\n", - "\n", - "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", "from langchain_core.messages import BaseMessage, HumanMessage\n", - "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", - "from langchain_core.tools import BaseTool\n", - "from langchain_experimental.tools import PythonREPLTool\n", "from langchain_openai import ChatOpenAI\n", "\n", "from langgraph.graph import END, StateGraph\n", "\n", "\n", - "class AgentState(TypedDict):\n", - " messages: Annotated[Sequence[BaseMessage], operator.add]\n", - " next: str\n", - "\n", - "\n", - "workflow = StateGraph(AgentState)\n", - "\n", - "\n", - "def create_worker_node(name: str, llm: ChatOpenAI, tools: list, system_prompt: str):\n", + "def create_worker_node(\n", + " workflow: StateGraph, name: str, llm: ChatOpenAI, tools: list, system_prompt: str\n", + "):\n", + " # Each worker node will be given a name and some tools.\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", @@ -151,22 +125,62 @@ " agent = create_openai_functions_agent(llm, tools, prompt)\n", " executor = AgentExecutor(agent=agent, tools=tools)\n", " chain = executor | (\n", + " # So the agents properly role-play in this simulation, we will\n", + " # tag their final message as a human message\n", " lambda x: {\"messages\": [HumanMessage(content=x[\"output\"], name=name)]}\n", " )\n", - " workflow.add_node(name, chain)\n", + " workflow.add_node(name, chain)" + ] + }, + { + "cell_type": "markdown", + "id": "a07d507f-34d1-4f1b-8dde-5e58d17b2166", + "metadata": {}, + "source": [ + "## Construct Graph\n", "\n", + "We're ready to start building the graph. Below, define the state and worker nodes using the function we just defined." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict\n", + "\n", + "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", + "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "\n", + "\n", + "# The agent state is the input to each node in the graph\n", + "class AgentState(TypedDict):\n", + " # The annotation tells the graph that new messages will always\n", + " # be added to the current states\n", + " messages: Annotated[Sequence[BaseMessage], operator.add]\n", + " # The 'next' field indicates where to route to next\n", + " next: str\n", + "\n", + "\n", + "workflow = StateGraph(AgentState)\n", "\n", "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", "\n", - "# Note: these worker nodes don't _have_ to be agents. They can be any DAG, tool, or function\n", - "create_worker_node(\"Researcher\", llm, [tavily_tool], \"You are a web researcher.\")\n", - "create_worker_node(\"Chart Generator\", llm, [create_plot], \"You are a chart generator.\")\n", + "\n", + "create_worker_node(\n", + " workflow, \"Researcher\", llm, [tavily_tool], \"You are a web researcher.\"\n", + ")\n", "# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION\n", "create_worker_node(\n", + " workflow,\n", " \"Coder\",\n", " llm,\n", - " [PythonREPLTool()],\n", - " \"You may generate safe python code to analyze data.\",\n", + " [python_repl_tool],\n", + " \"You may generate safe python code to analyze data \"\n", + " \"and generate charts using matplotlib.\",\n", ")" ] }, @@ -175,126 +189,185 @@ "id": "d6374825-912f-40c9-910d-afa267b401bf", "metadata": {}, "source": [ - "Almost done, now we need to create the team supervisor." + "Almost done, now create create the team supervisor. It will use function calling to choose the next worker node OR finish processing." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 19, "id": "17c108a0-6dc3-46fd-a5e6-a1fcfad5458a", "metadata": {}, "outputs": [], "source": [ - "# So the team supervisor is an LLM node. It just picks the next t\n", "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", "\n", - "\n", - "def create_supervisor(members: List[str], llm: ChatOpenAI, system_prompt: str):\n", - " options = [\"FINISH\"] + members\n", - " function_def = {\n", - " \"name\": \"route\",\n", - " \"description\": \"Select the next role.\",\n", - " \"parameters\": {\n", - " \"title\": \"routeSchema\",\n", - " \"type\": \"object\",\n", - " \"properties\": {\n", - " \"next\": {\n", - " \"title\": \"Next\",\n", - " \"anyOf\": [\n", - " {\"enum\": options},\n", - " ],\n", - " }\n", - " },\n", - " \"required\": [\"next\"],\n", - " },\n", - " }\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\"system\", system_prompt),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " (\n", - " \"system\",\n", - " \"Given the conversation above, who should act next?\"\n", - " \" Or should we FINISH? Select one of: {options}\",\n", - " ),\n", - " ]\n", - " ).partial(options=str(options))\n", - " if \"members\" in prompt.input_variables:\n", - " prompt = prompt.partial(members=\", \".join(members))\n", - " chain = (\n", - " prompt\n", - " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", - " | JsonOutputFunctionsParser()\n", - " )\n", - " workflow.add_node(\"supervisor\", chain)\n", - " conditional_map = {k: k for k in members}\n", - " conditional_map[\"FINISH\"] = END\n", - "\n", - " for member in members:\n", - " workflow.add_edge(member, \"supervisor\")\n", - " workflow.add_conditional_edges(\"supervisor\", lambda x: x[\"next\"], conditional_map)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", - "metadata": {}, - "outputs": [], - "source": [ - "create_supervisor(\n", - " [\"Researcher\", \"Chart Generator\", \"Coder\"],\n", - " llm,\n", + "members = [\"Researcher\", \"Coder\"]\n", + "system_prompt = (\n", " \"You are a supervisor tasked with managing a conversation between the\"\n", " \" following workers: {members}. Given the following user request,\"\n", " \" respond with the worker to act next. Each worker will perform a\"\n", " \" task and respond with their results and status. When finished,\"\n", - " \" respond with FINISH.\",\n", + " \" respond with FINISH.\"\n", ")\n", + "# Our team supervisor is an LLM node. It just picks the next agent to process\n", + "# and decides when the work is completed\n", + "options = [\"FINISH\"] + members\n", + "# Using openai function calling can make output parsing easier for us\n", + "function_def = {\n", + " \"name\": \"route\",\n", + " \"description\": \"Select the next role.\",\n", + " \"parameters\": {\n", + " \"title\": \"routeSchema\",\n", + " \"type\": \"object\",\n", + " \"properties\": {\n", + " \"next\": {\n", + " \"title\": \"Next\",\n", + " \"anyOf\": [\n", + " {\"enum\": options},\n", + " ],\n", + " }\n", + " },\n", + " \"required\": [\"next\"],\n", + " },\n", + "}\n", + "prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\"system\", system_prompt),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " (\n", + " \"system\",\n", + " \"Given the conversation above, who should act next?\"\n", + " \" Or should we FINISH? Select one of: {options}\",\n", + " ),\n", + " ]\n", + ").partial(options=str(options), members=\", \".join(members))\n", "\n", - "# Finally, add entrypoint\n", - "workflow.set_entry_point(\"supervisor\")\n", - "\n", - "\n", - "def enter(text: str) -> dict:\n", - " return {\"messages\": [HumanMessage(content=text)]}\n", - "\n", - "\n", - "graph = enter | workflow.compile()" + "supervisor_chain = (\n", + " prompt\n", + " | llm.bind_functions(functions=[function_def], function_call=\"route\")\n", + " | JsonOutputFunctionsParser()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "2c1593d5-39f7-4819-96d2-4ad7d7991d72", + "metadata": {}, + "source": [ + "Now connect all the edges in the graph." ] }, { "cell_type": "code", - "execution_count": null, - "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", + "execution_count": 20, + "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", "metadata": {}, "outputs": [], "source": [ - "results = graph.invoke(\"Code hello world and print it to the terminal\")\n", - "results[\"messages\"][-1].pretty_print()" + "workflow.add_node(\"supervisor\", supervisor_chain)\n", + "\n", + "\n", + "for member in members:\n", + " # We want our workers to ALWAYS \"report back\" to the supervisor when done\n", + " workflow.add_edge(member, \"supervisor\")\n", + "# The supervisor populates the \"next\" field in the graph state\n", + "# which routes to a node or finishes\n", + "conditional_map = {k: k for k in members}\n", + "conditional_map[\"FINISH\"] = END\n", + "workflow.add_conditional_edges(\"supervisor\", lambda x: x[\"next\"], conditional_map)\n", + "# Finally, add entrypoint\n", + "workflow.set_entry_point(\"supervisor\")\n", + "\n", + "graph = workflow.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "d36496de-7121-4c49-8cb6-58c943c66628", + "metadata": {}, + "source": [ + "## Invoke the team\n", + "\n", + "With the graph created, we can now invoke it and see how it performs!" ] }, { "cell_type": "code", - "execution_count": 10, - "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", + "execution_count": 21, + "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Python REPL can execute arbitrary code. Use with caution.\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", - "The chart summarizing the data on the 2023 California wildfires has been created successfully. You can see the representation of the statistics mentioned in the summary including the total number of fires, total acres burned, comparison with the five-year average, the size of the largest wildfire, and the number of fatalities.\n", - "\n", - "![2023 California Wildfires Overview](sandbox:/ca_wildfires_2023_chart.png)\n" + "The \"Hello, World!\" message has been successfully printed to the terminal.\n" ] } ], "source": [ "results = graph.invoke(\n", - " \"Write a research summary of CA wildfires in 2023. Include a chart.\"\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(content=\"Code hello world and print it to the terminal\")\n", + " ]\n", + " }\n", + ")\n", + "results[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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eHg48+bNs06BHDp0KCNGjKB79+7Url2b6Ohotm3bxo4dO2jWrBlg+aOsfPnydO7cOUXhjdTEx8czb9483njjDQA6dOhA165diYiIsCYmAKdPn6Z27drW62qCgoI4deoUc+fO5erVqzbTLF955RV8fX0ZPHiw9dqRH374gc6dOxMWFsYnn3zC1atXGTNmjDVZTk5E27Rpw969e+nTpw8lSpTg3LlzrFixghMnTmSoQMilS5cAyzS1U6dO8cEHH+Dm5ka7du1uu21aLl68SIsWLWjfvj0dO3akUKFC1nUzZszgypUr9OzZE5PJxMiRI3nqqac4cuSIdYpuRt/fiIgImjRpQmJiorXf+PHjcXd3v+PY4c4/p2mJiIjAw8PD5nfBzp07efjhh3FwsP1etnbt2owfP54DBw5QuXLldPcJpEjmAGJjY4mLiyMqKoqff/6ZpUuX3vbawHbt2tG/f39mz57NW2+9ZbNu9uzZPProo+TLl4+EhATCwsKIj4+nT58++Pv7c+rUKX755RciIyPx8fG57fkoVaoUMTExeHp6Eh4ezueff27zM5Ka7777jooVK/L666/Trl07fv75Z8aOHYu/vz9nz56lTp061sTY19eXpUuX0q1bN6Kjo+nbty9gmQ49ceJEOnToQI8ePbhy5QqTJk0iLCws1Wmwqf3uSc/PP/9MTEwM7du3x9/fn8aNGzN9+nSeffbZTJ1jsEwvbNGiBTVq1GDIkCE4ODhYv0j8448/qF27ts32bdu2pUyZMnz00UfWpHXFihUcOXKErl274u/vb51Ov3fvXjZv3mxNnHbu3Enz5s0pXLgww4YNIykpiffffx9fX98Uxzh8+HDee+892rVrR/fu3Tl//jzffPMNjRo1SvF/oUiWsO/AmUjuFxUVZQDGk08+meFtrl69mqItLCzMKFWqlE3brdMCU5uyljyd6OYpawkJCYafn59RqVIlIy4uztr+yy+/GIAxePBga1vnzp0NwBgwYECKmADDxcXFOHTokLVt9+7dBmB88803KWK4eUpTasfYs2dPw8PDw7h27VqKdTfbu3evARgffPCBYRiW6S2enp7G999/bxiGYRQqVMgYPXq0YRiGER0dbTg6Oho9evSwbr969WoDMFavXm1znDdPC1y4cKEBGCNHjrS2JSYmGg0bNszQOU5tn8nvj7e3t3Hu3Dmbvk2b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RHIqR0dHnJycdBsGERERATKWGyTLUQUtoqKiAMifP3+afTZt2kS/fv1s2sLCwli4cCEAR48eJSIigtDQUOt6Hx8fgoOD2bRpU6rJVXx8PPHx8dbl6OjodONMSEjgzJkzXL169bbHJCK5j4eHB4ULF8bFxcXeoYiIiEgukmOSK7PZTN++falfvz6VKlVKs19ERASFChWyaStUqBARERHW9cltafW51YgRIxg2bFiG4zx69CiOjo4EBATg4uKib7hF7hOGYZCQkMD58+c5evQoZcqUue3NAkVERESS5ZjkqlevXuzZs4f169ff89ceOHCgzWhYci371CQkJGA2mwkMDMTDw+NehSgi94i7uzvOzs4cP36chIQE3Nzc7B2SiIiI5BI5Irnq3bs3v/zyC+vWrbvtXY/9/f05e/asTdvZs2fx9/e3rk9uK1y4sE2fatWqpbpPV1dXXF1dMxWzvs0WuX/p8y0iIiJ3wq5/QRiGQe/evVmwYAGrVq2iZMmSt92mbt26rFy50qZtxYoV1K1bF4CSJUvi7+9v0yc6OpotW7ZY+4iIiIiIiGQ1u45c9erVixkzZrBo0SK8vLys10T5+Pjg7u4OQKdOnShSpAgjRowA4LXXXiMkJITPP/+cli1bMnPmTLZt28b48eMBy52T+/bty4cffkiZMmUoWbIk7733HgEBAYSHh9vlOEVERERE5P5n15GrMWPGEBUVRePGjSlcuLD1MWvWLGufEydOcObMGetyvXr1mDFjBuPHj6dq1arMnTuXhQsX2hTB6N+/P3369OHFF1+kVq1axMTEsGzZshx17USS2WDT4Yss2nWKTYcvkmTOMRXx5T5UokQJRo0aZe8wREREJD2rR8DakamvWzvSsl5yNLtPC0zt0aVLF2ufNWvWMHXqVJvt2rZty/79+4mPj2fPnj089thjNutNJhPvv/8+ERERXLt2jd9//52yZcvegyPKmGV7ztDgk1V0mLCZ12buosOEzTT4ZBXL9py5/cZ3aMSIEdSqVQsvLy/8/PwIDw9n//79Nn2uXbtGr169KFCgAHny5KFNmzY217ft3r2bDh06EBgYiLu7O+XLl+err76y2cf69eupX78+BQoUwN3dnaCgIL788ssMxxkUFISrq2ualR3trUSJEphMJkwmk7ViZLdu3bh8+bK9QxMREZHczsERVg9PmWCtHWlp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IiKijkv2fa5CkZz7XBERERERUejoMPtcERERERERdRYMV0RERERERAHAcEVERERERBQADFdEREREREQBwHBFREREREQUAAxXREREREREASD7PlehyLE6vclkkrkSIiIiIiKSkyMTtGYHK4YrL6qqqgAAycnJMldCREREREShoKqqChERET7P4SbCXtjtdly4cAFhYWEQBEHWWkwmE5KTk3H+/PkuuaEx28/2s/1dt/0APwO2n+1n+9l+tl/+9ouiiKqqKiQlJUGh8D2rij1XXigUCvTs2VPuMjyEh4fL/i+WnNh+tp/t77rtB/gZsP1sP9vP9ndVodJ+fz1WDlzQgoiIiIiIKAAYroiIiIiIiAKA4SrEabVarFy5ElqtVu5SZMH2s/1sf9dtP8DPgO1n+9l+tp/t71jt54IWREREREREAcCeKyIiIiIiogBguCIiIiIiIgoAhisiIiIiIqIAYLgiIiIiIiIKAIarEPXpp59i8uTJSEpKgiAI2Llzp9wlBVVOTg5uvvlmhIWFIS4uDlOmTMHJkyflLitocnNzMXjwYOfGeZmZmfjwww/lLks2a9euhSAImD9/vtylBMVzzz0HQRA8vvr16yd3WUH1008/YcaMGYiJiYFer8egQYNw9OhRucsKil69ejX78xcEAXPmzJG7tKCw2WxYvnw50tLSoNfr0adPHzz//PPoSutvVVVVYf78+UhNTYVer8eoUaNw5MgRuctqF/7ud0RRxIoVK5CYmAi9Xo9x48bh9OnT8hTbDvy1f/v27Rg/fjxiYmIgCAIKCwtlqbM9+foMrFYrFi9ejEGDBsFoNCIpKQmPPPIILly4IF/BfjBchSiz2YwhQ4bg9ddfl7sUWezfvx9z5szBF198gfz8fFitVowfPx5ms1nu0oKiZ8+eWLt2LY4dO4ajR4/ijjvuwD333INvvvlG7tKC7siRI3jjjTcwePBguUsJqgEDBuDixYvOrwMHDshdUtBcvXoVo0ePhlqtxocffohvv/0WL730EqKiouQuLSiOHDni8Wefn58PAHjwwQdlriw41q1bh9zcXLz22mv47rvvsG7dOqxfvx6vvvqq3KUFzWOPPYb8/Hy8/fbbOH78OMaPH49x48bhp59+kru0gPN3v7N+/Xq88sor2LBhAw4dOgSj0YgJEyagtrY2yJW2D3/tN5vNGDNmDNatWxfk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Ll++XIyPjxe1Wq04duzYTvXfhb/2b9682evzK1eulLXuQPL1GTiWoPf2tXfvXrlL90oQxS605TkREREREVE74ZwrIiIiIiKiAGC4IiIiIiIiCgCGKyIiIiIiogBguCIiIiIiIgoAhisiIiIiIqIAYLgiIiIiIiIKAIYrIiIiIiKiAGC4IiIiIiIiCgCGKyIiogATBAE7d+6UuwwiIgoyhisiIuo0Zs2aBUEQ8Nvf/rbZc3PmzIEgCJg1a1bA3u+5557D0KFDA3Y9IiLq2BiuiIioU0lOTsa2bdtQU1PjPFZbW4u8vDykpKTIWBkREXV2DFdERNSpDBs2DMnJydi+fbvz2Pbt25GSkoKbbrrJeayurg5PPvkk4uLioNPpMGbMGBw5csT5/L59+yAIAvbs2YOMjAwYDAaMGjUKJ0+eBABs2bIFq1atwldffQVBECAIArZs2eJ8fVlZGe69914YDAakp6fjgw8+aP/GExGRrBiuiIio08nOzsbmzZudj//85z9j9uzZHuc8/fTT+Nvf/oa33noLBQUF6Nu3LyZMmIArV654nLds2TK89NJLOHr0KFQqFbKzswEAWVlZ+N3vfocBAwbg4sWLuHjxIrKyspyvW7VqFR566CF8/fXXuOuuuzB9+vRm1yYios6F4YqIiDqdGTNm4MCBAyguLkZxcTE+++wzzJgxw/m82WxGbm4uXnzxRUycOBH9+/fHpk2boNfr8eabb3pca/Xq1bjtttvQv39/LFmyBJ9//jlqa2uh1+vRrVs3qFQqJCQkICEhAXq93vm6WbNmYdq0aejbty/WrFmD6upqHD58OGifARERBZ9K7gKIiIgCrXv37pg0aRK2bNkCURQxadIkxMbGOp8/e/YsrFYrRo8e7TymVqsxYsQIfPfddx7XGjx4sPPnxMREAMClS5f8zt9yf53RaER4eDguXbp0Xe0iIqLQxnBFRESdUnZ2NubOnQsAeP3116/5Omq12vmzIAgAALvd3qbXOV7bmtcREVHHxWGBRETUKd15552or6+H1WrFhAkTPJ7r06cPNBoNPvvsM+cxq9WKI0eOoH///q1+D41GA5vNFrCaiYioY2PPFRERdUpKpdI5xE+pVHo8ZzQa8fjjj2PRokWIjo5GSkoK1q9fD4vFgkcffbTV79GrVy8UFRWhsLAQPXv2RFhYGLRabUDbQUREHQfDFRERdVrh4eEtPrd27VrY7XY8/PDDqKqqQkZGBnbv3o2oqKhWX//+++/H9u3bcfvtt6OiogKbN28O6CbFRETUsQiiKIpyF0FERERERNTRcc4VERERERFRADBcERERERERBQDDFRERERERUQAwXBEREREREQUAwxUREREREVEAMFwREREREREFAMMVERERERFRADBcERERERERBQDDFRERERERUQAwXBEREREREQUAwxUREREREVEA/H/1tD65nuE1VgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "The chart has been successfully created and saved as \"california_wildfires_comparison.png\". This chart compares the acres burned by California wildfires in 2023 to the five-year average for each month. Unfortunately, I'm unable to display the image directly to you here, but the chart visualizes the data with the following characteristics:\n", + "\n", + "- The X-axis represents the months of the year from January to December.\n", + "- The Y-axis indicates the acres burned.\n", + "- A line graph shows the cumulative acres burned in 2023, marked with circles.\n", + "- Another line graph represents the five-year average cumulative acres burned by month, marked with x's and a dashed line.\n", + "- The chart includes a legend to distinguish between the two lines and is titled \"California Wildfires: Acres Burned in 2023 vs 5-Year Average\".\n", + "\n", + "You would need to view the \"california_wildfires_comparison.png\" file using an image viewer to see the actual chart.\n" + ] + } + ], + "source": [ + "results = graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(\n", + " content=\"Write a research summary of CA wildfires in 2023. Include a chart.\"\n", + " )\n", + " ]\n", + " }\n", ")\n", "results[\"messages\"][-1].pretty_print()" ] From 77459e82f74684cda326e6cabd84755f15969e6d Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Sat, 20 Jan 2024 13:02:46 -0800 Subject: [PATCH 15/16] push --- .../multi-agent-collaboration.ipynb | 280 ++++++++++-------- 1 file changed, 152 insertions(+), 128 deletions(-) diff --git a/examples/multi_agent/multi-agent-collaboration.ipynb b/examples/multi_agent/multi-agent-collaboration.ipynb index 6225774e3..3974788d2 100644 --- a/examples/multi_agent/multi-agent-collaboration.ipynb +++ b/examples/multi_agent/multi-agent-collaboration.ipynb @@ -7,11 +7,17 @@ "source": [ "# Basic Multi-agent Collaboration\n", "\n", - "A single agent can usually operate effectively using a handful of tools and a scoped domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. One way to improve your system's performance is through multi-agent collaboration. Each agent can specialize in a task or domain, and LangGraph can effectively orchestrate them to accomplish a larger goal. \n", + "A single agent can usually operate effectively using a handful of tools within a single domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. \n", "\n", - "This notebook is inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.\n", + "One way to approach complicated tasks is through a \"divide-and-conquer\" approach: create an specialized agent for each task or domain and route tasks to the correct \"expert\".\n", "\n", - "This and other \"advanced agent\" notebooks are designed to show how you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." + "This notebook (inspired by the paper [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://arxiv.org/abs/2308.08155), by Wu, et. al.) shows one way to do this using LangGraph.\n", + "\n", + "The resulting graph will look something like the following diagram:\n", + "\n", + "![multi_agent diagram](./img/simple_multi_agent_diagram.png)\n", + "\n", + "Before we get started, a quick note: this and other multi-agent notebooks are designed to show _how_ you can implement certain design patterns in LangGraph. If the pattern suits your needs, we recommend combining it with some of the other fundamental patterns described elsewhere in the docs for best performance." ] }, { @@ -49,75 +55,13 @@ "os.environ[\"LANGCHAIN_PROJECT\"] = \"Multi-agent Collaboration\"" ] }, - { - "cell_type": "markdown", - "id": "7d5fc0f3-5d9e-4e72-a281-177f101c2a7d", - "metadata": {}, - "source": [ - "## Create tools\n", - "\n", - "We will make 2 agents, 1 for plotting, and another for searching information on the web. Below are their tools:" - ] - }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "075c91c3-c249-471d-b259-41975faa83fb", "metadata": {}, "outputs": [], - "source": [ - "from typing import List, Tuple, Union\n", - "\n", - "import matplotlib.pyplot as plt\n", - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_core.tools import tool\n", - "\n", - "tavily_tool = TavilySearchResults(max_results=5)\n", - "\n", - "\n", - "@tool\n", - "def create_plot(\n", - " data: list,\n", - " labels: Union[List[str], None] = None,\n", - " title: str = \"Plot\",\n", - " xlabel: str = \"X\",\n", - " ylabel: str = \"Y\",\n", - " color: Union[str, List[str]] = \"blue\",\n", - " plot_type: str = \"bar\",\n", - ") -> str:\n", - " \"\"\"\n", - " Generates a bar or line plot from the provided data and returns the figure and axis objects.\n", - "\n", - " :param data: A list of numerical values for the bar heights or line points.\n", - " :param labels: A list of strings for the bar or point labels. Default is None.\n", - " :param title: Title of the plot. Default is 'Plot'.\n", - " :param xlabel: Label for the X-axis. Default is 'X'.\n", - " :param ylabel: Label for the Y-axis. Default is 'Y'.\n", - " :param color: Color of the bars or line. Can be a single color or a list of colors. Default is 'blue'.\n", - " :param figsize: Size of the figure as a tuple (width, height). Default is (10, 6).\n", - " :param plot_type: Type of plot ('bar' or 'line'). Default is 'bar'.\n", - " :return: Tuple containing the figure and axes objects.\n", - " \"\"\"\n", - " if plot_type not in [\"bar\", \"line\"]:\n", - " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", - "\n", - " fig, ax = plt.subplots(figsize=(10, 6))\n", - " x_positions = range(len(data))\n", - "\n", - " if labels and len(labels) == len(data):\n", - " plt.xticks(x_positions, labels)\n", - "\n", - " if plot_type == \"bar\":\n", - " ax.bar(x_positions, data, color=color)\n", - " elif plot_type == \"line\":\n", - " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", - "\n", - " ax.set_title(title)\n", - " ax.set_xlabel(xlabel)\n", - " ax.set_ylabel(ylabel)\n", - "\n", - " return \"Chart generated!\"" - ] + "source": [] }, { "cell_type": "markdown", @@ -133,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "4325a10e-38dc-4a98-9004-e1525eaba377", "metadata": {}, "outputs": [], @@ -152,9 +96,39 @@ "from langgraph.prebuilt.tool_executor import ToolExecutor, ToolInvocation\n", "\n", "\n", + "def add_agent_node(workflow: StateGraph, name: str, llm, tools, system_message: str):\n", + " \"\"\"Create an agent and add it to the graph builder.\"\"\"\n", + " functions = [format_tool_to_openai_function(t) for t in tools]\n", + "\n", + " prompt = ChatPromptTemplate.from_messages(\n", + " [\n", + " (\n", + " \"system\",\n", + " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", + " \" Use the provided tools to progress towards answering the question.\"\n", + " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", + " \" will help where you left off. Execute what you can to make progress.\"\n", + " \" If you or any of the other assistants have the final answer or deliverable,\"\n", + " \" prefix your response with FINAL ANSWER so the team knows to stop.\"\n", + " \" You have access to the following tools: {tool_names}.\\n{system_message}\",\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " ]\n", + " ).partial(system_message=system_message)\n", + "\n", + " chain = (\n", + " (lambda x: {**x, \"intermediate_steps\": []})\n", + " | prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", + " | llm.bind_functions(functions)\n", + " | (lambda x: _update_state(x, name=name))\n", + " )\n", + " workflow.add_node(name, chain)\n", + "\n", + "\n", "def _update_state(ai_message, name: str) -> dict:\n", - " \"\"\"The graph routes the logic based on the message type\n", - " and the sender origin. We nee\"\"\"\n", + " \"\"\"This is called after each worker agent is invoked.\n", + "\n", + " It is used to update the global graph state using the agent output.\"\"\"\n", " if isinstance(ai_message, FunctionMessage):\n", " result = ai_message\n", " else:\n", @@ -167,36 +141,8 @@ " }\n", "\n", "\n", - "def add_agent_node(workflow: StateGraph, name: str, llm, tools):\n", - " \"\"\"Create an agent and add it to the graph builder.\"\"\"\n", - " functions = [format_tool_to_openai_function(t) for t in tools]\n", - "\n", - " prompt = ChatPromptTemplate.from_messages(\n", - " [\n", - " (\n", - " \"system\",\n", - " \"You are a helpful AI assistant, collaborating with other assistants.\"\n", - " \" Use the provided tools to progress towards answering the question.\"\n", - " \" If you are unable to fully answer, that's OK, another assistant with different tools \"\n", - " \" will help where you left off. Execute what you can to make progress.\"\n", - " \" If you have the final answer, prefix your response with FINAL ANSWER.\"\n", - " \" You have access to the following tools: {tool_names}.\",\n", - " ),\n", - " MessagesPlaceholder(variable_name=\"messages\"),\n", - " ]\n", - " )\n", - "\n", - " chain = (\n", - " (lambda x: {**x, \"intermediate_steps\": []})\n", - " | prompt.partial(tool_names=\", \".join([tool.name for tool in tools]))\n", - " | llm.bind_functions(functions)\n", - " | (lambda x: _update_state(x, name=name))\n", - " )\n", - " workflow.add_node(name, chain)\n", - "\n", - "\n", - "def call_tool(state):\n", - " \"\"\"This a helper class we have that is useful for running tools\n", + "def call_tool(state, tool_executor):\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", @@ -204,16 +150,23 @@ " # 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=last_message.additional_kwargs[\"function_call\"][\"name\"],\n", - " tool_input=json.loads(\n", - " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", - " ),\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(content=str(response), name=action.tool)\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]}" ] @@ -230,17 +183,20 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", "metadata": {}, "outputs": [], "source": [ "import operator\n", - "from typing import Annotated, Sequence, TypedDict\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_community.tools.tavily_search import TavilySearchResults\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", + "from langchain_core.tools import tool\n", + "from langchain_experimental.utilities import PythonREPL\n", "from langchain_openai import ChatOpenAI\n", "from typing_extensions import TypedDict\n", "\n", @@ -252,8 +208,24 @@ " sender: str\n", "\n", "\n", - "tools = [tavily_tool, create_plot]\n", - "tool_executor = ToolExecutor(tools)\n", + "tavily_tool = TavilySearchResults(max_results=5)\n", + "\n", + "# Warning: This executes code locally, which can be unsafe when not sandboxed\n", + "\n", + "repl = PythonREPL()\n", + "\n", + "\n", + "@tool\n", + "def python_repl(\n", + " code: Annotated[str, \"The python code to execute to generate your chart.\"]\n", + "):\n", + " \"\"\"Use this to execute python code. If you want to see the output of a value,\n", + " you should print it out with `print(...)`. This is visible to the user.\"\"\"\n", + " try:\n", + " 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}\"\n", "\n", "\n", "workflow = StateGraph(AgentState)\n", @@ -263,35 +235,60 @@ " \"Researcher\",\n", " llm,\n", " [tavily_tool],\n", + " system_message=\"You should provide accurate data for the chart generator to use.\",\n", ")\n", - "add_agent_node(workflow, \"Chart Generator\", llm, [create_plot])\n", - "workflow.add_node(\"call_tool\", call_tool)\n", + "add_agent_node(\n", + " workflow,\n", + " \"Chart Generator\",\n", + " llm,\n", + " [python_repl],\n", + " system_message=\"Any charts you display will be visible by the user.\",\n", + ")\n", + "\n", + "# The \"tool executor\" node is called whenever any worker agent\n", + "#\n", + "tools = [tavily_tool, python_repl]\n", + "tool_executor = ToolExecutor(tools)\n", + "workflow.add_node(\"call_tool\", lambda state: call_tool(state, tool_executor))\n", "\n", "\n", "# Either agent can decide to end\n", - "def should_continue(state):\n", + "def router(state):\n", " # This is the router\n", " messages = state[\"messages\"]\n", " last_message = messages[-1]\n", " if \"function_call\" in last_message.additional_kwargs:\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 \"continue\"\n", "\n", "\n", "workflow.add_conditional_edges(\n", " \"Researcher\",\n", - " should_continue,\n", + " router,\n", " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", ")\n", "workflow.add_conditional_edges(\n", " \"Chart Generator\",\n", - " should_continue,\n", + " router,\n", " {\"continue\": \"Researcher\", \"call_tool\": \"call_tool\", \"end\": END},\n", ")\n", "# We will assume that any time a tool is called, the researcher will choose what to do next\n", - "workflow.add_edge(\"call_tool\", \"Researcher\")\n", + "workflow.add_conditional_edges(\n", + " \"call_tool\",\n", + " # Each agent node updates the 'sender' field\n", + " # the tool calling node does not, meaning\n", + " # this edge will route back to the original agent\n", + " # who invoked the tool\n", + " lambda x: x[\"sender\"],\n", + " {\n", + " \"Researcher\": \"Researcher\",\n", + " \"Chart Generator\": \"Chart Generator\",\n", + " },\n", + ")\n", "workflow.set_entry_point(\"Researcher\")\n", "graph = workflow.compile()" ] @@ -303,34 +300,51 @@ "source": [ "## Invoke\n", "\n", - "With the graph created, you can invoke it!" + "With the graph created, you can invoke it! Let's have it chart some stats for us." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "176a99b0-b457-45cf-8901-90facaa852da", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "HumanMessage(content=\"FINAL ANSWER:\\n\\nThe line graph of the UK's GDP over the past five years has been generated based on the estimated and provided figures:\\n\\n1. 2019: 2.238 trillion GBP\\n2. 2020: 2.018 trillion GBP\\n3. 2021: 2.14 trillion GBP\\n4. 2022: 2.2 trillion GBP\\n5. 2023: GDP growth of 0.1% in Q1 (Jan to Mar) - Note that the full-year value for 2023 is an estimate based on Q1 data and should be treated with caution.\\n\\nPlease note that the 2023 figure is not the full-year GDP but rather the growth rate for the first quarter. The actual GDP value for 2023 will be determined at the end of the year. The graph provides a visual representation of the GDP trend over the past five years, with an observable dip in 2020 due to the economic impact of the COVID-19 pandemic, followed by a recovery in subsequent years.\", name='Chart Generator')" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" + "name": "stderr", + "output_type": "stream", + "text": [ + "Python REPL can execute arbitrary code. Use with caution.\n" + ] }, { "data": { - "image/png": "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QUFB2rZtm9asWaOIiAjVrVtX165dizmma9eu+vbbb7VkyRJt2rRJZ86cUbNmze54rNdff10tWrSI93m+//57vfTSS+rQoYN2796tDz/8UJMmTdK0adOS7LUBnsQZ79Xt27crICBA8+bN0549e9S/f3/17ds3zvvw6NGjatiwoWrWrKmdO3eqS5cuateuna3/GADuIrnep+Hh4cqWLZsGDBig0qVLJ+trBDxBcr1XN2/erDp16mjlypXavn27atasqUaNGmnHjh3J+npjWEh258+ftyRZmzZtsizLskJCQqzUqVNbS5YsiTlm3759liRr69atd9x/8ODBVunSpe/Y36pVK+uFF16Is2/KlClW7ty5raioKOe+CCAFeNj3arSOHTtaNWvWjPm+V69eVokSJeIc06JFC6tevXpOfgWA50uq9+m/Va9e3ercubNTcwMpTXK8V6M99thj1tChQ50TPJE4c2WD0NBQSVKWLFkkmVYeERGh2rVrxxxTrFgx5c2bV1u3bk3w44aHhytt2rRx9qVLl06nTp3S8ePHnZAcSFmc9V4NDQ2NeQxJ2rp1a5zHkKR69eol6v0OwEiq9ykA50qu92pUVJSuXLli2/uZcpXMoqKi1KVLFz311FMqWbKkJCk4OFg+Pj7KlClTnGOzZ8+u4ODgBD92vXr1tHTpUq1bt05RUVE6ePCgJk6cKMlcOw4g4Zz1Xv3555/1xRdfqH379jH7goODlT179jseIywsTDdu3HDuCwE8WFK+TwE4T3K+VydMmKCrV6+qefPmTsufGKlsedYULCgoSLt379aPP/7o9Md+88039ffff+vZZ59VRESE/Pz81LlzZw0ZMkReXvRoIDGc8V7dvXu3mjRposGDB6tu3bpOTAdA4n0KuIvkeq8uWLBAQ4cO1fLlyxUQEPDAz/Uw+I07GXXq1EkrVqzQhg0blDt37pj9gYGBunXrlkJCQuIcf+7cOQUGBib48R0Oh8aOHaurV6/q+PHjCg4OVsWKFSVJBQsWdMprAFICZ7xX9+7dq1q1aql9+/YaMGBAnNsCAwPvmA303Llz8vPzU7p06Zz7YgAPldTvUwDOkVzv1UWLFqldu3ZavHjxHZfeJyfKVTKwLEudOnXSsmXLtH79ehUoUCDO7U888YRSp06tdevWxew7cOCATpw4ocqVKyf6+by9vZUrVy75+Pho4cKFqly5srJly/bQrwPwdM56r+7Zs0c1a9ZU27ZtNXLkyDuep3LlynEeQ5LWrFnzQO93IKVJrvcpgIeTnO/VhQsX6rXXXtPChQvVsGHDpHlBCWXLNBopzNtvv235+/tbGzdutM6ePRvzdf369ZhjOnToYOXNm9dav3699fvvv1uVK1e2KleuHOdxDh06ZO3YscN66623rCJFilg7duywduzYYYWHh1uWZVkXLlywpk+fbu3bt8/asWOH9e6771pp06a1fvnll2R9vYC7csZ7ddeuXVa2bNmsl19+Oc5jnD9/PuaYI0eOWOnTp7d69uxp7du3z/rggw8sb29va9WqVcn6egF3lFzvU8uyYv6dfeKJJ6zWrVtbO3bssPbs2ZNsrxVwZ8n1Xp0/f76VKlUq64MPPohzTEhISLK+3miUq2QgKd6vTz/9NOaYGzduWB07drQyZ85spU+f3nruueess2fPxnmc6tWrx/s4R48etSzLlKsnn3zS8vX1tdKnT2/VqlXL2rZtWzK+UsC9OeO9Onjw4HgfI1++fHGea8OGDVaZMmUsHx8fq2DBgnGeA8DdJef7NCHHAIhfcr1X7/b7cdu2bZPvxf6Lw7Isy5lnwgAAAAAgJWLMFQAAAAA4AeUKAAAAAJyAcgUAAAAATkC5AgAAAAAnoFwBAAAAgBNQrgAAAADACShXAAAAAOAElCsAAAAAcALKFQAAAAA4AeUKAODxLMtS7dq1Va9evTtu+/DDD5UpUyadOnXKhmQAAE9CuQIAeDyHw6FPP/1Uv/zyiz766KOY/UePHlWvXr00depU5c6d26nPGRER4dTHAwC4PsoVACBFyJMnj95//3316NFDR48elWVZeuONN1S3bl2VLVtWDRo0UIYMGZQ9e3a98sorunjxYsx9V61apaefflqZMmXSI488omeffVZ///13zO3Hjh2Tw+HQF198oerVqytt2rSaP3++HS8TAGAjh2VZlt0hAABILk2bNlVoaKiaNWum4cOHa8+ePSpRooTatWunNm3a6MaNG+rdu7du376t9evXS5K++uorORwOlSpVSlevXtWgQYN07Ngx7dy5U15eXjp27JgKFCig/Pnza+LEiSpbtqzSpk2rHDly2PxqAQDJiXIFAEhRzp8/rxIlSujy5cv66quvtHv3bm3ZskWrV6+OOebUqVPKkyePDhw4oCJFitzxGBcvXlS2bNm0a9culSxZMqZcTZ48WZ07d07OlwMAcCFcFggASFECAgL01ltvqXjx4mratKn+/PNPbdiwQRkyZIj5KlasmCTFXPp36NAhtWrVSgULFpSfn5/y588vSTpx4kScxy5fvnyyvhYAgGtJZXcAAACSW6pUqZQqlfkn8OrVq2rUqJHGjh17x3HRl/U1atRI+fLl06xZs5QzZ05FRUWpZMmSunXrVpzjfX19kz48AMBlUa4AAClauXLl9NVXXyl//vwxhevfLl26pAMHDmjWrFmqWrWqJOnHH39M7pgAADfAZYEAgBQtKChIly9fVqtWrfTbb7/p77//1urVq/Xaa68pMjJSmTNn1iOPPKKZM2fq8OHDWr9+vbp162Z3bACAC6JcAQBStJw5c+qnn35SZGSk6tatq8cff1xdunRRpkyZ5OXlJS8vLy1atEjbt29XyZIl1bVrV40fP97u2AAAF8RsgQAAAADgBJy5AgAAAAAnoFwBAAAAgBNQrgAAAADACShXAAAAAOAElCsAAAAAcALKFQAAAAA4AeUKAAAAAJyAcgUAAAAATkC5AgAAAAAnoFwBAAAAgBNQrgAAAADACf4PqQ4omJblBywAAAAASUVORK5CYII=", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAHWCAYAAACbsXOkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/OQEPoAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB0rElEQVR4nO3dd3gU5f7+8Xs3vYcAAUIJoXcIzYI0gSBSREUElOZRUZqIKILSpYoKiIDliEc6ioiVTkAUUEKv0nuAUBIgPTu/P/glX2MCJGGTSXm/rovrnJ19ZuaezZNxP5lnnrEYhmEIAAAAAHBfrGYHAAAAAID8gOIKAAAAAOyA4goAAAAA7IDiCgAAAADsgOIKAAAAAOyA4goAAAAA7IDiCgAAAADsgOIKAAAAAOyA4goAAAAA7IDiCgAKoNDQUFksFn377bdmR0EusXTpUvn5+enmzZum5mjWrJmaNWuWrds8efKkLBaLvvrqK7vuJz+5cuWKPDw89Msvv5gdBchTKK4ApDF69GhZLBZFRESk+36NGjXS/aIyderUVO0Mw1CfPn1ksVg0evToe+43Li5OH3/8sR555BEVKlRIzs7OCggIUIcOHbRo0SIlJSWl2WfyPycnJxUpUkQPP/ywhg8frtOnT6fZfnJB8c91ypUrpx49euj48eMZ+mwSEhI0Y8YMNWjQQF5eXvL09FSDBg00Y8YMJSQkZGgbOWnhwoWaNm2aafv/98/JwcFBZcqU0ZNPPqldu3Zlyz4nTJig77//PsPt/5nvn/8mTZp01/XatGmjQoUK6eLFi2nei4yMVIkSJfTAAw/IZrNl9hByXFJSkkaNGqUBAwbI09MzZXnZsmXv+Pk89thjJibOuw4dOqS33npLderUkZeXl0qUKKG2bdtq+/bt6bY/d+6cOnfuLF9fX3l7e+uJJ55Ic746c+aMxowZo4YNG6pQoUIqUqSImjVrprVr16a7zevXr+vll19W0aJF5eHhoebNm2vHjh2p2hQuXFgvvviiRowYYZ8DBwoIR7MDAMifDMNQ37599dlnn2nEiBH3LK4uX76sNm3aKCwsTK1bt9a7774rPz8/hYeHa+3aterWrZuOHj2a5j/0Xbt21eOPPy6bzaZr167pr7/+0rRp0zR9+nT997//VZcuXdLsa+DAgWrQoIESEhK0Y8cOffbZZ/r555+1d+9eBQQE3DHjrVu31LZtW23cuFHt2rVTr169ZLVatXLlSr322mv67rvv9PPPP8vDwyNLn1l2WLhwofbt26dBgwaZmiP555SUlKSDBw9q9uzZ+vXXX7V161bVqVPHrvuaMGGCOnXqpI4dO2Z4nVatWqlHjx6plgUHB991nVmzZqlGjRp6/fXXtXDhwlTvDR8+XBEREVq5cqWs1tz/d8wff/xRhw8f1ssvv5zmvTp16uiNN95Is/xuvyu5XWBgoGJiYuTk5JTj+/7iiy/03//+V08//bT69u2ryMhIffrpp3rwwQe1cuVKtWzZMqXtzZs31bx5c0VGRmr48OFycnLSRx99pKZNm2rXrl0qXLiwJGnFihWaPHmyOnbsqJ49eyoxMVFff/21WrVqpS+//FK9e/dO2abNZlPbtm21e/duvfnmmypSpIhmzZqlZs2aKSwsTBUrVkxp+8orr2jGjBlav369Hn300Zz7kIC8zACAfxk1apQhybh8+XK671evXt1o2rRpyusTJ04Ykoz3338/ZVm/fv0MScY777yToX22bt3asFqtxrJly9J9/6+//jLmz59/130mO3nypFGpUiXD2dnZ2LVrV8ryDRs2GJKMb775JlX7GTNmGJKMCRMm3DXjyy+/bEgyPv744zTvzZw505BkvPLKK3fdhr3ZbDYjOjr6ju+3bdvWCAwMTLP8Tp+Fvd3p5/TDDz8YkoyXX37Z7vv08PAwevbsmeH2kox+/fplaV+TJ082JBmrVq1KWfbnn38aVqvVeOutt7K0zcyKiYkxkpKS7msbHTp0MB555JE0ywMDA422bdve17Yzq2nTpqnOL7l1m1m1fft248aNG6mWRUREGEWLFjUaNWqUanly//rzzz9Tlh08eNBwcHAwhg0blrJs3759ac7XsbGxRpUqVYxSpUqlWr5kyZI0v/uXLl0yfH19ja5du6bJW6NGDaN79+6ZP1CggMr9f04DkOe89tpr+uSTTzRs2DC9995792y/ZcsWrVq1Si+//LKeeuqpdNvUr19fzz33XIb2HxgYqK+++krx8fGaMmXKPdsn/0X2xIkTd2xz9uxZ/fe//9Wjjz6q/v37p3m/X79+at68ub744gudPXtW0u3hk82bN0/T1mazqWTJkurUqVOqZdOmTVP16tXl6uqqYsWKqU+fPrp27VqqdcuWLat27dpp1apVql+/vtzc3PTpp5+mm7lZs2b6+eefderUqZShXGXLlk2TZfz48SpVqpRcXV3VokULHT16NM22tm3bpscee0w+Pj5yd3dX06ZN9fvvv9/x87qXf3/mK1asUNu2bRUQECAXFxeVL19e48aNSzUUVJKOHDmip59+WsWLF5erq6tKlSqlLl26KDIyUtLtIX63bt3S//73v5Rj7tWrV4YyxcTEKDY2NlPHMXjwYNWqVUt9+/ZVbGyskpKS9MorrygwMFCjRo2SdHsYWKdOneTn5ydXV1fVr19fP/zwQ6rtXL16VUOGDFHNmjXl6ekpb29vtWnTRrt3707VLnlo6+LFi/Xuu++qZMmScnd3V1RUlBISEjRmzBhVrFhRrq6uKly4sB555BGtWbPmrscQGxub5opJZly6dElFixZVs2bNZBhGyvKjR4/Kw8NDzz77bKr28+fPV8OGDeXu7q5ChQqpSZMmWr169R23/9VXX8lisejkyZOplid/FqGhoamWf/bZZypfvrzc3NzUsGFD/fbbb2m2md49V7169ZKnp6fOnTunjh07ytPTU0WLFtWQIUPS9MMrV66oe/fu8vb2lq+vr3r27Kndu3dn6D6uevXqpRp6Kd0egte4cWMdPHgw1fJvv/1WDRo0UIMGDVKWValSRS1atNDSpUtTllWvXl1FihRJta6Li4sef/xxnT17Vjdu3Ei1zWLFiqU61xYtWlSdO3fWihUrFBcXl2o7rVq10o8//pjqZwvgziiuANjV66+/rhkzZmjo0KGaMGFChtb58ccfJUnPP/+83XI89NBDKl++/D2/WErSsWPHJClliE16fv31VyUlJaUZOvZPPXr0UGJiolauXClJevbZZ7Vp0yaFh4enard582adP38+1ZDFPn366M0331SjRo00ffp09e7dWwsWLFDr1q3T3Mt1+PBhde3aVa1atdL06dPvOKzunXfeUZ06dVSkSBHNmzdP8+bNS3P/1aRJk7R8+XINGTJEw4YN09atW9MUsevXr1eTJk0UFRWlUaNGacKECbp+/boeffRR/fnnn3f8PO7m35/5V199JU9PTw0ePFjTp09XvXr1NHLkSL399tsp68THx6t169baunWrBgwYoE8++UQvv/yyjh8/ruvXr0uS5s2bJxcXFzVu3DjlmPv06XPPPF999ZU8PDzk5uamatWqpRnmdyeOjo767LPPdOLECY0bN04zZ87Ujh07NHv2bLm7u2v//v168MEHdfDgQb399tv64IMP5OHhoY4dO2r58uUp2zl+/Li+//57tWvXTh9++KHefPNN7d27V02bNtX58+fT7HfcuHH6+eefNWTIEE2YMEHOzs4aPXq0xowZo+bNm2vmzJl65513VKZMmTT30vxbWFiY4uPjVbdu3XTfT0hIUERERJp/MTExkiR/f3/Nnj1bGzdu1McffyzpdtHeq1cveXl5adasWSnbGjNmjLp37y4nJyeNHTtWY8aMUenSpbV+/foMfd738t///ld9+vRR8eLFNWXKFDVq1EgdOnTQmTNnMrR+UlKSWrdurcKFC2vq1Klq2rSpPvjgA3322WcpbWw2m9q3b69FixapZ8+eGj9+vC5cuKCePXveV/bw8PBUBZLNZtOePXtUv379NG0bNmyoY8eOpSqa7rRNd3d3ubu7pyzbuXOn6tatm2a4asOGDRUdHa2///471fJ69erp+vXr2r9/f1YOCyh4zL50BiD3yeqwwMDAQEOS8eabb2Zqf08++aQhybh+/Xqq5TExMcbly5dT/l27di3NPtMbFpjsiSeeMCQZkZGRhmH831C4L7/80rh8+bJx/vx54+effzbKli1rWCwW46+//rrjtgYNGmRIMnbu3HnHNjt27DAkGYMHDzYMwzAOHz6c7jDCvn37Gp6eninD+X777TdDkrFgwYJU7VauXJlmefJnvHLlyjvm+Kd7DQusWrWqERcXl7J8+vTphiRj7969hmHcHnZYsWJFo3Xr1obNZktpFx0dbQQFBRmtWrW66/6Tf05jxowxLl++bISHhxuhoaFGcHCwISllGGh6Qxv79OljuLu7G7GxsYZhGMbOnTszNJQxs8MCH374YWPatGnGihUrjNmzZxs1atQwJBmzZs3K8Db69+9vODk5GZ6enqmGVrVo0cKoWbNmyjEYxu3P9OGHHzYqVqyYsiw2NjbN0L4TJ04YLi4uxtixY1OWJf/cypUrl+Yzq127dpaG8H3xxRepfub/lNzf0vs3ceLEVG27du1quLu7G3///bfx/vvvG5KM77//PuX9I0eOGFar1XjyySfTHOs/+9a/h/DNnTvXkGScOHEi1TrJn8WGDRsMwzCM+Ph4w9/f36hTp06qPv3ZZ58ZktI9Z82dOzdlWc+ePQ1JqT5vwzCM4OBgo169eimv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/aVly5bJYrFo3rx5Gb7fJCM++ugjHTp0SH379tXKlStTrlCtWrVKK1asSJm2+d86d+6sIUOGaMiQIfLz80tzFaNp06bq06ePJk6cqF27dikkJEROTk46cuSIvvnmG02fPj3D96/9W7169bRkyRINHjxYDRo0kKen5z1/dv9ktVr1xRdfqE2bNqpevbp69+6tkiVL6ty5c9qwYYO8vb1TptLPqocffliFChVSz549NXDgwJSf3b+/cK9fv179+/fXM888o0qVKikxMVHz5s2Tg4ODnn766VTHvHbtWn344YcKCAhQUFCQHnjggXT3/cknn+j7779X+/btVaZMGV24cEFffvmlTp8+rXnz5qWaGCErPvnkEz3yyCOqWbOmXnrpJZUrV04XL17Uli1bdPbs2ZTnWLVr105jx45V79699fDDD2vv3r1asGBBhu8Vkm4PR23WrJnq1asnPz8/bd++Xd9++226z2X7J1dXV4WEhGjt2rUaO3ZsmvfPnTun+fPnp1nu6empjh07Srr9bLsrV65o7dq1cnBw0GOPPaYXX3xR7733np544gnVrl1bFSpU0DvvvKNx48apcePGeuqpp+Ti4qK//vpLAQEBmjhxYrr5qlevrgcffFDDhg3T1atX5efnp8WLF6cptJ2cnPTee++pT58+evTRR/Xss8/qxIkTmjt3bqY+x3vp2LGjGjZsqDfeeENHjx5VlSpV9MMPP6QMIb3XldNp06Zp1qxZeuihh+Tu7p7ms33yySfl4eEhSerbt68+//xztW3bVkOGDJGTk5M+/PBDFStWTG+88UbKOsuXL9dbb72lihUrqmrVqmm22apVq5R7ATt16qQHH3xQvXv31oEDB1SkSBHNmjVLSUlJGjNmTJq8a9asUfv27bnnCsgo0+YpBJDrzZ8/33jwwQcNDw8Pw8XFxahSpYoxZsyYVNNKG8bdp0X/7bffDDc3NyMoKMg4d+7cXfcXExNjTJs2zXjooYcMb29vw9HR0ShevLjRrl07Y8GCBUZiYmKafSb/c3R0NPz8/IwHHnjAGDZsmHHq1Kk020+euvleU3nfTVxcnPHRRx8Z9erVMzw8PAx3d3ejbt26xrRp04z4+Pg7rteoUSNDkvHiiy/esc1nn31m1KtXz3BzczO8vLyMmjVrGm+99ZZx/vz5lDaBgYGZmm775s2bRrdu3QxfX9+U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", "text/plain": [ - "
" + "
" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1cAAAHWCAYAAACbsXOkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/OQEPoAAAACXBIWXMAAA9hAAAPYQGoP6dpAACJIUlEQVR4nOzdd1gU58IF8LO79N6lF6mCvXexYSemR5MYTXLTNGr8TNGogCZqTFNjoukmUdOLHcWCvcQWBQRpiiAd6W3Zne8PZCMRdMGF2YXzex6em52Z3Tm7vBc5zMw7EkEQBBAREREREdF9kYodgIiIiIiIqC1guSIiIiIiItIAlisiIiIiIiINYLkiIiIiIiLSAJYrIiIiIiIiDWC5IiIiIiIi0gCWKyIiIiIiIg1guSIiIiIiItIAlisiIiIiIiINYLkiIiJqQHR0NCQSCX777TexoxARkY5guSIiaobw8HBIJBLk5eU1uL5z584IDg5WPb569SokEgk++OCDetsJgoAXX3wREokE4eHh99xvVVUVPvnkEwwePBjW1tYwMDCAs7MzQkND8eOPP0KhUNyxz7ovfX192NnZYeDAgVi4cCHS0tLueP26QnH7czp27Ihp06YhJSVFrc9GLpdj7dq16NOnD8zNzWFmZoY+ffpg7dq1kMvlar1Ga9qyZQtWr14t2v7/+32SyWRwd3fHgw8+iAsXLrTIPpcvX46//vpL7e1vz3f718qVK+/6vHHjxsHa2hrZ2dl3rCsqKoKTkxP69esHpVLZ1LdARKSV9MQOQETUXgmCgFdeeQVffPEFFi9efM9ylZubi3HjxuHs2bMYM2YMFi1aBBsbG2RlZWHfvn2YOnUqkpKSsHjx4nrPmzJlCsaPHw+lUombN2/i77//xurVq7FmzRp8/fXXeOKJJ+7Y1+zZs9GnTx/I5XKcO3cOX3zxBXbu3IlLly7B2dm50YxlZWWYMGECDh06hIkTJ2L69OmQSqWIjIzEnDlz8Mcff2Dnzp0wNTVt1mfWErZs2YKYmBjMnTtX1Bx13yeFQoHLly9j/fr12L17N06ePInu3btrdF/Lly/HI488gsmTJ6v9nNGjR2PatGn1lvXo0eOuz/nss8/QuXNnvPbaa9iyZUu9dQsXLkReXh4iIyMhlfJvvUTURghERNRkYWFhAgAhNze3wfVBQUHCsGHDVI9TU1MFAML777+vWjZz5kwBgPD222+rtc8xY8YIUqlU+P333xtc//fffwubNm266z7rXL16VfDz8xMMDAyECxcuqJYfPHhQACD8+uuv9bZfu3atAEBYvnz5XTO+8MILAgDhk08+uWPdunXrBADCSy+9dNfX0DSlUimUl5c3un7ChAmCh4fHHcsb+yw0rbHv07Zt2wQAwgsvvKDxfZqamgrPPPOM2tsDEGbOnNmsfb333nsCAGHPnj2qZadPnxakUqnwxhtvNOs1m6qiokJQKBStsi8iat/4pyIiIhHMmTMHn376KRYsWIB33nnnntufOHECe/bswQsvvICHHnqowW169+6NJ598Uq39e3h4YOPGjaiursaqVavuuf2IESMAAKmpqY1uk56ejq+//hojRozArFmz7lg/c+ZMDB8+HF999RXS09MB1J4+OXz48Du2VSqVcHFxwSOPPFJv2erVqxEUFAQjIyN06NABL774Im7evFnvuZ6enpg4cSL27NmD3r17w9jYGJ9//nmDmYODg7Fz505cu3ZNdaqbp6fnHVneffdduLq6wsjICCNHjkRSUtIdr3Xq1CmMHTsWlpaWMDExwbBhw3Ds2LFGP697+e9nvnXrVkyYMAHOzs4wNDSEt7c3li1bVu9UUABITEzEww8/DEdHRxgZGcHV1RVPPPEEioqKANSe4ldWVobvvvtO9Z6nT5+uVqaKigpUVlY26X3MmzcPXbt2xSuvvILKykooFAq89NJL8PDwQFhYGAAgPj4ejzzyCGxsbGBkZITevXtj27Zt9V6noKAA8+fPR5cuXWBmZgYLCwuMGzcO//zzT73t6k5t/emnn7Bo0SK4uLjAxMQExcXFkMvliIiIgK+vL4yMjGBra4vBgwcjKiqqSe+JiKgxPC2QiKiVvfbaa1i7di3efPNNLF++XK3nbN++HQDw1FNPaSzHgAED4O3trdYvlsnJyQAAW1vbRrfZvXs3FArFHaeO3W7atGk4ePAgIiMj8fzzz+Pxxx9HeHg4srKy4OjoqNru6NGjuHHjRr1TFl988UVs3LgRM2bMwOzZs5Gamop169bh/PnzOHbsGPT19VXbJiQkYMqUKXjxxRfxv//9D/7+/g3mefvtt1FUVIT09HR8/PHHAAAzM7N626xcuRJSqRTz589HUVERVq1ahSeffBKnTp1SbXPgwAGMGzcOvXr1QlhYGKRSKb799luMGDECR44cQd++fRv9TBrz389848aNMDMzw7x582BmZoYDBw5gyZIlKC4uxvvvvw8AqK6uxpgxY1BVVYVXX30Vjo6OyMjIwI4dO1BYWAhLS0v88MMPeP7559G3b1+88MILAABvb+975tm4cSM+++wzCIKATp06YdGiRZg6deo9n6enp4cvvvgCAwcOxLJly+Dg4IBz584hMjISJiYmiI2NxaBBg+Di4oK33noLpqam+OWXXzB58mT8/vvvePDBBwEAKSkp+Ouvv/Doo4/Cy8sL2dnZ+PzzzzFs2DDExcXdcbrqsmXLYGBggPnz56OqqgoGBgYIDw/HihUrVO+/uLgYZ86cwblz5zB69Gj1vzlERI0R+9AZEZEuau5pgR4eHgIA4fXXX2/S/h588EEBgFBYWFhveUVFhZCbm6v6unnz5h37bOi0wDoPPPCAAEAoKioSBOHfU+G++eYbITc3V7hx44awc+dOwdPTU5BIJMLff//d6GvNnTtXACCcP3++0W3OnTsnABDmzZsnCIIgJCQkNHga4SuvvCKYmZmpTuc7cuSIAEDYvHlzve0iIyPvWF73GUdGRjaa43b3Oi2wU6dOQlVVlWr5mjVrBADCpUuXBEGoPe3Q19dXGDNmjKBUKlXblZeXC15eXsLo0aPvuv+671NERISQm5srZGVlCdHR0UKPHj0EAKrTQBs6tfHFF18UTExMhMrKSkEQBOH8+fNqncrY1NMCBw4cKKxevVrYunWrsH79eqFz584CAOGzzz5T+zVmzZol6OvrC2ZmZsKUKVNUy0eOHCl06dJF9R4EofYzHThwoODr66taVllZecepfampqYKhoaGwdOlS1bK671vHjh3v+My6desmTJgwQe3MRERNxdMCiYhaUd2saX5+fk16XnFxMYA7j6ps2LAB9vb2qq/Bgwc36XXrXq+kpKTe8meffRb29vZwdnbGhAkTVKeR9e7du9HXqnsNc3PzRrepW1f3fvz8/NC9e3f8/PPPqm0UCgV+++03TJo0CcbGxgCAX3/9FZaWlhg9ejTy8vJUX7169YKZmRkOHjxYbz9eXl4YM2aMuh/DXc2YMQMGBgaqx0OGDAEA1eyJFy5cQGJiIqZOnYr8/HxVtrKyMowcORKHDx9Waza8sLAw2Nvbw9HREcHBwUhOTsZ7772nOg207rMAaj/rvLw8DBkyBOXl5YiPjwcAWFpaAgD27NmD8vJyjbx/ADh27BjmzJmD0NBQvPTSSzh79iw6d+6MhQsXoqKiQq3XePfdd2FrawupVKo6SlhQUIADBw7gscceU72nvLw85OfnY8yYMUhMTERGRgYAwNDQUDXxhUKhQH5+PszMzODv749z587dsb9nnnmm3mcGAFZWVoiNjUViYuL9fBxERI3iaYFERC1EIpHcsezNN9/Erl278OKLL8LKyqreNUV3U1dKSktLVb9AA8DDDz+Mzp07AwD+7//+747rb+6ltLS03uvXWbJkCYYMGQKZTAY7Ozt06tQJenp3/yej7jX+W9Ru11ABe/zxx7Fw4UJkZGTAxcUF0dHRyMnJweOPP67aJjExEUVFRXBwcGjwdXNycuo99vLyumvWpnB3d6/32NraGgBU13rV/aL+zDPPNPoaRUVFquc15oUXXsCjjz4KqVQKKysrBAUFwdDQULU+NjYWixYtwoEDB1Tl9PbXB2rf97x58/DRRx9h8+bNGDJkCEJDQ/HUU0/VGzf3y8DAALNmzVIVLXVKvYWFBfz9/ZGXl4cOHToAAJKSkiAIAhYvXnzHLJd1cnJy4OLiAqVSiTVr1uCzzz5DampqvbHe0OmqDY2BpUuX4oEHHoCfnx86d+6MsWPH4umnn0bXrl3VfetERHfFckVE1AxGRkYA0Ohf7cvLy1Xb3M7MzAy7d+/G0KFD8eSTT8LCwgIhISH33F9AQAAAICYmBoMGDVItd3Nzg5ubG4DaX/obu+9WY2JiYuDg4AALC4t6y7t06YJRo0Y16bU6deoEALh48WKjU4dfvHgRABAYGKha9vjjj2PBggX49ddfMXfuXPzyyy+wtLTE2LFjVdsolUo4ODhg8+bNDb6uvb19vcf/PWJxP2QyWYPLBUFQZQOA999/v9H3/d8jjg3x9fVt9DMvLCzEsGHDYGFhgaVLl8Lb2xtGRkY4d+4c3nzzzXpHxj788ENMnz4dW7duxd69ezF79mysWLECJ0+ehKur6z1zqKtu3BUUFDT7Nepyz58/v9EjjT4+PgBqp49fvHgxnn32WSxbtgw2NjaQSqWYO3dug0cGGxoDQ4cORXJysuqz+eqrr/Dxxx9jw4YNeP7555v9PoiI6rBcERE1g4eHB4DaiRPqfsmsU15ejuvXrzdammxtbbF3714MGjQIDz30EKKiojBgwIC77m/ixIlYuXIlNm/eXK9c3Y8TJ04gOTlZY5NkjBs3DjKZDD/88EOjk1p8//330NPTq1ecvLy80LdvX/z888+YNWsW/vjjD0yePLneURtvb2/s27cPgwYN0mhxAho+wtgUdZNBWFhYNLmQqis6Ohr5+fn4448/MHToUNXyxmZv7NKlC7p06YJFixbh+PHjGDRoEDZs2KCamfJ+3zPw72mR/y22TdGxY0cAgL6+/j0/u99++w3Dhw/H119/XW95YWEh7Ozs1N6njY0NZsyYgRkzZqC0tBRDhw5FeHg4yxURaQSvuSIiaoaRI0fCwMAA69evv+Ov5l988QVqamowbty4Rp/v4uKCqKgomJqaYsKECbh06dJd9zdo0CCMHj0aX3zxBbZu3drgNnVHUtRx7do1TJ8+HQYGBnj99dfVft7duLm5YcaMGdi3bx/Wr19/x/oNGzbgwIEDeO655+44gvL444/j5MmT+Oabb5CXl1fvlEAAeOyxx6BQKLBs2bI7XrempgaFhYXNzm1qaqo6ra45evXqBW9vb3zwwQeq0yxvl5ub2+zXrlN39Oz273F1dTU+++yzetsVFxejpqam3rIuXbpAKpWiqqpKtczU1FTtz6yh/CUlJVi9ejXs7OzQq1cvdd/GHRwcHBAcHIzPP/8cmZmZd923TCa7Y4z/+uuvqmuy1JGfn1/vsZmZGXx8fOp9NkRE94NHroiImsHBwQFLlizBokWLMHToUISGhsLExATHjx/Hjz/+iJCQEEyaNOmur+Hr64s9e/YgODgYY8aMwdGjR1V/yW/Ipk2bMHbsWEyePBnjxo3DqFGjYG1tjaysLOzbtw+HDx9usNCdO3cOmzZtglKpRGFhIf7++2/8/vvvkEgk+OGHHzR6vcnHH3+M+Ph4vPLKK4iMjFQdodqzZw+2bt2KYcOG4cMPP7zjeY899hjmz5+P+fPnw8bG5o6jGMOGDcOLL76IFStW4MKFCwgJCYG+vj4SExPx66+/Ys2aNWpfv/ZfvXr1ws8//4x58+ahT58+MDMzu+f37nZSqRRfffUVxo0bh6CgIMyYMQMuLi7IyMjAwYMHYWFhoZpKv7kGDhwIa2trPPPMM5g9e7bqe/ffsnHgwAHMmjULjz76KPz8/FBTU4MffvgBMpkMDz/8cL33vG/fPnz00UdwdnaGl5cX+vXr1+C+P/30U/z111+YNGkS3N3dkZmZiW+++QZpaWn44Ycf6k320RyffvopBg8ejC5duuB///sfOnbsiOzsbJw4cQLp6emq+1hNnDgRS5cuxYwZMzBw4EBcunQJmzdvvuv/Z/4rMDAQwcHB6NWrF2xsbHDmzBn89ttvDd6XjYioWcScqpCISNdt2rRJ6N+/v2BqaioYGhoKAQEBQkRERL1ppQXh7tOiHzlyRDA2Nha8vLyEjIyMu+6voqJCWL16tTBgwADBwsJC0NPTExwdHYWJEycKmzdvFmpqau7YZ92Xnp6eYGNjI/Tr109YsGCBcO3atTtev24a63tN5X03VVVVwscffyz06tVLMDU1FUxMTISePXsKq1evFqqrqxt93qBBgwQAwvPPP9/oNl988YXQq1cvwdjYWDA3Nxe6dOkivPHGG8KNGzdU23h4eDRpuu3S0lJh6tSpgpWVlWq6fEFo/LOo+1y//fbbesvPnz8vPPTQQ4Ktra1gaGgoeHh4CI899piwf//+u+5fnSnzBUEQjh07JvTv318wNjYWnJ2dhTfeeEPYs2ePAEA4ePCgIAiCkJKSIjz77LOCt7e3YGRkJNjY2AjDhw8X9u3bV++14uPjhaFDhwrGxsYCgLtOy753715h9OjRgqOjo6Cvry9YWVkJISEh93xfDRk2bJgQFBR0x/Lk5GRh2rRpqn24uLgIEydOFH777TfVNpWVlcL//d//CU5OToKxsbEwaNAg4cSJE8KwYcPq3fbgbmP4nXfeEfr27StYWVkJxsbGQkBAgPDuu+/edVwSETWFRBCacB4JERERERERNYjXXBEREREREWkAyxUREREREZEGsFwRERERERFpAMsVERERERGRBrBcERERERERaQDLFRERERERkQbwJsINUCqVuHHjBszNzSGRSMSOQ0REREREIhEEASUlJXB2doZUevdjUyxXDbhx4wbc3NzEjkFERERERFri+vXrcHV1ves2LFcNMDc3B1D7AVpYWIiaRS6XY+/evQgJCYG+vr6oWYiaiuOXdB3HMOkyjl/SZdo0fouLi+Hm5qbqCHfDctWAulMBLSwstKJcmZiYwMLCQvSBRdRUHL+k6ziGSZdx/JIu08bxq87lQpzQgoiIiIiISANYroiIiIiIiDSA5YqIiIiIiEgDWK6IiIiIiIg0gOWKiIiIiIhIA1iuiIiIiIiINIDlioiIiIiISANYroiIiIiIiDSA5YqIiIiIiEgDWK6IiIiIiEhrKJQCTqUW4GyeBKdSC6BQCmJHUpue2AGIiIiIiIgAIDImExHb45BZVAlAhu8Tz8DJ0ghhkwIxtrOT2PHuiUeuiIiIiIhIdJExmXh507lbxepfWUWVeHnTOUTGZIqUTH0sV0REREREJCqFUkDE9jg0dAJg3bKI7XFaf4ogyxUREREREYnqdGrBHUesbicAyCyqxOnUgtYL1Qy85oqIiIiIiESRX1qFw4m52Hzymlrb55Q0XsC0AcsVERERERG1CoVSwIXrhTiUkINDV3JxMaMIQhPO9HMwN2q5cBrAckVERERERC0mt6QKh67kIjohB0cS81BUIa+3PtDJAkP87PDrmXTcLKtu8LorCQBHSyP09bJplczNxXJFREREREQaU6NQ4vz1QkTfOjoVk1Fcb72FkR6G+NljmJ89gv3s4WBRezSqh5sVXt50DhKgXsGS3PrfsEmBkEkl0GYsV0REREREdF+yiipx+Eouoq/UHp0qqaypt76LiyWC/WsLVXc3K+jJ7pxXb2xnJ6x/qudt97mq5ahD97liuSIiIiIioiaRK5Q4c/Wm6nS/+KySeuutTPQx1Ncewf72GOJrD3tzQ7Ved2xnJ4wOdMSJpBzsPXIKIUP6YYCPg9YfsarDckVERERERPd0o7AC0Qm5OHQlB8eS8lFa9e/RKYkE6OpqhWC/2kLV1dWq2YVIJpWgn5cN8i8L6OdlozPFCmC5IiIiIiKiBlTVKHDm6k3VtVNXskvrrbc1NcBQv3+PTtmYGoiUVHuwXBEREREREQDgekE5oq/k4lBCDo4n56O8WqFaJ5UAPdytayei8LdHZ2dLSHXoqFJrYLkiIiIiImqnKuUKnE4tQHRC7WQUKbll9dbbmxti2K2Z/Yb42sHKhEen7oblioiIiIioHbmaV6aaiOJESj4q5UrVOplUgl7u1hh2a2a/QCcLHp1qApYrIiIiIqI2rKJagZMp+apCdTW/vN56Rwsj1al+A33sYGmsL1JS3cdyRURERETUhgiCgJS8slsz++XiVEo+qmr+PTqlJ5Wgt6c1gv0dEOxvD/8O5pBIeHRKE1iuiIiIiIh0XHl1DY4n5SP6Su3MftcLKuqtd7Y0wrBbZWqQjx3MDFkDWgI/VSIiIiIiHSMIApJySlUTUfydehPVin+PThnIpOjrZaM63c/HwYxHp1oByxURERERkQ4oqZTjeHI+ohNycfhKLjIK6x+dcrMxRrCfA4b52WOAty1MeXSq1fETJyIiIiLSQoIgID6rRDURxZmrN1GjFFTrDfSk6N/RFsF+9hjmb4+OdqY8OiUylisiIiIiIi1RVCHHsaQ8HLo1GUVWcWW99Z62Jgj2d8Awf3v097KFsYFMpKTUEJYrIiIiIiKRCIKA2BvFOHQlF4cScnE27SYUtx2dMtKXYkBH29pC5WcPTztTEdPSvbBcERERERG1osLyahxJzKu9dioxF7klVfXWd7Q3RbBf7cx+fb1sYKTPo1O6guWKiIiIiKgFKZUCYm4U1c7sl5CDC9cLcdvBKZgYyDDQ2w7D/O0R7GcPNxsT8cLSfRG1XK1YsQJ//PEH4uPjYWxsjIEDB+K9996Dv79/o8/58ssv8f333yMmJgYA0KtXLyxfvhx9+/ZVbTN9+nR899139Z43ZswYREZGtswbISIiIiK6TUFZNY4k5qpm9ssvq6633q+DmepUv96e1jDU49GptkDUcnXo0CHMnDkTffr0QU1NDRYuXIiQkBDExcXB1LTh80mjo6MxZcoUDBw4EEZGRnjvvfcQEhKC2NhYuLi4qLYbO3Ysvv32W9VjQ0PDFn8/RERERNQ+KZQC/kkvxKGEXERfycXF9EIItx2dMjPUwyCf2munhvrZw8XKWLyw1GJELVf/PZK0ceNGODg44OzZsxg6dGiDz9m8eXO9x1999RV+//137N+/H9OmTVMtNzQ0hKOjo+ZDExEREREByC2pwuErtbP6HUnMxc1yeb31AY7mCPavvXaqp7s1DPSkIiWl1qJV11wVFRUBAGxsbNR+Tnl5OeRy+R3PiY6OhoODA6ytrTFixAi88847sLW1bfA1qqqqUFX174WExcXFAAC5XA65XN7gc1pL3f7FzkHUHBy/pOs4hkmXcfxqXo1CiX/Si3AoMQ9HEvMRc6O43npzIz0M8rbFUF87DPW1RQcLo39XCgrI5YpWTqy7tGn8NiWDRBBuP2ApHqVSidDQUBQWFuLo0aNqP++VV17Bnj17EBsbCyOj2gH8008/wcTEBF5eXkhOTsbChQthZmaGEydOQCa783zW8PBwRERE3LF8y5YtMDHhBYVERERE7VVRNXC5UILLhRIkFEpQoah/k15XUwGdrAR0slLC0xyQ8R6+bU55eTmmTp2KoqIiWFhY3HVbrSlXL7/8Mnbv3o2jR4/C1dVVreesXLkSq1atQnR0NLp27drodikpKfD29sa+ffswcuTIO9Y3dOTKzc0NeXl59/wAW5pcLkdUVBRGjx4NfX19UbMQNRXHL+k6jmHSZRy/zSNXKHH+eiEOX8nHocQ8xGeV1FtvaayHwd52GOpniyE+drA353X9LUGbxm9xcTHs7OzUKldacVrgrFmzsGPHDhw+fFjtYvXBBx9g5cqV2Ldv312LFQB07NgRdnZ2SEpKarBcGRoaNjjhhb6+vujfzDralIWoqTh+SddxDJMu4/i9t8yiitqJKBJycSwpDyVVNap1EgnQ1cUSw/zsMczfAd3drCCT8vBUa9GG8duU/YtargRBwKuvvoo///wT0dHR8PLyUut5q1atwrvvvos9e/agd+/e99w+PT0d+fn5cHJyut/IRERERKTjqmuUOHO1AIeu1BaqhOz6R6dsTA0w1Lf2vlNDfe1ha8ajU6QeUcvVzJkzsWXLFmzduhXm5ubIysoCAFhaWsLYuHZ6ymnTpsHFxQUrVqwAALz33ntYsmQJtmzZAk9PT9VzzMzMYGZmhtLSUkRERODhhx+Go6MjkpOT8cYbb8DHxwdjxowR540SERERkajSb5YjOqF2Zr/jSXkoq/53cgmJBOjuZoVgv9qZ/Tq7WPLoFDWLqOVq/fr1AIDg4OB6y7/99ltMnz4dAJCWlgapVFrvOdXV1XjkkUfqPScsLAzh4eGQyWS4ePEivvvuOxQWFsLZ2RkhISFYtmwZ73VFRERE1E5U1ShwOrVAVaiSckrrrbczM8BQP3sE+ztgiI8drE0NREpKbYnopwXeS3R0dL3HV69evev2xsbG2LNnz32kIiIiIiJdlJZfjugrOYhOyMWJ5HxU3Db1uUwqQU93Kwy7VagCnSwg5dEp0jCtmNCCiIiIiKipKuUKnEzJVx2dSs0rq7fewdwQwf72GObngME+drA04cQe1LJYroiIiIhIJwiCgNS8MtVEFCdT8lFVo1St15NK0MvDGsH+DhjmZ49OTuaQSHh0iloPyxURERERaa3y6hqcSM5XFaq0gvJ6650sjW4dnbLHQB87WBjx6BSJh+WKiIiIiLSGIAhIzi1Vnep3KqUA1Yp/j07pyyTo42mjOt3Pr4MZj06R1mC5IiIiIiJRlVbV4HhSHqKv5OJQQi4yCivqrXexMkawf+1EFAO8bWFmyF9hSTtxZBIRERFRqxIEAVeySxGdUDuz35lrBZAr/p1F2kBPin5eNqqZ/bztTXl0inQCyxURERERtbjiSnnt0albp/tlFlXWW+9ha4JgP3sM87dH/462MDHgr6mkezhqiYiIiEjjBEFAXGaxaiKKc9duokb579EpQz0pBnjb3ipUDvCyMxUxLZFmsFwRERERkUYUlctxJKn2uqlDV3KRU1JVb31HO1MMu3XtVD8vGxjpy0RKStQyWK6IiIiIqFmUSgGxN4oRnZCDQ1dycS7tJm47OAVjfRkGetuqZvZztzURLyxRK2C5IiIiIiK13SyrxuHE2qNThxNzkVdaXW+9j4MZgm9NRNHb05pHp6hdYbkiIiIiokYplAIuZRSpZvb7J70Qwm1Hp0wNZBjoY6e6ka+rNY9OUfvFckVERERE9eSVVuFIYu1EFIev5OJmubze+gBHcwy7VaZ6e9jAQE8qUlIi7cJyRUQtQqEUcCq1AGfzJLBNLcAAHwfIpLxHCRFRa2jqz2CFUsCF6zdV06Rfyiiqd3TK3FAPg31rj04N9bOHk6VxK7wLIt3DckVEGhcZk4mI7XG37mEiw/eJZ+BkaYSwSYEY29lJ7HhERG2auj+Dc0oqcSghF9FXcnE0MQ9FFfWPTgU6WahO9evpYQ19GY9OEd0LyxURaVRkTCZe3nQOwn+WZxVV4uVN57D+qZ4sWERELeReP4PnjfZDhVyBQ1dyEXujuN42FkZ6GOJnX3vfKT97OFgYtV5wojaC5YqINEahFBCxPe6Of9QBQAAgARCxPQ6jAx15iiARkYbd62cwAHwYdaXe8i4ulgj2t0ewvz26uVpBj0eniO4LyxURaczp1IJbp6E0TACQWVSJ06kFGOBt23rBiIjagXv9DK4z0NsWj/RyxVA/e9iZGbZCMqL2g+WKiDQmp+Te/6g3ZTsiIlKfuj9bH+/jhge6u7RwGqL2icd+iUgjCsurcTA+R61tHcx5Hj8Rkaap+7OVP4OJWg6PXBHRfblZVo2vjqbgu+PXUFpVc9dtJQAcLY3Q18umdcIREbUjfb1s4GhhiKziqgbX82cwUctjuSKiZikoq8aXR1Lw/fGrKKtWAKidtneIrx2+OJwCAHdcVC0ACJsUyMksiIhagEwqQb+Otth64cYd6+p+6vJnMFHLYrkioibJL63CF0dS8MOJayi/VaqCnC0wZ6QvRgd2gEQiQQ93q9vusfIvPakEAY4WYsQmImrzMosqsDc2GwBgaaxf775VjrzXIFGrYLkiIrXklVbhy8Mp+P7ENVTIa0tVZxcLzB3ph5GdHCCR/PuX0LGdnTA60BEnknKw98gpjB7cF18cvYYjiXl4Z2ccvnqmj1hvg4iozVq5Ox4VcgV6eVjj5xf642RyLvYeOYWQIf0wwMeBR6yIWgHLFRHdVW5JFb44nIxNJ9NUpaqrqyXmjPTFiID6pep2MqkE/bxskH9ZQP+OtnCyNsPY1Yex73IODibkYLi/Q2u+DSKiNu3vqwXYeuEGJBIgIjQIejKp6mdwPy8bFiuiVsJyRUQNyimpxOeHUrD51DVUypUAgG5uVpg70hfB/vaNlqrG+DiYYcYgT3x5JBXLtsdhkLcdDPQ4YSkR0f1SKAWEbY0FADzRxx2dXSxFTkTUfrFcEVE9OcWV2HCrVFXV1Jaq7m5WmDvKF8P8ml6qbjd7pC/+PH8DKXll+PZYKl4c5q2p2ERE7daPp9MQl1kMCyM9zA/xEzsOUbvGckVEAIDs4kqsj07Gj6fTVKWqp7sV5ozyw1Bfu/sqVXXMjfTx1rgAzP/1H6zdn4gHe7jAwYL3WyEiaq7C8mp8sDcBADBvtB9szQxFTkTUvrFcEbVzWUWVWB+dhB//vo7qW6Wqt4c15ozyxWAfzZSq2z3UwwWbTl7DheuFWLk7Hh893l2jr09E1J58uPcKCsvl8O9gjqf6e4gdh6jdY7kiaqcyiyqwPjoZP52+jmpFbanq42mNuaP8MNDbVuOlqo5UKkFEaBAmf3YMf5zPwJP93dHLgze0JCJqqrgbxdh86hoAICw0EHoyXsdKJDaWK6J25kZhBT6LTsIvf6erSlVfLxvMHemLAS1Yqm7Xzc0Kj/Vyw89nriN8Wxz+mjmIM1kRETWBIAgI3x4LpQBM6OKEgd52YkciIrBcEbUbGYUV+OxgEn45cx1yhQAA6Odlg7mj/DDA27bV87w+1h+7LmXiUkYRfjlzHVP6urd6BiIiXbX9YiZOpxbASF+KhRM6iR2HiG5huSJq464XlOOz6GT8dvbfUjWgoy3mjPJF/46tX6rq2JkZYu5oPyzbEYf39yRgfGcnWJroi5aHiEhXlFfXYPnOywCAV4J94GJlLHIiIqrDckXURl0vKMenB5Pw29l01ChrS9UgH1vMGemHvl7acY3TtAEe+Ol0GhJzSvHxvisIDw0SOxIRkdb77GAysoor4WptjBeGdhQ7DhHdhuWKqI1Jy68tVb+f+7dUDfaxw5xRvujjqR2lqo6+TIqwSUF46utT+OHkNUzp6w5/R3OxYxERaa1r+WX44nAKAGDxxEAY6ctETkREt2O5ImojruWXYd2BJPxxPgOKW6VqiK8d5o7y1erZ+Ab72mFskCMiY7MQvi0WW/7Xr1Um1SAi0kXLdlxGtUKJIb52CAnsIHYcIvoPlisiHXc1rwyfHEjCXxf+LVXD/Owxe6QvenlYi5xOPW9P6ISDCTk4kZKP3TFZGN/FSexIRERaJzohB/suZ0NPKkHYpED+IYpIC7FcEemo1LwyfHIgEX+dz8CtToVgf3vMGemLHu66UarquNmY4KVh3lizPxHv7ryM4f4OMDbgqS5ERHWqa5RYuiMOADB9oCd8HHgKNZE2Yrki0jHJuaVYdyAJWy/8W6pGBDhg9khfdHezEjXb/XhpmDd+O5uOjMIKrD+UjHmj/cSORESkNTYeT0VKbhnszAwwe5Sv2HGIqBEsV0Q6IimnFJ8cSMT2f26oStWoTrWlqqurlajZNMHYQIa3J3TCK5vPYcOhZDzayxVuNiZixyIiEl1OcSXW7EsEALwxNgAWRrxtBZG2Yrki0nJJOSVYuz8J2y/egKAqVR0wZ6QvurhaihtOw8Z1dsRAb1scT87HuzsvY8PTvcSOREQkuvciE1BWrUA3Nys80tNV7DhEdBcsV0Ra6kp2CdbuT8TOS5mqUhUS2AGzR/qis0vbKlV1JBIJwiYFYfzaI4iMzcLRxDwM9rUTOxYRkWjOXruJ38+lAwAiQoMglXISCyJtxnJFpGUSsmpL1a6Yf0vV2CBHvDrSB0HObbNU3c7f0RxP9/fAxuNXEb49FrvnDIG+TCp2LCKiVqdUCgjfFgsAeLSXq05fV0vUXrBcEWmJ+Kzi2lJ1KUu1bFxnR8we6YtOThYiJmt9r432w7Z/biAppxTfn7iG5wZ7iR2JiKjV/Xr2Oi5lFMHcUA9vjA0QOw4RqYHlikhkcTdqS1VkbG2pkkiA8Z2d8OpIHwQ4tq9SVcfSWB9vjPHHW39cwuqoK3iguzPszAzFjkVE1GqKKuRYFZkAAJgzyhf25vwZSKQLWK6IRBKTUYS1+xOxNy4bQG2pmtDFCbNH+sKvA+9f8mhvN2w+lYZLGUVYFRmPVY90EzsSEVGrWb3vCvLLquHjYIZnBnqKHYeI1MRyRdTKYjKKsGZ/IqJuK1UTuzpj9ggf+LJUqcikEoSHBuHh9cfxy5l0TO3nwesNiKhduJJdgu9PXAMAhE0K5HWnRDqE5YqolVxKL8Ka/Vew73IOAEAqASZ1c8arI3zg48BS1ZBeHtZ4qKcL/jiXgfBtsfjj5YGcKYuI2jRBEBCxPRYKpYAxQR0wxNde7EhE1AQsV0Qt7J/rhVizPxEH4v8tVQ90d8GsET7wtjcTOZ32e2tsAPbEZOHC9UL8fi4dj/Z2EzsSEVGLiYzJwrGkfBjoSbFoQqDYcYioiViuiFrIheuFWLPvCg4m5AKoLVWTb5WqjixVanOwMMLskb5YsTse70UmYExnR1gY6Ysdi4hI4yqqFXhn52UAwEtDO8LNxkTkRETUVCxXRBp2Lu0m1uxLxKErtaVKJpWoSpWXnanI6XTTjEFe+Pnv60jJK8Mn+xPxNv+aS0Rt0OeHk5FRWAFnSyO8HOwjdhwiagaWKyINOXutAKv3JeJIYh6A2lL1UA8XzBzuA0+WqvtioCfFkkmBmP7t3/j22FU83seN16kRUZtyvaAc66OTAQBvTwiEsYFM5ERE1ByiTj+zYsUK9OnTB+bm5nBwcMDkyZORkJBw1+d8+eWXGDJkCKytrWFtbY1Ro0bh9OnT9bYRBAFLliyBk5MTjI2NMWrUKCQmJrbkW6F27MzVAjz99Sk8vP4EjiTmQU8qwWO9XXHg/4bh/Ue7sVhpSLC/A0Z1ckCNUkDE9jgIgiB2JCIijVm+6zKqapTo39EG47s4ih2HiJpJ1HJ16NAhzJw5EydPnkRUVBTkcjlCQkJQVlbW6HOio6MxZcoUHDx4ECdOnICbmxtCQkKQkZGh2mbVqlVYu3YtNmzYgFOnTsHU1BRjxoxBZWVla7wtaidOpxbgya9O4pEN/5aqJ/q44eD8YKx6pBs8bFmqNG3xxEAYyKQ4kpinmsqeiEjXHUvKw+6YLNUtKCQSzopKpKtEPS0wMjKy3uONGzfCwcEBZ8+exdChQxt8zubNm+s9/uqrr/D7779j//79mDZtGgRBwOrVq7Fo0SI88MADAIDvv/8eHTp0wF9//YUnnniiZd4MtRsnU/KxZl8iTqTkAwD0pBI82tsNrwR78+LjFuZha4r/DfXCpweTsWxnHIb62cNIn6fOEJHukiuUCN8WCwB4ur8HAhwtRE5ERPdDq665KioqAgDY2Nio/Zzy8nLI5XLVc1JTU5GVlYVRo0aptrG0tES/fv1w4sSJBstVVVUVqqqqVI+Li4sBAHK5HHK5vFnvRVPq9i92DgJOpRbgk4PJOJV6EwCgL5Pg4Z4ueGmoF1ysjAHw+/RfLTF+/zfIA7+dTcf1ggpsiE7CzOCOGnttov/iz2BqaRtPXENiTimsTfQxK9hLo2ON45d0mTaN36ZkkAhacuGCUqlEaGgoCgsLcfToUbWf98orr2DPnj2IjY2FkZERjh8/jkGDBuHGjRtwcnJSbffYY49BIpHg559/vuM1wsPDERERccfyLVu2wMSERyLaM0EAkool2H1diuSS2tM0ZBIB/R0EjHJRwsZQ5IDt1Lk8Cb5LlEFfKuDt7gpY8/tARDqoRA68e16GCoUEj3dUYGAHrfiVjIj+o7y8HFOnTkVRUREsLO5+dFlrjlzNnDkTMTExTSpWK1euxE8//YTo6GgYGRk1e98LFizAvHnzVI+Li4tV13Ld6wNsaXK5HFFRURg9ejT09Xlvn9YiCAJOpNQeqTpzrRBA7ZGqx3q54sWhXnCybP54a09aavyOEwTEfv03zlwrxN/Vrlj9YFeNvTbR7fgzmFrS23/FokKRgSBnc0Q80x8yqWavteL4JV2mTeO37qw2dWhFuZo1axZ27NiBw4cPw9XVVa3nfPDBB1i5ciX27duHrl3//cXK0bF2hp3s7Ox6R66ys7PRvXv3Bl/L0NAQhoZ3/ulbX19f9G9mHW3K0pYJgoCjSXlYsy8RZ67Vnv5noCfFlD5ueCnYG06WxiIn1E0tMX6XPtAFEz85gp0xWXhqgCcGeNtq9PWJbsefwaRpF9ML8eu52sm4IkI7w8jQoMX2xfFLukwbxm9T9i/qbIGCIGDWrFn4888/ceDAAXh5ean1vFWrVmHZsmWIjIxE7969663z8vKCo6Mj9u/fr1pWXFyMU6dOYcCAARrNT22HIAg4fCUXD68/jqe/Po0z127CQE+K6QM9ceSN4Yh4oDOLlZYJdLbAk/08AAAR22NRo1CKnIiISD1KpYCwbbEQBODBHi7o7an+teZEpN1EPXI1c+ZMbNmyBVu3boW5uTmysrIA1E5AYWxc+4vstGnT4OLighUrVgAA3nvvPSxZsgRbtmyBp6en6jlmZmYwMzODRCLB3Llz8c4778DX1xdeXl5YvHgxnJ2dMXnyZFHeJ2kvQRBw6Eou1uxPxPm0QgCAoZ4UU/u546Vh3uhgwdP/tNm80X7YfvEG4rNKsOV0GqYN8BQ7EhHRPf15PgPn0wphaiDDW+MCxI5DRBokarlav349ACA4OLje8m+//RbTp08HAKSlpUEqldZ7TnV1NR555JF6zwkLC0N4eDgA4I033kBZWRleeOEFFBYWYvDgwYiMjLyv67KobREEAdEJuVi9PxH/XC8EABjpS/FkPw+8OLQjHFiqdIK1qQH+L8Qfi/+KwYd7r2BiV2fYmLbcqTVERPerpFKOFbvjAQCvjvTlH/GI2hhRy5U6ExVGR0fXe3z16tV7PkcikWDp0qVYunRpM5NRWyUIAg4m5GDNvkT8k1479b+RvhRP9/fAC0O9YW/Oaed0zdS+7thyKg2XM4vxwd4ELH+wi9iRiIga9cmBJOSVVsHLzhQzBnmKHYeINEwrJrQgammCIGD/5RysPZCIi7dKlbG+DE8P8MD/hnRkqdJhMqkEEaFBeOzzE/jxdBqm9nVHZxdLsWMREd0hKacU3xxNBQAsmRgIQz3eBJ2orWG5ojZNEARExWVj7YFExGTUTqNpYvBvqbIzY6lqC/p62SC0mzO2/XMD4dti8etLAyCRaHZKYyKi+yEIApbuiEONUsDIAAcMD3AQOxIRtQCWK2qTBEHA3rhsrNmXiLjM2lJlaiDDtIGeeH6wF2xZqtqcBeMDEBWXjTPXbmLrhRuY3MNF7EhERCr7Lufg8JVcGMikWDwxUOw4RNRCWK6oTVEqBeyNy8Ka/Um4fFupemagJ54f0pGTHbRhTpbGmDXCB+/vScDyXZcxKrADzAz5I46IxFcpV2DZjjgAwHNDvOBpZypyIiJqKfzNg9oEpVJAZGwW1u5PRHxWCQDAzFAP0wd64rnBXrBmqWoXnhvshV/OXMe1/HJ8ejAJb47lFMdEJL6vj6YiraAcHSwMMWu4j9hxiKgFsVyRTlMqBeyOqS1VCdm1pcrcUA8zBnni2cFesDJhqWpPjPRlWDwhEM9/fwZfHUnBY73d4MW/EBORiG4UVmDdgSQAwMLxnWDKI+pEbRr/H046SaEUsOtSJj45kIgr2aUAAHMjPcwY5IXnBnnB0kRf5IQklpGdHBDsb4/ohFws2xGHb6b3ETsSEbVjK3bHo0KuQB9Pa4R2cxY7DhG1MJYr0ikKpYAdF2/gkwNJSMr5t1Q9N9gLMwZ5wdKYpaq9k0gkWDwxEMeSDuNAfA4OxGdjREAHsWMRUTt0KiUf2/+5AakECA8N4iymRO0AyxXphLpStXZ/IpJzywAAFkZ6eG5wR0wf5MlSRfV425vh2UFe+PxwCpZuj8MgHzveT4aIWlWNQomwbbEAgCl93RHkzPvvEbUHLFek1WoUSmy/daQq5VapsjTWx/ODvfDMIE9YGLFUUcNmjfDBH+czcDW/HN8cvYqXg73FjkRE7ciPp9MQn1UCS2N9/F+Iv9hxiKiVsFyRVqpRKLH1wg2sO5iE1LzaUmVloo//DemIaQM8YM5SRfdgbqSPBeMCMO+Xf/DJgUQ81NMFHSyMxI5FRO3AzbJqfLD3CgDg/0L8eBsQonaE5Yq0So1Cib8u3MC6A4m4ml8OALA20cfzQzrimYGevG8RNcnk7i7YdPIazqUVYsWuy1j9RA+xIxFRO/DB3gQUVcgR4GiOqX3dxY5DRK2Iv6mSVpArlPjzfAY+PZiEa7dKlY2pgepIFaeupeaQSiWICO2M0E+P4q8LN/BUfw/09rQROxYRtWExGUXYcjoNQO0kFnoyqciJiKg18TdWEpVcocQf59Lx6cFkpBXUlipbUwO8MLQjnurPUkX3r4urJZ7o44YfT19H2LZYbJs1GDIpZ+wiIs0TBAER22MhCMDErk7o39FW7EhE1Mr4myuJorqmtlStO5iE9JsVAAA7s39LlYkBhyZpzvwQf+y4mInYG8X46e80PNnPQ+xIRNQGbfvnBv6+ehPG+jIsHN9J7DhEJAL+BkutqrpGid/OpuPTg0nIKKwrVYZ4aVhHPNnPA8YGnC6bNM/WzBDzRvshYnscPtiTgAldnGBlwgvMiUhzyqpqsHzXZQDAzOHecLYyFjkREYmB5YpaRVWNAr+eScf66GRVqbI3N8RLw7wxta87SxW1uKf7e+DH02m4kl2Kj6OuIOKBzmJHIqI25NODScguroK7jQmeH9JR7DhEJBKWK2pRVTUK/HImHesPJuFGUSUAwKGuVPVzh5E+SxW1Dj2ZFOGTgjD1q1P44eQ1TOnnjgBHC7FjEVEbcDWvDF8dSQUALJ4YyH/biNoxlitqEZVyBX45cx3ro5OReatUdbAwxMvDvPFEX5YqEsdAHzuM7+KIXZeyELY1Fj+90B8SCSe3IKL7s2xHHKoVSgz1s8eoTg5ixyEiEbFckUZVyhX46XQaNhxKQVZxbalytDDCK8O98VhvN5YqEt3C8Z1wID4Hp1ILsPNSJiZ2dRY7EhHpsIPxOdgfnwM9qQRLJgbyDzZE7dx9lauqqioYGhpqKgvpsEq5Aj+eTsOGQ8nILq4CADhZGuGVYG881scNhnosVaQdXK1N8PIwH3y87wqW77yMEQEOnJ2SiJqlukaJpTviAAAzBnnCx8FM5EREJLYm/Uaxe/du/PTTTzhy5AiuX78OpVIJU1NT9OjRAyEhIZgxYwacnflX4PakUq7A5lO1pSq3pLZUOVsa4ZXhPni0tytLFWmlF4d1xC9nriOjsALro5PxfyH+YkciIh30zbFUpOaVwc7MELNH+oodh4i0gFrl6s8//8Sbb76JkpISjB8/Hm+++SacnZ1hbGyMgoICxMTEYN++fVi2bBmmT5+OZcuWwd7evqWzk4gqqhXYfOoaNhxKQV5pbalysTLGzOE+eKSXKwz0eEd60l5G+jIsntgJL206h88Pp+DRXm5wtzUROxYR6ZDs4kp8sj8RAPDWuACYG+mLnIiItIFa5WrVqlX4+OOPMW7cOEild/7S/NhjjwEAMjIy8Mknn2DTpk147bXXNJuUtEJ5dQ02n0zD54eTkVdaDQBwta4tVQ/3ZKki3TEmyBGDfexwNCkP7+yMwxfTeosdiYh0yHu741FWrUAPdys81MNF7DhEpCXUKlcnTpxQ68VcXFywcuXK+wpE2qm8ugY/nLiGLw6nIL+stlS52Rhj1nAfPNTTFfoylirSLRKJBGGTAjF2zRHsjcvG4Su5GOrHI+5EdG9nrxXgj/MZkEiA8ElBkEo5iQUR1WrSNVfFxcU4deoUqqur0bdvX5761w6UVdXg+xPX8OWRFBTcKlXuNiaYNcIHD/ZwYakinebbwRzPDPDEN8dSEb49FpFzhvLoKxHdlUIpIGxbLADgsV5u6OZmJW4gItIqaperCxcuYPz48cjOzoYgCDA3N8cvv/yCMWPGtGQ+EklpVQ2+P3EVXx5Owc1yOQDAw9YEs4b7YDJLFbUhc0f7Yts/GUjJLcP3J67i+SEdxY5ERFrslzPXEZNRDHMjPbw+lpPhEFF9av+G/Oabb8LLywtHjx7F2bNnMXLkSMyaNasls5EISirl+PRgEga/dwCrIhNws1wOLztTfPhoN+yfNwyP9nZjsaI2xcJIH2+MCQAArN6XiJySSpETEZG2KiqX4/09CQCA10b5wc6Mt6MhovrUPnJ19uxZ7N27Fz179gQAfPPNN7CxsUFxcTEsLCxaLCC1jpJKOTYeu4qvj6Wi8NaRqo52pnh1pA8mdXWGHgsVtWGP9HLFplPXcDG9CKsiE/DBo93EjkREWujjfVdQUFYNXwczPD3AQ+w4RKSF1C5XBQUFcHV1VT22srKCqakp8vPzWa50WHFdqTqaiqKKW6XK3hSzR/hiUjdnyHiRLrUDUqkEEaFBePCz4/jtbDqe7OeOHu7WYsciIi0Sn1WMH05eAwCEhwbxLA4ialCTJrSIi4tDVlaW6rEgCLh8+TJKSkpUy7p27aq5dNRiiirk+PZYKr45moriyhoAgI+DGV4d4YOJXVmqqP3p4W6NR3q54rez6QjfFos/XxnEGcCICEDt7zsR2+KgUAoY19kRg3zsxI5ERFqqSeVq5MiREASh3rKJEydCIpFAEARIJBIoFAqNBiTNKqqQ45ujqfjmWCpKbpUqXwczzB7pi/FdnFiqqF17Y6w/ImOy8E96EX47m47H+riJHYmItMCuS1k4kZIPQz0pFo7vJHYcItJiaper1NTUlsxBLaywvBrfHE3Ft8euoqSqtlT5dbhVqjo78S/0RAAczI0wZ6Qv3t11Ge9FxmNMZ0dYGuuLHYuIRFRRrcC7O+MAAC8N84abjYnIiYhIm6ldrjw8eOGmLiosr8ZXR1Kx8fhVlN4qVQGO5pg90hdjgxxZqoj+45mBnvjp7zQk55Zh7f5ELJ4YKHYkIhLR+kPJuFFUCRcrY7w0zFvsOESk5Zp0WuB/lZWV4eeff0ZFRQVCQkLg6+urqVx0n26WVeOroyn47vi1eqVq7ihfhASyVBE1xkBPirBJQZj2zWl8d/wqnujjBt8O5mLHIiIRXC8ox4ZDyQCARRM6wdhAJnIiItJ2apertLQ0PP300zh37hz69++Pr7/+GqNHj0ZiYiIAwNjYGLt378bQoUNbLCzdW0FZNb48koLvj19FWXXt9W+BThaYPdIXIYEdWKqI1DDUzx6jAzsgKi4b4dtjsem5fpBI+P8dovbmnZ1xqK5RYqC3LcZ2dhQ7DhHpALXnEZ0/fz6qq6uxYcMGmJiYYMyYMfD19UVmZiays7Mxbtw4hIeHt2BUupv80iqs2H0Zg987gPXRySirViDI2QJfPN0LO2cPxtjOPFpF1BSLJwTCQE+KY0n52BObLXYcImplRxJzsSc2GzKpBOGhQfwDCxGpRe0jV4cPH8a2bdvQt29fjBs3DnZ2dvjmm2/QoUMHAMDixYsxcuTIFgtKDcsrrcKXh1Pw/YlrqJDXHqnq7GKBuSP9MLKTA/8xIGomd1sTvDi0Iz45kIR3dsYh2N8eRvo8JYioPZArlIjYXjuJxbQBHvDjqcFEpCa1y1VOTo5qUgsbGxuYmJioihUAODo64ubNm5pP2I4plAJOpRbgbJ4EtqkFGODjoJoqPbekCl8cTsamk2mqUtXV1RJzRvpiRABLFZEmvBzsjd/PpiP9ZgU+P5SCOaN4XSlRe/Dd8atIyimFjakB5o7yEzsOEemQJk1ocfsv7PzlvWVFxmQiYnscMosqAcjwfeIZOFkaYe4oX1zJLsXmU9dQKVcCALq5WWHuSF8E+9vz+0KkQSYGelg4oRNmbTmPz6KT8HAvF7hacxpmorYst6QKa/bVXk/+xhh/3o6BiJqkSeVqyZIlMDGp/cWiuroa7777LiwtLQEA5eXlmk/XTkXGZOLlTecg/Gd5ZlEl3vz9kupxdzcrzB3li2F+LFVELWVCFyf84HUNp1ILsGJXPD59sqfYkYioBb2/Jx4lVTXo6mqJx3rzRuJE1DRql6uhQ4ciISFB9XjgwIFISUm5Yxu6PwqlgIjtcXcUq9vpyyT44unePFJF1AokktqL2SesPYKdlzLxZFIeBvrYiR2LiFrAheuF+OVMOgAgbFIQJ4IioiZTu1xFR0e3YAyqczq14NapgI2TKwQY6ctYrIhaSScnCzzV3wPfn7iG8O2x2DV7CPRkak+2SkQ6QKkUELYtFgDwUE8X9PKwFjkREeki/nagZXJK7l6smrodEWnGvNF+sDbRx5XsUmw6eU3sOESkYb+fS8c/1wthZqiHt8YGiB2HiHSU2uWqsLAQ69evVz1+8skn8dBDD6m+Hn30URQWFrZExnbFwdxIo9sRkWZYmRhg/hh/AMBHUVeQX1olciIi0pTiSjnei4wHAMwe6QMHC/4bS0TNo3a5+vLLL3H06FHV423btkEqlcLS0hKWlpa4dOkSVq9e3RIZ25W+XjZwsjRCYyf8SQA4WRqhr5dNa8YiIgBP9HFHkLMFiitr8MHehHs/gYh0wtp9icgrrUZHO1NMH+gldhwi0mFql6vffvsNM2bMqLds1apV+Pbbb/Htt99ixYoV2Lp1q8YDtjcyqQRhkwIB4I6CVfc4bFKg6n5XRNR6ZFIJIkKDAAA//X0dl9KLRE5ERPcrKacEG49fBQAsmRQIAz1eMUFEzaf2T5CUlBT4+/urHvv7+8PAwED1uFu3bkhMTNRsunZqbGcnrH+qJxwt65+W4GhphPVP9cTYzk4iJSOi3p42mNzdGYIAhG2LgVJ5t7k9iUibCULtDL01SgGjOnVAsL+D2JGISMepPVtgWVkZioqK4OZWe8+HM2fO3LFeqVRqNl07NrazE0YHOuJEUg72HjmFkCH9MMDHgUesiLTAW+M6YW9cNs6lFeKvCxl4qKer2JGIqBn2xmXjSGIeDGRSLJ7YSew4RNQGqH3kqmPHjjh37lyj68+cOQMvL56nrEkyqQT9vGzQy05APy8bFisiLeFoaYRXR/gCAFbsjkdpVY3IiYioqSrlCizbEQcA+N9QL3jYmoqciIjaArXL1YMPPohFixYhOzv7jnVZWVkICwvDgw8+qNFwRETa6tnBnvC0NUFuSRU+OcBTool0zZeHU5B+swKOFkaYOdxH7DhE1EaoXa7eeOMNmJmZwdfXFzNnzsSaNWuwZs0avPLKK/Dz84OpqSnefPPNJu18xYoV6NOnD8zNzeHg4IDJkycjIeHuM3DFxsbi4YcfhqenJyQSSYMzFIaHh0MikdT7CgjgPSuISHMM9WRYcmvymW+OpiI5t1TkRESkrozCCnwanQQAWDihE0wM1L5KgojortQuV+bm5jh27BimTp2KH3/8Ea+99hpee+01/PTTT5g6dSqOHTsGc3PzJu380KFDmDlzJk6ePImoqCjI5XKEhISgrKys0eeUl5ejY8eOWLlyJRwdHRvdLigoCJmZmaqv26eRJyLShBEBHTDc3x5yhYCl2+MgCJzcgkgXLN91GZVyJfp62mBSV04SRUSa06Q/1VhbW2PDhg1Yv349cnNzAQD29vaQSJp3LVBkZGS9xxs3boSDgwPOnj2LoUOHNvicPn36oE+fPgCAt956q9HX1tPTu2v5IiLShCWTgnA06RAOXcnFgfgcjOzUQexIRHQXJ5LzsfNiJqQSIDw0qNm/wxARNaRZx8ElEgkcHDQ/XWlRUe09Y2xs7v8GuYmJiXB2doaRkREGDBiAFStWwN3dvcFtq6qqUFVVpXpcXFwMAJDL5ZDL5fed5X7U7V/sHETN0R7Gr6ulAWYM9MAXR64iYnss+nlYwlBfJnYs0pD2MIbbkxqFEuHbYgAAU/q4wdfeuE1/bzl+SZdp0/htSgaJoCXnsSiVSoSGhqKwsFDtU/g8PT0xd+5czJ07t97y3bt3o7S0FP7+/sjMzERERAQyMjIQExPT4KmL4eHhiIiIuGP5li1bYGJi0qz3Q0TtR6UCePe8DMVyCSa6KzDaRSt+rBLRfxzOlOD3qzKY6AlY1F0BU32xExGRLigvL8fUqVNRVFQECwuLu26rNeXq5Zdfxu7du3H06FG4uqp3z5jGytV/FRYWwsPDAx999BGee+65O9Y3dOTKzc0NeXl59/wAW5pcLkdUVBRGjx4NfX3+K0C6pT2N363/ZGL+b5dgrC/FnjmD4fSfm4CTbmpPY7itKyirxujVR1FcWYPwSZ3wZF83sSO1OI5f0mXaNH6Li4thZ2enVrnSiulxZs2ahR07duDw4cNqF6umsLKygp+fH5KSkhpcb2hoCENDwzuW6+vri/7NrKNNWYiaqj2M34d7ueHHv9Nx9tpNfBCVhLVTeogdiTSoPYzhtm71gXgUV9agk5MFnh7g1a7uHcnxS7pMG8ZvU/av9myBLUEQBMyaNQt//vknDhw40GI3IS4tLUVycjKcnDgjEBG1DIlEgojQIEgkwLZ/buB0aoHYkYjolpiMIvz0dxoAICI0qF0VKyJqXc06crV//37s378fOTk5UCqV9dZ98803ar/OzJkzsWXLFmzduhXm5ubIysoCAFhaWsLY2BgAMG3aNLi4uGDFihUAgOrqasTFxan+OyMjAxcuXICZmRl8fGpvAjh//nxMmjQJHh4euHHjBsLCwiCTyTBlypTmvF0iIrV0drHElL7u2HIqDWHbYrHj1cH8JY5IZIIgIGxbLAQBCO3mjL5e9z9pFhFRY5p85CoiIgIhISHYv38/8vLycPPmzXpfTbF+/XoUFRUhODgYTk5Oqq+ff/5ZtU1aWhoyMzNVj2/cuIEePXqgR48eyMzMxAcffIAePXrg+eefV22Tnp6OKVOmwN/fH4899hhsbW1x8uRJ2NvbN/XtEhE1yfwQf1gY6eFyZjG2nE4TOw5Ru/fXhQycvXYTJgYyLBzfSew4RNTGNfnI1YYNG7Bx40Y8/fTT971zdebSiI6OrvfY09Pzns/76aef7icWEVGz2Zga4P9C/BG2LRYf7k3AxC5OsDY1EDsWUbtUWlWDFbviAQAzh/vAkRPNEFELa/KRq+rqagwcOLAlshARtQlP9nNHgKM5Csvl+CjqithxiNqtdQeSkFNSBQ9bEzw/pGWu6yYiul2Ty9Xzzz+PLVu2tEQWIqI2QU8mRdikIADA5lPXEHejWORERO1Pal4Zvj6aAgBYMjEQhnq8uTcRtbwmnxZYWVmJL774Avv27UPXrl3vmJrwo48+0lg4IiJdNcDbFhO6OmHnxUyEb4vFzy/2h0TCyS2IWsvS7bGQKwQE+9tjRICD2HGIqJ1ocrm6ePEiunfvDgCIiYmpt46/OBAR/Wvh+E7Yfzkbp68WYPvFTIR2cxY7ElG7cCA+GwcTcqEvk2DxxED+fkJErabJ5ergwYMtkYOIqM1xsTLGzGAffBh1Bct3XsaoTg4wMdCKe7cTtVlVNQos3V57y5ZnB3nB295M5ERE1J7c102E09PTkZ6erqksRERtzv+GdoSbjTGyiivx6cEkseMQtXlfH03F1fxy2Jsb4tWRvmLHIaJ2psnlSqlUYunSpbC0tISHhwc8PDxgZWWFZcuW3XFDYSKi9s5IX4ZFEwIBAF8eTsW1/DKRExG1XVlFlVh3oPaPGAvGBcDMkEeKiah1Nblcvf3221i3bh1WrlyJ8+fP4/z581i+fDk++eQTLF68uCUyEhHptJDADhjia4dqhRLLdlwWOw5Rm7Vy92WUVyvQ090Kk7u7iB2HiNqhJper7777Dl999RVefvlldO3aFV27dsUrr7yCL7/8Ehs3bmyBiEREuk0ikSBsUiD0pBLsu5yN6IQcsSMRtTl/Xy3AXxduQCIBIkI7QyrlJBZE1PqaXK4KCgoQEBBwx/KAgAAUFBRoJBQRUVvj42CO6QM9AQBLt8ehuoanURNpikIpIGxrLADgiT5u6OJqKXIiImqvmlyuunXrhnXr1t2xfN26dejWrZtGQhERtUWzR/nCzswQKXll2Hg8Vew4RG3GT3+nIS6zGBZGepgf4i92HCJqx5p8peeqVaswYcIE7Nu3DwMGDAAAnDhxAtevX8euXbs0HpCIqK2wMNLHm2P98fpvF7FmXyImd3eBg4WR2LGIdFpheTU+2JMAAJg32g+2ZoYiJyKi9qzJR66GDRuGK1eu4MEHH0RhYSEKCwvx0EMPISEhAUOGDGmJjEREbcbDPV3Rzc0KZdUKrIyMFzsOkc77KOoKbpbL4dfBDE/19xA7DhG1c82ao9TZ2RnvvvuuprMQEbV5UqkEEaFBmPzpMfxxLgNP9vNALw9rsWMR6aTLmcXYdPIaACB8UhD0ZPd1+04iovumVrm6ePEiOnfuDKlUiosXL951265du2okGBFRW9XdzQqP9XbFL2fSEb4tFltnDuLMZkRNJAgCwrfFQikA47s4YqCPndiRiIjUK1fdu3dHVlYWHBwc0L17d0gkEgiCcMd2EokECoVC4yGJiNqa18cEYPelLFzKKMIvZ67jib7uYkci0ik7LmbiVGoBjPSlWDi+k9hxiIgAqFmuUlNTYW9vr/pvIiK6P/bmhpgzyhfv7LyMVXsSMK6LEyyN9cWORaQTyqtrsHxX7Q25Xx7mA1drE5ETERHVUqtceXh4NPjfRETUfM8M9MRPf19HUk4pVu+7grBJQWJHItIJ66OTkVlUCVdrY7w4rKPYcYiIVNQqV9u2bVP7BUNDQ5sdhoioPdGXSRE2KRBPf30a35+4hif6uMPf0VzsWERaLS2/HJ8fTgEALJoQCCN9mciJiIj+pVa5mjx5slovxmuuiIiaZoivPcYEdcCe2GxEbI/F5uf7QSLh5BZEjVm2Mw7VNUoM9rHDmKAOYschIqpHrTlLlUqlWl8sVkRETbdoQiAM9aQ4npyPyJgsseMQaa1DV3IRFZcNPakEYZMC+YcIItI6vCEEEZHI3GxM8OIwbwDAOzsvo6Kaf6gi+q/qGiUitscCqL1e0bcDT6ElIu2j1mmBa9euVfsFZ8+e3ewwRETt1cvDvPHbmevIKKzAhkPJeG20n9iRiLTKd8evIiW3DHZmBpgzylfsOEREDVKrXH388cdqvZhEImG5IiJqBmMDGd6eEIiZW85hw6FkPNLLFW42nF6aCABySiqxZn8iAOCNMQGwMOJtC4hIO6l9nysiImpZ47s4YkBHW5xIycfyXZex/qleYkci0gqrIhNQWlWDbq6WeKSXq9hxiIgaxWuuiIi0hEQiQVhoIGRSCXbHZOFYUp7YkYhEdy7tJn47mw4ACA8NglTKSSyISHupdeRq3rx5WLZsGUxNTTFv3ry7bvvRRx9pJBgRUXsU4GiBp/t7YOPxqwjfFotdc4ZAX8a/g1H7pFQKCN9WO4nFI71c0cPdWuRERER3p1a5On/+PORyOQDg3LlzjU59yilRiYju32uj/LDtnxtIzCnFDyeu4dnBXmJHIhLFb2fTcTG9CGaGenhjrL/YcYiI7kmtcnXw4EHVf0dHR7dUFiIiAmBpoo/Xx/hjwR+X8PG+Kwjt7gw7M0OxYxG1qqIKOd6LjAcAzB3lCwdzI5ETERHdW5PONZHL5dDT00NMTExL5SEiIgCP9XZDZxcLlFTW4P3IBLHjELW6NfsSkV9WDW97U0wb4Cl2HCIitTSpXOnr68Pd3R0KBW9wSUTUkmRSCSJCgwAAv5y9jn+uF4obiKgVJWaX4LsTVwEAYZOCYKDH6w6JSDc0+afV22+/jYULF6KgoKAl8hAR0S29PGzwUA8XCAIQti0WSqUgdiSiFicIAsK3x0KhFBAS2AFD/ezFjkREpDa1rrkCgMOHD2PAgAFYt24dkpKS4OzsDA8PD5iamtbb7ty5cxoPSUTUXr05LgB7YrNw4Xoh/jifwXv8UJu3JzYLx5LyYaAnxaIJgWLHISJqErXL1fDhw5GZmYnJkye3YBwiIrpdBwsjvDrSFyt3x2Pl7niMCeoAcyN9sWMRtYhKuQLLdlwGALw4tCPcbU1ETkRE1DRqlytBqD0dJSwsrMXCEBHRnZ4d5IVf/r6OlLwyfHIgCQvHdxI7ElGL+PxQCjIKK+BsaYRXgn3EjkNE1GRNuuaK97EiImp9BnpSLJ5Ue3rUN0dTkZRTKnIiIs1Lv1mOz6KTAAALJ3SCsYFM5ERERE2n9pErAJg+fToMDe9+r5U//vjjvgIREdGdhvs7YGSAA/bH5yBieyy+f7Yv/+BFbcryXZdRVaNEPy8bTOjiJHYcIqJmaVK5Mjc3h7GxcUtlISKiu1g8MRBHEvNwJDEP+y7nYHRgB7EjEWnE8aQ87LqUBakECA8N4h8OiEhnNalcrV27Fg4ODi2VhYiI7sLTzhTPD/HCZ9HJWLYjDkN87WCkz1OnSLfJFUqEb48FADzd3wOdnCxETkRE1HxqX3PFvyIREYlv5nAfOFoYIa2gHF8dSRE7DtF923TyGq5kl8LaRB+vjfYTOw4R0X1Ru1zVzRZIRETiMTXUw4LxAQCATw8m40ZhhciJiJovv7QKH0VdAQDMH+MPKxMDkRMREd0ftcvVwYMHYWNj05JZiIhIDaHdnNHH0xoVcgWW77osdhyiZnt/TwJKKmsQ5GyBJ/q4ix2HiOi+qVWufvrpJwwbNgx6eve+ROv69es4duzYfQcjIqKGSSQShIcGQSoBdlzMxMmUfLEjETXZxfRC/HzmOgAgIjQIMikvPyAi3adWuVq/fj06deqEVatW4fLlO/9KWlRUhF27dmHq1Kno2bMn8vP5Dz0RUUsKcrbElL61f+kP3xaLGoVS5ERE6lMqBYRvi4UgAJO7O6O3J8+MIaK2Qa1ydejQIbz33nuIiopC586dYWFhAV9fX3Tp0gWurq6wtbXFs88+C3d3d8TExCA0NLSlcxMRtXvzQ/xhaayP+KwS/Hg6Tew4RGr760IGzqUVwsRAhgXjO4kdh4hIY9Seij00NBShoaHIy8vD0aNHce3aNVRUVMDOzg49evRAjx49IJWqfQkXERHdJ2tTA8wP8cPirbH4YO8VTOjqDBtTTghA2q2kUo4Vu+MBAK+O8EUHCyORExERaU6T7nMFAHZ2dpg8eXILRCEioqaa0tcdm0+lIT6rBB/uTcC7D3YROxLRXa07kITckip42prg2cGeYschItIoHmoiItJhejIpIkKDAABbTqchJqNI5EREjUvOLcU3x1IBAEsmBcJQjzfBJqK2heWKiEjH9etoi0ndnCEIQMT2WN6XkLSSIAhYuj0OcoWAEQEOGBHQQexIREQax3JFRNQGLBwfAGN9Gf6+ehPb/rkhdhyiO+y/nINDV3KhL5Ng8cRAseMQEbUIlisiojbAydIYs0b4AACW77qMsqoakRMR/atSrsDSHXEAgOcGd4SXnanIiYiIWkaTylVxcTGioqKwc+dO5ObmtlQmIiJqhucGe8HdxgTZxVVYdzBJ7DhEKl8fTUVaQTk6WBji1Vt/BCAiaovULlcXLlxAQEAAxo4di0mTJsHHxwd79uy5r52vWLECffr0gbm5ORwcHDB58mQkJCTc9TmxsbF4+OGH4enpCYlEgtWrVze43aeffgpPT08YGRmhX79+OH369H1lJSLSdkb6MtXpVl8fSUVqXpnIiYiAzKIKrDtQW/YXjOsEU8MmT1RMRKQz1C5Xb775Jry8vHD06FGcPXsWI0eOxKxZs+5r54cOHcLMmTNx8uRJREVFQS6XIyQkBGVljf9CUF5ejo4dO2LlypVwdHRscJuff/4Z8+bNQ1hYGM6dO4du3bphzJgxyMnJua+8RETablQnBwz1s0e1Qol3bp2GRSSmFbviUSFXoLeHNR7o7ix2HCKiFqX2n4/Onj2LvXv3omfPngCAb775BjY2NiguLoaFhUWzdh4ZGVnv8caNG+Hg4ICzZ89i6NChDT6nT58+6NOnDwDgrbfeanCbjz76CP/73/8wY8YMAMCGDRuwc+dOfPPNN40+h4ioLZBIJAibFIgxHx/G/vgcHIzPwfAAB7FjUTt1KiUf2/65AYkECA8NgkQiETsSEVGLUrtcFRQUwNXVVfXYysoKpqamyM/Pb3a5+q+iotr7s9jY2DT7Naqrq3H27FksWLBAtUwqlWLUqFE4ceJEg8+pqqpCVVWV6nFxcTEAQC6XQy6XNzuLJtTtX+wcRM3B8SsOdytDPDPAHV8fu4aI7bHo42EJQz3OX9QcHMPNV6NQImxrDADg8d6u8Hcw4efYyjh+SZdp0/htSoYmnfgcFxeHrKws1WNBEHD58mWUlJSolnXt2rUpL6miVCoxd+5cDBo0CJ07d27WawBAXl4eFAoFOnSof/+MDh06ID4+vsHnrFixAhEREXcs37t3L0xMTJqdRZOioqLEjkDUbBy/rc+vBrDQl+FqfjkWfrsHI11476v7wTHcdEezJIjPlsFEJqCLcBW7dl0VO1K7xfFLukwbxm95ebna2zapXI0cOfKOm1NOnDgREokEgiBAIpFAoVA05SVVZs6ciZiYGBw9erRZz78fCxYswLx581SPi4uL4ebmhpCQEI0dlWsuuVyOqKgojB49Gvr6+qJmIWoqjl9xSdxu4I0/YrA/ywBvPD4IHSyMxI6kcziGm+dmeTXCVh8DIMfr4zrhsX7uYkdqlzh+SZdp0/itO6tNHWqXq9TU1GaFUcesWbOwY8cOHD58uN6ph81hZ2cHmUyG7Ozsesuzs7MbnQDD0NAQhoaGdyzX19cX/ZtZR5uyEDUVx684Huntjh/PpON8WiE+3JeMjx/vLnYkncUx3DRrD8ajsEKOAEdzPD3AC3oynpYqJo5f0mXaMH6bsn+1y5WHh0ezwtyNIAh49dVX8eeffyI6OhpeXl73/ZoGBgbo1asX9u/fj8mTJwOoPeVw//799z27IRGRLpFKJYgIDcIDnx7Dn+cz8GQ/d/T2bP41rUTqiL1RhC2n0gAAYZOCWKyIqF1p8s0mEhMTsXXrVly9ehUSiQReXl6YPHkyOnbs2OSdz5w5E1u2bMHWrVthbm6uup7L0tISxsbGAIBp06bBxcUFK1asAFA7YUVcXJzqvzMyMnDhwgWYmZnBx6f2xoTz5s3DM888g969e6Nv375YvXo1ysrKVLMHEhG1F11drfB4bzf89Pd1hG+PxdaZgyGTcsY2ahmCICBiWxyUAjChqxMGeNuKHYmIqFU1qVytWLECS5YsgVKphIODAwRBQG5uLt566y0sX74c8+fPb9LO169fDwAIDg6ut/zbb7/F9OnTAQBpaWmQSv/9q9eNGzfQo0cP1eMPPvgAH3zwAYYNG4bo6GgAwOOPP47c3FwsWbIEWVlZ6N69OyIjI++Y5IKIqD2YP8YfOy9lIiajGD//fR1Tef0LtZBt/9zA6asFMNKX4u3xncSOQ0TU6tQuVwcPHsSiRYuwePFizJkzB9bW1gBqp2hfvXo13nrrLfTt27fR+1M15L+TYzSkrjDV8fT0VOt5s2bN4mmAREQA7MwM8dooPyzdEYf398RjQhcnWJrw+gvSrLKqGqzYVTsr78xgHzhbGYuciIio9al9IvSGDRvw/PPPIzw8XFWsgNp7Ui1duhTPPvus6kgUERFpl6cHeMDXwQw3y+X4eN8VseNQG/RZdBKyiivhZmOM/w1t+qUCRERtgdrl6vTp03j66acbXf/000/j5MmTGglFRESapS+TIjw0CADww8lriM9Sf1pZonu5mleGLw/Xziq8eEIgjPRlIiciIhKH2uUqOzsbnp6eja738vKqd4NhIiLSLoN87DCusyMUSgHh22LVOsWaSB3v7IxDtUKJIb52GB3I65uJqP1Su1xVVlbCwMCg0fX6+vqorq7WSCgiImoZC8d3gqGeFCdTCrDrEv8gRvfvYEIO9l3OgZ5UgrBJQZBIOBslEbVfTZot8KuvvoKZmVmD60pKSjQSiIiIWo6bjQleDvbG6n2JeHdnHEYEOMDYgKdwUfNU1yixbHvt7VFmDPKEj0PDvyMQEbUXapcrd3d3fPnll/fchoiItNtLw7zx65l0ZBRWYH10EuaF+IsdiXTUt8dSkZJXBjszQ8we6St2HCIi0aldrq5evdqCMYiIqLUY6cuwaEInvLz5HDYcTsGjvd3gZmMidizSMTnFlVi7PxEA8OZYf5gbcXp/IiK1r7kiIqK2Y2xnRwzysUV1jRLv7IwTOw7poJWR8SirVqC7mxUe7ukqdhwiIq2g9pGriooK7N+/HxMnTgQALFiwAFVVVar1MpkMy5Ytg5GRkeZTEhGRRkkktZMPjFtzBHtis3EkMRdDfO3FjkU64uy1AvxxLgMAEBEaBKmUk1gQEQFNOHL13Xff4fPPP1c9XrduHY4fP47z58/j/Pnz2LRpE28iTESkQ/w6mGPaAA8AQPi2WMgVSpETkS6oncq/9mjnY71d0c3NStxARERaRO1ytXnzZrzwwgv1lm3ZsgUHDx7EwYMH8f777+OXX37ReEAiImo5c0f5wdbUAMm5Zfju+FWx45AO+PXMdVzKKIK5oR5eHxMgdhwiIq2idrlKSkpCly5dVI+NjIwglf779L59+yIujuftExHpEktjfbwxtna2wDX7EpFbUnWPZ1B7VlQux6o9CQCAuaP9YG9uKHIiIiLtona5KiwsrHeNVW5uLjw9PVWPlUplvfVERKQbHu3lhq6uliipqsGqyHix45AW+3jfFRSUVcPHwUx1SikREf1L7XLl6uqKmJiYRtdfvHgRrq6cLYiISNdIpRKEhwYBAH49m44L1wvFDURaKSGrBD+cvAYACJ8UBH0ZJxwmIvovtX8yjh8/HkuWLEFlZeUd6yoqKhAREYEJEyZoNBwREbWOnu7Wqum0w7bGQKkURE5E2kQQBIRvi4VCKWBskCMG+9qJHYmISCupXa4WLlyIgoIC+Pv74/3338fWrVuxdetWrFq1Cv7+/rh58yYWLlzYklmJiKgFvTnWH2aGevgnvQi/nUsXOw5pkd0xWTiRkg9DPSnentBJ7DhERFpL7ftcdejQAcePH8fLL7+Mt956C4JQ+1dNiUSC0aNH47PPPkOHDh1aLCgREbUsBwsjzB7pg+W74rEqMh5jOzvCwkhf7FgksopqBd7deRkA8OIwb7jZmIiciIhIe6ldrgDAy8sLkZGRKCgoQFJSEgDAx8cHNjY2LRKOiIha1/SBXvjp7+tIyS3D2n2JWDQxUOxIJLINh5KRUVgBFytjvDzMW+w4RERarVlXo9rY2KBv377o27cvixURURtioCfFkluFauPxq0jKKRE5EYnpekE5NhxKBgC8PaETjA1kIiciItJunOqHiIjqCfZ3wKhOHVCjFBC+LU51Gji1P+/uvIyqGiUGdLTFuM6OYschItJ6LFdERHSHJRMDYaAnxdGkPOyNyxY7DongaGIeImOzILs1Vb9EIhE7EhGR1mO5IiKiO7jbmuCFIR0BAMt2xKFSrhA5EbUmuUKJ8O2xAICn+3vA39Fc5ERERLqB5YqIiBr0ynBvOFoYIf1mBb44nCJ2HGpF35+4hqScUtiYGuC1UX5ixyEi0hksV0RE1CATAz0svHVPo8+ik5BRWCFyImoNeaVVWB11BQDw+hh/WJpwOn4iInWxXBERUaMmdXVCXy8bVMqVWH7rXkfUtq2KjEdJVQ26uFjisd5uYschItIpLFdERNQoiUSC8ElBkEqAnZcycTw5T+xI1IIuXC/EL2fSAQDhoYGQSTmJBRFRU7BcERHRXQU6W+DJfh4AgIhtcahRKEVORC1BqRQQvq12EouHeriglwfvY0lE1FQsV0REdE//F+IHKxN9JGSXYPOpNLHjUAv443wGLlwvhKmBDG+NCxA7DhGRTmK5IiKie7IyMcD8EH8AwId7E5BfWiVyItKk4ko5Vu6OBwDMHukLBwsjkRMREekmlisiIlLLlL7uCHSyQHFlDT7Ye0XsOKRBn+xPRF5pFTramWLGIC+x4xAR6SyWKyIiUotMKkHEA0EAgJ/+TkNMRpHIiUgTknJK8e2xqwCAxZMCYaDHXw2IiJqLP0GJiEhtfTxt8EB3ZwgCELYtFoIgiB2J7oMgCIjYHosapYBRnRww3N9B7EhERDqN5YqIiJpkwbhOMDGQ4ey1m/jrQobYceg+RMVl40hiHgxkUiyaECh2HCIincdyRURETeJoaYSZw30AACt2xaO0qkbkRNQclXIFlu2MAwA8P8QLnnamIiciItJ9LFdERNRkzw/xgoetCXJKqvDJgUSx41AzfHk4BdcLKuBo8W9ZJiKi+8NyRURETWaoJ8OSibWnkX1zNBUpuaUiJ6KmuFFYgU+jkwAAC8YHwNRQT+RERERtA8sVERE1y4gABwT720OuELBsR5zYcagJlu+6jEq5En09bRDazVnsOEREbQbLFRERNYtEIsGSiYHQl0lwMCEXB+KzxY5EajiZko8dFzMhlQBhoYGQSCRiRyIiajNYroiIqNk62pvh2cG1N51duj0OVTUKkRPR3dQolAjfFgsAmNrPHUHOliInIiJqW1iuiIjovrw6whcO5oa4ml+Or4+mih2H7mLL6TTEZ5XA0lgf/zfaX+w4RERtDssVERHdFzNDPSwYHwAAWHcgCVlFlSInooYUlFXjw71XAADzQ/xgbWogciIioraH5YqIiO7b5O4u6OluhfJqBVbsvix2HGrAB3sTUFQhRycnC0zt5yF2HCKiNonlioiI7ptEIkFEaGdIJMDWCzfw99UCsSPRbWIyivDj6TQAQPikQMiknMSCiKglsFwREZFGdHG1xBN93AAAYVtjoVAKIiciABAEAeHbYiEIwKRuzujX0VbsSEREbRbLFRERacz8EH9YGOkhLrNYdaSExLX1wg2cuXYTxvoyLLx1bRwREbUMlisiItIYWzNDzBvtB6D2Gp/C8mqRE7VvpVU1WL6r9hq4WSN84GRpLHIiIqK2jeWKiIg06qn+HvDvYI7Ccjk+iroidpx27dODScgpqYK7jQmeu3U/MiIiajksV0REpFF6MinCQgMBAJtOXkPcjWKRE7VPqXll+OpICgBgycRAGOnLRE5ERNT2sVwREZHGDfS2w4QuTlAKQPj2WAgCJ7dobct2xEGuEDDMzx4jOzmIHYeIqF1guSIiohaxcEInGOlLcTq1ADsuZoodp105EJ+NA/E50JdJsGRSICQSTr1ORNQaWK6IiKhFuFgZ45VgHwDA8l2XUV5dI3Ki9qGqRoFlO2onsXh2kBe87c1ETkRE1H6IWq5WrFiBPn36wNzcHA4ODpg8eTISEhLu+bxff/0VAQEBMDIyQpcuXbBr165666dPnw6JRFLva+zYsS31NoiIqBEvDO0IV2tjZBZV4rODyWLHaRe+OXoVqXllsDc3xKwRPmLHISJqV0QtV4cOHcLMmTNx8uRJREVFQS6XIyQkBGVlZY0+5/jx45gyZQqee+45nD9/HpMnT8bkyZMRExNTb7uxY8ciMzNT9fXjjz+29NshIqL/MNKXYdGE2sktvjicgmv5jf98p/uXXVyJTw4kAgDeGhsAcyN9kRMREbUvoparyMhITJ8+HUFBQejWrRs2btyItLQ0nD17ttHnrFmzBmPHjsXrr7+OTp06YdmyZejZsyfWrVtXbztDQ0M4OjqqvqytrVv67RARUQPGBHXAEF87VCuUeGfnZbHjtGkrd8ejvFqBHu5WeLCHi9hxiIjaHT2xA9yuqKgIAGBjY9PoNidOnMC8efPqLRszZgz++uuvesuio6Ph4OAAa2trjBgxAu+88w5sbW0bfM2qqipUVVWpHhcX104bLJfLIZfLm/NWNKZu/2LnIGoOjl+qs3CsHyYl5yMqLhsH4jIxxNdO7Ehq0aUxfPbaTfx5PgMSCbB4vD8UihooFGKnIjHp0vgl+i9tGr9NySARtGR+XKVSidDQUBQWFuLo0aONbmdgYIDvvvsOU6ZMUS377LPPEBERgezsbADATz/9BBMTE3h5eSE5ORkLFy6EmZkZTpw4AZnszvt8hIeHIyIi4o7lW7ZsgYmJiQbeHRER/XlViuhMKRyMBLzZTQE9TqmkMUoB+PCSDOllEgxwUOIJb6XYkYiI2ozy8nJMnToVRUVFsLCwuOu2WnPkaubMmYiJiblrsVLXE088ofrvLl26oGvXrvD29kZ0dDRGjhx5x/YLFiyodzSsuLgYbm5uCAkJuecH2NLkcjmioqIwevRo6Ovz3HnSLRy/dLshlXKMXn0MOWXVyLUOxHODPMWOdE+6MoZ/+jsd6WVxMDfSw8czBsHWzFDsSKQFdGX8EjVEm8Zv3Vlt6tCKcjVr1izs2LEDhw8fhqur6123dXR0VB2hqpOdnQ1HR8dGn9OxY0fY2dkhKSmpwXJlaGgIQ8M7/yHS19cX/ZtZR5uyEDUVxy8BgI2+Pt4cF4A3fruIdQdT8FBPNzhYGIkdSy3aPIYLy6vx0b7aSSzmjfaDozWnXqf6tHn8Et2LNozfpuxf1JMyBEHArFmz8Oeff+LAgQPw8vK653MGDBiA/fv311sWFRWFAQMGNPqc9PR05Ofnw8nJ6b4zExFR8z3S0xXdXC1RWlWD9yLvfesNurePo67gZrkcfh3M8FR/D7HjEBG1a6KWq5kzZ2LTpk3YsmULzM3NkZWVhaysLFRUVKi2mTZtGhYsWKB6PGfOHERGRuLDDz9EfHw8wsPDcebMGcyaNQsAUFpaitdffx0nT57E1atXsX//fjzwwAPw8fHBmDFjWv09EhHRv6RSCcJDgwAAv59Lx7m0myIn0m3xWcX44eQ1AED4pCDoy3ghGxGRmET9Kbx+/XoUFRUhODgYTk5Oqq+ff/5ZtU1aWhoyMzNVjwcOHIgtW7bgiy++QLdu3fDbb7/hr7/+QufOnQEAMpkMFy9eRGhoKPz8/PDcc8+hV69eOHLkSIOn/hERUevq4W6NR3vVngIevi0WSqVWzKukcwRBqP38BGB8F0cM9NGNGRiJiNoyUa+5Umeiwujo6DuWPfroo3j00Ucb3N7Y2Bh79uy532hERNSC3hgbgMiYLFxML8KvZ6/j8T7uYkfSOTsvZeJkSgEM9aRYOL6T2HGIiAgiH7kiIqL2yd7cEHNG+QIAVkUmoKhC/PuY6JLy6hosv3VD5peDveFqzduGEBFpA5YrIiISxTMDPeHjYIb8smqsuTXbHalnQ3QybhRVwsXKGC8N8xY7DhER3cJyRUREotCXSRE2KRAA8N2Jq7iSXSJyIt2Qll+ODYdTAACLJ3aCkb5M5ERERFSH5YqIiEQzxNceIYEdoFAKiNgeq9a1uO3dOzvjUF2jxCAfW4wJavwej0RE1PpYroiISFSLJgTCQE+KY0n52BObJXYcrXb4Si72xmVDJpUgbFIQJBKJ2JGIiOg2LFdERCQqd1sTvDS0IwBg2Y7LqKhWiJxIO1XXKBGxPRYA8MwAT/h1MBc5ERER/RfLFRERie7lYB84Wxoho7ACnx9OFjuOVvr+xFUk55bB1tRANdMiERFpF5YrIiISnbGBDAsn1N6raX10MtJvloucSLvklFRi9a0ZFd8Y6w9LY32RExERUUNYroiISCtM6OKE/h1tUFWjxLu37uFEtVZFJqC0qgZdXS3xaC83seMQEVEjWK6IiEgrSCQShIcGQSoBdsdk4VhSntiRtML5tJv47Ww6ANR+PlJOYkFEpK1YroiISGsEOFrg6f4eAICI7bGQK5QiJxKXUikgfFvtJBYP93RFT3drkRMREdHdsFwREZFWmTfaH9Ym+riSXYpNJ6+JHUdUv51Lxz/pRTAz1MOb4/zFjkNERPfAckVERFrF0kQfr48JAAB8FHUFeaVVIicSR3GlHKsi4wEAc0b6wsHcSORERER0LyxXRESkdR7v44YgZwuUVNbggz0JYscRxZp9icgrrUZHe1M8M9BT7DhERKQGlisiItI6MqkEEaFBAICfz1zHxfRCcQO1ssTsEnx3/CoAIGxSEAz0+M81EZEu4E9rIiLSSr09bfBgDxcIAhC2LRZKpSB2pFYhCAIitsehRilgdGAHDPOzFzsSERGpieWKiIi01lvjAmBiIMP5tEL8eT5D7DitYk9sNo4m5cFAT4rFEwLFjkNERE3AckVERFqrg4URXh3hCwBYGRmPkkq5yIlaVqVcgXd2xgEAXhjSEe62JiInIiKipmC5IiIirfbsYE942Zkit6QKnxxIEjtOi/r8UArSb1bAydIIrwz3FjsOERE1EcsVERFpNUM9GZZMrD097pujqUjKKRU5UctIv1mOz6Jry+PC8Z1gYqAnciIiImoqlisiItJ6wwMcMCLAATVKAUt3xEEQ2t7kFit2xaOqRol+XjaY2NVJ7DhERNQMLFdERKQTlkwMhIFMisNXcrH/co7YcTTqeHIedl7KhFQChIcGQSKRiB2JiIiageWKiIh0gqedKZ4b4gUAWLojDpVyhciJNKNGoUTEttpJLJ7q74FOThYiJyIiouZiuSIiIp0xa7gPOlgYIq2gHF8fTRU7jkZsOnkNCdklsDLRx7zRfmLHISKi+8ByRUREOsPUUA8LxnUCAKw7kITMogqRE92f/NIqfBR1BQAwP8QfViYGIiciIqL7wXJFREQ65YHuzujtYY0KuQLLd8WLHee+fLA3AcWVNQh0ssCUvu5ixyEiovvEckVERDpFIpHcmvQB2P7PDZxKyRc7UrNcSi/CT39fBwBEPBAEmZSTWBAR6TqWKyIi0jmdXSxVR3rCtsWiRqEUOVHTCIKAsG0xEITaI3F9PG3EjkRERBrAckVERDppfog/LI31EZ9Vgh9Pp4kdp0n+PJ+Bc2mFMDGQqa4hIyIi3cdyRUREOsnG1AD/F1I7u94He6/gZlm1yInUU1pVgxW7a68VmzXCB46WRiInIiIiTWG5IiIinTW1rzsCHM1RVCHHh1EJYsdRyycHEpFbUgVPWxM8N9hL7DhERKRBLFdERKSz9GRShIcGAQC2nEpD7I0ikRPdXXJuKb65dX+uJZMCYagnEzkRERFpEssVERHptP4dbTGxqxOUAhC+LRaCIIgdqUGCIGDp9jjIFQKG+9tjREAHsSMREZGGsVwREZHOWzi+E4z1Zfj76k1s++eG2HEadCA+B4eu5EJfJsHiiYFixyEiohbAckVERDrP2coYM4d7AwBW7IpHWVWNyInqq6pRYOmOOADAs4O90NHeTORERETUEliuiIioTXh+SEe42Rgjq7gSnx5MEjtOPV8dScW1/HI4mBvi1RG+YschIqIWwnJFRERtgpG+DIsn1J5u99WRVFzNKxM5Ua3MogqsO1Bb9haMD4CZoZ7IiYiIqKWwXBERUZsxOrADhvjaoVqhxDs748SOAwBYuTseFXIFenlYY3J3F7HjEBFRC2K5IiKiNkMikSBsUhD0pBLsu5yDgwk5ouY5nVqArRduQCIBIkKDIJFIRM1DREQti+WKiIjaFB8HM8wY5AkAWLo9DtU1SlFyKJQCwrbFAgCe6OOOzi6WouQgIqLWw3JFRERtzuyRvrAzM0RqXhm+PZYqSoYfT6fhcmYxLIz0MD/ET5QMRETUuliuiIiozTE30sdb4wIAAGv3JyK7uLJV93+zrBof7E0AAPxfiD9szQxbdf9ERCQOlisiImqTHurhgu5uViirVuC93fGtuu+Poq6gsFwO/w7meLKfe6vum4iIxMNyRUREbZJUKrk1iQTwx/kMnL1W0Cr7jbtRjM2nrgEAwkIDoSfjP7VERO0Ff+ITEVGb1c3NCo/1cgMAhG+Lg0IptOj+BEFA+PZYKAVgQhcnDPS2a9H9ERGRdmG5IiKiNu31sf4wN9TDpYwi/HLmeovua/vFTJxOLYCRvhQLJ3Rq0X0REZH2YbkiIqI2zc7MEHNH187W9/6eBBSVy1tkP+XVNVi+8zIA4JVgH7hYGbfIfoiISHuxXBERUZs3bYAHfB3MUFBWjY/3XWmRfXx2MBlZxZVwtTbGC0M7tsg+iIhIu7FcERFRm6cvkyJsUhAA4IeT15CQVaLR17+WX4YvDqcAABZPDISRvkyjr09ERLqB5YqIiNqFwb52GBvkCIVSQPi2WAiC5ia3WLbjMqoVSgzxtUNIYAeNvS4REekWlisiImo33p7QCYZ6UpxIycfumCyNvGZ0Qg72Xc6GnlSCsEmBkEgkGnldIiLSPSxXRETUbrjZmOClYd4AgHd3XkZFteK+Xq+6Roml2+MAANMHesLHwfy+MxIRke4StVytWLECffr0gbm5ORwcHDB58mQkJCTc83m//vorAgICYGRkhC5dumDXrl311guCgCVLlsDJyQnGxsYYNWoUEhMTW+ptEBGRDnlpmDdcrIyRUViB9YeS7+u1Nh5PRUpeGezMDDB7lK+GEhIRka4StVwdOnQIM2fOxMmTJxEVFQW5XI6QkBCUlZU1+pzjx49jypQpeO6553D+/HlMnjwZkydPRkxMjGqbVatWYe3atdiwYQNOnToFU1NTjBkzBpWVla3xtoiISIsZG8jw9q17UG04lIzrBeXNep2c4kqs2Vf7h7s3xgbAwkhfYxmJiEg3iVquIiMjMX36dAQFBaFbt27YuHEj0tLScPbs2Uafs2bNGowdOxavv/46OnXqhGXLlqFnz55Yt24dgNqjVqtXr8aiRYvwwAMPoGvXrvj+++9x48YN/PXXX630zoiISJuN6+yIgd62qK5R4t1b96ZqqvciE1BWrUA3Nys80tNVwwmJiEgX6Ykd4HZFRUUAABsbm0a3OXHiBObNm1dv2ZgxY1TFKTU1FVlZWRg1apRqvaWlJfr164cTJ07giSeeuOM1q6qqUFVVpXpcXFwMAJDL5ZDLW+Zmk+qq27/YOYiag+OXtNnb4/wQ+tlJRMZmITo+C4O8be/YprExfD6tEL+fSwcALB7vD4WiBor7u3yLSOP4M5h0mTaN36Zk0JpypVQqMXfuXAwaNAidO3dudLusrCx06FB/mtsOHTogKytLtb5uWWPb/NeKFSsQERFxx/K9e/fCxMSkSe+jpURFRYkdgajZOH5JWw1ykOJwlhRv/nwGb3ZVQNbI+Ry3j2GlAHx0SQZAgn72SmRcPIaMi62Tl6g5+DOYdJk2jN/ycvVPH9eacjVz5kzExMTg6NGjrb7vBQsW1DsaVlxcDDc3N4SEhMDCwqLV89xOLpcjKioKo0ePhr4+z+cn3cLxS9puUIUco1cfRXa5HHk2QZgx0KPe+obG8K9n03H9ZBzMDPWw+tlBsDMzFCM60T3xZzDpMm0av3VntalDK8rVrFmzsGPHDhw+fBiurnc/b93R0RHZ2dn1lmVnZ8PR0VG1vm6Zk5NTvW26d+/e4GsaGhrC0PDOfxz19fVF/2bW0aYsRE3F8Uvayk5fH2+ODcBbf1zCJweS8WBPN9ibN/7vQVGFHB9GJQEA5o7yhZO1WWtHJmoy/gwmXaYN47cp+xd1QgtBEDBr1iz8+eefOHDgALy8vO75nAEDBmD//v31lkVFRWHAgAEAAC8vLzg6Otbbpri4GKdOnVJtQ0REVOfR3m7o4mKJkqoavL8n/q7brt53Bfll1fBxMMMzAz1bJyAREekMUcvVzJkzsWnTJmzZsgXm5ubIyspCVlYWKioqVNtMmzYNCxYsUD2eM2cOIiMj8eGHHyI+Ph7h4eE4c+YMZs2aBQCQSCSYO3cu3nnnHWzbtg2XLl3CtGnT4OzsjMmTJ7f2WyQiIi0nk0oQHhoEAPjlTDouXC9scLsr2SX4/sQ1AEDYpEDoN3aBFhERtVui/suwfv16FBUVITg4GE5OTqqvn3/+WbVNWloaMjMzVY8HDhyILVu24IsvvkC3bt3w22+/4a+//qo3CcYbb7yBV199FS+88AL69OmD0tJSREZGwsjIqFXfHxER6YZeHtZ4qKcLACBsWyyUSqHeekEQEL4tFgqlgDFBHTDE116MmEREpOVEveZKEIR7bhMdHX3HskcffRSPPvpoo8+RSCRYunQpli5dej/xiIioHXlrbAD2xGThn+u106w/2ttNtW5PXA6OJ+fDQE+KRRMCRUxJRETajOc0EBERAXCwMMLskb4Aam8QfLO8GqdSC3AqR4Lw7XEAgJeGdoSbjXbcooOIiLSPVswWSEREpA1mDPLCz39fR0peGQavPICyagUAGQA5pBLA24GzAxIRUeN45IqIiOgWAz0pxnepvaVHbbH6l1IA5v50AZExmQ09lYiIiOWKiIiojkIp4PdzGXfdJmJ7HBTKe18zTERE7Q/LFRER0S2nUwuQWVTZ6HoBQGZRJU6nFrReKCIi0hksV0RERLfklDRerJqzHRERtS8sV0RERLc4mKt3P0R1tyMiovaF5YqIiOiWvl42cLI0gqSR9RIATpZG6Otl05qxiIhIR7BcERER3SKTShA2qfYmwf8tWHWPwyYFQiZtrH4REVF7xnJFRER0m7GdnbD+qZ5wtKx/6p+jpRHWP9UTYzs7iZSMiIi0HW8iTERE9B9jOzthdKAjTiTlYO+RUwgZ0g8DfBx4xIqIiO6K5YqIiKgBMqkE/bxskH9ZQD8vGxYrIiK6J54WSEREREREpAEsV0RERERERBrAckVERERERKQBLFdEREREREQawHJFRERERESkASxXREREREREGsByRUREREREpAEsV0RERERERBrAckVERERERKQBLFdEREREREQaoCd2AG0kCAIAoLi4WOQkgFwuR3l5OYqLi6Gvry92HKIm4fglXccxTLqM45d0mTaN37pOUNcR7oblqgElJSUAADc3N5GTEBERERGRNigpKYGlpeVdt5EI6lSwdkapVOLGjRswNzeHRCIRNUtxcTHc3Nxw/fp1WFhYiJqFqKk4fknXcQyTLuP4JV2mTeNXEASUlJTA2dkZUundr6rikasGSKVSuLq6ih2jHgsLC9EHFlFzcfySruMYJl3G8Uu6TFvG772OWNXhhBZEREREREQawHJFRERERESk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "HumanMessage(content=\"FINAL ANSWER\\n\\nThe complete line graph of the UK's GDP from 2018 to 2022 is displayed above, with the following GDP values for each year:\\n\\n- 2018: 2.16 trillion GBP\\n- 2019: 2.23 trillion GBP\\n- 2020: 1.99 trillion GBP\\n- 2021: 2.23 trillion GBP\\n- 2022: 2.27 trillion GBP\\n\\nThe graph shows the fluctuations in the UK's GDP over the specified period, with a notable dip in 2020 likely due to the economic impact of the COVID-19 pandemic.\", name='Researcher')" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -338,7 +352,9 @@ " {\n", " \"messages\": [\n", " HumanMessage(\n", - " content=\"Fetch the UK's GDP over the past 5 years, then draw a line graph of it.\"\n", + " content=\"Fetch the UK's GDP over the past 5 years,\"\n", + " \" then draw a line graph of it.\"\n", + " \" Once you code it up, finish.\"\n", " )\n", " ],\n", " },\n", @@ -347,6 +363,14 @@ ")\n", "result[\"messages\"][-1]" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23cc22c7-33a8-4399-91e4-ac1abf8f0fec", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From d50e99137892a471309fb3bd6185f9f101e0932b Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Sat, 20 Jan 2024 14:20:24 -0800 Subject: [PATCH 16/16] updates --- examples/multi_agent/agent_supervisor.ipynb | 80 ++++++----- .../hierarchical_agent_teams.ipynb | 132 ++++++------------ 2 files changed, 91 insertions(+), 121 deletions(-) diff --git a/examples/multi_agent/agent_supervisor.ipynb b/examples/multi_agent/agent_supervisor.ipynb index ec3e3b52c..4895d1486 100644 --- a/examples/multi_agent/agent_supervisor.ipynb +++ b/examples/multi_agent/agent_supervisor.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "0d30b6f7-3bec-4d9f-af50-43dfdc81ae6c", "metadata": {}, "outputs": [], @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "30c2f3de-c730-4aec-85a6-af2c2f058803", "metadata": {}, "outputs": [], @@ -68,7 +68,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "f04c6778-403b-4b49-9b93-678e910d5cec", "metadata": {}, "outputs": [], @@ -97,11 +97,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "c4823dd9-26bd-4e1a-8117-b97b2860211a", "metadata": {}, "outputs": [], "source": [ + "from langchain.agents import AgentExecutor, create_openai_tools_agent\n", "from langchain_core.messages import BaseMessage, HumanMessage\n", "from langchain_openai import ChatOpenAI\n", "\n", @@ -122,7 +123,7 @@ " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", " ]\n", " )\n", - " agent = create_openai_functions_agent(llm, tools, prompt)\n", + " agent = create_openai_tools_agent(llm, tools, prompt)\n", " executor = AgentExecutor(agent=agent, tools=tools)\n", " chain = executor | (\n", " # So the agents properly role-play in this simulation, we will\n", @@ -144,7 +145,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "id": "6a430af7-8fce-4e66-ba9e-d940c1bc48e8", "metadata": {}, "outputs": [], @@ -152,7 +153,6 @@ "import operator\n", "from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict\n", "\n", - "from langchain.agents import AgentExecutor, create_openai_functions_agent\n", "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n", "\n", "\n", @@ -194,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "id": "17c108a0-6dc3-46fd-a5e6-a1fcfad5458a", "metadata": {}, "outputs": [], @@ -259,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 7, "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", "metadata": {}, "outputs": [], @@ -293,7 +293,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 8, "id": "56ba78e9-d9c1-457c-a073-d606d5d3e013", "metadata": {}, "outputs": [ @@ -310,7 +310,11 @@ "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", - "The \"Hello, World!\" message has been successfully printed to the terminal.\n" + "The code `print('Hello, World!')` was executed, and the output is:\n", + "\n", + "```\n", + "Hello, World!\n", + "```\n" ] } ], @@ -327,35 +331,46 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 12, "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", "metadata": {}, "outputs": [ - { - "data": { - "image/png": "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eHg48+bNs06BHDp0KCNGjKB79+7Url2b6Ohotm3bxo4dO2jWrBlg+aOsfPnydO7cOUXhjdTEx8czb9483njjDQA6dOhA165diYiIsCYmAKdPn6Z27drW62qCgoI4deoUc+fO5erVqzbTLF955RV8fX0ZPHiw9dqRH374gc6dOxMWFsYnn3zC1atXGTNmjDVZTk5E27Rpw969e+nTpw8lSpTg3LlzrFixghMnTmSoQMilS5cAyzS1U6dO8cEHH+Dm5ka7du1uu21aLl68SIsWLWjfvj0dO3akUKFC1nUzZszgypUr9OzZE5PJxMiRI3nqqac4cuSIdYpuRt/fiIgImjRpQmJiorXf+PHjcXd3v+PY4c4/p2mJiIjAw8PD5nfBzp07efjhh3FwsP1etnbt2owfP54DBw5QuXLldPcJpEjmAGJjY4mLiyMqKoqff/6ZpUuX3vbawHbt2tG/f39mz57NW2+9ZbNu9uzZPProo+TLl4+EhATCwsKIj4+nT58++Pv7c+rUKX755RciIyPx8fG57fkoVaoUMTExeHp6Eh4ezueff27zM5Ka7777jooVK/L666/Trl07fv75Z8aOHYu/vz9nz56lTp061sTY19eXpUuX0q1bN6Kjo+nbty9gmQ49ceJEOnToQI8ePbhy5QqTJk0iLCws1Wmwqf3uSc/PP/9MTEwM7du3x9/fn8aNGzN9+nSeffbZTJ1jsEwvbNGiBTVq1GDIkCE4ODhYv0j8448/qF27ts32bdu2pUyZMnz00UfWpHXFihUcOXKErl274u/vb51Ov3fvXjZv3mxNnHbu3Enz5s0pXLgww4YNIykpiffffx9fX98Uxzh8+HDee+892rVrR/fu3Tl//jzffPMNjRo1SvF/oUiWsO/AmUjuFxUVZQDGk08+meFtrl69mqItLCzMKFWqlE3brdMCU5uyljyd6OYpawkJCYafn59RqVIlIy4uztr+yy+/GIAxePBga1vnzp0NwBgwYECKmADDxcXFOHTokLVt9+7dBmB88803KWK4eUpTasfYs2dPw8PDw7h27VqKdTfbu3evARgffPCBYRiW6S2enp7G999/bxiGYRQqVMgYPXq0YRiGER0dbTg6Oho9evSwbr969WoDMFavXm1znDdPC1y4cKEBGCNHjrS2JSYmGg0bNszQOU5tn8nvj7e3t3Hu3Dmbvk2b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RHIqR0dHnJycdBsGERERATKWGyTLUQUtoqKiAMifP3+afTZt2kS/fv1s2sLCwli4cCEAR48eJSIigtDQUOt6Hx8fgoOD2bRpU6rJVXx8PPHx8dbl6OjodONMSEjgzJkzXL169bbHJCK5j4eHB4ULF8bFxcXeoYiIiEgukmOSK7PZTN++falfvz6VKlVKs19ERASFChWyaStUqBARERHW9cltafW51YgRIxg2bFiG4zx69CiOjo4EBATg4uKib7hF7hOGYZCQkMD58+c5evQoZcqUue3NAkVERESS5ZjkqlevXuzZs4f169ff89ceOHCgzWhYci371CQkJGA2mwkMDMTDw+NehSgi94i7uzvOzs4cP36chIQE3Nzc7B2SiIiI5BI5Irnq3bs3v/zyC+vWrbvtXY/9/f05e/asTdvZs2fx9/e3rk9uK1y4sE2fatWqpbpPV1dXXF1dMxWzvs0WuX/p8y0iIiJ3wq5/QRiGQe/evVmwYAGrVq2iZMmSt92mbt26rFy50qZtxYoV1K1bF4CSJUvi7+9v0yc6OpotW7ZY+4iIiIiIiGQ1u45c9erVixkzZrBo0SK8vLys10T5+Pjg7u4OQKdOnShSpAgjRowA4LXXXiMkJITPP/+cli1bMnPmTLZt28b48eMBy52T+/bty4cffkiZMmUoWbIk7733HgEBAYSHh9vlOEVERERE5P5n15GrMWPGEBUVRePGjSlcuLD1MWvWLGufEydOcObMGetyvXr1mDFjBuPHj6dq1arMnTuXhQsX2hTB6N+/P3369OHFF1+kVq1axMTEsGzZshx17USS2WDT4Yss2nWKTYcvkmTOMRXx5T5UokQJRo0aZe8wREREJD2rR8DakamvWzvSsl5yNLtPC0zt0aVLF2ufNWvWMHXqVJvt2rZty/79+4mPj2fPnj089thjNutNJhPvv/8+ERERXLt2jd9//52yZcvegyPKmGV7ztDgk1V0mLCZ12buosOEzTT4ZBXL9py5/cZ3aMSIEdSqVQsvLy/8/PwIDw9n//79Nn2uXbtGr169KFCgAHny5KFNmzY217ft3r2bDh06EBgYiLu7O+XLl+err76y2cf69eupX78+BQoUwN3dnaCgIL788ssMxxkUFISrq2ualR3trUSJEphMJkwmk7ViZLdu3bh8+bK9QxMREZHczsERVg9PmWCtHWlp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IiKijkv2fa5CkZz7XBERERERUejoMPtcERERERERdRYMV0RERERERAHAcEVERERERBQADFdEREREREQBwHBFREREREQUAAxXREREREREASD7PlehyLE6vclkkrkSIiIiIiKSkyMTtGYHK4YrL6qqqgAAycnJMldCREREREShoKqqChERET7P4SbCXtjtdly4cAFhYWEQBEHWWkwmE5KTk3H+/PkuuaEx28/2s/1dt/0APwO2n+1n+9l+tl/+9ouiiKqqKiQlJUGh8D2rij1XXigUCvTs2VPuMjyEh4fL/i+WnNh+tp/t77rtB/gZsP1sP9vP9ndVodJ+fz1WDlzQgoiIiIiIKAAYroiIiIiIiAKA4SrEabVarFy5ElqtVu5SZMH2s/1sf9dtP8DPgO1n+9l+tp/t71jt54IWREREREREAcCeKyIiIiIiogBguCIiIiIiIgoAhisiIiIiIqIAYLgiIiIiIiIKAIarEPXpp59i8uTJSEpKgiAI2Llzp9wlBVVOTg5uvvlmhIWFIS4uDlOmTMHJkyflLitocnNzMXjwYOfGeZmZmfjwww/lLks2a9euhSAImD9/vtylBMVzzz0HQRA8vvr16yd3WUH1008/YcaMGYiJiYFer8egQYNw9OhRucsKil69ejX78xcEAXPmzJG7tKCw2WxYvnw50tLSoNfr0adPHzz//PPoSutvVVVVYf78+UhNTYVer8eoUaNw5MgRuctqF/7ud0RRxIoVK5CYmAi9Xo9x48bh9OnT8hTbDvy1f/v27Rg/fjxiYmIgCAIKCwtlqbM9+foMrFYrFi9ejEGDBsFoNCIpKQmPPPIILly4IF/BfjBchSiz2YwhQ4bg9ddfl7sUWezfvx9z5szBF198gfz8fFitVowfPx5ms1nu0oKiZ8+eWLt2LY4dO4ajR4/ijjvuwD333INvvvlG7tKC7siRI3jjjTcwePBguUsJqgEDBuDixYvOrwMHDshdUtBcvXoVo0ePhlqtxocffohvv/0WL730EqKiouQuLSiOHDni8Wefn58PAHjwwQdlriw41q1bh9zcXLz22mv47rvvsG7dOqxfvx6vvvqq3KUFzWOPPYb8/Hy8/fbbOH78OMaPH49x48bhp59+kru0gPN3v7N+/Xq88sor2LBhAw4dOgSj0YgJEyagtrY2yJW2D3/tN5vNGDNmDNatWxfk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Ll++XIyPjxe1Wq04duzYTvXfhb/2b9682evzK1eulLXuQPL1GTiWoPf2tXfvXrlL90oQxS605TkREREREVE74ZwrIiIiIiKiAGC4IiIiIiIiCgCGKyIiIiIiogBguCIiIiIiIgoAhisiIiIiIqIAYLgiIiIiIiIKAIYrIiIiIiKiAGC4IiIiIiIiCgCGKyIiogATBAE7d+6UuwwiIgoyhisiIuo0Zs2aBUEQ8Nvf/rbZc3PmzIEgCJg1a1bA3u+5557D0KFDA3Y9IiLq2BiuiIioU0lOTsa2bdtQU1PjPFZbW4u8vDykpKTIWBkREXV2DFdERNSpDBs2DMnJydi+fbvz2Pbt25GSkoKbbrrJeayurg5PPvkk4uLioNPpMGbMGBw5csT5/L59+yAIAvbs2YOMjAwYDAaMGjUKJ0+eBABs2bIFq1atwldffQVBECAIArZs2eJ8fVlZGe69914YDAakp6fjgw8+aP/GExGRrBiuiIio08nOzsbmzZudj//85z9j9uzZHuc8/fTT+Nvf/oa33noLBQUF6Nu3LyZMmIArV654nLds2TK89NJLOHr0KFQqFbKzswEAWVlZ+N3vfocBAwbg4sWLuHjxIrKyspyvW7VqFR566CF8/fXXuOuuuzB9+vRm1yYios6F4YqIiDqdGTNm4MCBAyguLkZxcTE+++wzzJgxw/m82WxGbm4uXnzxRUycOBH9+/fHpk2boNfr8eabb3pca/Xq1bjtttvQv39/LFmyBJ9//jlqa2uh1+vRrVs3qFQqJCQkICEhAXq93vm6WbNmYdq0aejbty/WrFmD6upqHD58OGifARERBZ9K7gKIiIgCrXv37pg0aRK2bNkCURQxadIkxMbGOp8/e/YsrFYrRo8e7TymVqsxYsQIfPfddx7XGjx4sPPnxMREAMClS5f8zt9yf53RaER4eDguXbp0Xe0iIqLQxnBFRESdUnZ2NubOnQsAeP3116/5Omq12vmzIAgAALvd3qbXOV7bmtcREVHHxWGBRETUKd15552or6+H1WrFhAkTPJ7r06cPNBoNPvvsM+cxq9WKI0eOoH///q1+D41GA5vNFrCaiYioY2PPFRERdUpKpdI5xE+pVHo8ZzQa8fjjj2PRokWIjo5GSkoK1q9fD4vFgkcffbTV79GrVy8UFRWhsLAQPXv2RFhYGLRabUDbQUREHQfDFRERdVrh4eEtPrd27VrY7XY8/PDDqKqqQkZGBnbv3o2oqKhWX//+++/H9u3bcfvtt6OiogKbN28O6CbFRETUsQiiKIpyF0FERERERNTRcc4VERERERFRADBcERERERERBQDDFRERERERUQAwXBEREREREQUAwxUREREREVEAMFwREREREREFAMMVERERERFRADBcERERERERBQDDFRERERERUQAwXBEREREREQUAwxUREREREVEA/H/1tD65nuE1VgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "name": "stdout", "output_type": "stream", "text": [ "================================\u001b[1m Human Message \u001b[0m=================================\n", "\n", - "The chart has been successfully created and saved as \"california_wildfires_comparison.png\". This chart compares the acres burned by California wildfires in 2023 to the five-year average for each month. Unfortunately, I'm unable to display the image directly to you here, but the chart visualizes the data with the following characteristics:\n", + "# Research Report on Pikas\n", "\n", - "- The X-axis represents the months of the year from January to December.\n", - "- The Y-axis indicates the acres burned.\n", - "- A line graph shows the cumulative acres burned in 2023, marked with circles.\n", - "- Another line graph represents the five-year average cumulative acres burned by month, marked with x's and a dashed line.\n", - "- The chart includes a legend to distinguish between the two lines and is titled \"California Wildfires: Acres Burned in 2023 vs 5-Year Average\".\n", + "Pikas are small, mountain-dwelling mammals that are closely related to rabbits. They are known for their distinctive chirps and typically inhabit boulder fields at high elevations, up to 14,000 feet in treeless slopes like those found in the Southern Rockies. These animals are recognized for their ability to adapt to some of the most inhospitable climates.\n", "\n", - "You would need to view the \"california_wildfires_comparison.png\" file using an image viewer to see the actual chart.\n" + "## Climate Change Impact\n", + "\n", + "Pikas have been a topic of interest in climate change research due to their sensitivity to high temperatures and reliance on cold habitats. They have historically responded to climate shifts by moving to higher elevations or latitudes to find suitable cooler environments. For instance, pikas were once found in the Appalachian Mountains and even in the Mojave Desert, but as the Earth's climate warmed, they moved to cooler, high-elevation areas where they live today.\n", + "\n", + "Recent studies suggest that pikas are showing remarkable adaptability to climate change. Despite predictions that they might become endangered due to rising temperatures, these animals are displaying resilience. Some research indicates that pikas can adjust certain genes to make better use of oxygen in higher altitudes where the air is thinner, which could be a potential hope for their survival as climate change drives them to higher elevations.\n", + "\n", + "However, there have been reports of pikas disappearing from parts of the Great Basin, and in Colorado, pikas have retracted upslope by about 1,160 feet. It's been noted that while climate change may be a factor, it might not be the sole cause for these local disappearances.\n", + "\n", + "## Adaptation Strategies\n", + "\n", + "Pikas exhibit several interesting behaviors that help them cope with their challenging environment. During the summer, they engage in activities like \"making hay\" — collecting and storing vegetation in preparation for the harsh winters. Their diet and caching behavior are essential for their survival during the months when food is scarce.\n", + "\n", + "## Conservation and Research\n", + "\n", + "Conservationists and scientists continue to study pikas to understand their adaptation mechanisms and how they might inform broader climate change mitigation strategies. For example, studies have been conducted on pikas at different elevations to observe genetic changes and their effects on adaptation. Such research is crucial for predicting the future of pikas and potentially other species affected by climate change.\n", + "\n", + "## Conclusion\n", + "\n", + "Pikas serve as an important indicator species for the impacts of climate change on wildlife. Their ability to adapt to changing climates offers hope and also highlights the importance of understanding genetic adaptability in the face of environmental challenges. Conservation efforts and further research are essential to ensure the survival of pikas and to learn from their resilience.\n", + "\n", + "### Sources\n", + "- [The Conversation: Pikas are adapting to climate change remarkably well](https://theconversation.com/pikas-are-adapting-to-climate-change-remarkably-well-contrary-to-many-predictions-150726)\n", + "- [Stanford Sustainability: It's in the genes – potential hope for pikas hit by climate change](https://sustainability.stanford.edu/news/its-genes-potential-hope-pikas-hit-climate-change)\n", + "- [Colorado Sun: Colorado pika population and climate change](https://coloradosun.com/2023/08/27/colorado-pika-population-climate-change/)\n", + "- [PetaPixel: Photographing the American Pika – a tiny indicator of climate change](https://petapixel.com/2022/01/03/photographing-the-american-pika-a-tiny-indicator-of-climate-change/)\n", + "- [The Wildlife Society: Can pikas survive climate change after all?](https://wildlife.org/can-pikas-survive-climate-change-after-all/)\n" ] } ], @@ -363,11 +378,10 @@ "results = graph.invoke(\n", " {\n", " \"messages\": [\n", - " HumanMessage(\n", - " content=\"Write a research summary of CA wildfires in 2023. Include a chart.\"\n", - " )\n", + " HumanMessage(content=\"Write a brief research report on pikas.\")\n", " ]\n", - " }\n", + " },\n", + " {\"recursion_limit\": 100},\n", ")\n", "results[\"messages\"][-1].pretty_print()" ] diff --git a/examples/multi_agent/hierarchical_agent_teams.ipynb b/examples/multi_agent/hierarchical_agent_teams.ipynb index 7494dede1..801ec2e32 100644 --- a/examples/multi_agent/hierarchical_agent_teams.ipynb +++ b/examples/multi_agent/hierarchical_agent_teams.ipynb @@ -36,7 +36,7 @@ "outputs": [], "source": [ "# %%capture --no-stderr\n", - "# %pip install -U langgraph langchain langchain_openai langsmith" + "# %pip install -U langgraph langchain langchain_openai langchain_experimental" ] }, { @@ -109,7 +109,10 @@ " prelude: Optional[Union[Runnable, Callable]] = None, # Optional required steps\n", ") -> str:\n", " \"\"\"Create a function-calling agent and add it to the graph.\"\"\"\n", - " system_prompt += \"\\nYou are one of the following team members: {team_members}\"\n", + " system_prompt += \"\\nWork autonomously according to your specialty, using the tools available to you.\"\n", + " \" Do not ask for clarification.\"\n", + " \" Your other team members (and other teams) will collaborate with you with their own specialties.\"\n", + " \" You are chosen for a reason! You are one of the following team members: {team_members}.\"\n", " prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\n", @@ -151,7 +154,7 @@ " \"anyOf\": [\n", " {\"enum\": options},\n", " ],\n", - " }\n", + " },\n", " },\n", " \"required\": [\"next\"],\n", " },\n", @@ -262,14 +265,17 @@ " \"Search\",\n", " llm,\n", " [tavily_tool],\n", - " \"You are a research assistant who can search for things using a search engine.\",\n", + " \"You are a research assistant who can search for up-to-date info\"\n", + " \" using the tavily search engine.\",\n", ")\n", "create_worker_agent(\n", " research_graph,\n", " \"Web Scraper\",\n", " llm,\n", - " [tavily_tool],\n", - " \"You are a research assistant who can scrape specified urls for more detailed information.\",\n", + " [scrape_webpages],\n", + " \"You are a research assistant who can scrape\"\n", + " \" specified urls for more detailed information using\"\n", + " \" the scrape_webpages function.\",\n", ")\n", "supervisor_node = create_team_supervisor(\n", " research_graph,\n", @@ -321,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "id": "202806d6-80bf-4153-ac16-ed6059236f2a", "metadata": {}, "outputs": [], @@ -330,6 +336,9 @@ "from tempfile import TemporaryDirectory\n", "from typing import Dict\n", "\n", + "from langchain_experimental.utilities import PythonREPL\n", + "from typing_extensions import TypedDict\n", + "\n", "_TEMP_DIRECTORY = TemporaryDirectory()\n", "WORKING_DIRECTORY = Path(_TEMP_DIRECTORY.name)\n", "\n", @@ -340,9 +349,6 @@ " file_name: Annotated[str, \"File path to save the outline.\"],\n", ") -> Annotated[str, \"Path of the saved outline file.\"]:\n", " \"\"\"Create and save an outline.\"\"\"\n", - " if len(points) != len(subpoints):\n", - " raise ValueError(\"Each main point must have a corresponding list of subpoints.\")\n", - "\n", " with (WORKING_DIRECTORY / file_name).open(\"w\") as file:\n", " for i, point in enumerate(points):\n", " file.write(f\"{i + 1}. {point}\\n\")\n", @@ -406,48 +412,27 @@ " return f\"Document edited and saved to {file_name}\"\n", "\n", "\n", + "# Warning: This executes code locally, which can be unsafe when not sandboxed\n", + "\n", + "repl = PythonREPL()\n", + "\n", + "\n", "@tool\n", - "def create_plot(\n", - " data: Annotated[\n", - " Union[List[float], List[int]],\n", - " \"Numerical values for bar heights or line points.\",\n", - " ],\n", - " file_name: Annotated[str, \"File path to save the figure.\"],\n", - " labels: Annotated[\n", - " Union[List[str], None], \"Bar or point labels, defaults to None.\"\n", - " ] = None,\n", - " title: Annotated[str, \"Title of the plot.\"] = \"Plot\",\n", - " xlabel: Annotated[str, \"Label for the X-axis.\"] = \"X\",\n", - " ylabel: Annotated[str, \"Label for the Y-axis.\"] = \"Y\",\n", - " color: Annotated[Union[str, List[str]], \"Color(s) for the bars or line.\"] = \"blue\",\n", - " plot_type: Annotated[str, \"Type of plot ('bar' or 'line').\"] = \"bar\",\n", - ") -> Annotated[str, \"Path of the saved figure file.\"]:\n", - " \"\"\"Create a line or bar chart.\"\"\"\n", - " if plot_type not in [\"bar\", \"line\"]:\n", - " raise ValueError(\"Invalid plot_type. Expected 'bar' or 'line'.\")\n", - "\n", - " fig, ax = plt.subplots(figsize=(10, 6))\n", - " x_positions = range(len(data))\n", - "\n", - " if labels and len(labels) == len(data):\n", - " plt.xticks(x_positions, labels)\n", - "\n", - " if plot_type == \"bar\":\n", - " ax.bar(x_positions, data, color=color)\n", - " elif plot_type == \"line\":\n", - " ax.plot(x_positions, data, color=color, marker=\"o\") # 'o' for circular markers\n", - "\n", - " ax.set_title(title)\n", - " ax.set_xlabel(xlabel)\n", - " ax.set_ylabel(ylabel)\n", - " fig.savefig(str(WORKING_DIRECTORY / file_name))\n", - " plt.close(fig)\n", - " return f'Saved \"{title}\" plot to {file_name}'" + "def python_repl(\n", + " code: Annotated[str, \"The python code to execute to generate your chart.\"]\n", + "):\n", + " \"\"\"Use this to execute python code. If you want to see the output of a value,\n", + " you should print it out with `print(...)`. This is visible to the user.\"\"\"\n", + " try:\n", + " 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}\"" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "id": "1bcdbf44-9481-430c-8429-fa142ed8a626", "metadata": {}, "outputs": [], @@ -517,7 +502,7 @@ " authoring_graph,\n", " \"Generate Charts\",\n", " llm,\n", - " [read_document, create_plot],\n", + " [read_document, python_repl],\n", " \"You are a data viz expert tasked with generating charts for a research project.\"\n", " \"{current_files}\",\n", ")\n", @@ -556,7 +541,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "id": "95ae7e52-92ed-41a3-88c4-21b6d7c8b041", "metadata": {}, "outputs": [], @@ -601,53 +586,24 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", "metadata": {}, "outputs": [ { - "ename": "NameError", - "evalue": "name 'subpoints' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[13], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43msuper_graph\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mResearch and write a report about the climate impacts\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m 3\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m on crop yields in Bangladesh in 2023. Write the paper and include plots.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 4\u001b[0m \u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mrecursion_limit\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m150\u001b[39;49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 5\u001b[0m \u001b[43m)\u001b[49m\n\u001b[1;32m 6\u001b[0m results[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessages\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m]\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:531\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 521\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 522\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 523\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 529\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 530\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 531\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 532\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 533\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 534\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 535\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 536\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 537\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 538\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 539\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:567\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 559\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 560\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 566\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 567\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 568\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 569\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 570\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 571\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 572\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 573\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 574\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 575\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1486\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1484\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1485\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1486\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1487\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1488\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:342\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m done, inflight \u001b[38;5;241m=\u001b[39m concurrent\u001b[38;5;241m.\u001b[39mfutures\u001b[38;5;241m.\u001b[39mwait(\n\u001b[1;32m 333\u001b[0m [\n\u001b[1;32m 334\u001b[0m executor\u001b[38;5;241m.\u001b[39msubmit(proc\u001b[38;5;241m.\u001b[39minvoke, \u001b[38;5;28minput\u001b[39m, config)\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 338\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstep_timeout,\n\u001b[1;32m 339\u001b[0m )\n\u001b[1;32m 341\u001b[0m \u001b[38;5;66;03m# interrupt on failure or timeout\u001b[39;00m\n\u001b[0;32m--> 342\u001b[0m \u001b[43m_interrupt_or_proceed\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdone\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minflight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 344\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[1;32m 345\u001b[0m _apply_writes(checkpoint, channels, pending_writes, config, step \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m)\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:650\u001b[0m, in \u001b[0;36m_interrupt_or_proceed\u001b[0;34m(done, inflight, step)\u001b[0m\n\u001b[1;32m 648\u001b[0m inflight\u001b[38;5;241m.\u001b[39mpop()\u001b[38;5;241m.\u001b[39mcancel()\n\u001b[1;32m 649\u001b[0m \u001b[38;5;66;03m# raise the exception\u001b[39;00m\n\u001b[0;32m--> 650\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[1;32m 651\u001b[0m \u001b[38;5;66;03m# TODO this is where retry of an entire step would happen\u001b[39;00m\n\u001b[1;32m 653\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m inflight:\n\u001b[1;32m 654\u001b[0m \u001b[38;5;66;03m# if we got here means we timed out\u001b[39;00m\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58\u001b[0m, in \u001b[0;36m_WorkItem.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 58\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfuture\u001b[38;5;241m.\u001b[39mset_exception(exc)\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:3868\u001b[0m, in \u001b[0;36mRunnableBindingBase.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3862\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 3863\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 3864\u001b[0m \u001b[38;5;28minput\u001b[39m: Input,\n\u001b[1;32m 3865\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 3866\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Optional[Any],\n\u001b[1;32m 3867\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Output:\n\u001b[0;32m-> 3868\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3869\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3870\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_merge_configs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3871\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m{\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3872\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:531\u001b[0m, in \u001b[0;36mPregel.invoke\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 521\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 522\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 523\u001b[0m \u001b[38;5;28minput\u001b[39m: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 528\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 529\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]:\n\u001b[1;32m 530\u001b[0m latest: Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m--> 531\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 532\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 533\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 534\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mis\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mnot\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutput\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 535\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 536\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 537\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 538\u001b[0m \u001b[43m \u001b[49m\u001b[43mlatest\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n\u001b[1;32m 539\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m latest\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:567\u001b[0m, in \u001b[0;36mPregel.transform\u001b[0;34m(self, input, config, output_keys, input_keys, **kwargs)\u001b[0m\n\u001b[1;32m 558\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mtransform\u001b[39m(\n\u001b[1;32m 559\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 560\u001b[0m \u001b[38;5;28minput\u001b[39m: Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Any,\n\u001b[1;32m 566\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Iterator[Union[\u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Any], Any]]:\n\u001b[0;32m--> 567\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform_stream_with_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 568\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 569\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_transform\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 570\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 571\u001b[0m \u001b[43m \u001b[49m\u001b[43moutput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moutput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 572\u001b[0m \u001b[43m \u001b[49m\u001b[43minput_keys\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minput_keys\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 573\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 574\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m:\u001b[49m\n\u001b[1;32m 575\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01myield\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mchunk\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:1486\u001b[0m, in \u001b[0;36mRunnable._transform_stream_with_config\u001b[0;34m(self, input, transformer, config, run_type, **kwargs)\u001b[0m\n\u001b[1;32m 1484\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 1485\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m-> 1486\u001b[0m chunk: Output \u001b[38;5;241m=\u001b[39m context\u001b[38;5;241m.\u001b[39mrun(\u001b[38;5;28mnext\u001b[39m, iterator) \u001b[38;5;66;03m# type: ignore\u001b[39;00m\n\u001b[1;32m 1487\u001b[0m \u001b[38;5;28;01myield\u001b[39;00m chunk\n\u001b[1;32m 1488\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m final_output_supported:\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:342\u001b[0m, in \u001b[0;36mPregel._transform\u001b[0;34m(self, input, run_manager, config, input_keys, output_keys)\u001b[0m\n\u001b[1;32m 332\u001b[0m done, inflight \u001b[38;5;241m=\u001b[39m concurrent\u001b[38;5;241m.\u001b[39mfutures\u001b[38;5;241m.\u001b[39mwait(\n\u001b[1;32m 333\u001b[0m [\n\u001b[1;32m 334\u001b[0m executor\u001b[38;5;241m.\u001b[39msubmit(proc\u001b[38;5;241m.\u001b[39minvoke, \u001b[38;5;28minput\u001b[39m, config)\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 338\u001b[0m timeout\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstep_timeout,\n\u001b[1;32m 339\u001b[0m )\n\u001b[1;32m 341\u001b[0m \u001b[38;5;66;03m# interrupt on failure or timeout\u001b[39;00m\n\u001b[0;32m--> 342\u001b[0m \u001b[43m_interrupt_or_proceed\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdone\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minflight\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstep\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 344\u001b[0m \u001b[38;5;66;03m# apply writes to channels\u001b[39;00m\n\u001b[1;32m 345\u001b[0m _apply_writes(checkpoint, channels, pending_writes, config, step \u001b[38;5;241m+\u001b[39m \u001b[38;5;241m1\u001b[39m)\n", - "File \u001b[0;32m~/code/lc/langgraph/langgraph/pregel/__init__.py:650\u001b[0m, in \u001b[0;36m_interrupt_or_proceed\u001b[0;34m(done, inflight, step)\u001b[0m\n\u001b[1;32m 648\u001b[0m inflight\u001b[38;5;241m.\u001b[39mpop()\u001b[38;5;241m.\u001b[39mcancel()\n\u001b[1;32m 649\u001b[0m \u001b[38;5;66;03m# raise the exception\u001b[39;00m\n\u001b[0;32m--> 650\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n\u001b[1;32m 651\u001b[0m \u001b[38;5;66;03m# TODO this is where retry of an entire step would happen\u001b[39;00m\n\u001b[1;32m 653\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m inflight:\n\u001b[1;32m 654\u001b[0m \u001b[38;5;66;03m# if we got here means we timed out\u001b[39;00m\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/concurrent/futures/thread.py:58\u001b[0m, in \u001b[0;36m_WorkItem.run\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m\n\u001b[1;32m 57\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m---> 58\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 59\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 60\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfuture\u001b[38;5;241m.\u001b[39mset_exception(exc)\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:3868\u001b[0m, in \u001b[0;36mRunnableBindingBase.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 3862\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21minvoke\u001b[39m(\n\u001b[1;32m 3863\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 3864\u001b[0m \u001b[38;5;28minput\u001b[39m: Input,\n\u001b[1;32m 3865\u001b[0m config: Optional[RunnableConfig] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 3866\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs: Optional[Any],\n\u001b[1;32m 3867\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Output:\n\u001b[0;32m-> 3868\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbound\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 3869\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3870\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_merge_configs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3871\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43m{\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m}\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 3872\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/runnables/base.py:2034\u001b[0m, in \u001b[0;36mRunnableSequence.invoke\u001b[0;34m(self, input, config)\u001b[0m\n\u001b[1;32m 2032\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 2033\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i, step \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28menumerate\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39msteps):\n\u001b[0;32m-> 2034\u001b[0m \u001b[38;5;28minput\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[43mstep\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minvoke\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2035\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2036\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;66;43;03m# mark each step as a child run\u001b[39;49;00m\n\u001b[1;32m 2037\u001b[0m \u001b[43m \u001b[49m\u001b[43mpatch_config\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 2038\u001b[0m \u001b[43m \u001b[49m\u001b[43mconfig\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mseq:step:\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;241;43m+\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2039\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 2040\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 2041\u001b[0m \u001b[38;5;66;03m# finish the root run\u001b[39;00m\n\u001b[1;32m 2042\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/chains/base.py:162\u001b[0m, in \u001b[0;36mChain.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 160\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 161\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n\u001b[0;32m--> 162\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 163\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_end(outputs)\n\u001b[1;32m 164\u001b[0m final_outputs: Dict[\u001b[38;5;28mstr\u001b[39m, Any] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mprep_outputs(\n\u001b[1;32m 165\u001b[0m inputs, outputs, return_only_outputs\n\u001b[1;32m 166\u001b[0m )\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/chains/base.py:156\u001b[0m, in \u001b[0;36mChain.invoke\u001b[0;34m(self, input, config, **kwargs)\u001b[0m\n\u001b[1;32m 149\u001b[0m run_manager \u001b[38;5;241m=\u001b[39m callback_manager\u001b[38;5;241m.\u001b[39mon_chain_start(\n\u001b[1;32m 150\u001b[0m dumpd(\u001b[38;5;28mself\u001b[39m),\n\u001b[1;32m 151\u001b[0m inputs,\n\u001b[1;32m 152\u001b[0m name\u001b[38;5;241m=\u001b[39mrun_name,\n\u001b[1;32m 153\u001b[0m )\n\u001b[1;32m 154\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 155\u001b[0m outputs \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m--> 156\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_call\u001b[49m\u001b[43m(\u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 157\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 158\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_call(inputs)\n\u001b[1;32m 159\u001b[0m )\n\u001b[1;32m 160\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mBaseException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 161\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_chain_error(e)\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1376\u001b[0m, in \u001b[0;36mAgentExecutor._call\u001b[0;34m(self, inputs, run_manager)\u001b[0m\n\u001b[1;32m 1374\u001b[0m \u001b[38;5;66;03m# We now enter the agent loop (until it returns something).\u001b[39;00m\n\u001b[1;32m 1375\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_should_continue(iterations, time_elapsed):\n\u001b[0;32m-> 1376\u001b[0m next_step_output \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_take_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1377\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1378\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1379\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1380\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1381\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1382\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1383\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(next_step_output, AgentFinish):\n\u001b[1;32m 1384\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_return(\n\u001b[1;32m 1385\u001b[0m next_step_output, intermediate_steps, run_manager\u001b[38;5;241m=\u001b[39mrun_manager\n\u001b[1;32m 1386\u001b[0m )\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1102\u001b[0m, in \u001b[0;36mAgentExecutor._take_next_step\u001b[0;34m(self, name_to_tool_map, color_mapping, inputs, intermediate_steps, run_manager)\u001b[0m\n\u001b[1;32m 1093\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_take_next_step\u001b[39m(\n\u001b[1;32m 1094\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1095\u001b[0m name_to_tool_map: Dict[\u001b[38;5;28mstr\u001b[39m, BaseTool],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1099\u001b[0m run_manager: Optional[CallbackManagerForChainRun] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1100\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[AgentFinish, List[Tuple[AgentAction, \u001b[38;5;28mstr\u001b[39m]]]:\n\u001b[1;32m 1101\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_consume_next_step(\n\u001b[0;32m-> 1102\u001b[0m \u001b[43m[\u001b[49m\n\u001b[1;32m 1103\u001b[0m \u001b[43m \u001b[49m\u001b[43ma\u001b[49m\n\u001b[1;32m 1104\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_iter_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1105\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1106\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1107\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1108\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 1112\u001b[0m )\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1102\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1093\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_take_next_step\u001b[39m(\n\u001b[1;32m 1094\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[1;32m 1095\u001b[0m name_to_tool_map: Dict[\u001b[38;5;28mstr\u001b[39m, BaseTool],\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 1099\u001b[0m run_manager: Optional[CallbackManagerForChainRun] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 1100\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Union[AgentFinish, List[Tuple[AgentAction, \u001b[38;5;28mstr\u001b[39m]]]:\n\u001b[1;32m 1101\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_consume_next_step(\n\u001b[0;32m-> 1102\u001b[0m \u001b[43m[\u001b[49m\n\u001b[1;32m 1103\u001b[0m \u001b[43m \u001b[49m\u001b[43ma\u001b[49m\n\u001b[1;32m 1104\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43ma\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_iter_next_step\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1105\u001b[0m \u001b[43m \u001b[49m\u001b[43mname_to_tool_map\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1106\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor_mapping\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1107\u001b[0m \u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1108\u001b[0m \u001b[43m \u001b[49m\u001b[43mintermediate_steps\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1109\u001b[0m \u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1110\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1111\u001b[0m \u001b[43m \u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 1112\u001b[0m )\n", - "File \u001b[0;32m~/.pyenv/versions/3.11.2/lib/python3.11/site-packages/langchain/agents/agent.py:1198\u001b[0m, in \u001b[0;36mAgentExecutor._iter_next_step\u001b[0;34m(self, name_to_tool_map, color_mapping, inputs, intermediate_steps, run_manager)\u001b[0m\n\u001b[1;32m 1196\u001b[0m tool_run_kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mllm_prefix\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 1197\u001b[0m \u001b[38;5;66;03m# We then call the tool on the tool input to get an observation\u001b[39;00m\n\u001b[0;32m-> 1198\u001b[0m observation \u001b[38;5;241m=\u001b[39m \u001b[43mtool\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[1;32m 1199\u001b[0m \u001b[43m \u001b[49m\u001b[43magent_action\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtool_input\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1200\u001b[0m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1201\u001b[0m \u001b[43m \u001b[49m\u001b[43mcolor\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcolor\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1202\u001b[0m \u001b[43m \u001b[49m\u001b[43mcallbacks\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_child\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[1;32m 1203\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_run_kwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 1204\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 1205\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1206\u001b[0m tool_run_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39magent\u001b[38;5;241m.\u001b[39mtool_run_logging_kwargs()\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:374\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 372\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (\u001b[38;5;167;01mException\u001b[39;00m, \u001b[38;5;167;01mKeyboardInterrupt\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 373\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_tool_error(e)\n\u001b[0;32m--> 374\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m e\n\u001b[1;32m 375\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 376\u001b[0m run_manager\u001b[38;5;241m.\u001b[39mon_tool_end(\n\u001b[1;32m 377\u001b[0m \u001b[38;5;28mstr\u001b[39m(observation), color\u001b[38;5;241m=\u001b[39mcolor, name\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs\n\u001b[1;32m 378\u001b[0m )\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:346\u001b[0m, in \u001b[0;36mBaseTool.run\u001b[0;34m(self, tool_input, verbose, start_color, color, callbacks, tags, metadata, run_name, **kwargs)\u001b[0m\n\u001b[1;32m 343\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 344\u001b[0m tool_args, tool_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_to_args_and_kwargs(parsed_input)\n\u001b[1;32m 345\u001b[0m observation \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m--> 346\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_run\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_args\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_manager\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrun_manager\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mtool_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 347\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_arg_supported\n\u001b[1;32m 348\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_run(\u001b[38;5;241m*\u001b[39mtool_args, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mtool_kwargs)\n\u001b[1;32m 349\u001b[0m )\n\u001b[1;32m 350\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m ToolException \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[1;32m 351\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mhandle_tool_error:\n", - "File \u001b[0;32m~/code/lc/langchain/libs/core/langchain_core/tools.py:641\u001b[0m, in \u001b[0;36mStructuredTool._run\u001b[0;34m(self, run_manager, *args, **kwargs)\u001b[0m\n\u001b[1;32m 632\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc:\n\u001b[1;32m 633\u001b[0m new_argument_supported \u001b[38;5;241m=\u001b[39m signature(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc)\u001b[38;5;241m.\u001b[39mparameters\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcallbacks\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 634\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m (\n\u001b[1;32m 635\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mfunc(\n\u001b[1;32m 636\u001b[0m \u001b[38;5;241m*\u001b[39margs,\n\u001b[1;32m 637\u001b[0m callbacks\u001b[38;5;241m=\u001b[39mrun_manager\u001b[38;5;241m.\u001b[39mget_child() \u001b[38;5;28;01mif\u001b[39;00m run_manager \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[1;32m 638\u001b[0m \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[1;32m 639\u001b[0m )\n\u001b[1;32m 640\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m new_argument_supported\n\u001b[0;32m--> 641\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 642\u001b[0m )\n\u001b[1;32m 643\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mNotImplementedError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTool does not support sync\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", - "Cell \u001b[0;32mIn[10], line 15\u001b[0m, in \u001b[0;36mcreate_outline\u001b[0;34m(points, file_name)\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;129m@tool\u001b[39m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcreate_outline\u001b[39m(\n\u001b[1;32m 11\u001b[0m points: Annotated[List[\u001b[38;5;28mstr\u001b[39m], \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mList of main points or sections.\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[1;32m 12\u001b[0m file_name: Annotated[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mFile path to save the outline.\u001b[39m\u001b[38;5;124m\"\u001b[39m],\n\u001b[1;32m 13\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Annotated[\u001b[38;5;28mstr\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mPath of the saved outline file.\u001b[39m\u001b[38;5;124m\"\u001b[39m]:\n\u001b[1;32m 14\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Create and save an outline.\"\"\"\u001b[39;00m\n\u001b[0;32m---> 15\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(points) \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mlen\u001b[39m(\u001b[43msubpoints\u001b[49m):\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEach main point must have a corresponding list of subpoints.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 18\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m (WORKING_DIRECTORY \u001b[38;5;241m/\u001b[39m file_name)\u001b[38;5;241m.\u001b[39mopen(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mw\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m file:\n", - "\u001b[0;31mNameError\u001b[0m: name 'subpoints' is not defined" - ] + "data": { + "text/plain": [ + "HumanMessage(content='The document titled \"North American Sturgeons: Overview\" has been successfully created and saved as `North_American_Sturgeons_Overview.txt`. This document includes key information on the distribution, habitat, and conservation status of various sturgeon species found in North America, as well as a simplified table summarizing these details.\\n\\nFor further reference or detailed information, you can access and read the document. If you require any additional information or updates to the document, please let me know.', name='Author Docs')" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "results = super_graph.invoke(\n", - " \"Research and write a report about the climate impacts\"\n", - " \" on crop yields in Bangladesh in 2023. Write the paper and include plots.\",\n", + " \"Write a brief research report on the North American sturgeon. Include a chart.\",\n", " {\"recursion_limit\": 150},\n", ")\n", "results[\"messages\"][-1]"

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 z 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%0D1yTG0bvn+d-qbW*67M|VI-N?UV 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