diff --git a/README.md b/README.md index a05acca1c..ffdba3b2a 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,6 @@ functions = [format_tool_to_openai_function(t) for t in tools] model = model.bind_functions(functions) ``` - ### Define the agent state The main type of graph in `langgraph` is the `StatefulGraph`. @@ -293,7 +292,7 @@ Output from node '__end__': ### Streaming LLM Tokens -You can also access the LLM tokens as they are produced by each node. +You can also access the LLM tokens as they are produced by each node. In this case only the "agent" node produces LLM tokens. In order for this to work properly, you must be using an LLM that supports streaming as well as have set it when constructing the LLM (e.g. `ChatOpenAI(model="gpt-3.5-turbo-1106", streaming=True)`) @@ -417,10 +416,9 @@ Langchain Expression Language allows you to easily define chains (DAGs) but does ## Examples - ### ChatAgentExecutor: with function calling -This agent executor takes a list of messages as input and outputs a list of messages. +This agent executor takes a list of messages as input and outputs a list of messages. All agent state is represented as a list of messages. This specifically uses OpenAI function calling. This is recommended agent executor for newer chat based models that support function calling. @@ -431,6 +429,7 @@ This is recommended agent executor for newer chat based models that support func **Modifications** We also have a lot of examples highlighting how to slightly modify the base chat agent executor. These all build off the [getting started notebook](examples/chat_agent_executor_with_function_calling/base.ipynb) so it is recommended you start with that first. + - [Human-in-the-loop](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/human-in-the-loop.ipynb): How to add a human-in-the-loop component - [Force calling a tool first](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/force-calling-a-tool-first.ipynb): How to always call a specific tool first - [Respond in a specific format](https://github.com/langchain-ai/langgraph/blob/main/examples/chat_agent_executor_with_function_calling/respond-in-format.ipynb): How to force the agent to respond in a specific format @@ -447,13 +446,16 @@ This agent executor uses existing LangChain agents. **Modifications** We also have a lot of examples highlighting how to slightly modify the base chat agent executor. These all build off the [getting started notebook](examples/agent_executor/base.ipynb) so it is recommended you start with that first. + - [Human-in-the-loop](https://github.com/langchain-ai/langgraph/blob/main/examples/agent_executor/human-in-the-loop.ipynb): How to add a human-in-the-loop component - [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 - ### Multi-agent Examples +- [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 @@ -482,7 +484,6 @@ This class is responsible for constructing the graph. It exposes an interface inspired by [NetworkX](https://networkx.org/documentation/latest/). This graph is parameterized by a state object that it passes around to each node. - #### `__init__` ```python @@ -638,7 +639,6 @@ It can be used in two places: - As the `end_key` in `add_edge` - As a value in `conditional_edge_mapping` as passed to `add_conditional_edges` - ## Prebuilt Examples There are also a few methods we've added to make it easy to use common, prebuilt graphs and components. diff --git a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb b/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb deleted file mode 100644 index 71ddb19a1..000000000 --- a/examples/advanced_agents/multi-agent/agent-simulation-evaluation.ipynb +++ /dev/null @@ -1,409 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", - "metadata": {}, - "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", - "\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", - "\n", - "![diagram](./img/virtual_user_diagram.png)\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": "6ef4528d-6b2a-47c7-98b5-50f14984a304", - "metadata": {}, - "source": [ - "## 1. Define Chat Bot\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", - "\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)." - ] - }, - { - "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", - "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 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", - "\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", - "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", - "## 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\")" - ] - }, - { - "cell_type": "markdown", - "id": "2e0bd26e-8c1d-471d-9fef-d95dc0163491", - "metadata": {}, - "source": [ - "## 3. 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": 13, - "id": "4d495ccc-0f5f-4194-89e5-15dccbbb7412", - "metadata": {}, - "outputs": [], - "source": [ - "simulation = create_simulation(my_chat_bot)" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "fa91e733-3493-43c2-ba21-171cf78deef8", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Skipping write for channel input which has no readers\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()" - ] - }, - { - "cell_type": "markdown", - "id": "73ff30e4-1992-4bc9-834d-f4c08b281d20", - "metadata": {}, - "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", - "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": "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", - "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." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dde4f2b5-cfe8-4ff0-99ea-fe2c5fed70c0", - "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/multi_agent/agent_supervisor.ipynb b/examples/multi_agent/agent_supervisor.ipynb new file mode 100644 index 000000000..4895d1486 --- /dev/null +++ b/examples/multi_agent/agent_supervisor.ipynb @@ -0,0 +1,419 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a3e3ebc4-57af-4fe4-bdd3-36aff67bf276", + "metadata": {}, + "source": [ + "## Agent Supervisor\n", + "\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", + "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 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": 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": "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": 3, + "id": "f04c6778-403b-4b49-9b93-678e910d5cec", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, List, Tuple, Union\n", + "\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", + "Define a helper function below, which make it easier to add new agent worker nodes." + ] + }, + { + "cell_type": "code", + "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", + "from langgraph.graph import END, StateGraph\n", + "\n", + "\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", + " \"system\",\n", + " system_prompt,\n", + " ),\n", + " MessagesPlaceholder(variable_name=\"messages\"),\n", + " MessagesPlaceholder(variable_name=\"agent_scratchpad\"),\n", + " ]\n", + " )\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", + " # 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)" + ] + }, + { + "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": 5, + "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_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", + "\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", + " [python_repl_tool],\n", + " \"You may generate safe python code to analyze data \"\n", + " \"and generate charts using matplotlib.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d6374825-912f-40c9-910d-afa267b401bf", + "metadata": {}, + "source": [ + "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, + "id": "17c108a0-6dc3-46fd-a5e6-a1fcfad5458a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser\n", + "\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", + ")\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", + "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": 7, + "id": "14778e86-077b-4e6a-893c-400e59b0cdbf", + "metadata": {}, + "outputs": [], + "source": [ + "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": 8, + "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 code `print('Hello, World!')` was executed, and the output is:\n", + "\n", + "```\n", + "Hello, World!\n", + "```\n" + ] + } + ], + "source": [ + "results = graph.invoke(\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": 12, + "id": "45a92dfd-0e11-47f5-aad4-b68d24990e34", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "# Research Report on Pikas\n", + "\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", + "## 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" + ] + } + ], + "source": [ + "results = graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(content=\"Write a brief research report on pikas.\")\n", + " ]\n", + " },\n", + " {\"recursion_limit\": 100},\n", + ")\n", + "results[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d363d2c-e0da-4cce-ba47-ad2aa9df0fef", + "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/multi_agent/hierarchical_agent_teams.ipynb b/examples/multi_agent/hierarchical_agent_teams.ipynb new file mode 100644 index 000000000..801ec2e32 --- /dev/null +++ b/examples/multi_agent/hierarchical_agent_teams.ipynb @@ -0,0 +1,642 @@ +{ + "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 langchain_experimental" + ] + }, + { + "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 += \"\\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", + " \"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 up-to-date info\"\n", + " \" using the tavily search engine.\",\n", + ")\n", + "create_worker_agent(\n", + " research_graph,\n", + " \"Web Scraper\",\n", + " llm,\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", + " 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": 6, + "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", + "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", + "\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", + " 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", + "# 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}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "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, python_repl],\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": 8, + "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": 9, + "id": "6b8badbf-d728-44bd-a2a7-5b4e587c92fe", + "metadata": {}, + "outputs": [ + { + "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", + " \"Write a brief research report on the North American sturgeon. Include a chart.\",\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/multi_agent/img/hierarchical-diagram.png b/examples/multi_agent/img/hierarchical-diagram.png new file mode 100644 index 000000000..b3fa81275 Binary files /dev/null and b/examples/multi_agent/img/hierarchical-diagram.png differ diff --git a/examples/multi_agent/img/supervisor-diagram.png b/examples/multi_agent/img/supervisor-diagram.png new file mode 100644 index 000000000..84497aa4c Binary files /dev/null and b/examples/multi_agent/img/supervisor-diagram.png differ 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..3974788d2 --- /dev/null +++ b/examples/multi_agent/multi-agent-collaboration.ipynb @@ -0,0 +1,397 @@ +{ + "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 within a single domain, but even using powerful models like `gpt-4`, it can be less effective at using many tools. \n", + "\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 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." + ] + }, + { + "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": "code", + "execution_count": null, + "id": "075c91c3-c249-471d-b259-41975faa83fb", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "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": 3, + "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 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", + " \"\"\"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", + " 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 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", + " # Based on the continue condition\n", + " # we know the last message involves a function call\n", + " last_message = messages[-1]\n", + " # We construct an ToolInvocation from the function_call\n", + " tool_input = json.loads(\n", + " last_message.additional_kwargs[\"function_call\"][\"arguments\"]\n", + " )\n", + " # We can pass single-arg inputs by value\n", + " if len(tool_input) == 1 and \"__arg1\" in tool_input:\n", + " tool_input = next(iter(tool_input.values()))\n", + " tool_name = last_message.additional_kwargs[\"function_call\"][\"name\"]\n", + " action = ToolInvocation(\n", + " tool=tool_name,\n", + " tool_input=tool_input,\n", + " )\n", + " # We call the tool_executor and get back a response\n", + " response = tool_executor.invoke(action)\n", + " # We use the response to create a FunctionMessage\n", + " function_message = FunctionMessage(\n", + " content=f\"{tool_name} response: {str(response)}\", name=action.tool\n", + " )\n", + " # We return a list, because this will get added to the existing list\n", + " return {\"messages\": [function_message]}" + ] + }, + { + "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": 4, + "id": "4dce3901-6ad5-4df5-8528-6e865cf96cb0", + "metadata": {}, + "outputs": [], + "source": [ + "import operator\n", + "from typing import Annotated, List, Sequence, Tuple, TypedDict, Union\n", + "\n", + "from langchain.agents import create_openai_functions_agent\n", + "from langchain.tools.render import format_tool_to_openai_function\n", + "from langchain_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", + "\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", + "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", + "llm = ChatOpenAI(model=\"gpt-4-1106-preview\")\n", + "add_agent_node(\n", + " workflow,\n", + " \"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(\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 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", + " router,\n", + " {\"continue\": \"Chart Generator\", \"call_tool\": \"call_tool\", \"end\": END},\n", + ")\n", + "workflow.add_conditional_edges(\n", + " \"Chart Generator\",\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_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()" + ] + }, + { + "cell_type": "markdown", + "id": "8c9447e7-9ab6-43eb-8ae6-9b52f8ba8425", + "metadata": {}, + "source": [ + "## Invoke\n", + "\n", + "With the graph created, you can invoke it! Let's have it chart some stats for us." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "176a99b0-b457-45cf-8901-90facaa852da", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Python REPL can execute arbitrary code. Use with caution.\n" + ] + }, + { + "data": { + "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": [ + "result = graph.invoke(\n", + " {\n", + " \"messages\": [\n", + " HumanMessage(\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", + " # 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": "23cc22c7-33a8-4399-91e4-ac1abf8f0fec", + "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 +}