From cc0f6e5667b63bbfd1272ff1972abc2826bec6ec Mon Sep 17 00:00:00 2001 From: Pyoungwon Seo <485field@gmail.com> Date: Mon, 20 Jan 2025 22:37:16 +0900 Subject: [PATCH 1/2] branching --- .../11-LangGraph-Branching.ipynb | 896 ++++++++++++++++++ .../assets/11-langgraph-branching-graph.png | Bin 0 -> 45796 bytes 2 files changed, 896 insertions(+) create mode 100644 17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb create mode 100644 17-LangGraph/01-Core-Features/assets/11-langgraph-branching-graph.png diff --git a/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb b/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb new file mode 100644 index 000000000..a1bcffbbc --- /dev/null +++ b/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb @@ -0,0 +1,896 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "269ce58d", + "metadata": {}, + "source": [ + "# Branch Creation for Parallel Node Execution\n", + "\n", + "- Author: [seofield](https://github.com/seofield)\n", + "- Design: \n", + "- Peer Review: \n", + "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n", + "\n", + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/99-TEMPLATE/00-BASE-TEMPLATE-EXAMPLE.ipynb) [![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/99-TEMPLATE/00-BASE-TEMPLATE-EXAMPLE.ipynb)\n", + "\n", + "## Overview\n", + "\n", + "![branching-graph](./assets/11-langgraph-branching-graph.png)\n", + "\n", + "Parallel execution of nodes is essential for improving the overall performance of graph-based workflows. `LangGraph` provides native support for parallel node execution, significantly enhancing the efficiency of workflows built with this framework.\n", + "\n", + "This parallelization is achieved using **fan-out** and **fan-in** mechanisms, utilizing both standard edges and `conditional_edges`.\n", + "\n", + "### Table of Contents\n", + "\n", + "- [Overview](#overview)\n", + "- [Parallel Node Fan-out and Fan-in](#parallel-node-fan-out-and-fan-in)\n", + "- [Fan-out and Fan-in of Parallel Nodes with Additional Steps](#fan-out-and-fan-in-of-parallel-nodes-with-additional-steps)\n", + "- [Conditional Branching](#conditional-branching)\n", + "- [Sorting Based on Reliability of Fan-out Values](#sorting-based-on-reliability-of-fan-out-values)\n", + "\n", + "### References\n", + "\n", + "- [How to create branches for parallel node execution](https://langchain-ai.github.io/langgraph/how-tos/branching/)\n", + "----" + ] + }, + { + "cell_type": "markdown", + "id": "0125f25f", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Setting up your environment is the first step. See the [Environment Setup](https://wikidocs.net/257836) guide for more details.\n", + "\n", + "\n", + "**[Note]**\n", + "\n", + "The langchain-opentutorial is a package of easy-to-use environment setup guidance, useful functions and utilities for tutorials.\n", + "Check out the [`langchain-opentutorial`](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b352a8fe", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install langchain-opentutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f778b21d", + "metadata": {}, + "outputs": [], + "source": [ + "# Install required packages\n", + "from langchain_opentutorial import package\n", + "\n", + "package.install(\n", + " [\n", + " \"langchain\",\n", + " ],\n", + " verbose=False,\n", + " upgrade=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ff3a1c12", + "metadata": {}, + "source": [ + "You can set API keys in a `.env` file or set them manually.\n", + "\n", + "[Note] If you’re not using the `.env` file, no worries! Just enter the keys directly in the cell below, and you’re good to go." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2e6bb264", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment variables have been set successfully.\n" + ] + } + ], + "source": [ + "from dotenv import load_dotenv\n", + "from langchain_opentutorial import set_env\n", + "\n", + "# Attempt to load environment variables from a .env file; if unsuccessful, set them manually.\n", + "if not load_dotenv():\n", + " set_env(\n", + " {\n", + " \"OPENAI_API_KEY\": \"\",\n", + " \"LANGCHAIN_API_KEY\": \"\",\n", + " \"LANGCHAIN_TRACING_V2\": \"false\",\n", + " \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n", + " \"LANGCHAIN_PROJECT\": \"\", # set the project name same as the title\n", + " }\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "2a87109f", + "metadata": {}, + "source": [ + "## Parallel Node Fan-out and Fan-in\n", + "\n", + "**Fan-out / Fan-in**\n", + "\n", + "In parallel processing, **fan-out** and **fan-in** describe the processes of dividing and consolidating tasks.\n", + "\n", + "- **Fan-out (Expansion)**: A large task is divided into smaller, more manageable tasks. For example, when making a pizza, the dough, sauce, and cheese can be prepared independently. Dividing tasks to process them simultaneously is fan-out.\n", + "\n", + "- **Fan-in (Consolidation)**: The divided smaller tasks are brought together to complete the overall task. Just like assembling the prepared ingredients to create a finished pizza, fan-in collects the results of parallel tasks to finalize the process.\n", + "\n", + "In essence, **fan-out** distributes tasks, and **fan-in** gathers the results to produce the final output.\n", + "\n", + "---\n", + "\n", + "This example illustrates a fan-out from `Node A` to `B and C`, followed by a fan-in to `D`.\n", + "\n", + "In the State, the `reducer(add)` operator is specified. This ensures that instead of simply overwriting existing values for a specific key in the State, the values are combined or accumulated. For lists, this means appending the new list to the existing one.\n", + "\n", + "LangGraph uses the `Annotated` type to specify reducer functions for specific keys in the State. This approach allows attaching a reducer function (e.g., `add`) to the type without changing the original type (e.g., `list`) while maintaining compatibility with type checking." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0da53871", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, Any\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "# Define State (using add_messages reducer)\n", + "class State(TypedDict):\n", + " aggregate: Annotated[list, add_messages]\n", + "\n", + "\n", + "# Class for returning node values\n", + "class ReturnNodeValue:\n", + " # Initialization\n", + " def __init__(self, node_secret: str):\n", + " self._value = node_secret\n", + "\n", + " # Updates the state when called\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']}\")\n", + " return {\"aggregate\": [self._value]}\n", + "\n", + "\n", + "# Initialize the state graph\n", + "builder = StateGraph(State)\n", + "\n", + "# Create nodes A through D and assign values\n", + "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", + "builder.add_edge(START, \"a\")\n", + "builder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\n", + "builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n", + "builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n", + "\n", + "# Connect the nodes\n", + "builder.add_edge(\"a\", \"b\")\n", + "builder.add_edge(\"a\", \"c\")\n", + "builder.add_edge(\"b\", \"d\")\n", + "builder.add_edge(\"c\", \"d\")\n", + "builder.add_edge(\"d\", END)\n", + "\n", + "# Compile the graph\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "3e3ba6e8", + "metadata": {}, + "source": [ + "Visualize the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "89b5086c", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "6aff4a26", + "metadata": {}, + "source": [ + "You can observe that the values added by each node are **accumulated** through the `reducer`." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "64a87798", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm B to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='04834ec3-a39e-4713-9662-12bda90f5acf')]\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='04834ec3-a39e-4713-9662-12bda90f5acf')]\n", + "Adding I'm D to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='04834ec3-a39e-4713-9662-12bda90f5acf'), HumanMessage(content=\"I'm B\", additional_kwargs={}, response_metadata={}, id='54171388-7830-40a2-b130-46c4d0d0b38b'), HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='9887bbbc-e0d3-4e5e-8e52-a6201e044ad2')]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='04834ec3-a39e-4713-9662-12bda90f5acf'),\n", + " HumanMessage(content=\"I'm B\", additional_kwargs={}, response_metadata={}, id='54171388-7830-40a2-b130-46c4d0d0b38b'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='9887bbbc-e0d3-4e5e-8e52-a6201e044ad2'),\n", + " HumanMessage(content=\"I'm D\", additional_kwargs={}, response_metadata={}, id='acd59483-71f2-416d-8ec6-65be0cee82ea')]}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the Graph\n", + "graph.invoke({\"aggregate\": []}, {\"configurable\": {\"thread_id\": \"foo\"}})" + ] + }, + { + "cell_type": "markdown", + "id": "3ccf5a22", + "metadata": {}, + "source": [ + "### Handling Exceptions During Parallel Processing\n", + "\n", + "LangGraph executes nodes within a \"superstep\" (a complete processing step involving multiple nodes). This means that even if parallel branches are executed simultaneously, the entire superstep is processed in a **transactional** manner.\n", + "\n", + "As a result, if an exception occurs in any of the branches, **no updates** are applied to the state (the entire superstep is rolled back).\n", + "\n", + "> **Superstep**: A complete processing step involving multiple nodes.\n", + "\n", + "![branching-graph](./assets/11-langgraph-branching-graph.png)" + ] + }, + { + "cell_type": "markdown", + "id": "af5ad6dd", + "metadata": {}, + "source": [ + "For tasks prone to errors (e.g., handling unreliable API calls), LangGraph offers two solutions:\n", + "\n", + "1. You can write standard Python code within nodes to catch and handle exceptions directly.\n", + "2. Set up a **[retry_policy](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.graph.CompiledGraph.retry_policy)** to instruct the graph to retry nodes that encounter specific types of exceptions. Only the failed branches are retried, so you don’t need to worry about unnecessary reprocessing.\n", + "\n", + "These features enable complete control over parallel execution and exception handling." + ] + }, + { + "cell_type": "markdown", + "id": "f501c7df", + "metadata": {}, + "source": [ + "## Fan-out and Fan-in of Parallel Nodes with Additional Steps\n", + "\n", + "The previous example demonstrated how to perform `fan-out` and `fan-in` when each path consists of a single step. But what happens when a path contains multiple steps?" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0b0ab72a", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "# Define State (using add_messages reducer)\n", + "class State(TypedDict):\n", + " aggregate: Annotated[list, add_messages]\n", + "\n", + "\n", + "# Class for returning node values\n", + "class ReturnNodeValue:\n", + " # Initialization\n", + " def __init__(self, node_secret: str):\n", + " self._value = node_secret\n", + "\n", + " # Updates the state when called\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']}\")\n", + " return {\"aggregate\": [self._value]}\n", + "\n", + "\n", + "# Initialize the state graph\n", + "builder = StateGraph(State)\n", + "\n", + "# Create and connect nodes\n", + "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", + "builder.add_edge(START, \"a\")\n", + "builder.add_node(\"b1\", ReturnNodeValue(\"I'm B1\"))\n", + "builder.add_node(\"b2\", ReturnNodeValue(\"I'm B2\"))\n", + "builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n", + "builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n", + "builder.add_edge(\"a\", \"b1\")\n", + "builder.add_edge(\"a\", \"c\")\n", + "builder.add_edge(\"b1\", \"b2\")\n", + "builder.add_edge([\"b2\", \"c\"], \"d\")\n", + "builder.add_edge(\"d\", END)\n", + "\n", + "# Compile the graph\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "abbfdf81", + "metadata": {}, + "source": [ + "Visualize the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b6abb4f4", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "bcd2d8ad", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm B1 to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='e0e0b30d-2611-41ec-a735-0e8835b45205')]\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='e0e0b30d-2611-41ec-a735-0e8835b45205')]\n", + "Adding I'm B2 to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='e0e0b30d-2611-41ec-a735-0e8835b45205'), HumanMessage(content=\"I'm B1\", additional_kwargs={}, response_metadata={}, id='00c18d89-67b8-4163-9783-7e20bfb73059'), HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='30287806-99a5-4d83-96d5-5c350bc618a8')]\n", + "Adding I'm D to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='e0e0b30d-2611-41ec-a735-0e8835b45205'), HumanMessage(content=\"I'm B1\", additional_kwargs={}, response_metadata={}, id='00c18d89-67b8-4163-9783-7e20bfb73059'), HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='30287806-99a5-4d83-96d5-5c350bc618a8'), HumanMessage(content=\"I'm B2\", additional_kwargs={}, response_metadata={}, id='36d71515-3960-48ee-ab9a-e4482c33090f')]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='e0e0b30d-2611-41ec-a735-0e8835b45205'),\n", + " HumanMessage(content=\"I'm B1\", additional_kwargs={}, response_metadata={}, id='00c18d89-67b8-4163-9783-7e20bfb73059'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='30287806-99a5-4d83-96d5-5c350bc618a8'),\n", + " HumanMessage(content=\"I'm B2\", additional_kwargs={}, response_metadata={}, id='36d71515-3960-48ee-ab9a-e4482c33090f'),\n", + " HumanMessage(content=\"I'm D\", additional_kwargs={}, response_metadata={}, id='c3c583c6-a496-4f39-a112-c90d41849fa1')]}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute Graph Aggregation with an Empty List, perform a basic aggregation operation across all data using an empty list as the initial state.\n", + "graph.invoke({\"aggregate\": []})\n" + ] + }, + { + "cell_type": "markdown", + "id": "8b35570b", + "metadata": {}, + "source": [ + "## Conditional Branching\n", + "\n", + "When the fan-out is non-deterministic, you can directly use `add_conditional_edges`.\n", + "\n", + "If there is a known \"sink\" node to connect to after the conditional branching, you can specify `then=\"node_name_to_execute\"` when creating the conditional edge." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "93354095", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, Sequence\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph import END, START, StateGraph\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "# Define State (using add_messages reducer)\n", + "class State(TypedDict):\n", + " aggregate: Annotated[list, add_messages]\n", + " which: str\n", + "\n", + "\n", + "# Class for returning unique values per node\n", + "class ReturnNodeValue:\n", + " def __init__(self, node_secret: str):\n", + " self._value = node_secret\n", + "\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']}\")\n", + " return {\"aggregate\": [self._value]}\n", + "\n", + "\n", + "# Initialize the state graph\n", + "builder = StateGraph(State)\n", + "\n", + "# Define nodes and connect them\n", + "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", + "builder.add_edge(START, \"a\")\n", + "builder.add_node(\"b\", ReturnNodeValue(\"I'm B\"))\n", + "builder.add_node(\"c\", ReturnNodeValue(\"I'm C\"))\n", + "builder.add_node(\"d\", ReturnNodeValue(\"I'm D\"))\n", + "builder.add_node(\"e\", ReturnNodeValue(\"I'm E\"))\n", + "\n", + "\n", + "# Define the routing logic based on the 'which' value in the state\n", + "def route_bc_or_cd(state: State) -> Sequence[str]:\n", + " if state[\"which\"] == \"cd\":\n", + " return [\"c\", \"d\"]\n", + " return [\"b\", \"c\"]\n", + "\n", + "\n", + "# List of nodes to process in parallel\n", + "intermediates = [\"b\", \"c\", \"d\"]\n", + "\n", + "builder.add_conditional_edges(\n", + " \"a\",\n", + " route_bc_or_cd,\n", + " intermediates,\n", + ")\n", + "\n", + "for node in intermediates:\n", + " builder.add_edge(node, \"e\")\n", + "\n", + "\n", + "# Connect the final node and compile the graph\n", + "builder.add_edge(\"e\", END)\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "9f69fcdf", + "metadata": {}, + "source": [ + "Here is a reference code snippet. When using the `then` syntax, you can add `then=\"e\"` and omit adding explicit edge connections.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "e718ebeb", + "metadata": {}, + "outputs": [], + "source": [ + "## Using the `then` Syntax\n", + "# builder.add_conditional_edges(\n", + "# \"a\",\n", + "# route_bc_or_cd,\n", + "# intermediates,\n", + "# then=\"e\",\n", + "# )" + ] + }, + { + "cell_type": "markdown", + "id": "3400d24c", + "metadata": {}, + "source": [ + "Visualize the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "9ac61928", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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Uv3www8zZ850Op1UbhcJP7nke/LkyQ0bNlB/eZ/qM+QKhSIvL++pp55i/IUJf7jku2vXrm+//XbZsmXUbxrBpZ2AgICioqLXX3/92LFj1G8dI8uaNWtOnz793XffIdk6smuS+fn527Zt++GHH1AVABuz7/J9++23eTzeu+++i6oAlBfTP//88+rqagqusiDB1Lv02FTFAAARpElEQVR8jUbj3Llz+/TpM2PGDIRlIJ4FMm/evPT09OHDh9fVUbf6OfbQjhw5MmLEiFdffRX52SH0j3fJyspKTU3Nzc196aWXsrKyUJdDGubNDluxYsXt27d/++031IUA9D2uR3h4+O7du4uKit577z3UtZCGSbPDrFbr1KlTQ0NDfWe+lE8E12Pp0qVdunSZO3duWVkZ6lpIEBQUxIyDs3379r322mtvvfVWTk4O6lr+gn6ocKfRo0f36NHjtddeGzRoENqx/6NrbGxkwMHZggUL2Gz26tWrURdyNx/qcT0iIyO3bt3qcDhycnJofcQmk8m4XN/qFx7I8ePHn3zyyQEDBrz//vuoa2mFj+7Z2bNn9+vXLzc3d9q0adRMkyOd0Wik75Xt5cuX19fXHz161GevWvtcj9uiQ4cOu3fvrqysfO211+jY9UokEjpOazx58uScOXM6duy4cuVKn02t7/a4Ld58880LFy7k5uaOHz9++vTpqMt5AGazmXbTGhcvXqzVapctWxYQEIC6ljb4bo/bIiUlZffu3WazecKECTR6wIRKpRIKYT1+kXS//PJLWlpaenr6qlWrfD+1NOhxW7zyyivDhg1btGhRWlravHnzUJfTNo1GExsbi7qKthkMhoULF8pkstOnT9PoBk9vnyzpOzZt2vTrr79OmzatT58+qGtpxdixYysrK1vu8iUIgiCITp06UTzP2ksbNmw4c+ZMdnb2U089hbqWB0ODocJdpkyZ8vHHH//000/z5s1rbKTuYd5e6tu3L4vFaum6WCyWUql8/vnnUdd1t3Pnzo0dO1an061cuZJ2qaVlj9uisLBw2bJl2dnZubm5qGv5S319/UsvveTpdD26d+/+1VdfIS3qfzgcjvfee6+2tnbhwoW0GMy0in49bovMzMz9+/fbbLYRI0acPXsWdTl/Cg0NvXMpY4VC8cwzzyCt6H/s2rWrd+/e6enp33zzDX1TS+/gerz00ktfffXV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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6b961327", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm B to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='da51b3ce-e49c-422f-aac1-ac1a4c620a50')]\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='da51b3ce-e49c-422f-aac1-ac1a4c620a50')]\n", + "Adding I'm E to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='da51b3ce-e49c-422f-aac1-ac1a4c620a50'), HumanMessage(content=\"I'm B\", additional_kwargs={}, response_metadata={}, id='4da170a0-c01d-476c-807f-5de7307aafcc'), HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='c43a6e78-7096-44ae-9041-2a9e5278db68')]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='da51b3ce-e49c-422f-aac1-ac1a4c620a50'),\n", + " HumanMessage(content=\"I'm B\", additional_kwargs={}, response_metadata={}, id='4da170a0-c01d-476c-807f-5de7307aafcc'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='c43a6e78-7096-44ae-9041-2a9e5278db68'),\n", + " HumanMessage(content=\"I'm E\", additional_kwargs={}, response_metadata={}, id='9976cd5c-dfdf-412d-8a50-833d26c864bd')],\n", + " 'which': 'bc'}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the Graph (set `which`:`bc`)\n", + "graph.invoke({\"aggregate\": [], \"which\": \"bc\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "e4877888", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='f19f377c-03a3-43ca-9abc-755babeada1d')]\n", + "Adding I'm D to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='f19f377c-03a3-43ca-9abc-755babeada1d')]\n", + "Adding I'm E to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='f19f377c-03a3-43ca-9abc-755babeada1d'), HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='cbadef64-f5ac-414f-89bd-d7de7fbf8f36'), HumanMessage(content=\"I'm D\", additional_kwargs={}, response_metadata={}, id='285ae8b7-7b9b-4076-b3b1-35ce66a62ef1')]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='f19f377c-03a3-43ca-9abc-755babeada1d'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='cbadef64-f5ac-414f-89bd-d7de7fbf8f36'),\n", + " HumanMessage(content=\"I'm D\", additional_kwargs={}, response_metadata={}, id='285ae8b7-7b9b-4076-b3b1-35ce66a62ef1'),\n", + " HumanMessage(content=\"I'm E\", additional_kwargs={}, response_metadata={}, id='8abc9b73-6fe8-42a7-b04b-aa30a25fbd4b')],\n", + " 'which': 'cd'}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the Graph (set `which`:`cd`)\n", + "graph.invoke({\"aggregate\": [], \"which\": \"cd\"})" + ] + }, + { + "cell_type": "markdown", + "id": "0c93638e", + "metadata": {}, + "source": [ + "## Sorting Based on Reliability of Fan-out Values\n", + "\n", + "Nodes spread out in parallel are executed as part of a single \"**super-step**.\" Updates from each super-step are sequentially applied to the state only after the super-step is completed.\n", + "\n", + "If a consistent, predefined order of updates is required during a parallel super-step, the output values can be recorded in a separate field of the state with an identifying key. Then, use standard `edges` from each fan-out node to the convergence point, where a \"sink\" node combines these outputs.\n", + "\n", + "For example, consider a scenario where you want to sort the outputs of parallel steps based on their \"reliability.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "57e475ae", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Annotated, Sequence\n", + "from typing_extensions import TypedDict\n", + "from langgraph.graph import StateGraph\n", + "from langgraph.graph.message import add_messages\n", + "\n", + "\n", + "# Logic to merge fan-out values, handle empty lists, and concatenate lists\n", + "def reduce_fanouts(left, right):\n", + " if left is None:\n", + " left = []\n", + " if not right:\n", + " # Overwrite\n", + " return []\n", + " return left + right\n", + "\n", + "\n", + "# Type definition for state management, configuring structures for aggregation and fan-out values\n", + "class State(TypedDict):\n", + " # Use the add_messages reducer\n", + " aggregate: Annotated[list, add_messages]\n", + " fanout_values: Annotated[list, reduce_fanouts]\n", + " which: str\n", + "\n", + "\n", + "# Initialize the graph\n", + "builder = StateGraph(State)\n", + "builder.add_node(\"a\", ReturnNodeValue(\"I'm A\"))\n", + "builder.add_edge(START, \"a\")\n", + "\n", + "\n", + "# Class for returning parallel node values\n", + "class ParallelReturnNodeValue:\n", + " def __init__(\n", + " self,\n", + " node_secret: str,\n", + " reliability: float,\n", + " ):\n", + " self._value = node_secret\n", + " self._reliability = reliability\n", + "\n", + " # Update the state when called\n", + " def __call__(self, state: State) -> Any:\n", + " print(f\"Adding {self._value} to {state['aggregate']} in parallel.\")\n", + " return {\n", + " \"fanout_values\": [\n", + " {\n", + " \"value\": [self._value],\n", + " \"reliability\": self._reliability,\n", + " }\n", + " ]\n", + " }\n", + "\n", + "\n", + "# Add parallel nodes with different reliability values\n", + "builder.add_node(\"b\", ParallelReturnNodeValue(\"I'm B\", reliability=0.1))\n", + "builder.add_node(\"c\", ParallelReturnNodeValue(\"I'm C\", reliability=0.9))\n", + "builder.add_node(\"d\", ParallelReturnNodeValue(\"I'm D\", reliability=0.5))\n", + "\n", + "\n", + "# Aggregate fan-out values based on reliability and perform final aggregation\n", + "def aggregate_fanout_values(state: State) -> Any:\n", + " # Sort by reliability\n", + " ranked_values = sorted(\n", + " state[\"fanout_values\"], key=lambda x: x[\"reliability\"], reverse=True\n", + " )\n", + " print(ranked_values)\n", + " return {\n", + " \"aggregate\": [x[\"value\"][0] for x in ranked_values] + [\"I'm E\"],\n", + " \"fanout_values\": [],\n", + " }\n", + "\n", + "\n", + "# Add aggregation node\n", + "builder.add_node(\"e\", aggregate_fanout_values)\n", + "\n", + "\n", + "# Define conditional routing logic based on state\n", + "def route_bc_or_cd(state: State) -> Sequence[str]:\n", + " if state[\"which\"] == \"cd\":\n", + " return [\"c\", \"d\"]\n", + " return [\"b\", \"c\"]\n", + "\n", + "\n", + "# Configure intermediate nodes and add conditional edges\n", + "intermediates = [\"b\", \"c\", \"d\"]\n", + "builder.add_conditional_edges(\"a\", route_bc_or_cd, intermediates)\n", + "\n", + "# Connect intermediate nodes to the final aggregation node\n", + "for node in intermediates:\n", + " builder.add_edge(node, \"e\")\n", + "\n", + "# Finalize the graph\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "4a4645d3", + "metadata": {}, + "source": [ + "Visualize the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "61d29bec", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "086416d2", + "metadata": {}, + "source": [ + "The results from executing nodes in parallel are sorted based on their reliability.\n", + "\n", + "**Reference**\n", + "\n", + "- `b`: reliability = 0.1 \n", + "- `c`: reliability = 0.9 \n", + "- `d`: reliability = 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "bff43203", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm B to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='ecf02c68-88bc-469c-b863-182e4daec1bc')] in parallel.\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='ecf02c68-88bc-469c-b863-182e4daec1bc')] in parallel.\n", + "[{'value': [\"I'm C\"], 'reliability': 0.9}, {'value': [\"I'm B\"], 'reliability': 0.1}]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='ecf02c68-88bc-469c-b863-182e4daec1bc'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='a4f32e94-2ff9-43a1-8ae0-e6237979fa5d'),\n", + " HumanMessage(content=\"I'm B\", additional_kwargs={}, response_metadata={}, id='e6e20712-91e8-44b2-bb25-9321822d6260'),\n", + " HumanMessage(content=\"I'm E\", additional_kwargs={}, response_metadata={}, id='8972efe5-ef49-429a-ab8b-bf08e5487822')],\n", + " 'fanout_values': [],\n", + " 'which': 'bc'}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the Graph (set `which`:`bc`)\n", + "graph.invoke({\"aggregate\": [], \"which\": \"bc\", \"fanout_values\": []})" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "9e2fe2fb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Adding I'm A to []\n", + "Adding I'm C to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='44beec0c-8fd5-469f-a705-1101fe4e422a')] in parallel.\n", + "Adding I'm D to [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='44beec0c-8fd5-469f-a705-1101fe4e422a')] in parallel.\n", + "[{'value': [\"I'm C\"], 'reliability': 0.9}, {'value': [\"I'm D\"], 'reliability': 0.5}]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'aggregate': [HumanMessage(content=\"I'm A\", additional_kwargs={}, response_metadata={}, id='44beec0c-8fd5-469f-a705-1101fe4e422a'),\n", + " HumanMessage(content=\"I'm C\", additional_kwargs={}, response_metadata={}, id='37cf7718-673c-44ae-912d-f2b92c267faf'),\n", + " HumanMessage(content=\"I'm D\", additional_kwargs={}, response_metadata={}, id='6204397b-8bed-4c68-afe0-a5dfd7140aea'),\n", + " HumanMessage(content=\"I'm E\", additional_kwargs={}, response_metadata={}, id='588c2b73-b994-44a2-aa5a-d66bac8bacbd')],\n", + " 'fanout_values': [],\n", + " 'which': 'cd'}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the Graph (set `which`:`cd`)\n", + "graph.invoke({\"aggregate\": [], \"which\": \"cd\"})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "langchain-kr-lwwSZlnu-py3.11", + "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.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/17-LangGraph/01-Core-Features/assets/11-langgraph-branching-graph.png b/17-LangGraph/01-Core-Features/assets/11-langgraph-branching-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..cd01e2ce718ff2b1a39a1e9254d20b7f5375db62 GIT binary patch literal 45796 zcmeFZc{r5s|2KTu$`VRsU#c;-kUjZeRF=UYM3JRJ*0B>}B(3%t5|b@s-?HyU8x7gF zm>CjTL-uW$`1OTQh%qPGSGvpo_{D;NI;Eq24oD--0p@W-4{(^;c{^t7Efbw3UMeqm4 zhdKxy0H{o0qugTxfGP+04IRrzbbpT6TI_CxuN@`ah408dfnA2&lvCMyD=C#TJnJT7 zUD+A^s|XS|@%r2=Ln_t%n%Ci8F>E{J(wpq?8gn@&w_AqoOXnZOGqY;V^Vt}lPR@q& z6vFy_`b_9&55BK`6N*X8P#LrLIPS3b=&jXW8}q;ieRHnb5%BNX$Kuxo@d_>upXdPK zr`Q8wu#Za@%;*8&n@%<(03<$zu>gSB$tPR@0OOzs2No&B2#y<2XHEwH_B$;Jem5)j ze?Is>v-JPHH6FAu7$SEkTagbpYPVCYN!?*P4XFJBl%EG$K7_K~xqOUW)EE9;-m*P< zFQIjAsf3ImjQNz`Z5~BrDdbL1jc*KFtX?;I(=dIh`6qq7^1eXti(W0F*LX|g^wjkH zKc}_lpLFf`_hxDm*-xEncvg_PG#Nbo3^~}~TmJA9quOQP_Y7UHVJvyLp9^WJ_I`b> zlqzDSM=G(^+hL_oRqMbN8OfO4@@anOty(ML=>Fjj!?E$l!7~_%<|o%Vd|yE5xXsQz z*jec&J&EL)XbLPLRy}w@O#9~^lH91YySB)3g>Ti39~QDu-5l=IzrC}oM)@P{xeUwN$esKtPmi%i7UElCpfv^8+_ow)X(CS+<#_y zw9H?|y1aDc(s$br?Yysh5p4{Sp11Lnyye=-k9m2XI`|R9SG~N$unZH=U@TrdRR-xU zmsx&vZaZiUtTo3X6wpB%%9Kn$6_Qt^+8Odc%h5_&KMw@ z$40%t<69^C`_@gRhfNO(jD24q(q|)+u4PA`DTt6t5Vt@Am$~9i@_*V75tk#AU|{+7 zm!H{T_lc?}fs1Zd4q4a~<3)zb0TJM$UK%6{0<2_v^zx66 z-11}C8AijGCM86XkZGoqz)IcX{``>&^Gk5hH;YWsHh`yyOKJeX$AZmzJm~PW4n3d; z0Y5P8fs9DNyMyVn$#O>JnoTDvf3w^_Z+1<2>R`0qUASJmLzfkHlCmzIy7#=U=^WX= zPm|-r1@QUAQ~0n~*g-5m2QYz%^BmcB&^j1MP-Hr(L@$0LN4Z|LAtPe=CX%0s1{TCL zONBESjhmo${yi!jAH>X}(&Y5irXE*Qw-2q#1Auo^!-43Wh-bzEiORGpV!8=nEP-5Y{( zZZR54&K}?oHG+?pMv&bWrs{yChFwWO&%uQD5r)A=2dwLOd$pPDr8c=x$H{ROD5STm zMncW5#en_#79-!bfZyHO)c}Ad9iBc3?Pd2kfMcCJA>%=PHtX+%Xmeo9#BVLZd-t6)C8-2CT# z4Cze|7JNzip-}xl+sVz3zP^4MfkJXojz(@-s{^?VD*vw>oUO;g_to-6^>o`IX~&C5 zim%~2e95mwI-@VF;(0!(E;1}E;~J9R<`mi3w?6)Gp(Cz#Y`)*r+>;S5>Uvk0onreI>(!b2S0h98FlDltIHv zhaj0;PqwNDG|T{>4XJ{LJl?xzY?rGn7w*c7xk1(*vs5f%3D)-g;;ccFZI2r@yw>lF z!bWm`u7N-|yA!GZlEfbQk(6R{Uq zFUW=NHiu{TOB!%7aRryzEkBLNqVd}1s*sPPU>Z2g{+TDzI=5_H5mbWC(n%|@TAUxi zi>^``06GhANytCpxmQ2=?M~CS1C0muVQ$;^*HWY7JkM5&f986nLxxnHeWN)#D(Mh`H_)}i0h_Z9On+B74k5ft& zRh|ban!)m=O83{aigt!e+4I4$?M`^)3C94Ch3P$!U0f>9u`JcB?=@8B(H!e;FMOoI zXvnG<`hp;*!s@s*!ngQH)VB2?duAz6&g3|v)kdmp0z2uH59=J+iix+LRjJgW7935%6st zcU5(@vT3(`9j8mh+U7w9J<#iKBsHA(Zf_i((;wpIg3&P=woCd>i>Efkpo_}&G^142$T80`?UFgL&Rt={s4SPF=TgcUmio&- zYMM-By16e6Fml28&q(GZ{s~|&zW#AArG9eBI->S!S$_QJ`62G7^c5}|5i$)IFEYx| zwg3o=@97x(NQG`b$uE)D4ksQ|L_k z0wgm(;kwK!=}}W=!rou$KPOZp{whig(IeGt>7DY9UdSUmIknb3xh%A%9ohgm>Si;> z(ifjBPCY+pb-rv=TA27r@sMv^Vt{*EO+Wi>%S$VPyYz)xuN$A-gsEt6Q^?>xN>(v9 z^CK^f1XjbPi_7b!koE}|R_J)6l*2j0lQyCXR03Z&Z+ma-patO}6*nuc*UIHw-m611 z`T%+-_`zZHC%=h$m-F%Yv8~R_<3BGUmwzNN!dZJGZR2)@U((>_lVA}h ztqGsH6i2v@LG|{{gD{ZEbk$-N9ZEkj=&9vTM&7*f5iMXMZJYaRK~ILjz2(}qI{Y;z zy#2C}Gj=XB09~PEu2-05LS!Xv*^J)xN00?@i6Ki)F8Wk&1k=({XyMbLNZ{&&?`_cx zHd_^?lp>>TE$8Mm`!vQ2A{wB_p(*5EnBHobxrqG|Bi#6Woo6geav|IIEY}tsIJ__&oWWa3tNWh(q zlLI#PUhjWU6yP{sTJD7zVg)a96jW67-E%71DIO?pH*s}lx)Ax8+dud8>qa_Dp06ZV zCJXQXr#b^O!?Tw8r=nX^-B#_aKuDXQHDV2i zq3lY4yoj#n{u2BDT>IrDj#ocCT(kYr#Ry}H_PEs$MAt(Tj{&VKPoEwiwzo$z*eFcW zFGYa_Me~!~VBl7B zKK{v|vg4Z`>2SAgcaZ4?9f5RzjTPUXdIY@#62$4w0yV@p z1)0T~rFNlmG zoZM~Nq;ANQxA>KWfEtXV;n?F1%&LqU>8BDo5+q#j!<_p>c5@4|f|}K9ftv9>g$E?T zgey^Uq?gZ&)NT~84Zr)$;Scm9J54eS6Qc{6lP_PWsTf=~2xD-~&Bb{1-orrE#h~=! zv2XcXTai{MJVRtszDJJ-+Job?po$@%uc_D6>aqW_70CW|Xna4GTM;bny40h-W#-^i z#3RMqx@>o>0n`{GygsYk7)w?&#_AMcZ#A&)Ljx2+vDeM_8Y>$4LWp{!GYXF zGs0HfjTgwaV@~eB*(FzYr#k=KWvS{d8Vdkok*Rh8Ng&H^KB`DjH$S&X+3b^uiLMh| zi{vmrppg9g0%@9n^KGb`1R&=7{hj)%#O{=7WOV$++-UO<&+c>e4@g()1Lu25_}mdW zjT0yBOt~y8vxLlq$4AQJw3^*-hkaWmuf8N;SQ7hvL8>iYmT2)|Yb##(Bojh_%aB(<_BAmEJ2l@`>+yoUE8y&2KUy4l#b&X-=G91q=+#5ddgsl#=cpH1 zn=s0|lhVt5jgR*IcH3^1xKqoVk2TLi_sS<;NPHaFs|+aD3Aw!e+OW)P?VDs+{2ZPq~lqyz=&c0I?IGw>gjzJQ-4M z0SeeJH?kSUn$-R0_9}vG8+>xIf*2xigN|qHOB1IQ<&~3w0!VrJ&J^=pJ=z+wYnH^` zpi_(dL3@F3gw0KyWMC0_LzSi70HoxX?{h8ULy|jhHvQ`U=DY^LCk6I+;@S%Recm98c6LV` z*t#p*2-jEA$?_rFJb+Ia{Guj~wfTWl%KejeK;jD+3ydgf@cQOsa`~|_0Lx~~F0+jE zlKPV`3`7QjDY`%C&4oX=O03iYS6*=BUInpeMWeF&+G@ax9{BRbxvGKh3lDkz&BYJJ_XN^8O8)2PW&m_#GOmFi=hj%;<_6InC{54- z3*5A)V}%B`VPEKA1)vyOxZ)gVS)t6m0ssL5;B1)lHOaoBhtS>yZ6+WuTTF^57YVl8 z6$!TMCixWi)4*XnpO&gRGyusnJl@)y-<3H2ecVo0hXlF9@n%+Y*lJ^^=Kd_^c#Kn< z@_|}SL*+mWNKcwU(}SwwJ^7CsNORPJSMN@o0Sfu6<)?vIch-1h(;_tZaHYPPT~Kw8 zDzZ}Z%cSQrk8)4CN;e%}26J-cO=&?Psjy^~qrzMgi1DNW|G5UxWM}}TU<7qd0>28i z%?$1G5)}&2Kv(FidHE$Qdd%oimrGA8s|@K1!Gh0&iiiLIHvK%(awm@?f@w;*Q6h5z zzl_&cVlovRtq9(YSPNUFaR{nxda=eX$)8;1-2F|FE|(G11%|_HiF4w2ave`E~eggsE+BkCGu199rHu^c2@!8T14zkJp9rAF^tTLuy zye4P^G8w1d^?MaYBu>r&0TT4$*)dZ7HKAaF(?$Pl1sYX}F&e0*$2T9pmZ8}YN2n~+ z@xE3wZp0sGVtc~XpO>sTxRu!EVdJ&F@mnh<`cbl7lhi(!b1Jg&_c`~}NV)6Ti~;8n zwMPS@M@fB>ZcM~ZIXvkh=5&@-uU*UiXMJ)~xm%5W;k_Awqjxx9!H#Tav%3o&ZPOr3 z@B+CzzvQT0*34+VL>uUWrd5Z@+Wz4bHA4FY(8RzGcD7Jt@wH6(U;L;tQ89mFLRy2T z;8%#%SGp0kRW8|az$~Qe{^Nt8tQ|bmVCfVOA;I_1y0hq2LH@6EW>+{Z1;8rU3Y+Wl zA4iK7ZC0g;^E@i4R*Q58Lqgi5WMUFD`VJ2GqHr6`AJJ8Y!6e_4CDW0w-@g?syv(GZ z<74~v3nTn@PWG*@TLReNw#^Jr_p1zH zfwWJmU3vsWFBLUT6AIq_GGiC02qC0KLH_&GvsLg?zmm+$E>2+MAUa7yoG_MR|0I+t zNK0UEH7(|2g(5dDg5@`!pj$NUbe}ElZwEA45_kq zh|m2}S_DH^0*s}QnSA>h{i?e~B6mmPqFOcujGoubLr4kyZ&{8J)wM5YkAm~3{!{w} zr)0@rlrc2AccEU_2T z(!&2QM_x0+K){ckk2EuPhF0-o-_K%W&&1B~)p z_2Seywp9D2f7vNeGh*j4Hg^JEtUI`s-nOxX-dYoNzre->D46AfiDM%Qw_9QJ8+7kN zR`4`C($;@_MC)>K@TsK~pau*|0HoVq3CM|n{{`Zq@Z6tif4sg9-g4>4<29jwyR~hHmK(;iP!~&AtlSGIO1E{Gar`O4{ z6K!5tYIRG|)&HKR00X0=wbn!ARFWV-JIaCCO8MjFk|xR-fv)&l-oQlhxETByX(-`p z7adF@=_Y-@>e4Gqc>{B^8X99=E_hd^4eR25Ec4<0M@Xaz2+sYg!BauDxpq zT~B8)&)P0U@DlrWC$GbI_bY%fC=#>F;?tBm>ls{=rP5Q4KeO)!1KGkIe~hjGd8%M4 z`;TI8dS%t(R#AU>B;Oe0o|4{kh#me;{JisTeC8=|@9h$AR~{$A{0i zmxY_Iztzi7^Uh>2`!_0Gos-?~)Ok+cE%DL`EK1aHI>{?{cj3PRn-zUcwi{0lufJaV z?7Nr_mstcD$hz;q1OwJ}xE5Sk7lz-}q=qjJ=5fFpX=qaEIj%@OnuO$j*|M!AV_lf8 zukc51(0Xuf$UD9GW_Y(r~ zSn5ds9DS68pf3h#VBuR&t3{&{YYofK+tznMR5N)0Qm_y_cF&GVzGd!mZCg%Ffyp{F z-Os{0Fm9Zt`~L&t26yuMB6g~e$82=B`LMs=jM7v;Q+29+Xc2>%ENv!=I1?0&mI(gi zy1WC?Rnf|-ycM^nQDv2^E1;(Wp6;97OYay6h|do!H}Y^a6H!z6eZo@Y-{Pp|{W=>zW*Ry3-u)SRa44)7Z&hEEgIoG?BMsNj%?+ys zSJZA{hv*$Fy8lg|L^Ef$a^w9{?E_P=S`VIIf9!s+4_fsbB}B^)WuXd)XY>mkVE z!9yc*<9y~azIzZ-jr+s(t5M2sJU`DZ>|Nofk>eB`9@{12e+714EO8b zDY}DJipI5coil0JFWv=GxM>580 z(caE?ggY#?Cg&Ls4RngG#gFdJmdLZb8@``SwCeQ=S(2h2RmKS(RfrO62oK|GqGXLM zJlusE!LXo*I3|OD_fyG;_019!A~z{2r_q0&ONb8K&pwx#j5sy762mqK5=X&hnM2lZ ziHLgk2@TiLbO%I66Sd|tY9$MQ4rpV+Lx7q{0Kjk+7 z>2FeRE-fzlItT7ezZg3@lE{4YiLTuTL=H|EZrBrp@rQQx4>~zc0&z4@?wY9eAXoQk+3xmP0(MrQ2zX4MvQ7*j zdewtUCmIx5A4DGgd_u~;#*cLy%5SV1T<-3cmb(5%Era>rzMgkZLN-i4CuuvFKUpyq zXzh@xXi-$NI01mgLy{e#e%F=0QOCiu5duv@`q%7(q^wlg`W<3L1npCZSZ>^r{fmrEw)?K{XST( z5Dhwj4|&D#$_L%b=XvZN-2XCv5flQqmUzMeJurL$?UDSS_Zm*bc6Ag+HVY~%wMw$G zmRQCxiOKzYDzUSWk3|a9r6%t99rnM_vV6sZEHgt%)@t#|!640yu^{fYOXAwaoRXdo zt>|34y*_Fj22>ffFMtd`RftCq&BVU@~-hr!W8K(fyus7tBDqZddNEi9?U^O-1380rDXFu+^HsfJ~f32p17v z3mL3s9muL!G(#ATVY^_fO)u#o3Oz^H>H!pEof^x}BA>DpR*|vIH zark^(snv3-F7}q$qDy5>wYJ7>i_%)pDZbuv~#v{q>v(7D*6$vTwX5-If}b!-MTC zBy%Lwke&WsI)a@Y`?1Y)WZ1p^OMp;+2nc;IYOCZ0pG}Mu_;S!$D1q4}2!00&tmuQ% z?PZn0J)>Bc52)M0ZnZzL*TS=vz>#veipSg#HV^j5^U1pJoqYrrsk(zjpT7;{rLYH& zS8%%0I)JbtT&P@f3vhS7YBJwZe5>S&e&iX@QmA>N$dqo+&oShgXl2z|smQ?xp3b*3 z4=Q)1Slf?VI^Zt%bOf;CQrv756WhPh_RKdLv2WY^D$jxC5sQh|OV6lsmgeB&r+r3~ zxU{_VLiW3OOtgzK)^0FlYjU?OHPZ-o100=Gu`GZ2XH_+2?g7sgu0SK$KVadF9!9uw zeDt#@2x8IQz5Yy;xEPpP#kfY2PSuy%T6jq}F_m&Ba@Mo6&I>w%6LI76-9%iGD*3~( z4GmF54?qD*r()2{1?sUp6X*|#>`BXfP^G9%qS znSB`%eu@|3Mo`t^(lb=@qPuqHO5`ZWNm&KTeQZA_AssIr>1!xk%$6-WjL6ctih$Y_ zSlKg4j8=4mh84N}a%uX)@CdZp`!8rVs!i0#iLqq7_eVB3!flt7t6vC3($?Ud%ETK8 zH}Whb$Ku+DWb0}ikDpB*OSU>VUN+0rWNU^`_O_BS(^29Gyh`=B8~`qnTh|4ZVk2|t zD=`-5Gpe?|x&CQVR~#RKUtk1eD@Lu()Bvz?*XDOEy(Ht0bUol;)3a|OLo(_El?K~v zVEjeGk-Y<<%1oWoSF#nr`dC6rNS18ZIv*rw^bWKzolt(yC;;lY=Q$FqEjApQWfrN? zRYU5b6K(|~M@pa&eE_cD<<*wW-X?qnmeT@R$jY=0%F$6U)Yv64TR)$2jvgsRK`dWI z;q>l^^8_jz6w?vnm3YX#_OfUQ3XTpk@cI%>If1C}ObVAJ4p z4@BfH6W}D+1wU8m3-Y-E9sCckWWS#4%W~M=1;y$T?TPY4ar7LzP}=$WX8zReqZ2~0 zFqUtXzB3(7c;yGi}3Y;vo9py zx@AtDlE{rN?)E^ZtC3H|x`6W-PlLnNHPN6S_c#ixS{$?PBY!&5${%Ki$~E@5fQiVJoQ zYy$*$Jr$m|jz>%J;TH7we^O5Tp~=Ho!?1eq;4sv_QT?VqXOpIF9mD}2&@3AMg*u)& zyG!S&%pW|Pgf#Zf5+o9rJ4F3U1=Yfv1W|u{k-r3|W-o`Hk&IJhjkj;xB(A2Q4z25# zld>Aa2`C0Yz~Y1Q;+7I+*LNr(fKQV-S?`IbPtB&jb^s9h>sr6^{+5z(ljsw3UW>@B z!LlhIBK2^LAVddq>biu`nlTntPI6uXDTUojfCZS4U^H&+o?7V-XHIU4%^$5hUg6<> zzyQNaIW8TxGa(1D% zLT7`5W@|*PzD$5X>()j$vNK*7+0`D2y?if9=33`?Ygm2QPUAdyWh85PWzqLRN35^z zA9C}UCiN2=lCrm6uB|=_9v%dLezcgEN}503@g{?JA5raVYox}Z6?nYbV&Zkmvies4 zb+Q9xKdafQb)!pW^%<{2zzdU%C8)`}h1V0+4->-VLgFwI2)|YW-?{LFtq6QWUVosascS*X!~bMTKh`%rd&I;g2?4XF-?S5Z1?SDg)~1z#N>#m~~0#^8{E zf*hKG@YPBua`8eM=@b;gi?V~*s`p+ORBb>}I#H!Z^+;r>*9)8u+7(}dId{qR;O)Wg z)p%sX!FWLFg@AW|s}YJP;8%M`XScubp~#W-`T{5>IIAZ&W59jNi#BMhesj=@-wW}N zPsM)>?k(T8xDbnCMjx<}UVh#$d$3|0Q65fMubQu`@rwv1&Z8DQ2A_V(%%c22P}WcE zcHyU%pBA}DHU}&e$ma$Xv7MH@^)7$Heh+_KTqKdn;y>Tm=AYX5im>ZL_hAweC`1+u zOd5{6Vd|}$kc&;qtlNgTH5x3r9e$m?GGp@yEwr>&(bT26@$I5k%m)p1`<-o}us@Zn zPi3rX{H~ycG|46iB;*C3B8z}L*?=3et@{n~l@Aqnv|}(+;k&E?_8hWRz4%bzvJ1M- zi#W$1iFjEO;LM`R^wm2?3|5WYE!d8 z;!`hjUkpl#-ucZDvU%+Vc+0-{gK&MsXLfu4kq;<&L9thIbx+-(_Y85yQAj|yfGdFY zc@d+tL1<}ObU>4?X`XJvW;Mj%fHJSWmY}xfW^2LKX>2qp>eD8v-vXnASsh;dT^RHNs!Wq6b%O z8l&)awXTA!h=a%(*Q>v97!&t{+fDt_&1WuRvaw~E$A_^NER`Kors8j$C?08$IMnxa z+^lAm3;KOL%I}m1^yW;3zrotj40!(1o~0HR5qvm5m4HG|r%yOiZlRPHse7_0FY-}V zg39-$wH8z>wQ=FE9pcZnl};DL>u@Pm@Hiv0Uf%b`Q4de;C3JLpGMT{i)9f>1a0~)5 zO(LQnEM9O!)N5A+gvG`<`CT_}3KtthYzWW4^KaL>6WXP<`F&2{yR#CD#VJ!FS7u!x zrsf~7G9sc1Rv3A|m-ZXZ7>>7Orv9iNq zzAFE3luO^*z{b9JkhCv&7}R)OejkSo=qh5TW|ogP1?7e^Cri(zpjr#21F&;n4*SCJ zQ~~0qd#Ki3NT5!o3s(?sap&^|@CV;zg!zg>CM5849m(MFG;$+!3sRJY7y|Iyw)k6z zn^OlpQ=aaI7o2PZFk5NSSBC_9+C((HUM#9E2gC$YJ{u~6V#RRJ_?qIeE~@VGvF7u2 z&LLF+su{*fn94WgbuyNkSVXjnw-7`j>c#J5zh8>O$B!@VYo=>a%VMbw7xSB?c#o+^ zf%RwXn#YB6XdS;%LbRulmPp#5^uE2}lI^#A$;T-b0 z@wL+Q|%JQPTA(kv1oU zcBmBL@Hf&?u%ZhmDq5xM(`J2i%wt`mDzSuZ!E_eGpg<%3bQX~hO;YM615#2_UP=VW zZR^&NN1p@06!I`Le012@$00w>=`B)fWUrt#`uoh(eI-I@#j8H*-wHRJ7Bws@WJ2?> zK*m3&WhA+ZN*SGqrRh3pSRmYRY%Xm_&AMS-U(~;S?W;TV?FweF$i=TI(QC40yvzYV ziJGDeTOdC_XFu-CL{{zFiUbm^tnYVsLDzL)ex<&I!@FC*e`CF8>8;IKRxabR?{ZK2x3ALXuMB;p8ZTPmD43Kms>!HwTq9AA+~ZJ3!A1byBMV z9xLb}Q#s_jquWHckdG5Y|GqM$ZceMXH z7B%VnDWJQ7IW0QL6{=oI_;l2=**vgUe_@mXRwG7O;UslHJHH){r>o<0)iB1^fF3_; zn^25_lh%7`k$;WGMvm%@Ki*<^VounPfUB1QHA1&!WUJimUST*r1_a?{(DQKq5^KZQ zfMz+!XELN(EdlKFTBNoH<=aK_^3(~`f^1%Xs}wDTD( zTxis6oZbA=#*A z-4uBOb5Z&1u!n-T5!!|8zB4-At>MQ4Me9cKO zlHRN=KU|V}>%}rt5sI@$udf+(Ol>6M{KGsHk)fKzfOv`dprlQWSbS7c8#598 zgIiRd%+kP{8F<$ujbnuO#*~D(+7B;Q04;$RdC(NDe;^{&z;FHc;aO3LctyQLqpKPg zeq~{M!AK1~d*VHiEir#0Td=$1q-X#q$v+{?k$jap^Pw8Z)0k_sZS_< z`F#X#XWLkv!5CIFx-x56%s^z75^+eJTAix)tz@kqF;*kVt^gx!e{HaUZTxDS{K2V>ZRDip{f9!*}>SIMIu3z(T|NIKFe zLX}5+m-{ZcZvBOJfCrx9ygPGJtAT#!I^_yOT)dNkW1(O=8YW8-YA5-1z?7ImWoTtH%!MB4;yAkRXj2rtcjoc>SWo`P{9IX zUBS4jGw%D47r24Olm3S(QH4X%IxcvBEIXpaK4Tu`VZF6R&d2O3Gz~6ZT{lR;%kG z4Ls0WfMXeasIhGKA-WPO`}kPxXh9!|*l5$T_(|c=sfxlSiBP9Od!^_Wn3b|0GbbAw z*21@K$fj>K`hus!Pf z05^nW2$)j))@-0f?RAjr2}-0rwXk>d@<-9cTM{KWRXTUx$c=k!7%uZO!IKfN@Oin- zsn1>Pl}MwUOXjg&ET0hOyo4MbbpC}m@(!!s7iz z+gLFvAtN;|@{Lk8f|ncF2<2jExVGpp<@3}*yl~fMZQm}KJo5L7Kp0u_^PVjhz9V2& z@B9>3=>{cSC7;tUC{{b}$(%awC`-pN>zMPQz_3l$9ZYo~IX+5w+-k=KNXNKxp#4Br z^=_(mJ)vKsMD@5BuIPl`u3k}J_6npLk5O9Hd!z>L%*YMgxN4}&4&7EswrbkW+SR{# z0C84=7sCg9p*McKfdxXY*NgWne{rCEb=ZsPd9TzLz-#}w65l^EW3Bd#$5A&`Z4Gs^ zHXX8T;rA99gN7Tj1(&H2R@{^biSRBFBh#OuS%FXY2v0eiyitclEt$;@wYK%h2QR0X zwD!mI1)kz2_9UpH%XN^PG^bcC_461DFMG6XkPZ)3H?9}Zrq&vx z^Kr35UZ2B{v6^^?Q!4PJPW*!JwcPJ;rH75=;0RlpYa9n*Gx;J*P|8xp)nv9O<}6;# zJ4;PisCx?W%h@@7H>lM3qN)c1omrO*=M#Vw+0SfdQCq0BukWC!;LUcmkD&pT+kt`v zcA`0EcF-6ND%OrLf?v|&*yC!dJ-ia2T1AfD^~lk2>V9cTbB#HLSLtdAsr__R)X_?GpAo(Hl*l`192K}$SLT-W z&h3mM3oD5O;f>x_gn!PMIvIzr^dVj)zv+SOFRDIBtWjH`%g^e*+hF%|Q+tat8n{qf z5oYHVXj`*h?@3?wykw$Te*f{7a|iWzGiRnEsW}{rrD%8uKR7jX_%cBt4^1dR3~mf$ zRA@{%nC0_jK?0vzLeGx|U{zOzY%sW{C*AUR61AyFOsW*A#K^^p4#KIxM+>|(;RhpL zglF}C!*CT_4~NeFb(%R*z>3^zt@sMNwswTxIvRWXn0h$1y8`*NXIns|?CJktvQNWl zd6453UrjJgzV*75C}gEJB1m=VT}y1uL zv^T1o@OabBi8EK_4%3sFh3mJIg%nw?2RB$JIU8823POs~94maAoWmwkLfe1%Hd-}g z+=a-hupAD$pwp4n%3Z@@+!KP_UeE<^=vqX*_GstvpT5Vc_i!Efk(rja@zhA)kg$aY zFSlgr93N{=$TodDNQ1-O$i2}uGj$=9xE{#1*fbViJX2RC+X03S@o}PO4_a@JvP_Ct zZk#_D3vyFbwLOJ(9_!F%se|&m(uqxzMxRDk-P{h1L_Gf7azLfNd*K|w0g}u=g#Ad< z$Di-|cm7^UOXv1bf|nuL!u_f&1+&Em4<=pr@zLV#HsoP%PiTWCnQ3yEzI_EXArjk9 zFJ=rE;>tBer?coSG(4VLJIb{EOT@2Kvxpx5eZl#|wIO9jS%7LaoDPR-V`^vWBHo;a zq%8%ZVebc)>cF*`2(a}N0duf3KQ(dOToc_ZzMNd6^{@-#R=kopTT=KVi>icK-&?8f zz3LcilOjyL9y_$ML@Kj43Te&k5ca)6o~p3Udfj`$OgfF6pk=#N$w4}!mV*w1ttJQ!K>eGcxSgtLlpg#!qC{b32oK73H9H?Dv=S%*wj29v9)I^m zQQs8;QCYAeTK05tFcFQb)clHU7&iuWC2oGm#cvylE+#SyzoKp_Wf8*B+!MRR=~oa3&KlX&=F0tR!Se=odZ7l%CVJxoz25iBcuRb&tjY>%L)YXKR+CZhqOl{i2)IYuf z)nn|s56)FVLMjT<*1eYkpShsV*Z`;UWr2KwWv@?Me_LG8I=Pz~jFD|V8rQOLUf}2g zDMRVFh?)5*-c@~1=y-e|N@;#qXU2Pva_Cud01?-5s*Vxd5Q#_&l@SFES?(y@?HfYORZ@q?b>5}n>E${<&qz`fu_VGket;tu7% z*Cb0cuDS`;Q9DC69;U2}os;|L@0HaN7e57eu1^?TV;YV%Sg8lXw#zm6)rxjgx5Ig` znE0dF+aWm}aMR?h2LP1Q3~8sGOg^|gs?ZqSbt^srX1VP9gCpHeBZ z%LAI05*#r$naeCldcHo!x1%dUy-+2iCi{$-L7~k5o2;fcD;$>_A;jWE*KSM2t~gjx zOmsR940muWXb)&W2|W=eVG+bIdZd_Zxpm zgDJ+|*(qusS@(u)*}fhyHlMHUK;p1)4zLG@>!#NAx^B?)$5xkGqwii#;m%8QN~fz) z#r)Zt$4~BOctPC=kQXlK(Eh4_3~1L`n{G7x=x~SbKgRShdbe&{QEN00e?0IYvM}Cb z`TKVE{iw%}HxL~0$fBw;ZFcp152Q>5-UZnb$b>vl#++J9niLi8*7j0sl!fmU+0yB( zK;1l{QaP#pZw-1N4u%D2{i?KCv+drs92NLL2Y#XM#_Gs=!lb`Hd7d`QDN+oH{q z-DOGfpdNKXTQ-!5xp~G|pzmFUcTYV~!H_UP;-Z2em3xcCs(MYlRK=7&T-di0q%y0( zuf|8=H9O9oK98YIPm?SGSHlFm>3l$Wv3aO5wclGZ;3^+0@>>`w!CBI10Tj4dXHu^+Wb9X%Ganp8k@&)lMk`bsi@y*f z@FELJ1j~9LJju!65We@}d%eT0sG9sntz1S>b#PaY*A(PFG0ySOdf4w$*Z&5 z6#Ud-rj$Xkml0P^+N`4>7Cws6#8=OVmVhg+4DdbNi;boY5Zr5i=J0fQ2ZfSgsXDwZMe&AR5Wp139$0OD~cyek~hRuBmqu zyZVjIgYtyyWy8^*Jc`SZF+Yq4n?AaMW5{Ef{SN-`4l_GPYpAIJOe`4bGhhyv8h1gL zxo}m@2bY1o{2TzrUXv^WeE;{A0WAE@-%V}rkMz{g)wV;V^$k~O5v+M^Z+{mHlL#<* zF93Rhh4xBtBWZPzcB)`bXEr2gPe)H+X7`kd=FdUj4zds>_~0oq$sIbrq3}+lFl}~^ ze#mQjI~qGnz#kEhw>?P>mHjU7+n6F>V=pRWED`86jMHPFKP=e`I$rD(fsdGMaRtH9 z(%gGPB(B<|-X$Gw^BN5g#3&KBY0G)GG9AA9>M}VW_z>Fz>89*k|FxQ>^nw_m z5)YnLm2L;QYU`C;qnQ7i)#J+bX$B`a1PqZ`e4il_6hC*f6fmbjAwI?Q&yaD|g0J5D zG@M(LxVniXN#xrf^5}-|HE?sQAbPo1pZ{K49;@&^xD8fe5gWGZ#U55K9Q0Nl15VDJ z=w5GUZ;mX5i#AH|?!7}}^Q*@>(6XT(h&Z@^|A6wg7n~P@$O9$9U;Y6F7Ig8eGbPL@ z1~X}O^Xa?;Pw0;Ym6W11CrQHuP}~)8jp$2a;buEG(!+mo z$O7+GFvj)buSURK2Y*bv$H%_9pzi%slH9^Uu=i3c-d=Ci%e z_jw-2ah?Z0(e--g_D)=(GH1%mvao%*HeZrhDs4-g3kdb!{Nz%XaCk5)017g6Xfx^! z84=B)#V?68t0(-b&X<9L)q&!JdE!dOY#5>hoYmCXnb&1jP3NHTShDltBjLhHL{v7y zb^+a*F&^Unel>rjFMR3@M_nPej=MpI)aT~xuEjQ~K^!OsUhE`mYCWlFAQ+x$apFS* z)a`n{V<*n01D|MRD}BT2*uSu#6KM@fS*I2Mh`_e@vrPf%NUlH<6iwRz2 zMy7G0lwvIMLKhald$1P!b>D5=yS7P_Y{}rdnhw${WlsM)z^c;Q5Af1mZ|<2+c4wo@ zmaq~%I8AsxQ8H{w&WpU*95hjcXI%D8j3&t}z`Dwl+optNH{hBym(!@J(P>WRmN#;} zAQE&csn*UOcgK`*zCip(OnMm*%4H4t1xd)f!u>?rOQTd9!J!YEUh4STlHYX_Z|?s# z{Krt%(iq;NxkG*X^EMpu&F|tZd&zA>x}=q6D^Xq;c4i6wkSl;9E$Cw%<*H?~*cS*@ z_fyN!phb?c6(r@EIE$z4HV1A3HvT2^CLTQ0C0edC) zZG#{82{QaIP@95hEiw+VdUdQf2%4vcFLkh@Y+*0aKXJBfzi|#h)H!a^Z+ge4*COs$ z@fXc&?I^`cdEA3&6AWUQG#nHXd56m~e$jg=9%p4o2{8M2oVgmYTM_=e0Hb)>LLvh* zbO#sf`~oq*5VIVXpJth0aSQET-lv6BkGGfcoVM{}5y-ywYa)sZP;fnSUPr#p&^gGQ z8dK@|wKvlFajh#kz^UkE*GY`=xjchD25kMlDdVd*dP=AbO>ac4GlgbuOtvGt(|7E9 zu_c+vNN~NmlcL>Gy6SM-Y`Z79cvaT{Slb?{cPhBU@=9H3B|uZ`dc!rB4=Lfp$W1sa z8Gd@cn1)J$(a)tJuS(-u7i>I-?Xi&pUb@)OW5D7Ws9++%`0sq@AZx=2ZmlU{pFwX!7^T@kAo?vd^Yr^-DUGN#M9ExHd6@fI2zV z575o&7FY5R18p4%uw55y7G9X0KP{yDx@@QE``*poliGMH5!qU8xGn242kjKLtmvu5 z?ypG4WS7UC8kSg-OSIUFrbOw8>@;fJC}xUj=dV!Qgc#R7GbM@7qfz4?MGWU$l^-YpZ3 zsO~9!ZEu26X`8J%%VVeqpqVF_36p>Jh3L0k(KFRz%d=-Q1R2NA2q{!`H20#&RUkX{ zc4cuFc4H}O0m(~iwALWKh^DO;TOUd?XDyL*xy-;o!lxmI3jf)-aiARR8@J<2LVhbde5NEV5iJ%%mP_@kMMb(j@MvP!uq-}5i$Mp)h58el#O<&?&(sh zJFqO#DR;KIiV;kZCM|n4dkf#9g}`(O+Hl(3kz= z7&bTaNLRA|8wuaOy}!dnuHLr)n$b$5anM-_*;Lx_DA}kkgsA$YA@rq+g2iY8tT7LW zgEIVzA-8`IEsK&_bAKSkr_l+4YMMyvhv%n{!q-8I$Xd>-W5&0MD_p!V2ltoVpOo20 z4$Q9NA#^D){=3r*D@~X@ISnx?`RzAU$j7rk&;NPTXERbHMGi-Y9T#1jk&4W{(-ISihVnxPs@?si)u(02pe@6@Rz%E zbt&sEd~QLX8)gXP;9D+w3w_P<7KBKxw*lSVO+Lp#MbdhdRX`<@J+uzunUhDK5F4wQ zy0s8Kik&4O<6d`%3mS}ASF@916Bg-s?1>&xP<)AziN0-e_xF-n zzBBMo*!@6-q*xkX$k$7|~|E+KPWSi~`Rj9Z!6 zgPz{Q*YItMZ_<3ZoBmBjZ zagghaD?luQOzB*cj1F=Okb9NtBxk|4z32px0TunUozgt+5L_d_Y+-D*{VCTi`ARe) z;0Lv&T@q?1CQ*euUm|@bH_Ne4%|EeVrb)1ugmSAGxqm!m>686(LCl|4lM7iJ*2y!{ya%ar_%%@y zegCufQiUODEHo^oAQl-ODXb^SkanPf^E`q<26Q>-asU?}i&i+p<8mZ+(tJYFeMl%> z4ocVLUK7n?>iNcOnr|}Cj@Q5bGdfI(y=y4DTvn96Jix)W(+QK?w{TE*TyN6fc{8g@ zPg8I2k4r@MtbH{d0Tob|jFw0U9vV@S_jSo@oKW=PXm8dQMghed(W$-s`IsDEe@O6P z?ljxF*J!JG^A%Es=c9!WKR63T&c}LopLtndWf9W{LFy3kUY~OkGVE4#xHJv$T-HhN zF-HGNuQ2nbJ4dOvJ?=)=Rbq}ct9O3YwCCAq$DSPKe+VFmHdAQRzsyt6_mmNpObo);nG|a=Sfsm9W5^dqtq}ErZBTj~DEYVv;9Tjj(_0V$V%sQ4zJ`O}aF@v(g6mzWagCF6(eo z_up_s_;}>yl^x6fDxJ?spJMwhXdxZmD(Tu3 z?U%%d9k_`PYazf%_*M+uvwO_U5jNV226@lfz8zclx@OU|b&{pgLyHD~lCNAJXA~Yc zEFIV$+}y*j;1@NlIBMNgdvr*(rLSZnv37E90|55f)#F*hZw8H0TaKAvAmC}1vqm+X zQj%Y>G7TZa4UqQr!XO<|kEapO{`C6m)zT%aS=2$({RhjwdU?KNnD4ztnhaSKP48dP zRlE0lVT0>TN@yIAUGKF;Nm&NycGOAn+i&gPDQ}&{M)mLpIPGi7!Fp1FUB1|uXPDav5D2yg#g=Eg?&mafSgmPVaLV5Bu$ac zyR<3WaF3Y;BAIBW#U8JgLTzQTH+hl+wzqm=%7%^`zF6>iSIAGxSJ9?i=fcQa4u39X z&EhpnF`=vH4EYPglZe-p4umxN2XzmIcq`PWen49o%&vq*Uj2mFT!7kVc6mO(Jg{eZ+L%Axff02*o-Q0Qh9Pd_`|du4 zdF#Ita8fvTDcILtST2LJNz=csywv6C9yIA)c4V>gm>`0slJt)hWgbaF_Ri+@OgN~6 z$d9zF9(Xi7kN^+5+{?QadZ)x;|Fi`)umON3A}^@45^C}@^g@(;@C@x3I}MYj%)2}l z-}NbgaB_wnXr}cRW*ncM=^1uyHaiEQ#WX#M=JHb!f;ev|;GvG0;AXEv!WSt<+3Pq* zmkJUG7314isx2sNEQe~$kbmq>BJFXcx2B0g_C11q5%ZvpkdFD5Et<$qvptZmzIy8t zv!B=fNj$Z~NyWD@wAJf8#sMJrmt87WYiwXCu&BQv?$~vYyn5>mIm{uZ6QHQBWNV=1 zi9I(sM>$DMPoPwbBKZw>A71O@TW!7(nhW7%ajqVA{u9hS6xzt~n4PGjp>d~PD1`k^ z*) zcqm+~MbmBwvYeafEf~U#V%pQIPPy z9o1?>4`#RJqqHudXXqdDn$CRHw|aW&dAkU86{&!89{q>@Ww_PqcDl_Dv@DU_@tM;{ zo+)cDRmYD)Dr?)vkc6i?lbR|J=ET{ereV)7tN((tD(o}-+~lWc5*shiAIC?NDjp@J zi$9x^ZFJjf8ry|W;s5j!rP5-(naSLGx-mK2U_=tz`7qMz4j{yRQ0Qkt0>YpsPa#&8 zv_sWsFP1(q4%f)n7qbilkMAHY3YEWcbD)IOwp=;6X}0nJ2fb9#dzkIx zdU9i|_wz%=RA+41@;wGh!;MT{gA(Rb#8XHC>o0mA6Og()&Vsrlkr;XquSnRMIkz>o zUuWp1b4S9ia&_bU5??H@Jf}wnwOA*P{n?Xn$qckCezhZywUwGXkEjwNo_7l|Xj= z4sol73XzRajDg}TIrhE(Tr14$G)#uDa zGs~{DD}D^j2Y@jds}92v+w zefD$dfA(`#3@%#~>WJWcK*Gf0V|?l;eU=9<@<(8sywA;5N1dND;7>uOriF;cV&rVG z>3^Pt^Cmejb5 zRCLNRaqN1ZT-lwj-k}OO&TCaiFmES97%qqQ5^m9+Zn_B)utAFRIiBrE>Ct%=)Hr#0@D$gt#FZwLKM-*H!{5e)P|ng#>xLJ@ir@cTycSxxIjurwaVZ(Q}E&-Bx@@p5B9PHPBql>HlOA z(d?~qNf?Nt3sKR8eA|retvB5*{^=A~@D@tN-TXe)k|>g(U{)K=mmI-cH9LF63e*PA zO}M z1BQ`RD_rb%I~9XaaLD;cY~+OFaNaH;N;D_gjV*V-Pu;#?Qb|qr8^h=$zaCPNCf$u} zya#*7qOk=(ykm{wpbwg5M%0-`MDvwgF$lbrxA7%u@ag+3OpQJka3mh+;_RfjYUFsijza0 zBg0#v#YxQ-3NSxlQq)dq9kdS!1t^88pz?Yz$YbziqTMwI|ILZ~AK2rtrd~fA zgq(2Zo`x7FR#+oJjCZ?g{P;8qm56jo3Tp#tTDKlL9n_*U|P!ZP8U)jM+g+8(E!wM$S=XDIFobbl3$dbt~s?M4ryyu_b58vJe<&fV4sp9 zH-$7^RAO*&>H@`6YckV>C~PsDlW_rQzVHVI9Dzz*HXO80`*x0WdxuNU_Ukj-&@Nfr z+|Xc^FIF5=kmwxCKVGvf67xqzapeA1WV${mg(9mv{ZHuCy2vH$SFFx1)s zMm6a;L}izSMS-X$e!b31^14y=wPY5M2aoS};0oJt4H`U&3)UTp>^Y7e0Ypji(Ica! zFHuI8T-a1Y#k4o)y3t^v*Yp6Fxc%BD$WzV&$ajsv5__$JTHCh+n5mmdP)7Z+OX2rc z_qZLC!+mf82ukZdkf{$>ya9ztA~GS@)r2d|*`yp87k8HDN+o9Jq0EDCU}q3SZ!e^n zE@ZMz`OW(`0tgRGYS=G{=7s3}qQNx@uY@B47mtXcqN++Pn;cp1E4~Z8NjW|=j2YSc z8^C7r#l<56EY1g@z}1mAXh6#)6IB6L-lnQei7b4ZC(0b6w6qEs?oY1d4%eG^e8_vl zwaWB29s0!pN<;6&mxNum@F+;3_Co;3Ce+PZD0cz22e(nqIuh3u9l+uM%C>(QDyq#+ zi;xe1bbw4B(uKs6-#w{njwdq|;k_;J6K~9bf=25#o-8dpqa#x7>YvtQp6lwrFN=CR zyUqS_v&7`*q{dGvjt+WLUz>UXsBaq{p1rkIf#My{5vArW%NEu*bL zC8cOaE(~7|+!hrExk^G_=%n3A@!~blOE;n`B=(y@1JA}IBjFCdIS*+C)MQ|TX3SY) zi!H!W&bnD-aczn{Iumx~b|K4Q@d_&Jc-(zo$~yUW_0V9C*V>9{f|vm9L9#c|BuWcI z(gWW#yS9(?&&9LNQEeOg+(U`LxGvDvdz7JO6IVHD9h5GhcGV40XwN#8 z)^&VkJnCx^4K8`zF^kTYLnd9x)x>uf11aAJM6nU$af!%0>dhBr9RYl*2wZ?uZ@9V{ z-1MOULWh&x%*9)E4Y(8h&SG`s z2jc(cYFs1~+HMFi{>5!H=b2xHFHvH0U3OXA>wx`-HsZ(|%#w3l$m?}-i4%hIq!^%| zkqLPwWu`26RT9GRPkz$Zlb6D`gO5+)fCw>2Ie#2O3k=OA=SZ1!ijGUO16Ya>{jX@1 zy;}9f&Ihnm)sVP#So9X%dmz)ny|V=tS3_|VoUbMlR=qUp$yr57;k<^8+Ne()R;>qc z(-@>!@q@0D7>b`r&Np__LQ;^1S*4Vc{U9dp#95`-d+pm9Mfy_CIkXJO^afSzLSzp2 z9i}0ibmV>L?awfw%-{&#WE!!;qORb{;d?D=zT^D7auqzz+%`bc zsE!q__>1-V8PSJcWINq4Ku!T_gVOj}rU-bQ%bs&%jlIXW^;TmHo2UETidQ25!Fev` z88=NQkNeDn9?35KK1dNpmlv^D!tW^{Q$Yy|Dtd}}R}vMnJ7JL>r2-IXQm9w9_GACr zDcv>$72pueYepdld|$xGR7ceosv{rKlLOk67XI=1dmbBQpD|8T(v@b?J>dbHM4C9d z0G%2tK9xEjqyA-2niM+9NQ*5)r7go(0B)5+N$*^mgFdd$4Ad)t#TzUh;H-ib;Cj1w zU}Wip@b9I4i=KL;2Hg{WT{%J#-*?Fp&C;llP}T@gL%8Yt;U;8{_at~=&RTwXRM8mK zAK^+C2v1_+7sQq?L#VFt_5vZcP=mOCcURf+45|Cu+*1x3=}1JTYv2yLl0_@Fw*%n~ z%Cu1LB7wB1@u3HXtdI?})J`9b7btB1Efl-1Otew(n`}J3&=X3mlWN~nWgX(*Sg1>( zyjoRih-XSyl#ZTYp4S-TmF)Y)KpRjT)w=3|mC#&!DIgZSb=nVyu1`CIP% zz43jsvHkU<+Of&0QYhhD{hDFR@8>G6j(=O}Ne+q9K|~po`B^fz2=Q{exyPWcUnd>W zt9zgoe>z~3sJ?lj$=lKGUN!biKGNiXF1hA5aocN;IaZevZzqOwk=Uh6+2(XL2Fl-E z&FVY73LW3xT(TDN5Q|XepvC2xD}^_SmbPHtzZSc$iBJDo?<<@7`j4I*8o(!(Hl`?dZ!0}5>@LFMju4uEBx z47vc2yLobjVdO+_j0{~VJT|*;tu#^>e-wlxWAp|;=0|V6#mlTRC~D0=c_R#9o(9Ec z0x6${O7)ZVs>a3nOTpm-!Jc8{`6w(RjLZ|o$tGebXhZ4Tr2NGbmj3jjfr3Eo<9?PW zb8&G&EShxS^Rr1~lbS)DfUdpV0fzym3EBFDek%-!ga6dwmf(!$c zvA8J@!s0N!|7@7BSg?*OsN!%5t3rZUdWV{LF$4##3VRu9H0JZ%pjYS)BSM@Q_Q2lsyg)cX!uy#iuc(>F|TJSlg zi5!w%08Uc0`!CG7o0eo(`#f6qF>}nkh(;09-TdiU4}T(a z-yF$D9-7Z-*?Hsg+xfpy8c$ZEek9V!Vaxnb|L~lv{NyX2_qU?ww!DE{s0l(m6wYRH z$(H+nQvR8SnF^`CaqF7B69Pg(GFuzKJz4j7-wc1cX2apH%TDj_x(@}7M8OPytAdg; zU%hMjHFOaBK-m^*)S4)=b%h9CY3TmK@An&m$IJkk$-dp7G<34B-d2p8-XNgH4K7riUw%-T;>}#$iIRy3;lyzbkER4)L?t8|n>W9w2K@`6Ab`2yn&J5D4 z_@Vsbu;~uB^1f)j&$i6CB$0j>&>ww?I zg9E7;l`_Pxlko3W{;L(*Ezry{+LTyaxfbNNDg68k*D^`!RY}P`N0{F|5;l;bIJ>6a zc7@RFy;ZMn^}ygd(;Vk&`D@O+o*oMEbG8z~i>J%LUT5P!SbF(jw5G_I*$PGdGLJsC z#?$6uze!2iL^kyhF~=7>*Ks}8W)d?ry^qpny)Y}t)VjTw65iPlcuv_SBIJ<_umCmZj_}#qW7iTDKrn7kS^bo(LySzj?m5riV^$Ry=DCp`~^D~iRsgx(>fl9 zOr&dX6ce+2-fp-1YXBZqZ9xWmG{IsjITKpxo$-!S(Y?e z0OJ!Es#99%Ts7)#$N`pb>WF+xAp^ABi^+bbwdeY2DfeACMUhjeVGBNj5rR&+HPw48c{J*+Z%s~)@zYb1%$e-f_4f|`% za7u+AqHTNZGqiQGLA98KB-*;cDSw{RqBCa$WnmIiw(|tNI|yU>N}&0rw7~b6dUCB= zJTT9-nRMl1>T^%WyVp*w(WVp~MqHB+sqWX!nU?HLNysnlX@txJc+Bn@!>4RHWahG_ z;{MP!wVAgY!W*`1ss;qdrXs2Uqaw&t9vOg|?5a~I^qOhCbzU6twrKo0XJ04a+wV%D z8mkRC47?hm#g3a@Bn^x>1n)Cg$lZ~#%bFP46d3@~4+hg>vb+6)^rzi|j0Y?8=uHA5*6kqr@QD1eq&Prkjp-$?6~+e+-qS_dus zMgZ2k6W#aCm3(On(G8FzTdYS{Wx^_?XW<7nY_)RasT@cF^xup=9cr_RB*Aoro!Lt8;flM^1vLBzCdKfl4EX~>PAv}_FfNyAf(-R( zTdHqp`+K?JWdj}a??+N?G&lELHG%a#)hABRQU@t&9-=Ie3nbn)4GkXhAWjXp!T?&~ z;06x5!fY7R;FgPgT#Z1|FX((4Vkj>V#62(|WL>h5K!c^5@g!mU(r$%a{@;oqU z)Tf1~>;0g&d(Por5;BT_#L0q)0!{C&p#;^0L^R0{PyqlCsOK+(JU!WQkdnDi$9yrx zBbK&b#fs_oW)>80Z|^L^b`3tz@U2ueO&+@K2SjMoWKqI+Gt9%2=F8 zHIxhfH;(wN>)Ok6&}-oAl!7MhMD{Y?yO8gO^vU%MR^`KF4yY%Sn3Y#rn<~@uZ?#FTWlO8@rMxYTWc!s@%uWu}BAb zf9&*DO~k+~q*vH*N~_@#N(1^r#V1OtCR9s8l6JithN&hy>>|!GyBbFdUJ}J%WI_sG zKuVPSJ*z|{ItG%QJ45$$0YI<8wzKT{1w*MlDY2^%ilt$5+Ii@Pb5AkwMP6yQ{Ni74 znR@o8=PrN7!`u#w5=Igf-*hN`wHCuM2ie^AE4hRLyqftyTdLyE@Pqi{89Rp71lDk#5|qW_Ip}jWBuhJHW+IJUUC#$Z zvlUv2rz<*f{%;QOhR-nd=V;|R?D*w;pRhW9c-)_IXu5a^vmdO^cBQ(KIoC~B=a1$KUC1#Tfu=8L!HPxJ z$tBNwt3(=46x3`Vxv;=$FWb>Ys^UIbWx-#r7%Ov7^$oAt%GY>w-OP^SY#;v$m`>;! zx?nEG#2Z^pY#-9E9JWsJk3qLf2xmihK3O^n^3rB+53R-{_a>3r3=gI}zmLHpwefFk zA*ziEncv%*DPa|5)qQwsI?1PYDHxOsz*2Z4ASlg((*NqWo5H_RV45}uivN9i%YSAn znVIWr(v;6xvMIpj%|M3cbm;_B76SLSCfut*zvTt+2VEF>q{_PjMUEV0=KQ%BTlrW- zGm`WX)Xa!foPAjbOr%dxI0!0P2$VkLeTr0a(PK3~{$f4E1JmJlo|hnK`C<&8e|?Z@zWhzeaOi>=0VN5uTy!ZmViElVUb+KXlf&hi_i~W?;hyU!w*kN)WvdR5-)05uCkh4r$1KsaW74MD- z&~mLOUfou;=a}_<_J=B*4)Q{&v6+#LQE7%V!&{Ag?^$YfC?l0G5Dkf>-@k&(E%{Rp z3*tV6N6Xrd z&|ALybPd@Q0?Pt0rYkw#qvKh!g#pF+BYL_2?Hb+yh0uBn#&560=uj3XvkYOz9PDey zauLzKo)$i-P6|7lIWoK~+f4kfn1GruWhbFImHTm_ehRo#HOjz{zQSK3JoE z=+7tjF!K5NB5}Gj8xX%o-m)62$tCYbrL;s?ad`{VOx{X%a|schKJ&vKCYl>AzBg3s z3Ji1ah2C#Uezr$%P-EXubL;^I1NGd6+yvt?Qj$Nx&lP%K8<=^g_4%p}#T`2C?xBSV zn|DhRePA-nJ%Jcdsv`j%8qPnu0p($tK7+nN*F=tO&Aj_pCDG=+hjRVwLlA)nliIzP z%In*gin_m#ozOHN^HWw@ktyL7e|l^1zX8Etdd~MwW8TI+BL~Dm69#C&qofg@!c?(- zL%)P0?`VJ)yZfYepzphPcYaOzT+c`!(6A@4j#B34@tCJ7k7JzqsLUjE`Zk7kEaluYc&!0R)|9-v{)qSxE&3A%H5}&-*8Z za(Zm>S$rgyG*{wqMJ)UemiHR1*FqRtcmpw6OStcE-AZE)-O%Ev?x82 zfdoOPb`Kund9dE&z%Wyj*Pqq# zj~4Az9|UZA8syyf&_Z1h%Hl+sYa%OdZ*`HdS*vD9>fbc;b6V-!rE_&L7+YOU@Y^ydC|bk;@qF5gUg?TE1Tv0{@uhF zC-vT0%8}dMf>uGPX$p{&44VxdG_Z8pbI^tIY>6Vqd*-lQ_$y_{#4kf%>+l(Q0N_vD z_l@h?p3&z|E|?pSA^p@{sv(tftESX@8uFFx7hRImqRj%{@{(RNesc2ui4~SJj5jAt{sHkx{ku(r^TDorBA2X zD!ZWgqB{Cttea*xa-5s#`0!zqp1|IwGf!%tA2%j1mrR*{yQK~g(If9~w<0bQ(HCR( z@U&e&J?Z6#8f#8H9}BU3CvJ3S!GI!@CVUin0io|8l0+9|Vt6|M7kqLNIUFddwi4%E zCOm~=562H0Um6VX${ApGXBB`XLzr+$pP3XE>mCrUs!_YJD1A*IFjdm+3;vZWDK9@Q{mI_JzpdBL5tA1*JMU^?2iD~EJYR4VO`wPUcyLkflxr)Le_M#E z&?>h%Z$VC8Nqe)6_mM_}gC&W#i+!6X&~pWb3tAw_NV1xqy8ffE?tD(bHeTt}1*|nC z{y1O^>W558x4k_s;hBPBNI!Z9zh6g%;3`W>L27@l=ouQ)J!MZJcEK0~^6l}MIArl- z?W5&|okuQMstyEWNy|m*3J;3e?lzCn`fe$Ga=7$G_XFoPCBEPO_)LE6`AIfVF$S^oj(b|UW+01(HHJmmp7 zw)Nto&Z95TBz9Q~8JQi?2kcwDnV{FKMvb=Cj*Y*p)p5ZI+R(W4D7g_=hPHg$Pe0bu zpDL|9L{*zNUGS*Pm|T@Wn%Bg(HEdot3Zu_A4V;iy`0e`d}KX=HRa{PDzJ8yU#94A7q{`T{=JU#x6h8LgV}H9CzW9`+;4} z(EmDL?{hd`|LRg;_(9L0J$uiiXFv!mBL7%NasS&de&?`EXjOfy;k`MduW&CTpC%vd zz@Kk#UkaU{8=IYG%57JI#xETtA*+9&xuSf2;ek)W5Et#f*y>mA7%MqUTAariZd>@`V2SG3R1w2jZe*n;54ej^EI?zYg*F^MA0-pAmuXYw4zE|{&z54k&X_3u#1 z>!#C~v3s_Rp`25(G%xH2ZYxx;#`;yE(PI^j|AYj&?>W(P1jYuer3>0CF4wlKt+<^l z&E9A>_)TiMv#=W-?6v~Yx8ITU=XPFh)9#qVRx<}KnqPWT8hqwC_a9I~wc%P0Jd&J8 z<8_F>a-zXoP-h=U+;hFTpON_+Hq3!()uDK281`B3$}8W$Fh}}!U#Sb61<%cl4V^!; z@u^!e%t0!-^8U!LgDw+QWsKgQW*tI1(Pjhmsb-gkmVP4Bzr~*m`BiAS@YAYM<-YF` z0sWlsyYiKRA|VC+e)HEaYvmt@rNsBI#uoSeBGez&n(5*Fj^Phx9WAUGFMpGTWh2#( z^eVeeun}wPk{%&jl}oSr1T6w$D;7gi?$YT7y*>Xb) zq~$D8)R+Hli9jq0BR>N6&T+Y}}su?ZRApXxGCO0#Ab{ zhlAl@Q%}o3jcsZdbtuhoC3gxRhK7SOp~HWE7A?PPgVyY>Qs9YCmr~nCTg1@du(6ui z+k0TQqnm?2va5R6&rMP8i6<6@S>;;1c|V+R;~!4{d}Jt7CH9Lx zju-8zX)wbiC52KNtCp5r>aO!2hj3OAuIbP30bzx&$L~G|GJqRNvhiP6Q;)~w~x^u4lsFw%YCD*PRuuz3uM;DWp<9IT1XDc3cs$iT>$zp_zWw!umop`+s0u zM|RLqrrwx>;4PyF?L!-tqa^l)4Mg{koM4XpXJ9x6ZS6$~@8b}gWPKEkwhF^Iaak8? z5az9f@n)3vacahR&GzHI+U78h++Q>i1~X|@bvm_Y#LTwJ!+`TEq0|>EHEJXE{rOhQ zaH&Sbic03s-05(Nxi0>;Q5hU3j`~6aNU4rj+wAw2_vgUG6DGvphxrzXEA29+Bpl|O z9(U34Nq3jZJ%`J#j$aC%AIj2030(K$iv3rDzell&acRh|umQaukKwU;u;LQ9gC;vf zY`P(_kxF!)xB9BRjrZ^5zg6yyyVmqK&e0UIsga83(0{#ERw=VU=iz*&XWB`* z=iuWE-Xq|v)|?t4^4+HRWRil6@)~&YwjFQ%o%u|E&yw}`O}qsY1tTQ9O2Y4d{`CDM zx*_wV9Mm6*sVnTnpFD{kiPYfGS7&e>9d!TW9tghyHZdCV?HBTX)|sa1{>M10*zPy} z;_r`EQYx2EHLsigv+}Q<%5z)MJM6ttk+y|qEjxu;1=fJ)Rpova8lK&j=N#uQBVq$C zDhi+3}{(8LF(;l_@u*k|>`)hT52f4rkSgC6{oU0VI@>Nk-Y@O>%Bo{3YF5R9{r*xPnF2qM}h zZhrBK*ys+UXiD+1s|4n*9N)KOdQW1h1KTu1-(vzyIc zKHxb9?wAr0@E>oCi}jCR7#MnC`Buj>I5m#|mRky7re$RBxh6FtdprK^X}>MWwq@S~ zyW8qtg#G%@n|twZzi-h#Q?SXUdk=OgGQ+!E3MFtgiM1#>1XdDuJi{D2TO?m6pJCpi zHB3gefV)xZPT@_w*wZXL@ z6qDb#9Dj^-OQ!wDr{u2<@0(YWX!Y}@uYRR?D4y$pM*)062J(hi3Qot!`ahl|bp|kA z8tkwR^+kf$y0*YGOQp85A7Az8+7uxp&)j{lh~oh;mr4wJC!*A{w4~|dQTV@{O8(0j z>-E=f<25^iT^`MJKDS)aunvqX@pWFcPp}oISN}gQC96`wJqMh(q|e8fJy`(Xvz zdoYA#Wkj#QJ*%^yO&M7Zr+URdyY_?1sv?0i7nl#PhqI%9&kI=@L4=U<|5vwZr%#o) zS8BZYKlx0D?mit_fnu$Z1@AY3rP=@VC(+pwU^6#%6p&UpVk6d=+g^ox5#I;`77XfAiE56=}4w6(I z;cwclXI;`RCa|wUT}}L)sNNaIAtzj?$@1P`|R<1c#6mh<`LD{E_6v5FDDo?NKAw^>NQ*#lh#DBfrU;I6c^-j}@ z+rS2By@{#F|CPn{ysFT1a29^E<~WuYKq+6E=JM`}%Po>(f;@Mo{^}M@bmb&|M@60x zJeH5dqvQ2!6T@3h5y7ueFbJq?QdX<0v1SL5Bov2Win^u#cgBYGEO~Z&Mv`t>&18vR z20zetZDL}Fl<i5R-FyZ_;wh+g?st`+?iC{{5VBQ^ zeWh>Y{aQ}_8sa4J1m3?|=81P#{>LL)O7yDaY)b}8g;1&wj^b4lqu_ncQyTvHXEkc{ zU5i`<<2jRsFD~=$9@gEVrhe`lD%DyL>yKSX4VWNwH~sEW$ytSZD#+~A88mUldsc1F zHziuG7(~NP=v}ZoOPTP2``6%9U7f2ocl9w6xY z1fd#4;A5TX%=~NCt1(K$3%eX|VQ_@aB}2|G5xi-b6ya zFG0sVrE0Q?#Wy89>ks_1IwHtXY%|I`O~GS);(H+WL&XeOWZe5~z^%}qyCQ6JF&|6k zp#rwThQ~Oi5WtPXw%HHSZ;pQED7z)f4V6Q1uzwO3d@nqJ~CmtkhJLs-Y8N zI8==_))>VxgovVAp)E>do}%Iyf<)C+Vz@i!+;!LegAW7crU7*#tU9UoA*q!AmF8yUiy=_CS`a=jQD-Eev~FS zGLz|a0Y&*Ku{o>K&ZyE=W50)5P2+gGLbOZLd2q3M* z+#!MA5Zj`o7EBEt==-q0V^9Cf!*sqvS?5|ki?_hW6=`UQ2xN_dFzq7d} zdX%S@Tgzt@HVkI-@xsPv&-T zY+S8|sV7bJ&$`#h^{C0kVu6P#P3xHQiS>i3W_n^0{ynG4e)_nfEteqv54&4X!9S57 z#fwGsu6n)hFFZ{|93EdP5>4OF|5DUvK%BEMmQCwJgzB#*zW54HtM_w8Uf8>Vju+w2 z9J7+5i0cUj-28^H@u{zM#_-{c7*lwxh33 z!9KFTcjYnFi!kb4?W{J0CRmBdDY3%S3s_iKh?7B64ihg!4TKEk@!EU4^P>vmNNsWM z!Ol@{Zq%m%!4&7CXpW*z152&(h6l3zoeM+_)L07Y_dp2@i&k z+e1DK2&MdBK4s}!jqF55DTtd3?v0Uu+@q%pwBA{;0B(A1M zi6@HDZrx(VGbr5l9I}o=X5W=+jD9N^=NN{?9juKmEwBmWezL-G5js)?=}kvoX+$Qs zltU(Wdl)59U;GUc9a$DQH_^26-f}`f?J@56?~$wr+;0)SEaUF-SnhMWmG%uL7upUZ z#Fz-5E!r&_-SMY%jyd@ED3MuD8GP6lOntJJHB~s%nRsT)ck}iHqIj`5_EsEUs$Y9LjJf2EbyggFo@<0Gv-x%*L&4Z)O%7w&{o3nMzyz$-ImUcsTGlOanLWwuDg zFU+Vs(lc0}T(zUvw`X2;tio^gHHBE=d3OYpGpli`PhNZ%&f@?D%;fhjKOnsy5aZC} z_Bj;KsM5Xx<=~kTN^2BlQj7)FR+^6=ZfB->RW)D!J0nxDp1pMIBZ?gtiQqKQv~0I4 z2yr}8Ihba^By$XiYUZsGheY$D5ZyDH2_USnh+r8%x+)8+!5INk65P(Fu6G)!kO)_p zfo8w4E5|077*5Sv&^HQD8;DfQYzRuTmJOZ39U?#~5X^o(hmanJuCJu> z=_7sj9oX{X+sV|nwl-+riDxkHmGz%KyjR0in^*Vgl=ch-3RXRo7_Pn-u!@b0jI5bb zs3;Mw7{g=9UILR%z&{t#Y(U16l{)>4*EP2G?CT2+xGNm8xGQZfS0Am~xk*5uPOZJ& z%@G(xwYoic@F4SwzbpRNt6U@HaEhVFW}OG+D{-c!OUAIT5;QJOULZ6~nY4y%O%8-F zTCE1RCn=LYv%vaV5c`ukJME&x1Aev5q$(Y$NyQ)(9v?8(Kbf89aC`lZe$-_j441Yn zzediJy|yD1GmmvKw{@rT*ZX!XW4gQJeEhry4n?PUSEf+f{a|ul%N&Pi}9&v`SezOf2WHp zjhC^V)jYe?F9l43CVX|HTsaIBt>ezC?*MxtW|3~L`ct}P%P|sqH(SN$0^ew( z!trc}TpaM3A_K&~q;wGn(x-{6Vr-$5xO~2y*^XxbqumdMTdn^HV?DI6r;=v@^kj7576f{ zg~Di<^4N0&S!gm*PCi*UIl?vCrKi>bA3j>-De-%3@?3dRzM>! z*He$26p3&}ze13CpkTR|mXC;v*F&Of$03>03F^Mbct@!MB8Fn-H>)%5F2CZkhd>nV z&xewExH<((kK}7lUR~=}m=BzA-2d^EEpHT=SU=KtIYCFZb=NQI3(R1^`1VtADZ8f2 z#!D1^NDue9(T>W4>2jw%jJS%3v6%T8`EI^5II7HHqE;H5$xciro@+QM&V{&19p^N9 zVTL4l@(J}nu0 zn8Xm+YYP6ZQJ#%rGOvDBRlO%CGii=5H7(by$Dc##nMqqd7?6_TypSAWa;g*4|LGGO z2A)zndQOmS;eu?-USO0b8{CuIe;<-fzPZwpm1n0FlbI2CNLBx!%(8f@G}Ej=1Qb=| z(0^KDDq8viBwK4m+4>^iRTMIc9?QA%3ZmM>)+vJ#PAUDuuCZet;y?c_z|}{Nsjf-I zh9JN6AxE7Jb1|h<|3Ef8gJqLy_-T$Fd4i+!-JYpNb{;RNWEX6_lA<*go0>x8tU-4! z$;p{bm@+1xiu&u8lHYPv4D5fl>EB=HvE<&uN-zb$I;A~9BVPu$Cet9QF*Dp)xebHJ#(?6%zt_=@PX3Jz~XfXY)3M6{6)f#ZE8!@7=0F1)Zryd8X((_^00)6VQ zOb@KB9X}UK2LMGhU5r8P{|BUTClr!bprW??*YqckV0W?Ox!1DsU~mc{q5be8`^Q%~ zW=%fj25!%K*qQb=cCL-l+Gcn9e|n(a~8>SAXd(HdE{C z{vw51Vq>FsXhCTDn`rFsE0X*nr97v3O`*eT^sPkt8uB?&US@6I;95dYxVrrpE=h4LOD)JGc7q?32K7 ziq~NDE%L`{oR0J}n2*UpOW3_naI%}Phn@FO>GM7g5#24CG`C?Qx7Sm0xE|-(inAGG zOEikUfNYrQ)@<6DM3+oykr&AtchfZ{%kTPpnVKY?AJ^OL#?6sp%DtJ1$Ndj%wTvoycE)D+)-|`bQdT`NU@{ zC~0>brM9KgB1;OQZ7?Bf6JcgzCS9_e=eh)fFQ}<r{4 z_hdX<(a9Zp*R?95t#|FscY2T+`3)AXukj*#v&$K-GuwtD(c}#Y9h+gonMC@Ahug4} zb@F&e9x*zJ1D*%VhjMi!5TEg!{_4$atAGErnf?~^Mrrcxch+`hB(N4B9WIN<)00JN z&_NYx2qu~H2(Yw1myK`2GsX~`#*5mVqq${c#!8g?^Yw3VGYL>mpj7m4GYhg7l?fFr zbf@ptsTolZZgBr}I9HdGjAeG|MfxV+WAvRMfjHdxtQ$lm<)C+9+0 zM#IyJB`d)Wt6IxjM;g2(FPEQ}{&u-(n?7aQ11s4UkY3wXaghz2a>NBvA333y-rK%~ zS^+qx)o#38qrO?g&%x=NKQZzpWIeNv$0MYCeZp`2h}GCaHAygqY(r~qSNd^{hewPQ z`po(9c_Cv_HRr-(?&|r_H=Jqo-+TLe^9~D_k>cDRJERe*$T5kAwZQhz1#FJ<6*%!j>H{jsaHvWk$Pnth z2s7xr`!=YdSfS3Z>rfunsQJHhvpexHrAIEE31kwsCc;D>$jIe4g-02tp?80e&-|6f?05`{{Yru7~>ArcX&?DvlMN?@Bd8yr&nemXy&{A8{>gAH>>0Pb0{ zxRz^5cM4Z$3W%zt(FU4A9^(A~HXHn#oLKLy=HH-SGFwIfMjJIB1Qt(E22VJ2p-o3Q_ibh-xf+_WL;|J(r@0sbVxN? zuYenj!$HH%gn5BWxhMM{6Fd2rqJ7@eTlV@uk13%k$~Q(eMWbD;O@o3M23h3ZcHCLY zLXJA+N4+;&-eh|UUF(Sso1KD5UR_{ zTQw$Yf_ZIEk>8&?(7lw5bN!Ftei!oTkaU?Ltk`<=2<9)r!H} zHu~L@uTgxw6A-coEUacN-{aj?^0fpV;N}U9`hsWB#D{*117&q{>`r?ALsh2zAt5kL z1wu5jNiC?jSz}vYo_PEfP&iF(`T6U;^sN$&lP5(ZTw^6!cDpdvfz!n(qh|9U--w-( z(~_Y6$=1h>dIf+V9OoFe-a$G!F}tU#%-sf8Bq{`Epx8}i=PvAZcxcV+UZ*nDE`MaP zhZoLE17bi-JjrJOi9aQzFBpT6FVOx(V3Hb=a+nyjev|YI{Zc@N#)LVYHI{Jx26B2+ z8HKwp{M`(Z;-0B;j`pA6x*`|^eaWozp^ic2IDC`F_vdpYH$CkiQ?Zn?lAIc!tQP={ zTQAqHkGLcjQO6sAfvPHkFAbNB4m+elz>`qkoezZ%0s=b^z>`Qa#>iaZ%-zta%B$vlbM?50QU6ny+gsG(FCf4HV?X~_II?eFIb81C% zXn$z0hU_2iSMs&(AMJlmTL1Os6{U~9;+D(l)^zOT@uEr^YWUUD6PI@G53lZ1J6>SJ z6n8llmTL|$~$E#0v-@B{U_`5AmH9VMDNqqVfQ( Date: Sat, 25 Jan 2025 17:43:14 +0000 Subject: [PATCH 2/2] proofreading / formatting --- .../11-LangGraph-Branching.ipynb | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb b/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb index a1bcffbbc..b96f72c37 100644 --- a/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb +++ b/17-LangGraph/01-Core-Features/11-LangGraph-Branching.ipynb @@ -16,12 +16,12 @@ "\n", "## Overview\n", "\n", - "![branching-graph](./assets/11-langgraph-branching-graph.png)\n", - "\n", - "Parallel execution of nodes is essential for improving the overall performance of graph-based workflows. `LangGraph` provides native support for parallel node execution, significantly enhancing the efficiency of workflows built with this framework.\n", + "Parallel execution of nodes is essential for improving the overall performance of graph-based workflows. LangGraph provides native support for parallel node execution, significantly enhancing the efficiency of workflows built with this framework.\n", "\n", "This parallelization is achieved using **fan-out** and **fan-in** mechanisms, utilizing both standard edges and `conditional_edges`.\n", "\n", + "![branching-graph](./assets/11-langgraph-branching-graph.png)\n", + "\n", "### Table of Contents\n", "\n", "- [Overview](#overview)\n", @@ -140,11 +140,10 @@ "\n", "In essence, **fan-out** distributes tasks, and **fan-in** gathers the results to produce the final output.\n", "\n", - "---\n", "\n", - "This example illustrates a fan-out from `Node A` to `B and C`, followed by a fan-in to `D`.\n", + "This example illustrates a fan-out from `Node A` to `Node B` and `Node C`, followed by a fan-in to `Node D`.\n", "\n", - "In the State, the `reducer(add)` operator is specified. This ensures that instead of simply overwriting existing values for a specific key in the State, the values are combined or accumulated. For lists, this means appending the new list to the existing one.\n", + "In the **State**, the `reducer(add)` operator is specified. This ensures that instead of simply overwriting existing values for a specific key in the State, the values are combined or accumulated. For lists, this means appending the new list to the existing one.\n", "\n", "LangGraph uses the `Annotated` type to specify reducer functions for specific keys in the State. This approach allows attaching a reducer function (e.g., `add`) to the type without changing the original type (e.g., `list`) while maintaining compatibility with type checking." ] @@ -205,7 +204,7 @@ "id": "3e3ba6e8", "metadata": {}, "source": [ - "Visualize the graph." + "Let's visualize the graph." ] }, { @@ -279,14 +278,14 @@ "id": "3ccf5a22", "metadata": {}, "source": [ - "### Handling Exceptions During Parallel Processing\n", - "\n", - "LangGraph executes nodes within a \"superstep\" (a complete processing step involving multiple nodes). This means that even if parallel branches are executed simultaneously, the entire superstep is processed in a **transactional** manner.\n", + "### Handling Exceptions during Parallel Processing\n", "\n", - "As a result, if an exception occurs in any of the branches, **no updates** are applied to the state (the entire superstep is rolled back).\n", + "LangGraph executes nodes within a \"superstep\". This means that even if parallel branches are executed simultaneously, the entire superstep is processed in a **transactional** manner.\n", "\n", "> **Superstep**: A complete processing step involving multiple nodes.\n", "\n", + "As a result, if an exception occurs in any of the branches, **no updates** are applied to the state (the entire superstep is rolled back).\n", + "\n", "![branching-graph](./assets/11-langgraph-branching-graph.png)" ] }, @@ -368,7 +367,7 @@ "id": "abbfdf81", "metadata": {}, "source": [ - "Visualize the graph." + "Let's visualize the graph." ] }, { @@ -438,9 +437,7 @@ "source": [ "## Conditional Branching\n", "\n", - "When the fan-out is non-deterministic, you can directly use `add_conditional_edges`.\n", - "\n", - "If there is a known \"sink\" node to connect to after the conditional branching, you can specify `then=\"node_name_to_execute\"` when creating the conditional edge." + "When the fan-out is non-deterministic, you can directly use `add_conditional_edges`." ] }, { @@ -514,12 +511,14 @@ "id": "9f69fcdf", "metadata": {}, "source": [ - "Here is a reference code snippet. When using the `then` syntax, you can add `then=\"e\"` and omit adding explicit edge connections.\n" + "If there is a known \"sink\" node to connect to after the conditional branching, you can specify `then=\"node_name_to_execute\"` when creating the conditional edge.\n", + "\n", + "Here is a reference code snippet. When using the `then` syntax, you can add `then=\"e\"` and omit adding explicit edge connections." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "id": "e718ebeb", "metadata": {}, "outputs": [], @@ -538,7 +537,7 @@ "id": "3400d24c", "metadata": {}, "source": [ - "Visualize the graph." + "Let's visualize the graph." ] }, { @@ -643,11 +642,11 @@ "source": [ "## Sorting Based on Reliability of Fan-out Values\n", "\n", - "Nodes spread out in parallel are executed as part of a single \"**super-step**.\" Updates from each super-step are sequentially applied to the state only after the super-step is completed.\n", + "Nodes spread out in parallel are executed as part of a single \"**super-step**\". Updates from each super-step are sequentially applied to the state only after the super-step is completed.\n", "\n", "If a consistent, predefined order of updates is required during a parallel super-step, the output values can be recorded in a separate field of the state with an identifying key. Then, use standard `edges` from each fan-out node to the convergence point, where a \"sink\" node combines these outputs.\n", "\n", - "For example, consider a scenario where you want to sort the outputs of parallel steps based on their \"reliability.\"" + "For example, consider a scenario where you want to sort the outputs of parallel steps based on their \"reliability\"." ] }, { @@ -757,7 +756,7 @@ "id": "4a4645d3", "metadata": {}, "source": [ - "Visualize the graph." + "Let's visualize the graph." ] }, { @@ -788,7 +787,7 @@ "id": "086416d2", "metadata": {}, "source": [ - "The results from executing nodes in parallel are sorted based on their reliability.\n", + "The results from executing nodes in parallel are then sorted based on their reliability.\n", "\n", "**Reference**\n", "\n", @@ -874,7 +873,7 @@ ], "metadata": { "kernelspec": { - "display_name": "langchain-kr-lwwSZlnu-py3.11", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -888,7 +887,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.7" } }, "nbformat": 4,