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": {