From 4894b9784146a86fe9f52c2b87fd1b9ffff5fdbf Mon Sep 17 00:00:00 2001 From: Amna Mubashar Date: Mon, 9 Dec 2024 00:11:29 +0100 Subject: [PATCH 1/5] Updated tutorial 36 --- ...g_Fallbacks_with_Conditional_Routing.ipynb | 1133 +++++++++-------- 1 file changed, 603 insertions(+), 530 deletions(-) diff --git a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb index 9e91f81c..eb2409ec 100644 --- a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb +++ b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb @@ -1,551 +1,624 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "IR5wivW8THt7" - }, - "source": [ - "# Tutorial: Building Fallbacks to Websearch with Conditional Routing\n", - "\n", - "- **Level**: Intermediate\n", - "- **Time to complete**: 10 minutes\n", - "- **Components Used**: [`ConditionalRouter`](https://docs.haystack.deepset.ai/docs/conditionalrouter), [`SerperDevWebSearch`](https://docs.haystack.deepset.ai/docs/serperdevwebsearch), [`PromptBuilder`](https://docs.haystack.deepset.ai/docs/promptbuilder), [`OpenAIGenerator`](https://docs.haystack.deepset.ai/docs/openaigenerator)\n", - "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and a [Serper API Key](https://serper.dev/api-key) for this tutorial\n", - "- **Goal**: After completing this tutorial, you'll have learned how to create a pipeline with conditional routing that can fallback to websearch if the answer is not present in your dataset.\n", - "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "F-a-MAMVat-o" - }, - "source": [ - "## Overview\n", - "\n", - "When developing applications using **retrieval augmented generation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation))**, the retrieval step plays a critical role. It serves as the primary information source for **large language models (LLMs)** to generate responses. However, if your database lacks the necessary information, the retrieval step's effectiveness is limited. In such scenarios, it may be practical to use the web as a fallback data source for your RAG application. By implementing a conditional routing mechanism in your system, you gain complete control over the data flow, enabling you to design a system that can leverage the web as its data source under some conditions.\n", - "\n", - "In this tutorial, you will learn how to create a pipeline with conditional routing that directs the query to a **web-based RAG** route if the answer is not found in the initially given documents." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LSwNKkeKeq0f" - }, - "source": [ - "## Development Environment" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "eGJ7GmCBas4R" - }, - "source": [ - "### Prepare the Colab Environment\n", - "\n", - "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", - "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "FwIgIpE2XqpO" - }, - "source": [ - "### Install Haystack\n", - "\n", - "Install Haystack 2.0 with `pip`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "uba0mntlqs_O" - }, - "outputs": [], - "source": [ - "%%bash\n", - "\n", - "pip install haystack-ai" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "WBkJ7d3hZkOJ" - }, - "source": [ - "### Enable Telemetry\n", - "\n", - "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "HvrOixzzZmMi" - }, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(36)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "QfECEAy2Jdqs" - }, - "source": [ - "### Enter API Keys\n", - "\n", - "Enter API keys required for this tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "13U7Z_k3yE-F", - "outputId": "6ec48553-12d2-4c89-ca13-fc5d34fbc625" - }, - "outputs": [], - "source": [ - "from getpass import getpass\n", - "import os\n", - "\n", - "if \"OPENAI_API_KEY\" not in os.environ:\n", - " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", - "if \"SERPERDEV_API_KEY\" not in os.environ:\n", - " os.environ[\"SERPERDEV_API_KEY\"] = getpass(\"Enter Serper Api key: \")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i_AlhPv1T-4t" - }, - "source": [ - "## Creating a Document\n", - "\n", - "Create a Document about Munich, where the answer to your question will be initially searched:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "5CHbQlLMyVbg" - }, - "outputs": [], - "source": [ - "from haystack.dataclasses import Document\n", - "\n", - "documents = [\n", - " Document(\n", - " content=\"\"\"Munich, the vibrant capital of Bavaria in southern Germany, exudes a perfect blend of rich cultural\n", - " heritage and modern urban sophistication. Nestled along the banks of the Isar River, Munich is renowned\n", - " for its splendid architecture, including the iconic Neues Rathaus (New Town Hall) at Marienplatz and\n", - " the grandeur of Nymphenburg Palace. The city is a haven for art enthusiasts, with world-class museums like the\n", - " Alte Pinakothek housing masterpieces by renowned artists. Munich is also famous for its lively beer gardens, where\n", - " locals and tourists gather to enjoy the city's famed beers and traditional Bavarian cuisine. The city's annual\n", - " Oktoberfest celebration, the world's largest beer festival, attracts millions of visitors from around the globe.\n", - " Beyond its cultural and culinary delights, Munich offers picturesque parks like the English Garden, providing a\n", - " serene escape within the heart of the bustling metropolis. Visitors are charmed by Munich's warm hospitality,\n", - " making it a must-visit destination for travelers seeking a taste of both old-world charm and contemporary allure.\"\"\"\n", - " )\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "zMNy0tjtUh_L" - }, - "source": [ - "## Creating the Initial Pipeline Components\n", - "\n", - "First, define a prompt instructing the LLM to respond with the text `\"no_answer\"` if the provided documents do not offer enough context to answer the query. Next, initialize a [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) with that prompt. It's crucial that the LLM replies with `\"no_answer\"` as you will use this keyword to indicate that the query should be directed to the fallback web search route.\n", - "\n", - "As the LLM, you will use an [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator) with the `gpt-4o-mini` model.\n", - "\n", - "> The provided prompt works effectively with the `gpt-4o-mini` model. If you prefer to use a different [Generator](https://docs.haystack.deepset.ai/docs/generators), you may need to update the prompt to provide clear instructions to your model." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "id": "nzhn2kDfqvbs" - }, - "outputs": [], - "source": [ - "from haystack.components.builders.prompt_builder import PromptBuilder\n", - "from haystack.components.generators import OpenAIGenerator\n", - "\n", - "prompt_template = \"\"\"\n", - "Answer the following query given the documents.\n", - "If the answer is not contained within the documents reply with 'no_answer'\n", - "Query: {{query}}\n", - "Documents:\n", - "{% for document in documents %}\n", - " {{document.content}}\n", - "{% endfor %}\n", - "\"\"\"\n", - "\n", - "prompt_builder = PromptBuilder(template=prompt_template)\n", - "llm = OpenAIGenerator(model=\"gpt-4o-mini\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "LepACkkWPsBx" - }, - "source": [ - "## Initializing the Web Search Components\n", - "\n", - "Initialize the necessary components for a web-based RAG application. Along with a `PromptBuilder` and an `OpenAIGenerator`, you will need a [SerperDevWebSearch](https://docs.haystack.deepset.ai/docs/serperdevwebsearch) to retrieve relevant documents for the query from the web.\n", - "\n", - "> If desired, you can use a different [Generator](https://docs.haystack.deepset.ai/docs/generators) for the web-based RAG branch of the pipeline." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "VEYchFgQPxZ_" - }, - "outputs": [], - "source": [ - "from haystack.components.builders.prompt_builder import PromptBuilder\n", - "from haystack.components.generators import OpenAIGenerator\n", - "from haystack.components.websearch.serper_dev import SerperDevWebSearch\n", - "\n", - "prompt_for_websearch = \"\"\"\n", - "Answer the following query given the documents retrieved from the web.\n", - "Your answer shoud indicate that your answer was generated from websearch.\n", - "\n", - "Query: {{query}}\n", - "Documents:\n", - "{% for document in documents %}\n", - " {{document.content}}\n", - "{% endfor %}\n", - "\"\"\"\n", - "\n", - "websearch = SerperDevWebSearch()\n", - "prompt_builder_for_websearch = PromptBuilder(template=prompt_for_websearch)\n", - "llm_for_websearch = OpenAIGenerator(model=\"gpt-4o-mini\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vnacak_tVWqv" - }, - "source": [ - "## Creating the ConditionalRouter\n", - "\n", - "[ConditionalRouter](https://docs.haystack.deepset.ai/docs/conditionalrouter) is the component that handles data routing on specific conditions. You need to define a `condition`, an `output`, an `output_name` and an `output_type` for each route. Each route that the `ConditionalRouter` creates acts as the output of this component and can be connected to other components in the same pipeline. \n", - "\n", - "In this case, you need to define two routes:\n", - "- If the LLM replies with the `\"no_answer\"` keyword, the pipeline should perform web search. It means that you will put the original `query` in the output value to pass to the next component (in this case the next component will be the `SerperDevWebSearch`) and the output name will be `go_to_websearch`.\n", - "- Otherwise, the given documents are enough for an answer and pipeline execution ends here. Return the LLM reply in the output named `answer`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "id": "qyE9rGcawX3F" - }, - "outputs": [], - "source": [ - "from haystack.components.routers import ConditionalRouter\n", - "\n", - "routes = [\n", - " {\n", - " \"condition\": \"{{'no_answer' in replies[0]}}\",\n", - " \"output\": \"{{query}}\",\n", - " \"output_name\": \"go_to_websearch\",\n", - " \"output_type\": str,\n", - " },\n", - " {\n", - " \"condition\": \"{{'no_answer' not in replies[0]}}\",\n", - " \"output\": \"{{replies[0]}}\",\n", - " \"output_name\": \"answer\",\n", - " \"output_type\": str,\n", - " },\n", - "]\n", - "\n", - "router = ConditionalRouter(routes)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wdyko78oXb5a" - }, - "source": [ - "## Building the Pipeline\n", - "\n", - "Add all components to your pipeline and connect them. `go_to_websearch` output of the `router` should be connected to the `websearch` to retrieve documents from the web and also to `prompt_builder_for_websearch` to use in the prompt." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "4sCyBwc0oTVs", - "outputId": "fd2347d4-9363-45e0-e734-87e4a160f741" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from haystack import Pipeline\n", - "\n", - "pipe = Pipeline()\n", - "pipe.add_component(\"prompt_builder\", prompt_builder)\n", - "pipe.add_component(\"llm\", llm)\n", - "pipe.add_component(\"router\", router)\n", - "pipe.add_component(\"websearch\", websearch)\n", - "pipe.add_component(\"prompt_builder_for_websearch\", prompt_builder_for_websearch)\n", - "pipe.add_component(\"llm_for_websearch\", llm_for_websearch)\n", - "\n", - "pipe.connect(\"prompt_builder\", \"llm\")\n", - "pipe.connect(\"llm.replies\", \"router.replies\")\n", - "pipe.connect(\"router.go_to_websearch\", \"websearch.query\")\n", - "pipe.connect(\"router.go_to_websearch\", \"prompt_builder_for_websearch.query\")\n", - "pipe.connect(\"websearch.documents\", \"prompt_builder_for_websearch.documents\")\n", - "pipe.connect(\"prompt_builder_for_websearch\", \"llm_for_websearch\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "d0HmdbUJKJ_9" - }, - "source": [ - "### Visualize the Pipeline\n", - "\n", - "To understand how you formed this pipeline with conditional routing, use [draw()](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method of the pipeline. If you're running this notebook on Google Colab, the generated file will be saved in \\\"Files\\\" section on the sidebar." - ] - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "IR5wivW8THt7" + }, + "source": [ + "# Tutorial: Building Fallbacks to Websearch with Conditional Routing\n", + "\n", + "- **Level**: Intermediate\n", + "- **Time to complete**: 10 minutes\n", + "- **Components Used**: [`ConditionalRouter`](https://docs.haystack.deepset.ai/docs/conditionalrouter), [`SerperDevWebSearch`](https://docs.haystack.deepset.ai/docs/serperdevwebsearch), [`ChatPromptBuilder`](https://docs.haystack.deepset.ai/docs/chatpromptbuilder), [`OpenAIChatGenerator`](https://docs.haystack.deepset.ai/docs/openaichatgenerator)\n", + "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and a [Serper API Key](https://serper.dev/api-key) for this tutorial\n", + "- **Goal**: After completing this tutorial, you'll have learned how to create a pipeline with conditional routing that can fallback to websearch if the answer is not present in your dataset.\n", + "\n", + "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "F-a-MAMVat-o" + }, + "source": [ + "## Overview\n", + "\n", + "When developing applications using **retrieval augmented generation ([RAG](https://www.deepset.ai/blog/llms-retrieval-augmentation))**, the retrieval step plays a critical role. It serves as the primary information source for **large language models (LLMs)** to generate responses. However, if your database lacks the necessary information, the retrieval step's effectiveness is limited. In such scenarios, it may be practical to use the web as a fallback data source for your RAG application. By implementing a conditional routing mechanism in your system, you gain complete control over the data flow, enabling you to design a system that can leverage the web as its data source under some conditions.\n", + "\n", + "In this tutorial, you will learn how to create a pipeline with conditional routing that directs the query to a **web-based RAG** route if the answer is not found in the initially given documents." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LSwNKkeKeq0f" + }, + "source": [ + "## Development Environment" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eGJ7GmCBas4R" + }, + "source": [ + "### Prepare the Colab Environment\n", + "\n", + "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", + "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FwIgIpE2XqpO" + }, + "source": [ + "### Install Haystack\n", + "\n", + "Install Haystack 2.0 with `pip`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "uba0mntlqs_O" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "svF_SUK4rFwv", - "outputId": "60894eea-2cec-4be8-d13c-83d2c81656f4" - }, - "outputs": [], - "source": [ - "pipe.draw(\"pipe.png\")" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: haystack-ai in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (2.8.0)\n", + "Requirement already satisfied: haystack-experimental in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (0.3.0)\n", + "Requirement already satisfied: jinja2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.1.4)\n", + "Requirement already satisfied: lazy-imports in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (0.3.1)\n", + "Requirement already satisfied: more-itertools in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (10.2.0)\n", + "Requirement already satisfied: networkx in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.2.1)\n", + "Requirement already satisfied: numpy in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (1.26.4)\n", + "Requirement already satisfied: openai>=1.1.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (1.31.1)\n", + "Requirement already satisfied: pandas in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.2.2)\n", + "Requirement already satisfied: posthog in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.5.0)\n", + "Requirement already satisfied: python-dateutil in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.9.0.post0)\n", + "Requirement already satisfied: pyyaml in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (6.0.1)\n", + "Requirement already satisfied: requests in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.32.3)\n", + "Requirement already satisfied: tenacity!=8.4.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (8.3.0)\n", + "Requirement already satisfied: tqdm in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (4.66.4)\n", + "Requirement already satisfied: typing-extensions>=4.7 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (4.12.1)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (4.4.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (1.9.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (0.27.0)\n", + "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (2.7.3)\n", + "Requirement already satisfied: sniffio in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (1.3.1)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from jinja2->haystack-ai) (2.1.5)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pandas->haystack-ai) (2024.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pandas->haystack-ai) (2024.1)\n", + "Requirement already satisfied: six>=1.5 in /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/site-packages (from python-dateutil->haystack-ai) (1.15.0)\n", + "Requirement already satisfied: monotonic>=1.5 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from posthog->haystack-ai) (1.6)\n", + "Requirement already satisfied: backoff>=1.10.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from posthog->haystack-ai) (2.2.1)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (3.7)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (1.26.18)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (2024.6.2)\n", + "Requirement already satisfied: exceptiongroup>=1.0.2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (1.2.1)\n", + "Requirement already satisfied: httpcore==1.* in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (1.0.5)\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (0.14.0)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.18.4 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (2.18.4)\n" + ] }, { - "cell_type": "markdown", - "metadata": { - "id": "jgk1z6GGYH6J" - }, - "source": [ - "## Running the Pipeline!\n", - "\n", - "In the `run()`, pass the query to the `prompt_builder` and the `router`. In real life applications, `documents` will be provided by a [Retriever](https://docs.haystack.deepset.ai/docs/retrievers) but to keep this example simple, you will provide the defined `documents` to the `prompt_builder`." - ] - }, + "name": "stderr", + "output_type": "stream", + "text": [ + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49m/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "%%bash\n", + "\n", + "pip install haystack-ai" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WBkJ7d3hZkOJ" + }, + "source": [ + "### Enable Telemetry\n", + "\n", + "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "HvrOixzzZmMi" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "d_l4rYmCoVki", - "outputId": "3bd7956a-7612-4bc1-c3e5-a7a51be8981f" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Munich is in southern Germany.\n" - ] - } - ], - "source": [ - "query = \"Where is Munich?\"\n", - "\n", - "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", - "\n", - "# Print the `answer` coming from the ConditionalRouter\n", - "print(result[\"router\"][\"answer\"])" - ] + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "from haystack.telemetry import tutorial_running\n", + "\n", + "tutorial_running(36)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QfECEAy2Jdqs" + }, + "source": [ + "### Enter API Keys\n", + "\n", + "Enter API keys required for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "13U7Z_k3yE-F", + "outputId": "6ec48553-12d2-4c89-ca13-fc5d34fbc625" + }, + "outputs": [], + "source": [ + "from getpass import getpass\n", + "import os\n", + "\n", + "if \"OPENAI_API_KEY\" not in os.environ:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter OpenAI API key:\")\n", + "if \"SERPERDEV_API_KEY\" not in os.environ:\n", + " os.environ[\"SERPERDEV_API_KEY\"] = getpass(\"Enter Serper Api key: \")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i_AlhPv1T-4t" + }, + "source": [ + "## Creating a Document\n", + "\n", + "Create a Document about Munich, where the answer to your question will be initially searched:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "5CHbQlLMyVbg" + }, + "outputs": [], + "source": [ + "from haystack.dataclasses import Document\n", + "\n", + "documents = [\n", + " Document(\n", + " content=\"\"\"Munich, the vibrant capital of Bavaria in southern Germany, exudes a perfect blend of rich cultural\n", + " heritage and modern urban sophistication. Nestled along the banks of the Isar River, Munich is renowned\n", + " for its splendid architecture, including the iconic Neues Rathaus (New Town Hall) at Marienplatz and\n", + " the grandeur of Nymphenburg Palace. The city is a haven for art enthusiasts, with world-class museums like the\n", + " Alte Pinakothek housing masterpieces by renowned artists. Munich is also famous for its lively beer gardens, where\n", + " locals and tourists gather to enjoy the city's famed beers and traditional Bavarian cuisine. The city's annual\n", + " Oktoberfest celebration, the world's largest beer festival, attracts millions of visitors from around the globe.\n", + " Beyond its cultural and culinary delights, Munich offers picturesque parks like the English Garden, providing a\n", + " serene escape within the heart of the bustling metropolis. Visitors are charmed by Munich's warm hospitality,\n", + " making it a must-visit destination for travelers seeking a taste of both old-world charm and contemporary allure.\"\"\"\n", + " )\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zMNy0tjtUh_L" + }, + "source": [ + "## Creating the Initial Pipeline Components\n", + "\n", + "First, define a prompt instructing the LLM to respond with the text `\"no_answer\"` if the provided documents do not offer enough context to answer the query. Next, initialize a [ChatPromptBuilder](https://docs.haystack.deepset.ai/docs/chatpromptbuilder) with that prompt. `ChatPromptBuilder`accepts prompts inf the form of `ChatMessage`. It's crucial that the LLM replies with `\"no_answer\"` as you will use this keyword to indicate that the query should be directed to the fallback web search route.\n", + "\n", + "As the LLM, you will use an [OpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/openaichatgenerator) with the `gpt-4o-mini` model.\n", + "\n", + "> The provided prompt works effectively with the `gpt-4o-mini` model. If you prefer to use a different [ChatGenerator](https://docs.haystack.deepset.ai/docs/generators), you may need to update the prompt to provide clear instructions to your model." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "nzhn2kDfqvbs" + }, + "outputs": [], + "source": [ + "from haystack.components.builders import ChatPromptBuilder\n", + "from haystack.dataclasses import ChatMessage\n", + "from haystack.components.generators.chat import OpenAIChatGenerator\n", + "\n", + "prompt_template = [ChatMessage.from_user(\"\"\"\n", + "Answer the following query given the documents.\n", + "If the answer is not contained within the documents reply with 'no_answer'\n", + "Query: {{query}}\n", + "Documents:\n", + "{% for document in documents %}\n", + " {{document.content}}\n", + "{% endfor %}\n", + "\"\"\")]\n", + "\n", + "prompt_builder = ChatPromptBuilder(template=prompt_template)\n", + "llm = OpenAIChatGenerator(model=\"gpt-4o-mini\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LepACkkWPsBx" + }, + "source": [ + "## Initializing the Web Search Components\n", + "\n", + "Initialize the necessary components for a web-based RAG application. Along with a `ChatPromptBuilder` and an `OpenAIChatGenerator`, you will need a [SerperDevWebSearch](https://docs.haystack.deepset.ai/docs/serperdevwebsearch) to retrieve relevant documents for the query from the web.\n", + "\n", + "> If desired, you can use a different [ChatGenerator](https://docs.haystack.deepset.ai/docs/generators) for the web-based RAG branch of the pipeline." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "VEYchFgQPxZ_" + }, + "outputs": [], + "source": [ + "\n", + "from haystack.components.websearch.serper_dev import SerperDevWebSearch\n", + "\n", + "prompt_for_websearch = [ChatMessage.from_user(\"\"\"\n", + "Answer the following query given the documents retrieved from the web.\n", + "Your answer shoud indicate that your answer was generated from websearch.\n", + "\n", + "Query: {{query}}\n", + "Documents:\n", + "{% for document in documents %}\n", + " {{document.content}}\n", + "{% endfor %}\n", + "\"\"\")]\n", + "\n", + "websearch = SerperDevWebSearch()\n", + "prompt_builder_for_websearch = ChatPromptBuilder(template=prompt_for_websearch)\n", + "llm_for_websearch = OpenAIChatGenerator(model=\"gpt-4o-mini\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vnacak_tVWqv" + }, + "source": [ + "## Creating the ConditionalRouter\n", + "\n", + "[ConditionalRouter](https://docs.haystack.deepset.ai/docs/conditionalrouter) is the component that handles data routing on specific conditions. You need to define a `condition`, an `output`, an `output_name` and an `output_type` for each route. Each route that the `ConditionalRouter` creates acts as the output of this component and can be connected to other components in the same pipeline. \n", + "\n", + "In this case, you need to define two routes:\n", + "- If the LLM replies with the `\"no_answer\"` keyword, the pipeline should perform web search. It means that you will put the original `query` in the output value to pass to the next component (in this case the next component will be the `SerperDevWebSearch`) and the output name will be `go_to_websearch`.\n", + "- Otherwise, the given documents are enough for an answer and pipeline execution ends here. Return the LLM reply in the output named `answer`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "qyE9rGcawX3F" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "dBN8eLSKgb16" - }, - "source": [ - "✅ The answer to this query can be found in the defined document.\n", - "\n", - "Now, try a different query that doesn't have an answer in the given document and test if the web search works as expected:" - ] + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/pypdf/_crypt_providers/_cryptography.py:32: CryptographyDeprecationWarning: ARC4 has been moved to cryptography.hazmat.decrepit.ciphers.algorithms.ARC4 and will be removed from this module in 48.0.0.\n", + " from cryptography.hazmat.primitives.ciphers.algorithms import AES, ARC4\n" + ] + } + ], + "source": [ + "from haystack.components.routers import ConditionalRouter\n", + "\n", + "routes = [\n", + " {\n", + " \"condition\": \"{{'no_answer' in replies[0].content}}\",\n", + " \"output\": \"{{query}}\",\n", + " \"output_name\": \"go_to_websearch\",\n", + " \"output_type\": str,\n", + " },\n", + " {\n", + " \"condition\": \"{{'no_answer' not in replies[0].content}}\",\n", + " \"output\": \"{{replies[0].content}}\",\n", + " \"output_name\": \"answer\",\n", + " \"output_type\": str,\n", + " },\n", + "]\n", + "\n", + "router = ConditionalRouter(routes)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wdyko78oXb5a" + }, + "source": [ + "## Building the Pipeline\n", + "\n", + "Add all components to your pipeline and connect them. `go_to_websearch` output of the `router` should be connected to the `websearch` to retrieve documents from the web and also to `prompt_builder_for_websearch` to use in the prompt." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "4sCyBwc0oTVs", + "outputId": "fd2347d4-9363-45e0-e734-87e4a160f741" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "_v-WdlSy365M", - "outputId": "603c9346-8718-427e-d232-4cc71799a2bb" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.']\n" - ] - } - ], - "source": [ - "query = \"How many people live in Munich?\"\n", - "\n", - "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", - "\n", - "# Print the `replies` generated using the web searched Documents\n", - "print(result[\"llm_for_websearch\"][\"replies\"])" + "data": { + "text/plain": [ + "\n", + "🚅 Components\n", + " - prompt_builder: ChatPromptBuilder\n", + " - llm: OpenAIChatGenerator\n", + " - router: ConditionalRouter\n", + " - websearch: SerperDevWebSearch\n", + " - prompt_builder_for_websearch: ChatPromptBuilder\n", + " - llm_for_websearch: OpenAIChatGenerator\n", + "🛤️ Connections\n", + " - prompt_builder.prompt -> llm.messages (List[ChatMessage])\n", + " - llm.replies -> router.replies (List[ChatMessage])\n", + " - router.go_to_websearch -> websearch.query (str)\n", + " - router.go_to_websearch -> prompt_builder_for_websearch.query (str)\n", + " - websearch.documents -> prompt_builder_for_websearch.documents (List[Document])\n", + " - prompt_builder_for_websearch.prompt -> llm_for_websearch.messages (List[ChatMessage])" ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack import Pipeline\n", + "\n", + "pipe = Pipeline()\n", + "pipe.add_component(\"prompt_builder\", prompt_builder)\n", + "pipe.add_component(\"llm\", llm)\n", + "pipe.add_component(\"router\", router)\n", + "pipe.add_component(\"websearch\", websearch)\n", + "pipe.add_component(\"prompt_builder_for_websearch\", prompt_builder_for_websearch)\n", + "pipe.add_component(\"llm_for_websearch\", llm_for_websearch)\n", + "\n", + "pipe.connect(\"prompt_builder.prompt\", \"llm.messages\")\n", + "pipe.connect(\"llm.replies\", \"router.replies\")\n", + "pipe.connect(\"router.go_to_websearch\", \"websearch.query\")\n", + "pipe.connect(\"router.go_to_websearch\", \"prompt_builder_for_websearch.query\")\n", + "pipe.connect(\"websearch.documents\", \"prompt_builder_for_websearch.documents\")\n", + "pipe.connect(\"prompt_builder_for_websearch\", \"llm_for_websearch\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "d0HmdbUJKJ_9" + }, + "source": [ + "### Visualize the Pipeline\n", + "\n", + "To understand how you formed this pipeline with conditional routing, use [draw()](https://docs.haystack.deepset.ai/docs/drawing-pipeline-graphs) method of the pipeline. If you're running this notebook on Google Colab, the generated file will be saved in \\\"Files\\\" section on the sidebar." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 }, - { - "cell_type": "markdown", - "metadata": { - "id": "wUkuXoWnHa5c" - }, - "source": [ - "If you check the whole result, you will see that `websearch` component also provides links to Documents retrieved from the web:" - ] + "id": "svF_SUK4rFwv", + "outputId": "60894eea-2cec-4be8-d13c-83d2c81656f4" + }, + "outputs": [], + "source": [ + "pipe.draw(\"pipe.png\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jgk1z6GGYH6J" + }, + "source": [ + "## Running the Pipeline!\n", + "\n", + "In the `run()`, pass the query to the `prompt_builder` and the `router`. In real life applications, `documents` will be provided by a [Retriever](https://docs.haystack.deepset.ai/docs/retrievers) but to keep this example simple, you will provide the defined `documents` to the `prompt_builder`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "d_l4rYmCoVki", + "outputId": "3bd7956a-7612-4bc1-c3e5-a7a51be8981f" + }, + "outputs": [ { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "_EYLZguZGznY", - "outputId": "df49a576-9961-44b4-e89d-2c5195869360" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'llm': {'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", - " 'index': 0,\n", - " 'finish_reason': 'stop',\n", - " 'usage': {'completion_tokens': 2,\n", - " 'prompt_tokens': 271,\n", - " 'total_tokens': 273}}]},\n", - " 'websearch': {'links': ['https://en.wikipedia.org/wiki/Munich',\n", - " 'https://worldpopulationreview.com/world-cities/munich-population',\n", - " 'https://en.wikipedia.org/wiki/Demographics_of_Munich',\n", - " 'https://www.macrotrends.net/cities/204371/munich/population',\n", - " 'https://www.britannica.com/place/Munich-Bavaria-Germany',\n", - " 'https://www.statista.com/statistics/519723/munich-population-by-age-group/',\n", - " 'https://www.citypopulation.de/en/germany/bayern/m%C3%BCnchen_stadt/09162000__m%C3%BCnchen/',\n", - " 'https://www.quora.com/How-many-people-live-in-Munich',\n", - " 'https://earth.esa.int/web/earth-watching/image-of-the-week/content/-/article/munich-germany/']},\n", - " 'llm_for_websearch': {'replies': ['According to the documents retrieved from the web, the population of Munich is approximately 1.47 million as of 2019. However, the most recent estimates suggest that the population has grown to about 1.58 million as of May 31, 2022. Additionally, the current estimated population of Munich is around 1.46 million, with the urban area being much larger at 2.65 million.'],\n", - " 'meta': [{'model': 'gpt-4o-mini-2024-07-18',\n", - " 'index': 0,\n", - " 'finish_reason': 'stop',\n", - " 'usage': {'completion_tokens': 85,\n", - " 'prompt_tokens': 436,\n", - " 'total_tokens': 521}}]}}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "result" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "Munich is located in southern Germany.\n" + ] + } + ], + "source": [ + "query = \"Where is Munich?\"\n", + "\n", + "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", + "\n", + "# Print the `answer` coming from the ConditionalRouter\n", + "print(result[\"router\"][\"answer\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dBN8eLSKgb16" + }, + "source": [ + "✅ The answer to this query can be found in the defined document.\n", + "\n", + "Now, try a different query that doesn't have an answer in the given document and test if the web search works as expected:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" }, + "id": "_v-WdlSy365M", + "outputId": "603c9346-8718-427e-d232-4cc71799a2bb" + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": { - "id": "6nhdYK-vHpNM" - }, - "source": [ - "## What's next\n", - "\n", - "🎉 Congratulations! You've built a pipeline with conditional routing! You can now customize the condition for your specific use case and create a custom Haystack 2.0 pipeline to meet your needs.\n", - "\n", - "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", - "- [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline)\n", - "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", - "\n", - "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", - "\n", - "Thanks for reading!" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "[ChatMessage(content='As of May 31, 2024, Munich has a population of approximately 1,594,632 residents, making it the third-largest city in Germany, after Berlin and Hamburg. The metro area population is about 1,585,000, reflecting a modest increase from the previous year. Additionally, estimates suggest that the city has consistently had over 1.5 million inhabitants in recent years. This information has been gathered from various web sources.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 91, 'prompt_tokens': 401, 'total_tokens': 492, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]\n" + ] } - ], - "metadata": { + ], + "source": [ + "query = \"How many people live in Munich?\"\n", + "\n", + "result = pipe.run({\"prompt_builder\": {\"query\": query, \"documents\": documents}, \"router\": {\"query\": query}})\n", + "\n", + "# Print the `replies` generated using the web searched Documents\n", + "print(result[\"llm_for_websearch\"][\"replies\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wUkuXoWnHa5c" + }, + "source": [ + "If you check the whole result, you will see that `websearch` component also provides links to Documents retrieved from the web:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { "colab": { - "provenance": [] + "base_uri": "https://localhost:8080/" }, - "kernelspec": { - "display_name": "Python 3", - "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.9.6" + "id": "_EYLZguZGznY", + "outputId": "df49a576-9961-44b4-e89d-2c5195869360" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'websearch': {'links': ['https://en.wikipedia.org/wiki/Munich',\n", + " 'https://www.macrotrends.net/cities/204371/munich/population',\n", + " 'https://www.britannica.com/place/Munich-Bavaria-Germany',\n", + " 'https://en.wikipedia.org/wiki/Demographics_of_Munich',\n", + " 'https://www.statista.com/statistics/505774/munich-population/',\n", + " 'https://www.citypopulation.de/en/germany/bayern/m%C3%BCnchen_stadt/09162000__m%C3%BCnchen/',\n", + " 'https://eurocities.eu/cities/munich/',\n", + " 'https://www.coe.int/en/web/interculturalcities/munich',\n", + " 'https://www.worldometers.info/world-population/germany-population/']},\n", + " 'llm_for_websearch': {'replies': [ChatMessage(content='As of May 31, 2024, Munich has a population of approximately 1,594,632 residents, making it the third-largest city in Germany, after Berlin and Hamburg. The metro area population is about 1,585,000, reflecting a modest increase from the previous year. Additionally, estimates suggest that the city has consistently had over 1.5 million inhabitants in recent years. This information has been gathered from various web sources.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 91, 'prompt_tokens': 401, 'total_tokens': 492, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]}}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" } + ], + "source": [ + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6nhdYK-vHpNM" + }, + "source": [ + "## What's next\n", + "\n", + "🎉 Congratulations! You've built a pipeline with conditional routing! You can now customize the condition for your specific use case and create a custom Haystack 2.0 pipeline to meet your needs.\n", + "\n", + "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", + "- [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline)\n", + "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", + "\n", + "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", + "\n", + "Thanks for reading!" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 0 + "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.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 } From d1f47e68937e45791714267683c2e6eaec44dd27 Mon Sep 17 00:00:00 2001 From: Amna Mubashar Date: Mon, 9 Dec 2024 00:19:24 +0100 Subject: [PATCH 2/5] Updated tutorial 40 and removed 37 --- ...g_Fallbacks_with_Conditional_Routing.ipynb | 57 +- ...ing_Pipeline_Inputs_with_Multiplexer.ipynb | 554 ------------------ ...at_Application_with_Function_Calling.ipynb | 314 ++-------- 3 files changed, 65 insertions(+), 860 deletions(-) delete mode 100644 tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb diff --git a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb index eb2409ec..42f9171d 100644 --- a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb +++ b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb @@ -64,64 +64,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "id": "uba0mntlqs_O" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Requirement already satisfied: haystack-ai in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (2.8.0)\n", - "Requirement already satisfied: haystack-experimental in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (0.3.0)\n", - "Requirement already satisfied: jinja2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.1.4)\n", - "Requirement already satisfied: lazy-imports in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (0.3.1)\n", - "Requirement already satisfied: more-itertools in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (10.2.0)\n", - "Requirement already satisfied: networkx in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.2.1)\n", - "Requirement already satisfied: numpy in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (1.26.4)\n", - "Requirement already satisfied: openai>=1.1.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (1.31.1)\n", - "Requirement already satisfied: pandas in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.2.2)\n", - "Requirement already satisfied: posthog in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (3.5.0)\n", - "Requirement already satisfied: python-dateutil in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.9.0.post0)\n", - "Requirement already satisfied: pyyaml in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (6.0.1)\n", - "Requirement already satisfied: requests in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (2.32.3)\n", - "Requirement already satisfied: tenacity!=8.4.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (8.3.0)\n", - "Requirement already satisfied: tqdm in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (4.66.4)\n", - "Requirement already satisfied: typing-extensions>=4.7 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from haystack-ai) (4.12.1)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (4.4.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (1.9.0)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (0.27.0)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (2.7.3)\n", - "Requirement already satisfied: sniffio in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from openai>=1.1.0->haystack-ai) (1.3.1)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from jinja2->haystack-ai) (2.1.5)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pandas->haystack-ai) (2024.1)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pandas->haystack-ai) (2024.1)\n", - "Requirement already satisfied: six>=1.5 in /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/site-packages (from python-dateutil->haystack-ai) (1.15.0)\n", - "Requirement already satisfied: monotonic>=1.5 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from posthog->haystack-ai) (1.6)\n", - "Requirement already satisfied: backoff>=1.10.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from posthog->haystack-ai) (2.2.1)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (3.7)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (1.26.18)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from requests->haystack-ai) (2024.6.2)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from anyio<5,>=3.5.0->openai>=1.1.0->haystack-ai) (1.2.1)\n", - "Requirement already satisfied: httpcore==1.* in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (1.0.5)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.1.0->haystack-ai) (0.14.0)\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (0.7.0)\n", - "Requirement already satisfied: pydantic-core==2.18.4 in /Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages (from pydantic<3,>=1.9.0->openai>=1.1.0->haystack-ai) (2.18.4)\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49m/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ "%%bash\n", "\n", diff --git a/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb b/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb deleted file mode 100644 index 1f7f04e4..00000000 --- a/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb +++ /dev/null @@ -1,554 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "JFAFUa7BECmK" - }, - "source": [ - "# Tutorial: Simplifying Pipeline Inputs with Multiplexer\n", - "\n", - "\n", - "- **Level**: Intermediate\n", - "- **Time to complete**: 10 minutes\n", - "- **Components Used**: [Multiplexer](https://docs.haystack.deepset.ai/docs/multiplexer), [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [HuggingFaceAPIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapidocumentembedder), [HuggingFaceAPITextEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapitextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder), [HuggingFaceAPIGenerator](https://docs.haystack.deepset.ai/docs/huggingfaceapigenerator) and [AnswerBuilder](https://docs.haystack.deepset.ai/docs/answerbuilder)\n", - "- **Prerequisites**: You must have a [Hugging Face API Key](https://huggingface.co/settings/tokens) and be familiar with [creating pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines)\n", - "- **Goal**: After completing this tutorial, you'll have learned how to use a Multiplexer to simplify the inputs that `Pipeline.run()` get\n", - "\n", - "> As of version 2.2.0, `Multiplexer` has been deprecated in Haystack and will be completely removed from Haystack as of v2.4.0. We recommend using [BranchJoiner](https://docs.haystack.deepset.ai/docs/branchjoiner) instead. For more details about this deprecation, check out [Haystack 2.2.0 release notes](https://github.com/deepset-ai/haystack/releases/tag/v2.2.0) on Github.\n", - "\n", - "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jy3ZkDzu9-CW" - }, - "source": [ - "## Overview\n", - "\n", - "If you've ever built a Haystack pipeline with more than 3-4 components, you probably noticed that the number of inputs to pass to the `run()` method of the pipeline grow endlessly. New components take some of their input from the other components of a pipeline, but many of them also require additional input from the user. As a result, the `data` input of `Pipeline.run()` grows and becomes very repetitive.\n", - "\n", - "There is one component that can help managing this repetition in a more effective manner, and it's called [`Multiplexer`](https://docs.haystack.deepset.ai/docs/multiplexer).\n", - "\n", - "In this tutorial, you will learn how to drastically simplify the `Pipeline.run()` of a RAG pipeline using a Multiplexer." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "RJPsjBXZKWnb" - }, - "source": [ - "## Setup\n", - "### Prepare the Colab Environment\n", - "\n", - "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", - "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "CcK-dK--G5ng" - }, - "source": [ - "### Install Haystack\n", - "\n", - "Install Haystack 2.0 with `pip`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "0hwJTyV5HARC" - }, - "outputs": [], - "source": [ - "%%bash\n", - "\n", - "pip install haystack-ai \"huggingface_hub>=0.23.0\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3N_97P0OV9cx" - }, - "source": [ - "### Enable Telemetry\n", - "\n", - "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "BKilNUd8V_Uc" - }, - "outputs": [], - "source": [ - "from haystack.telemetry import tutorial_running\n", - "\n", - "tutorial_running(37)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "uTNEeEcBJc_4" - }, - "source": [ - "### Enter a Hugging Face API key\n", - "\n", - "Set a Hugging Face API key:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "aiHltCF7JgaV", - "outputId": "b973435d-94c1-458a-8212-c543fd45ffab" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Enter a Hugging Face API Token:··········\n" - ] - } - ], - "source": [ - "import os\n", - "from getpass import getpass\n", - "\n", - "if \"HF_API_TOKEN\" not in os.environ:\n", - " os.environ[\"HF_API_TOKEN\"] = getpass(\"Enter Hugging Face token:\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "e57ugQB7dYsQ" - }, - "source": [ - "## Indexing Documents with a Pipeline\n", - "\n", - "Create a pipeline to store the small example dataset in the [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore) with their embeddings. You will use [HuggingFaceAPIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapidocumentembedder) to generate embeddings for your Documents and write them to the document store with the [DocumentWriter](https://docs.haystack.deepset.ai/docs/documentwriter).\n", - "\n", - "After adding these components to your pipeline, connect them and run the pipeline.\n", - "\n", - "> If you'd like to learn about preprocessing files before you index them to your document store, follow the [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline) tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "My_fx0lNJUVb", - "outputId": "b731efb8-14bb-4f13-ca49-d8706a777dd5" - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/anakin87/.virtualenvs/tutorials/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n", - "Calculating embeddings: 100%|██████████| 1/1 [00:00<00:00, 1.13it/s]\n" - ] - }, - { - "data": { - "text/plain": [ - "{'doc_writer': {'documents_written': 5}}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from haystack import Pipeline, Document\n", - "from haystack.document_stores.in_memory import InMemoryDocumentStore\n", - "from haystack.components.writers import DocumentWriter\n", - "from haystack.components.embedders import HuggingFaceAPIDocumentEmbedder\n", - "\n", - "documents = [\n", - " Document(content=\"My name is Jean and I live in Paris.\"),\n", - " Document(content=\"My name is Mark and I live in Berlin.\"),\n", - " Document(content=\"My name is Giorgio and I live in Rome.\"),\n", - " Document(content=\"My name is Giorgio and I live in Milan.\"),\n", - " Document(content=\"My name is Giorgio and I lived in many cities, but I settled in Naples eventually.\"),\n", - "]\n", - "\n", - "document_store = InMemoryDocumentStore()\n", - "\n", - "indexing_pipeline = Pipeline()\n", - "indexing_pipeline.add_component(\n", - " instance=HuggingFaceAPIDocumentEmbedder(\n", - " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", - " ),\n", - " name=\"doc_embedder\",\n", - ")\n", - "indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store), name=\"doc_writer\")\n", - "\n", - "indexing_pipeline.connect(\"doc_embedder.documents\", \"doc_writer.documents\")\n", - "\n", - "indexing_pipeline.run({\"doc_embedder\": {\"documents\": documents}})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "e9hOmQx4L2Lw" - }, - "source": [ - "## Building a RAG Pipeline\n", - "\n", - "Build a basic retrieval augmented generative pipeline with [HuggingFaceAPITextEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapitextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) and [HuggingFaceAPIGenerator](https://docs.haystack.deepset.ai/docs/huggingfaceapigenerator). Additionally, add [AnswerBuilder](https://docs.haystack.deepset.ai/docs/answerbuilder) to help you enrich the generated answer with `meta` info and the `query` input.\n", - "\n", - "> For a step-by-step guide to create a RAG pipeline with Haystack, follow the [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) tutorial" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "ueu5W07IWyXa", - "outputId": "51419b90-14d8-4e4a-cd24-8884053b9688" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\n", - "🚅 Components\n", - " - embedder: HuggingFaceAPITextEmbedder\n", - " - retriever: InMemoryEmbeddingRetriever\n", - " - prompt_builder: PromptBuilder\n", - " - llm: HuggingFaceAPIGenerator\n", - " - answer_builder: AnswerBuilder\n", - "🛤️ Connections\n", - " - embedder.embedding -> retriever.query_embedding (List[float])\n", - " - retriever.documents -> prompt_builder.documents (List[Document])\n", - " - prompt_builder.prompt -> llm.prompt (str)\n", - " - llm.replies -> answer_builder.replies (List[str])\n", - " - llm.meta -> answer_builder.meta (List[Dict[str, Any]])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from haystack.components.embedders import HuggingFaceAPITextEmbedder\n", - "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", - "from haystack.components.builders import PromptBuilder, AnswerBuilder\n", - "from haystack.components.generators import HuggingFaceAPIGenerator\n", - "\n", - "template = \"\"\"\n", - " <|user|>\n", - " Answer the question based on the given context.\n", - "\n", - "Context:\n", - "{% for document in documents %}\n", - " {{ document.content }}\n", - "{% endfor %}\n", - "Question: {{ question }}\n", - "<|assistant|>\n", - "Answer:\n", - "\"\"\"\n", - "pipe = Pipeline()\n", - "pipe.add_component(\n", - " \"embedder\",\n", - " HuggingFaceAPITextEmbedder(\n", - " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", - " ),\n", - ")\n", - "pipe.add_component(\"retriever\", InMemoryEmbeddingRetriever(document_store=document_store))\n", - "pipe.add_component(\"prompt_builder\", PromptBuilder(template=template))\n", - "pipe.add_component(\n", - " \"llm\",\n", - " HuggingFaceAPIGenerator(api_type=\"serverless_inference_api\", api_params={\"model\": \"HuggingFaceH4/zephyr-7b-beta\"}),\n", - ")\n", - "pipe.add_component(\"answer_builder\", AnswerBuilder())\n", - "\n", - "pipe.connect(\"embedder.embedding\", \"retriever.query_embedding\")\n", - "pipe.connect(\"retriever\", \"prompt_builder.documents\")\n", - "pipe.connect(\"prompt_builder\", \"llm\")\n", - "pipe.connect(\"llm.replies\", \"answer_builder.replies\")\n", - "pipe.connect(\"llm.meta\", \"answer_builder.meta\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "5xxvPqyurZTi" - }, - "source": [ - "## Running the Pipeline\n", - "Pass the `query` to `embedder`, `prompt_builder` and `answer_builder` and run it:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "AIsphy4hJDpE", - "outputId": "4498e7c9-0ff2-424c-9ddd-535f8630572e" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'answer_builder': {'answers': [GeneratedAnswer(data=' Mark lives in Berlin, as stated in the first sentence of the context provided.', query='Where does Mark live?', documents=[], meta={'model': 'HuggingFaceH4/zephyr-7b-beta', 'finish_reason': None, 'usage': {'completion_tokens': 0}})]}}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "query = \"Where does Mark live?\"\n", - "pipe.run({\"embedder\": {\"text\": query}, \"prompt_builder\": {\"question\": query}, \"answer_builder\": {\"query\": query}})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "wrH2MGSBLvVC" - }, - "source": [ - "In this basic RAG pipeline, components require a `query` to operate are `embedder`, `prompt_builder`, and `answer_builder`. However, as you extend the pipeline with additional components like Retrievers and Rankers, the number of components needing a `query` can increase indefinitely. This leads to repetitive and increasingly complex `Pipeline.run()` calls. In such cases, using a Multiplexer can help simplify and declutter `Pipeline.run()`." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ewDXDrw9N0CG" - }, - "source": [ - "## Introducing a Multiplexer\n", - "\n", - "The [Multiplexer](https://docs.haystack.deepset.ai/docs/multiplexer) is a component that can accept multiple input connections and then distributes the first value it receives to all components connected to its output. In this seeting, you can use this component by connecting it to other pipeline components that expect a `query` during runtime.\n", - "\n", - "Now, initialize the Multiplexer with the expected input type (in this case, `str` since the `query` is a string):" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "id": "kArO87EKN3N-" - }, - "outputs": [], - "source": [ - "from haystack.components.others import Multiplexer\n", - "\n", - "multiplexer = Multiplexer(str)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "vBGC2wO5LWIL" - }, - "source": [ - "## Adding the `Multiplexer` to the Pipeline\n", - "\n", - "Create the same RAG pipeline but this time with the Multiplexer. Add the Multiplexer to the pipeline and connect it to all the components that need the `query` as an input:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - }, - "id": "CTmnCZvgEAut", - "outputId": "a0ab0df0-32f7-4778-954a-e7b9cc8b612d" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "\n", - "🚅 Components\n", - " - multiplexer: Multiplexer\n", - " - embedder: HuggingFaceAPITextEmbedder\n", - " - retriever: InMemoryEmbeddingRetriever\n", - " - prompt_builder: PromptBuilder\n", - " - llm: HuggingFaceAPIGenerator\n", - " - answer_builder: AnswerBuilder\n", - "🛤️ Connections\n", - " - multiplexer.value -> embedder.text (str)\n", - " - multiplexer.value -> prompt_builder.question (str)\n", - " - multiplexer.value -> answer_builder.query (str)\n", - " - embedder.embedding -> retriever.query_embedding (List[float])\n", - " - retriever.documents -> prompt_builder.documents (List[Document])\n", - " - prompt_builder.prompt -> llm.prompt (str)\n", - " - llm.replies -> answer_builder.replies (List[str])\n", - " - llm.meta -> answer_builder.meta (List[Dict[str, Any]])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from haystack.components.embedders import HuggingFaceAPITextEmbedder\n", - "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", - "from haystack.components.builders import PromptBuilder, AnswerBuilder\n", - "from haystack.components.generators import HuggingFaceAPIGenerator\n", - "\n", - "template = \"\"\"\n", - " <|user|>\n", - " Answer the question based on the given context.\n", - "\n", - "Context:\n", - "{% for document in documents %}\n", - " {{ document.content }}\n", - "{% endfor %}\n", - "Question: {{ question }}\n", - "<|assistant|>\n", - "Answer:\n", - "\"\"\"\n", - "pipe = Pipeline()\n", - "\n", - "pipe.add_component(\"multiplexer\", multiplexer)\n", - "\n", - "pipe.add_component(\n", - " \"embedder\",\n", - " HuggingFaceAPITextEmbedder(\n", - " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", - " ),\n", - ")\n", - "pipe.add_component(\"retriever\", InMemoryEmbeddingRetriever(document_store=document_store))\n", - "pipe.add_component(\"prompt_builder\", PromptBuilder(template=template))\n", - "pipe.add_component(\n", - " \"llm\",\n", - " HuggingFaceAPIGenerator(api_type=\"serverless_inference_api\", api_params={\"model\": \"HuggingFaceH4/zephyr-7b-beta\"}),\n", - ")\n", - "pipe.add_component(\"answer_builder\", AnswerBuilder())\n", - "\n", - "# Connect the Multiplexer to all the components that need the query\n", - "pipe.connect(\"multiplexer.value\", \"embedder.text\")\n", - "pipe.connect(\"multiplexer.value\", \"prompt_builder.question\")\n", - "pipe.connect(\"multiplexer.value\", \"answer_builder.query\")\n", - "\n", - "pipe.connect(\"embedder.embedding\", \"retriever.query_embedding\")\n", - "pipe.connect(\"retriever\", \"prompt_builder.documents\")\n", - "pipe.connect(\"prompt_builder\", \"llm\")\n", - "pipe.connect(\"llm.replies\", \"answer_builder.replies\")\n", - "pipe.connect(\"llm.meta\", \"answer_builder.meta\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "i2wW4nbEQKhJ" - }, - "source": [ - "## Running the Pipeline with a Multiplexer\n", - "\n", - "Run the pipeline that you updated with a Multiplexer. This time, instead of passing the query to `prompt_builder`, `retriever` and `answer_builder` seperately, you only need to pass it to the `multiplexer`. As a result, you will get the same answer." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "YbIHBCKPQF4f", - "outputId": "32fb9d11-eec2-49d7-9ab1-97a90d9bbc28" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "{'answer_builder': {'answers': [GeneratedAnswer(data=' Mark lives in Berlin, as stated in the first sentence of the context provided.', query='Where does Mark live?', documents=[], meta={'model': 'HuggingFaceH4/zephyr-7b-beta', 'finish_reason': None, 'usage': {'completion_tokens': 0}})]}}" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "pipe.run({\"multiplexer\": {\"value\": \"Where does Mark live?\"}})" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kPiSU2xoKmio" - }, - "source": [ - "## What's next\n", - "\n", - "🎉 Congratulations! You've simplified your pipeline run with a Multiplexer!\n", - "\n", - "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", - "- [Creating a Hybrid Retrieval Pipeline](https://haystack.deepset.ai/tutorials/33_hybrid_retrieval)\n", - "- [Building Fallbacks to Websearch with Conditional Routing](https://haystack.deepset.ai/tutorials/36_building_fallbacks_with_conditional_routing)\n", - "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", - "\n", - "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", - "\n", - "Thanks for reading!" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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.10.12" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb index 1f1201bd..426805ab 100644 --- a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb +++ b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb @@ -10,7 +10,7 @@ "\n", "- **Level**: Advanced\n", "- **Time to complete**: 20 minutes\n", - "- **Components Used**: [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [SentenceTransformersDocumentEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [SentenceTransformersTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder), [OpenAIGenerator](https://docs.haystack.deepset.ai/docs/openaigenerator), [OpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/openaichatgenerator)\n", + "- **Components Used**: [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [SentenceTransformersDocumentEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformersdocumentembedder), [SentenceTransformersTextEmbedder](https://docs.haystack.deepset.ai/docs/sentencetransformerstextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [ChatPromptBuilder](https://docs.haystack.deepset.ai/docs/chatpromptbuilder), [OpenAIChatGenerator](https://docs.haystack.deepset.ai/docs/openaichatgenerator)\n", "- **Prerequisites**: You must have an [OpenAI API Key](https://platform.openai.com/api-keys) and be familiar with [creating pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines)\n", "- **Goal**: After completing this tutorial, you will have learned how to build chat applications that demonstrate agent-like behavior using OpenAI's function calling feature.\n", "\n", @@ -75,11 +75,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "id": "9V0-VuEgwt2u" }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], "source": [ "from haystack.telemetry import tutorial_running\n", "\n", @@ -97,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "id": "WM-sVkYonutA" }, @@ -138,7 +147,7 @@ { "data": { "text/plain": [ - "{'replies': [ChatMessage(content='Natürliche Sprachverarbeitung (NLP) ist ein Bereich der künstlichen Intelligenz, der sich mit der Wechselwirkung zwischen Menschensprache und Maschinen befasst. Es zielt darauf ab, Computern das Verstehen, Interpretieren und Generieren menschlicher Sprache zu ermöglichen.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 74, 'prompt_tokens': 34, 'total_tokens': 108}})]}" + "{'replies': [ChatMessage(content='Natural Language Processing (NLP) ist ein Teilgebiet der künstlichen Intelligenz, das sich mit der Interaktion zwischen Computern und menschlicher Sprache befasst. Es umfasst Techniken zur Verarbeitung, Analyse und Generierung von Sprache, um Maschinen das Verständnis und die Verarbeitung von textuellen und gesprochenen Informationen zu ermöglichen.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 66, 'prompt_tokens': 33, 'total_tokens': 99, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]}" ] }, "execution_count": 4, @@ -185,12 +194,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Natural Language Processing (NLP) ist ein Bereich der künstlichen Intelligenz, der sich mit dem Verstehen und der Verarbeitung von menschlicher Sprache durch Computer befasst." + "Natural Language Processing (NLP) ist ein Teilbereich der Informatik und Künstlichen Intelligenz, der sich mit der Interaktion zwischen Computern und menschlicher Sprache befasst. Das Ziel von NLP ist es, Computern zu ermöglichen, menschliche Sprache zu verstehen, zu interpretieren, zu analysieren und zu generieren, um nützliche Aufgaben wie Textübersetzung, Sentiment-Analyse oder Chatbots durchzuführen." ] } ], "source": [ - "from haystack.dataclasses import ChatMessage\n", "from haystack.components.generators.chat import OpenAIChatGenerator\n", "from haystack.components.generators.utils import print_streaming_chunk\n", "\n", @@ -376,190 +384,11 @@ "name": "stderr", "output_type": "stream", "text": [ - "/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_token.py:88: UserWarning: \n", - "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", - "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", - "You will be able to reuse this secret in all of your notebooks.\n", - "Please note that authentication is recommended but still optional to access public models or datasets.\n", - " warnings.warn(\n" + "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", + " warnings.warn(\n", + "Batches: 100%|██████████| 1/1 [00:03<00:00, 3.23s/it]\n" ] }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "041743b7cebb47458bfa61e945699655", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "modules.json: 0%| | 0.00/349 [00:00 For a step-by-step guide to create a RAG pipeline with Haystack, follow the [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) tutorial." ] @@ -625,18 +454,19 @@ "outputs": [ { "data": { - "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAQsAVMDASIAAhEBAxEB/8QAHgABAAICAwEBAQAAAAAAAAAAAAcJBggEBQoDAQL/xABdEAAABgEDAQMECQ8KBAMHAwUAAQIDBAUGBxESIQgTMRQZIkEJFRgyVld1lNIWIzc4UWFxdJKTlbK00dMXJDM2QlRysbPUNVJVgWKCoSU0Q1NjkaImc+Eng5bBwv/EABQBAQAAAAAAAAAAAAAAAAAAAAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCqoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb1+x/+x/ydaJsPPs+huRsGYWTkOA6niq0UXUjMv8A5P63+H322Hbr9j6q9acfVlOAwY9Vm1dHJHkbKSQ1YtITsSDIvBwiLYj9fgApkAcu2qZtDZyq6xiuwp8Vw2n476TSttZHsZGRjiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN6/Y//Y/5OtE2Hn2fQ3I2DMLJyHAdTxVaKLqRmX/yf1v8PvvzsAdgCTrTOh57nsNyLgzCychwXU8VWii6kZkf/wAL9b/D764mBAjVUJiHDYbjRWEE20y0nilCS8CIgCBAjVUJiHDYbjRWEE20y0nilCS8CIh9xwb68g4xST7i0kph1sBhcmTIWRmTbaEmpSjItzPYiPw6jE9INacX1vx163xiZ37Ud5UeQwtbanGVkoyLkbalo9IiJRbKPoot9j3Ig1f7fXYHh69VknNcKjNQs/itmp5hBElFogi96f8A9T7h+vwMU021TMorOVXWMV2FOiuG09HfSaVtrI9jIyPwMenAaSdvnsDQ9eayTmuFRmoWfxWzU8wgiSi0QRe9P7jn3D9fgYCmAByrapmUVnKrrGK7CnRXDaejvpNK21kexkZH4GOKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM40St8MotUcen6gVUi5xNmSlU2JHXxNSfUai2Pmkj2M0ltuRevwPBwAeljTzI8cyzC6i0xKTFlY8/HQqGuGZd2Te3Qi28NvuDIhRX2I+25ddmDJkVlmt6zwGc6XlcHc1KiqM+rzRf+ppLx8S67kd3WG5lTagYzX5Bj9gzZ1M5pLzElhRKSpJlv6vWA7Kxr2LavlQZKVLjSWlMupStSDNCiMjIlJMjLoZ9SMjL1DpcG08x3TWqdrMZrG6iudd784rClG2S+CEbpSZmSdyQnci2Iz3UfVRmeRAAAArY9kM9kMTRIsdMNMLElWSiVHub+KvcmC8FMMKL+16lLLw8C67mAgv2UvOtKct1cbj4ZDKRl8Ldq8uYTiSiurLoTZpIvTcT61kZfc69RpAP1a1OLUpSjUpR7moz3MzH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADafsRdt257MGTIqrVb1ngE50vK4O5qVEUZ9Xmi/9TSXj4l13I9WAAemDDcyptQMZr8gx+wZs6mc0l5iSwolJUky39XrHdCizsRdt257MGTIqrVb1ngE50vK4O5qVEUZ9Xmi/wDU0l4+JddyPaPt6+yPR0URYNpPYLW7ZxEOz8iaI092y4ncmmD/AOYyPZSy8OpF13Acn2Q32QtNCix0w0wsSVZKJUe5v4q9yYLwUwyov7XqUsvDwLr1FVq1qcWpSlGpSj3NRnuZmC1qcWpa1Gpaj3NSj3Mz+6PwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASHrZ0uMdT/y0MIv/AMTEeCQ9bumQUhfcpIZf/gYCPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABaX7GN2o6XRvSvG8JtK2ykN5FkFxMfnQaybMOMlmNDJtKG2GHO9NalK5bHu2SSNRES0mAq0Eia49Mnq0/8ALTxC/wDwHoW1D7S2m2lV+qlybJUwrJtlMmQyxCkSiiNK34uSFMtrTHQexmSnTSRkRn4DsJuvOA1su9jy8mixlUlM3kUxx1C0tFXOEo0Sm3DTweb9AyM2jVsexHsZkRh5lQF+2u3a8xvyvItPEV1uqNdafyreNZlS2BqN19JNsNLb8n+tI4ucluuGlKD9FfAyMUEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALMfY7jtcU070vz9nGrzJKCjyjJoFknHq9c+VHVLgV5MrNhsjWpHJhSTURHx3LcVnDZ7s2eyGal9ljT+Rh+IV2Ny6t6e5Yqct4bzr3erQ2gyI0PILjs2npt93qAsdyDB1YprJqrNzLEtXL6szKWxb00rAp9m2xIZVDaZXDlsxX20Mutm2aSU/sRpUW6iIthyO0Dola5Te4VRafaeOPY9pBURpvc3SJCUZA0S2Vt0bLqlbPkTcYnFKUbiCdQwk/FY290+tJuV4FjV3LlKbl2VZGmPIZQgkJW40laiSRkZkW6j23M/wjIFRXDPpLeT94iR9EBpt2g7+xjZmvUcsOyp7Gcl0qs6EltUzypFXLccQ+lM5nbnHTxNRGtZcUmg9z26iigWa9tD2SjVXANW9TtKK+uxh/HGDdqUyJUJ5Us2XWCJRmpLyU8vrh7Hw28OgrKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB6CNINXMrj5dhOEWJ1OM42rG4D1UuxrZDr18hNah2QuPLS6llpTLh8VMLQazQlSyMiMtsf021W1ZqtDcDyKVc0WT5DqVk8cqhqdWyY7cOJKckSV8j8qWakoiN7tJSSOBJIld6e6jivSbtl9kDD63Hbefk09zKo9U3Gf8ui3E1mM6thCJBMMuJWyzy48VG0lPIunUjGWV3at7HejbmOUzGRWMIsbkqtKiNJaupqILj0VUf633hLJKO4cUlLfvEcjNKUme4Crjt3tWLHa61Nbt5UWbZpsiJ+RCjKjMuK7lvqhtTjhoL7xrV+EQMJj7YeodDqx2mM/y7GJirCgtZ5Pw5KmVtG4ju0J34LIlF1I/EiEOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQ9efsgr/Eov+kkZF2fcUxDOCsqy9rClWjJlIZcOQ63yaPZJp2Soi9E9j+76f3hJutuC4fExi2yGwgmdmiMTEd0n3CM3OHBouPLY9j2Pw/s9d+oDU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZnp1o1murapycPx2XfqhEk5CYnEzbJW/EzIzLx2MZr7jHW34uLn8hH0huB7C9/WnUn8Si/rqFqQDz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkACgnCey3rrheUV9xG05ujVGcI1oJKPriD6LT771kZ/5iQNftBtYc9mV8GnwC7fqYye+Us2SR3jx9PBRkfol0I9v7ShdsADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IPcY62/Fxc/kI+kPQkADz2+4x1t+Li5/IR9IdbknZV1axCim3NzgtpXVcJpT0iU8lBIbQRbmZ+kPRIIF7dv2pmpXyS7+qA8/wCAAAAAAAAAAAAAAAAAAAAAAAso9he/rTqT+JRf11C1IVW+wvf1p1J/Eov66hakAAAAAAAAAAAAAAIa7RXaShaDnjNXHp/qkyzJpDrFVUqntQGlk0klPOvSXfQabQSk9TIzM1ERF47Re92/osbTTIb13B3lZTjl7ApLXGo1wxISRy1bMvMSm0m28lRctuidzSZGafEZX2sOzJYa33ODZTj6MZmZFib0nu6nM4BzKixYfQlLjb6CSoyNPBKkqJJmR9fHYyxK+7JWSZFoaWOtUOmeHZbIySBbS/qPr119euLGeJaUKMmjcccIjc2NSSLdX9nqA/iw7a+oNXeZpQydCHyvcOryubmO3lUZTTUBSOaXG3O6+uOGkl/WyT/YMuW+xDGMk14y677VuCZJpti07PIN7pYm2Yxt65brG20vTSUb6zc5I7xJEhsyIjMzMi32IzEt33Z4yO01T1uyVqbVpgZviTNDXNrdcJ1p9DDrZqeLu9ko3cLqk1Htv0EcU3Zf1n02yHTnJMIs8Fdusb0+jYZLYvlzFx3XEvG4462bTaVcSNLfEz2M/SI0p6GAyef2+cdLTDHL2uxuZJy+9t5FAziU2YzCcjzo+3lKH5Lh92222RoM1nv0cR6JbnxVHb4x9rT7PbjJsckUuS4c/FjS8erp7Nn5W5K6RCjSGtkOk4e5GexGnie5dBhV57H5ar08xB6Nb43kmotPkFhkk8sorTfpLV+dwKUy4zso0o2aa4q2MyNG5EkzLjk9j2OrTMdBMixydUad6e5zMso9lAnaf0xxoTSoykrjpe5ISt3ZXfbmZdCd6F02MMSpdd8vj9r5681PxeZpnTU2mM21dplXaLJpTSJiFHI2aIkk5xJSOJly9Ei3MjIcG97Qmd6rawdnWym4HZ6e4Vc3b8uvlyLxp1VrHOKpTZvxm9ja9EyWRLNRdehjLT7Lmq+rOpN3kGrlphjMO0wKZhiixE5feNqeeS4l/i+nY9vSP3xdSSXE+pji412ZdcJ+RaNxc3u8EssV05kqQ09WeWNz5sbyY2EG4SkG3z4kkjIti8T5fdD7N+yO1Lshu/LDFfyWuWZVicqO9i+V9Xe58o9rf6buOf8Aa33268fUJAxjtNZfnOt2X4Jjul3l1VilwxXWuRv37bDTbTiSV3qWjZNS1kXM+7Sfgkt1FyIRTpN2GL7S+5rsdk4to5l+BQ7FbxXl7jhvZG5EU4a+6Uo2zbUtO/EnDUexEXTYiIp70T0dudN9SdX8gs5MB+FmF41ZQG4ji1ONNpYJsydJSEkSty8EmotvWAmEAAAAAAAAAAAAAEC9u37UzUr5Jd/VE9CBe3b9qZqV8ku/qgPP+AAAAAAAAAAAAAAAAAAAAAAAso9he/rTqT+JRf11C0K/yKqxSokWt3Zw6erjkSnps+Qhhlot9t1LWZJLqZF1MVe+wvf1p1J/Eov66hvR2qlYqrDKX6os0rcIsIVqza0s63Ql2IuYwSjS260oyJxBksyNJGStzI0mSiIBLOO5JUZfTRreitYV3UyiNTE+ukIkMOkRmkzQ4gzSrYyMuh+JGQ7EaM5nrFmOc3WAQ7I4endNcYo3ZtV0vJZuNpl2j0hxCibfjsKee7tKG3ExuTalFJSauW3Tm6nFk9ZjGrqrDPcolWOA4bT1MOTVWr8BMu+Uw84ctaGlFupapETdBmaVEZEolbJMg3Q9s4ZWRV/lbHl5td+UXvC7029+PPhvvx3Mi3223HJGn8qwbq9ctcL9ifMs9U8YxWImjo/bSUgp5tVy3luJhpcJDzK3pCUceBpS4gzIkrUajwmkyHL3tFM0zSBqUi0lrxv2kX7U5ZLtVu3M11lpiUaVtMt17jSlmSWGEEZd56R+iW4b7ANLdRMhv9Mc4zypqc1yRjFmE4xAvLuysHZzlUqXJlHMmNG5yTH/AJulhJ8EpbQbpLJJcen11ezdrG/5OazEc0NzTK4RZT5OQXedT4bdhJaUy01FTbkiQ82Rmp1wkIUknO7MkqIi4qDZ3MtZMA05sWa/LM5xvGJ7zRPtRbm3jxHVtmZpJaUuLSZp3Soty6bkf3B22I5vjuf1Ptri9/V5JWd4pry2omNymeZbbp5tqNO5bluW/rIa8Z/iVlE070RwPIrYsqu7jLoHlk53k6t6NGW9ZqRzc9NaEJjIb5L9JRERq6mY623uL2toNe9TqqxuJUrHraVBxymanSCrYfk0NmO68qK2okOl35vuKStKi3b3IiVuYDbMBodb5qly7zal0/1PyK/IoGM0iLj2/flpk2dlaKbdlxjNamm+DTR79wSWyM1p4+iZDtc/v5WGTtV6eLqdOp6etu6lmKxkmSSm1z3kQilToTE7kp6MbqXmNu796pPFKSJRkA25y7UOhwaTUxreW6iZbPnHgxIkR6W++oi3UaWmULXxSWxqXtxSR7qMiGRjUrDYTeYa1VORxoOUKl4pppDsWKm0upipTUyc844iPI+u+m4aYZJWSyPn6PMlGhO2AR9XbKHhdPm1DnN1kuQsYja3edNe2LzsGBI8hNTETyYz7qI+mYpCG20JQ5wbXz5dTAb6ANYLvAbXHT0SwJWZZe5b3U3v8htFZDLVJlMQ6x/vkko3PraVvOMEru+O57K35kShH2C2GTwo+mF7TZPlV3Z3OQ5I/ErrK8kymHqWK3PONHcbWsydUakw9nlkpwjX77bYiDeABoVpnn+XSdMsm1Qm6gMTrCgxOdOu6+Dk0uwcenvMKVHacgLZaZrVNLQtJNtkpe5bGoyLkrMdaIb2iel2Fxpmod3OyBuOiVbUk/K50ayyc2I3drZhvoWa2nu8cSsmmiSTyiIl79VANiMk1+0ww27k01/qPiVHbxTST9fZXsWPIaNSSUnm2twlJ3SojLcvAyP1jL6O9rcmqIlrT2EW2q5bZOx5sF9LzLyD8FIWkzSovvkYgbNYDVz2gtJqqPAcmO0GPW2RvMzzJb7jndMQo6XVnvyWflD+5mZ9UmfUQ1oVmlrkNIed5vqMzVxKqqkyctgVuUTXpDapDakNxDru6abrXGVnsjuubyltpIlK35KDeeRIaiR3X33UMsNJNbjriiSlCSLczMz6ERF6xjeVaq4VgtdX2GS5hQY9AsC3hyrWzYjNSS2JX1tS1ES+hkfQz6GQ0gTkeS3nZl1eRqNkeRxstr41ZjMmsmyXYp1tY+pgmZ7yGnDSt11t5br7pmf9G40ZcEqJWWaiX2nNXrRp5TMauNYHjNDh8qzqbmVbRrBUry6UhCUoesSkE4ju4zmxluaUmkkmSehhuNi+W0eb0rFxjlzX39Q+aian1cpEmO4aVGlRJcQZpPZRGR7H0MjIfxl+X1GBY3Nvr2X5DVQkkp9/u1ucSNRJLZKCNRmalEWxEZ9RqLrXbWN9/LHcUGY38KJQVtBU48mjuHokU7SWfNMg0MqSlw1FNibp24KI+qT9HbpNYZTVHkOb4TaZpdfUfLyXE62VLu7Zx9UNxPOxnvIdcMyYSphuOZkni2gz3IkkewDe0Bojk+pOW0eL2KsZyWwLSq0zVmFByrJLuRHNFemAbkgk2S23nmo7stvum5JkrbkZJURKSssgVb2OKo0/o8z1Jl1eAZM9aXSriuyGW8XFCY5Qqtq2d4yHEq5vPEvdC3DTwR6JbGG2+P5dU5TJumKuX5U7TzlVs4ibWkmpCW23DQRqIiVsl1B7p3LczLfcjImLZbU5rWvT6WX5bDZmSYC3ibWgu/jvLYeSXIi3JLja08i3I+O5GZdRoZppnNZWVNWWYZnkWL4PaVtvmMV5myfhWF7Jcsn0MsqkINLzrjURqOrukqJSzeI1Eok7Dj47a5lDp9OcHl5FHxqFOwyFawTn5TLoXLC4sXnnX3UuRWVLluMqUjeMS2yM3dzJRK3SFiQgXt2/amalfJLv6o42mGO3GS6zZY3fZdfW0XCodHSobj2D0SLLsERvKpEp1lpZJWpZSWeSFbpMuiiVxTx5Pbt+1M1K+SXf1QHn/AAAAAAAAAAAAAAAAAAAAAAAWUewvf1p1J/Eov66hakKGuxh2wk9kmfk0wseO+duGmWiLvu7S2SDUZ/hM9y/9RtL559z4uk/PAFoACr/AM8+58XSfngeefc+LpPzwBaAAq/88+58XSfngeefc+LpPzwBaAAq/wDPPufF0n54Hnn3Pi6T88AWgAKv/PPufF0n54Hnn3Pi6T88AWgAKv8Azz7nxdJ+eB559z4uk/PAFj+OYHT4ve5DdQmXVWt8+h+fLfeU4tzgng0guR7JbQnckoTsRbqPxUZnkIq/88+58XSfngeefc+LpPzwBaAAq/8APPufF0n54O/zf2XKRhV8qsdwJuUsmW3u8RKNJemglbbfe3AWQgKv/PPufF0n54Hnn3Pi6T88AWgAKv8Azz7nxdJ+eB559z4uk/PAFoACr/zz7nxdJ+eB559z4uk/PAFoACr/AM8+58XSfngeefc+LpPzwBaAAq/88+58XSfngeefc+LpPzwBaAAq/wDPPufF0n54Hnn3Pi6T88AWgCBe3b9qZqV8ku/qjTnzz7nxdJ+eDBdcfZTy1n0nybC3MG9r03ENyKUpErc2zUWxHt6yAV/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD15+yC5+JRf8ARSI8Eh68/ZBc/Eov+ikBHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz3QrRm77Qeq1Dp/jsqBCublTyY79o4tuOnu2XHlc1IQtRei2oi2SfUy8PEbheZU1v+FOn/6Rnf7Ma6diPUiDo92lsZzayZckwqGHaz3I7R7Le4VkoybSZ9CNR7JIz6FuLe9MO1XqJfZvR1l5jrFhWXUeStb1Xil7XFSONx1vNlIfmspakNqNHd80d2fJSTJOx9A0P8yprf8ACnT/APSM7/Zh5lTW/wCFOn/6Rnf7Mbt4l2lNccmo9E7U4Wn7LOqLRsxmfJp3KseTEXJN5au++vIUhlw+6IkGkzSnvVbGsdJqfqhm+pVJp/DkxqCNnuK61tY4uQ2l8qyQ83XSXUPk2ajcSg230GbfMz3Iy5ddyDT/AMyprf8ACnT/APSM7/Zh5lTW/wCFOn/6Rnf7Mb5ZD2wsr0hrdQaLUKhqJue485Vpq1Y8mT5BbosXVMxlk0ZOvoNtxDneITzUZJ9DkZkQ4VL208mxGDmU/OsfO4pqTHXr1m5p8YuKRg323ENlAWmxb6uOG6k0OIUZbJXuktiAaNeZU1v+FOn/AOkZ3+zDzKmt/wAKdP8A9Izv9mN17jIdSaXtKaLZBq2nFoEZiiyawTGxtEg1wkpixlvNPKdUonTSkk7LQSSM+Xo7bGfUZZnup+qxdnXNskr8ZpsJyHPKyxqKqH5Qu0jNORZS4xyHVK7pZraUalJShPEzSW6uuwaf+ZU1v+FOn/6Rnf7MPMqa3/CnT/8ASM7/AGY3RxHWfM9P8MyBdXR4krL7XWR3FJq2kTGYEl15DaFSzSt91aFbkgzJJmnZOxJIz3GVZB2ps405r9TKPI6zHrfM8ZsKODXTK5L8OtklbOE1HcfQtbq2iaWTnPZZ8iSW3HfoGgnmVNb/AIU6f/pGd/sw8yprf8KdP/0jO/2Ys00L1N1ByHV7UvBs9Xjj8jFYtU8xKxuG+y0/5WmQszX3rzhpURNJLh97lyMlbJk211Sw6izWtw+xymog5XZNE9CpJE1tEyS2ZrIlNtGfJRbtuFuRf2FfcMBT95lTW/4U6f8A6Rnf7MPMqa3/AAp0/wD0jO/2YtEha/yMe1TrtPM7o26a8unnio5lRMTOjWDSeSiUtsiS/HUSS9I1t90R9CdPch2/aG1cl6OYCxY1NU3d5HbWkOhpa9902mXp0p5LTXerIjNLZGZqUZFvsky6b7gKpPMqa3/CnT/9Izv9mMq1M9h+1kzPKVWULJcGaYOOy1xkT5hK3Qgkn4RDLbcvujcLO+0hq7pE9qLVZRGwqytsdwB7MYUmoizG2HXifU0lpxtx41cS4L34qI1bke6djISPqt2iZWlmX4E1OjRVY5aY1fZBcOk0tUhpNfGjvkTJ8ySRGTrm5KJW+ydjLruFZnmVNb/hTp/+kZ3+zDzKmt/wp0//AEjO/wBmN5tJ+17qBmeXYUdhjDU3H8pkIachVeLXsaRSNutmtp16bJYTGkoSZJQtSO7L0+SeaSH1xjtDa3ZF2fLzV1NXhyqyq8vdTRx4UtcuaxDnqbecS4cgktqOOzI4o4r3WlCt9lGgg0U8yprf8KdP/wBIzv8AZh5lTW/4U6f/AKRnf7MWnYfrPJ1C1vmY9jvkE3Cq3GYdtMtCQs3ly5qzXFabVyJJJ8nbW4ojSZ/XWupdd8i1w1WhaH6SZTnVhFcnRqSEqSURo+Kn3NyS22R7HtyWpKd9j2332MBUf5lTW/4U6f8A6Rnf7MPMqa3/AAp0/wD0jO/2Y3twftW6iy8kKuu8fjWsSZVT5iZ1bid7Vs1EhiOp5Dclye0hDza+CkEtBtq5ERcC5Ftx8b7Q2uWQnoqvyXT9hvVWrXLhF5LOUdQtENMs1u/Xv5wSm+RE2nujSoyLmoiNRho15lTW/wCFOn/6Rnf7MPMqa3/CnT/9Izv9mNwc71RzfVJ3SJJRqCDqBjuq1hjjzppfOrdfj180u/Sjl3pIU2olcOe+/TkXiWT5J2zcs0vr8txbM6Cof1Lqbqrp4LtI1MerJybFt12PJ7lCXZJEhEaTzZQS1GbREk/TIyDRfzKmt/wp0/8A0jO/2YeZU1v+FOn/AOkZ3+zG81d20csxfGc5mZViqr1yngxJFRZ1mP2lFEsZcmSmK3BU3YtkpDhOuNKNaFLSaFmexGgyHVzM0znTTtPx8t1dcxp5FJpbe2qixJmQhKWmpcJx1oyfWo1qLiRJWRp5b+9Tt1DS3zKmt/wp0/8A0jO/2YeZU1v+FOn/AOkZ3+zG6EzIdVsx1n7M2Q5zExWqrLi1sJ1fV1BSFy4HeU0pSGpDq1cHj4K9JSEoIlFsRKI9y/jTvWbOMTx+uqqOlw9nKMk1Yvsas30tTW4K3m0SluTUIW+44lSnI5LNvlxMt0F3e/NIaZeZU1v+FOn/AOkZ3+zGv/at7FOcdj76lvqytcfs/qi8q8k9opD7vDyfuefed6y3tv36Ntt/BW+3Te3qw7U+c43WZDi9nV47K1IgZvXYXFmsJfYqHDnMNyGJTjalrdQSW1qJTZLMzUlJEr0uldnspOp2cZlmVJi+dnQuWeH2djAbkY7FeajPodi1kgl7uurPls6kjR/YNPvlci2DRkAAAAAAAAAAAAAAAAAAAAAABP8A2DMKptSO1RiGK5Eg10dzGtIEsiXwUTblZKSZpV/ZUW+5H6jIhd5phpvmuGtJrsk1kZzDH41authwl0seK+ZGSUtuyHycUbriEp23STZK5GaiM9jLziAA9E+J9nSDi+OaEVX1ZR5P8lylK77yVKPbPeC9F8O9Puf6bn4r97t69y6fIuy2dmdlJq9RmKezf1FPUOLLKtbfKO6VeUREZSFO7LSRkSzX6JmW6SJJ7LLz4AA9B8nsm1uXY5m55zqA9kObZS5BdVk8CO1AOtVBWbkIojBKWTZNuGpZ8lKNZqVufXpkn8jl1m+n2Y4hqrqdGzmqyGuKtJFfUMVXkhbK3eTxccNTpmaFbmfAjbTsguu/nMAB6E6Ls65DOznD77UHVyJnkTG66yq24KqNqEqUzMZbaWp11D6jNfFst1EREr1JSe5n1OPdlW+pmtOqSVrK3bYTgN3GtaWplUjJSiZYbcbajOykvFz4Ic4pWSE9C6pV02oCAB6H1dmiCpLhfVpH9PUxOov/ALonpsaT8j/pv/D/AEv3/eDo+0LozFk47rfkLds/b/VxX0sJdRWUrVm+wiG6sl8WVvo7/ml49ySba0Ek1IVzJJl5/AAX29h+ReY/NyqokULFdiHdx5ce+nY09js+bNVyS626xIkvuvJQ2hrZ5Rl48S3Ii22mdRSSZ7U5xNe7NaLi3JWSFOILr0JXiXif/wBzHlsFwvsOGbUuL9n+dVWc3yewyDOpUCsYJpbipDyaxh9SfRSZJIm2XFGpWyfR233MiMN8MMwDCdPpdpMoK+vg2Fq+qTYWBud5KluKMzM3Xlma1kRn0I1bJLoRERbDqdctM6nW3AHcdevl0U5qXGsq24hLQp6vmx3UusPoSroritJbpPxIzLpvuWZP5VWRsqh44484VvLiOzmWSjuGhTLakIWo3CTwIyU6guJqIz33IjIj2ZHltRiLdeu3mohJsJzNbF5JUo3pLquLbZERGe5n6/AiIzMyIjMBqWjRPJ8x15zSj1JylWW0+Q6Yqo3MkrqZNZGaNya4Rso2W4g3kkfe7Gsz9IvRJIzVnsx2uQZbi9pqHqbEzGvpKKzx46lqlRAblRpjLTLilKJ9aicNLfpH71Xo8Ut7HylaNrHEsNYbPT2Dj9zNkVMSPMs7tsoya+CT6XVMtuGp4nVLUTKuiGlEXJO5kR7l0dB2lKHI42JTItHeprstt/amgmutMEixT5O/IOYhJPGtMfu4zh8lpSo90mSDJRGA6jRnSXMNKHKaol6wlkmD0kc4dfTyKaO1LUwSODKJEwnDNzu08djShs1cS5b9d+fp7jFZ2btBFUMqVIzSNVqnSXG6uv7yTLTJlvPm2iMlazUZd/w236kkzPYj2KZB8pUpmDFekyHEssMoNxxxZ7JSki3MzP7hEA1V7KWFS+zJoAl53FrzIMgvLRcx6lhyoa58KLx7mEy4t+Q23szFYjoNJOHxUZkRGW5jO8ivYfaIxe707y7TbLMbx++guxZNlZSqom2S47pMjYmvLJZKIjSfAy5EW/QZJqX2g6PTrRprU9mquMsxVyOzN7yibZJ4oriOaJHCQ6zuk90FsRmszcTsk+pl5rrD/wB/k/8A7qv8zAejLDNNs4rqm0qcv1lYzGrfp3qqM17RsQ3EqWRJTIfcS6pTziUkZej3aT5KM077GXwxrQKDjzegqPqujyP5LK5yBv5MlPtnyrvIuf8ASn3O3v8Ab0/+Xf1jzmgA9Bdt2WXXHfLabUqPTW7GfT87iS1VbchDTkiMtgoy21PES0pJZma90mouhEg9lF/MvskQcioshm5HqO/Y6l21xAvm8yhxWYvkEqCk0wiYi8lIJptK3SNClKNffObq3VuXn2AB6L7jRW31I0ryzDdS9T2soO6SwcWbU1jNV7WOMrJ1p1pJOOGpZOobWZqWZegREREZjoa/s2WuR5lIvNTtUoWeMScRsMPfhRqRqsJyNLWypxw1IfX6ezRkfTY+RGRJ2Mlee4AHoMw7s25NTZVphOyHWVjKaXT6Q8urgO0bMeQ80uE7EQl+Ql4+a0IcI+ZILlxPdJmrkXZ1HZpg1VxST/q0ju+1uoVrnnd+SJLvPLESU+Sb9904eU79515cPeFv088AAL7e0honGRhurlqi1l3aM2vKiycranHkWr0VEViOwae58obW6k+4Jw1tKbcTv6O5l1rH7dC8ihUWAU87HGarGIsu0fqrRePO0MyzedTCOSuREefed3SaWiJ1xRGvcy/sDUoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWMexgZKrHWMftbHHsossdore+lnLoMdnWqCnPw6thlCvJmnOB9z5WfXboZfdFc42W7Mnb/1I7KGE2OLYdXY7Lrp1iuzdXcRHnnSdU022ZEaHkFx4tJ6bb779QFsma46xmuomqWYv0mWUNPYae1lBHtqahkpuHDmPSVvKaaJrvlOMIOLyRxNTeyiMiMjIR3V6ZxJdNoxEzDRmOeJxMptpMxqqw11TcoiiLiwpUmu4OORUyOSXFpcLZK2UKXt020988/rr/0TB/0bK/3Qeef11/6Jg/6Nlf7oBvTY28Gu0h7X2bvwo77D1hPpYcNbKVsrTBrWYTLRIMtjI5BOlx28TGXUukB6aahaFY/R4yUKjxzHbZw5dfAPyRVwcaKw2qQttOzalteVn3jm3M/R3NRkR1RY/wCyM5rjLVmzFwfCpEWytvb2RDs0Wc6OU7vVvd+2y/OWhpXeuKX6CUly2PbdKTKctTfZdtacOypVbBp8NWwUdl3d+vkqVutslH1KSXrMBPjWE54/guNX+EYVkFdrbS0trY5bkttWOxZFhYuQH2kwkOukSZqTlOodaS2a2UJjIIjSZpIcjINM40iBq8eAaY5CiO3pgdNHm2+OSI8vIp8l1ZyH3CfbS5JkNE0you83WajVxI0mRnqf55/XX/omD/o2V/ug88/rr/0TB/0bK/3QCwnVrHsUg9nrT/CcLxw8XoMty6kq/a9dQqrfNtMxD8hbsdaELStTMNwzNaSNRdT8RQVY/wDEJP8A+6r/ADMbeZD7KJqdled4/mFrjWJzLuhM11vI7RMRhzg6jvfJCnEwpzg+6nvFNmvZW2/QttPnnTfeW4rbktRqPb7pgP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdrVYneXsdUitprCxYSs21OxIq3UkrYjNJmkjLfYyPb75CQtdMWun8yenN1E9yEiExykpjLNsuLJGrdW23QiPf7mx/cHYdmbOvaPJnqGU5xh2fVrkfRL6S6flF0++ZJErdofNk4vgj0FpReW25KioT47N7fXFfknx/CogGoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcnsR9g+p7WOCX17NyWbRv1liULuo7aFJWk2krI+pb77mf/oNjfMx498YNn83b+iO79hm+w3nfy8j9nQLCQFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQdTjuXUmXFYqpLWJbJrpjldLVDeS6liS2Rd4yoy6EtPIty8SPofUBXtF9hso4UlmQxqJaNPsrJxtxLDe6VEe5GXo+ox3+d+xQwtRLZqwtNQZqHGmiZQ3HioShJEZnuRHv1Mz6nv/kN4c9zyh0wxCzynJ7BNVQ1rZOy5i21uE2k1Ekj4oI1H1Mi2IjPqO5hzGbCGxKjuE7HfbS624nwUlRbkZfhIwFcPmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RDzMePfGDZ/N2/oiyQAFbfmY8e+MGz+bt/RHSZv7ENj2IYhc3f1d2cg6+I5JJruGy5cUme3gLPxhWtn2Isw+S3/ANQwHm6AAAAAAAAAAAAAAAAFuPsM32G87+Xkfs6BYSK9vYZvsN538vI/Z0CwkAAAAAAAAAAAAAAAAAAaE6Hzcsg6K6qa1WOdZjkt3i8/I1VVBMuXl1aUMJdJtLrG/wBdJJmai5HskkpJJFxG+wxXBtLcW02x+dR49UohVM6XImyYrjrj6XXn1Gp5R94pR7KMz9Hw9RERANGOz4rtCXVxpbmkUs1uKvIHmHskmX2XVcqokwX07uuxISHCcjqb35JSguRcTSot9yHW6MYXI0/7O3akyyozXL27iltsoqo7a7142kLaShaZakEZfzv0S3f3JR7n90bi6edkfSTSjNCyvE8Nj0t4knSbfZlyFNsk4WyybZU4bbZGR7eikh+WHZH0ls8oynInsPaK3yiLIh3D7MyS0mW2+ni9u2hwkJUsvFaSJW5me+5mA1m1T0+vMT7AFrnDmqOoFnlM3H6q3dlyMifJCHTSnk22hJkSW1FIMlJ8Vd22ZmZkZn1eo+caq6tdoC6wWhPN36nFsdq5DMXCsnh0kl96RHQ6uW+5JUSn0kpXDindJGn0tjV6W7mQ6RYllWl6tO7Sp8qw5UJmuOt8peR/N2uJNo7xKyc6cE9eW57dTPqMW1M7KOlGsMusl5bh8e0mVsYocaUiU/GeJgvBpTjTiFLQW5+iszLqf3TAay3Gp+rPZ4xXR/VzViTZmy1DnY5mFLHnJkR3dzdcrppNsrUwTyu7Qlbidz9Mi3LfYYZkWVa0VtTotg8u2y2ff6iFZZXdNU981X2KjUSXGa+JJlL4xm2W1INTSDL1kki3G/N3pLiGR6ds4HZ0UaZiLLMeM3VOGruktsKQplPjvsk20bdf7PUcPVnQ/BtcqWLVZxjse+iRHe/jGtbjLsdfgam3W1JWjfpvxUW+xb+ADR/NZ+vOKaGM0uS3OUYeuRqJVVtBcyb6NMtygvmtLjUh+Ks0u8Fl/wDE99vsZGRERb36bafN6a44dQ3f5BkhG+t9U7JbJc+UZq29HvFeCS26JIiIuvTqYxmF2ZNNK7BafDY2MpZxyotW7uFDTNkbtzW1mtLxud5zWfIzMyUoyP1kYlAAAAAAAAAAAAAAAAAAAYVrZ9iLMPkt/wDUMZqMK1s+xFmHyW/+oYDzdAAAAAAAAAAAAAAAAC3H2Gb7Ded/LyP2dAsJFe3sM32G87+Xkfs6BYSAAAAAAOBkLVg/QWTdStlu1XGdTEXIUaW0vGg+BqMiMyTy23MiM9vUYCJsZ7VOKWOJ0lxeJk00i9XZuVVbDiybKRMiw5RsKfQlhk1HySbTpoIjNKXPFRJUoufbdpTE4uRaTVtbJRcMajOPHWzGVmlCI7cZT3fGk07nus2W+J8TI3evVPE8Y097P2RaeJxpUKbWLexvTtOL16jdc62S1oXIeV9b6NKUxHPkW6j9LdJbFvg3uK7ywweNUSMhi1NrUVePUdHZ1q1qcgxoikLnvJNSC2efWuRttuWyGDMy6kkJVwntUYXl+OZrkbkxFbjuOZK5jTc5alOnYPIaYUSmW0JNSzWt80IQglmskkovfbF2i+0xp2ishTVXEwvLJ71WzC9pp3lpy2mydcYOL3PfJcJBkripBGZGRlvuIdvux3ax25TeOKrI9ZAzJjIaelbtptYg4qKZmvJpUmMnvY7qFNmtC0cy2SRHvyURSLjehEmgzHAriLDrK2HRQbWTNiosJMx2TbSyjoJ1Uh5JuPETaH0qdcPmfJHo7eAd9iPaZ03zm0rYFPkK3nrJp96I5IrpUZh7uCM30E860lvvGyIzW3y5o2Pkkth/MXtOaazK+5nJyJbcOqq3bt9+RXSmUPQG/fyY5raIpLRGZFyY5kZqSRbmot4mseyNkl7pdheIS72vhuVOIXdbOnRlOLNVvYNNoU82k0J5MlzmbmZpUZOJ9Hqe3Pzbs7ZvqtCU/kR41TymamPjkOqqpT78RuCubFesHVOLYQo1uMxUtttcOKdtjWfIzSEy4vrPiWYXsSmrZsv2xmRX50VqZVyohSY7Rsk480p5pCXEEclnZSTMlct07klW0RZD2za2TnVNjuEN4rct2lW3ZRp+TZOqhTJNyU9GbajtriuLeUpTCzIyIiNJpMtyUkz6rthR7G/zvTugwu3Zj55YIm0zkVCFqfjVM9sm5M4jSRkgmTjpURr2I1J2IzVsR9/jWkWfaYan5HOxTHMKsMSmx6murU2V1KjSoEKFGJpLZNphOJMyWt5RbOFvuW+x7gMtsu0/hGHvv1mY2iaPIK5USPdR4kWZNh1kiQ22tpDktMdKCSrvUklxfAlGe2xGRkXS572qaCtxytscZkLmPqy2Bjc+LPqZrcqN3mzzxFFUhD5uHGJam9knyM0mRLLofws9AMguEX5SZlYv2+1Eg5RP3dcPlWwzi9wx/R9XP5kzun3pbq9I/X0troNqDF1J+rKsPGbWQnLp2S+RWU+RHS6k61qthINaI6+Jts9+tXoqIlcSLfkakhKB9ojT76loF+i+U/Dny3K+LGjwJLs52S2Z96wUNLZyO8RxUakd3ySRbmREPnM7SGncKkpLQ79ciPcoechMw6+VJkuIaVweWqO20p1CW1EaVqWhJIMtlbGIUsuyJlLd3SZUiyhXuSrk2825iovbGgjk/PXHUa4r8Pk6lLaIyGuCyMnEmajNKttsqvdBcxw7JLSx0uZxeCxPxBvG2GrWRIa9qXm3ZDpPM8GnDeS4uSZrJZpUakEo1KPcgHa4Z2rsZPTnH77MbFmDZXUWTbMQ6mBJkm3WlIcJiS6ltLhtN90TZqdcNKOXIyNJdC4mnvaqYnUNdKzSMxSKZwmNmV5NaYkoahNvmk20IQbau8TxNe6kOKMlNKSafuYhL7Muo2IYrldBg03GFNZFhtbjBWFtJkNPViokRcY+6QhlZOoWTilkZqQaFKMzSvwGUZf2a7m9+reLDlVjVfdRcbo4aXnXOTNVBf7yUhRE2ZEtaXZBJSRmR7p5GnrsGfRu0hp3JiXcj2+cYKmVGTLZlV0ph4zkKNMbumltEt8nVJUTZtJWSzLZO4xvJe0/Bk1VErT+pPLrm3yRWMN11qqTS9xIRGckvm6bsZTiSbQ36Rd2exnt0MjIYRr1gsaiuM6y+/zOiwy1uZVQWHXFk8omoMmAy6tBv8kcOrj8n0T5JNKk/wBo+JdDpz2dYuuOJYLYZXSMvY2heQzLhi4kKlSraxkupYZsEGqO0ngppDrqFcGzQS2SSgiLcglzDe1BRWeIPWuUxFY3Ys3krH01sBTluqdJY6rOGUds3JKNt9zS2RpNKyURcTH3m9qDFTyXAKuoj2t/Hy5Ep5qbX1Ux1MZphXdrU4hDKlJNL6kNLSrj3fLdfEtt4xldlvM/aLShxTlNPt8NpplE/Eg39jQsPNuLZ4SmpEJsnEuGhhPeNGjgo3VFv6KVCR6zRy3wrV3ErrF6+haxGux1yhegOyXmXoRuSUPuvMETayeNw20EonFIMzTyNRmZgO1vu0nhNRXZlIjS51m5iiJRWfktROdYjOx0kpxpx9thaEmRKIz23PjuoiMknt/M/tJ4ZjFNUSMonqqLKVUsXE2BEiyZ/taw4gjNyQtpk+5aI+Rd66TaT4GfTYyLGpOguRSOzTl2BeW1qMlyiXZv2ExLrhR1FOnOOvbK7vkZkw6aC9HxSRdC6jqrfsvyLPW/KL+bCgXuH5M7BcmRJGQWMPuER4yGO4XBZI40xs+75ETxp4m4sjJRbAJaf1ow2NWTp67n+bwrlGPPEiM8pz2wWtCUMIbJBrWZm6gyNBGRpVy34kZjg03aBwHIMpTj9fenInLXIaZeKFITDkOMEpT7bUs2yYdW2SVGpKFqMuKty6GMa9zsmRrre5tJtVe0kphuXCqGi28ntlR/JHpx9NuZRm2UIPfpzdMyI+JiPdMuynkGB6XWlCuLTOZNXY5Jpccu3Mjs5rRPOMLaOR5K+lTcDmfE1pjkvfdREfHZICetNdWsZ1dqE2uKSpdjVrZakNTnq2VFYfQ4SjSbS3m0Jc24mSiQZmg+iuJnsMwHTYVjLGF4bQ49FJJRqmAxAaJJbFxabSgtv+yR3IAAAADCtbPsRZh8lv8A6hjNRhWtn2Isw+S3/wBQwHm6AAAAAAAAAAAAAAAAFuPsM32G87+Xkfs6BYSK9vYZvsN538vI/Z0CwkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFa2fYizD5Lf/UMZqMK1s+xFmHyW/8AqGA83QAAAAAAAAAAAAAAAAtx9hm+w3nfy8j9nQLCRXt7DN9hvO/l5H7OgWEgAAAAAAAAAAAAAAADXGVl2eapq1IyCizgtPsXxGdNqYKGa2NKVOfhpMpL8pT6VbNE6S0EhvgrZs1GrqREGxwDVyj7XWWWGm03KEafRZ7GP4pWZJfuqufIzT5RDOU81HaNlfJaEFyJK1pIyUkuRH0PMabVbNso1xyeBT19M5gdJRQn1rnWTkd85Ulp19KlJKKvbZKG0KTzIkocJz01H3aQnIBrhpZ2gMtynB8GhQKBvKs6vaA8qlNWNkiBGhwHnleTEt5uMfJaiMkIJLJcu6UpZp8T4LvbNm22H2uT4zgibSop8Qh5hZOWF0UNTDTxSlKjJImXObyUxTUnwSsldVI9HkGzoCA8h7T89hcubjuHJuMer7mvx+ZOm2nkb658p1hvuYzJMud8bRyWyWaltkRksiNXExkmhV1Y5Xf6qXcufKkwFZY/WVsZx5SmYzENhmM4TaTPZPKQ3IUrbbcz6gJYAa7Tdf8AJMezDUHvKdN7FhZVUYjT1Uec2hK332W3XXSdNhJkokSW1LQo1knu1ElRdd+RB7TORz7RrHW8Dhry88pk4w5CbvjOEk2a4pxySknGJRt8VtoUXdEpKlHsSjIiMNgQEIafdpN7NsvocWkYt7W3cmZdQrRtFgT7UFVcplC1trJtPfIWuQyRGZNmXI9y3LYY1D7Qmb6iZXpmxhlDUswLp+8mzWLG1Whb1dBkHEQ5yTFc4d4t5h4ti36cOWxmsBsoAh7D9dLfOMwzylrMWhb4wuVGTFk3RM2Ul9o9mjVFUzs0w/1Nt/vFEaS3Mi8BKOOTbCyx+sl21aVNaPxmnZdcUhL/AJK8pJGtrvEkRL4qM08iLY9ty8QHYAAAAAAAAAAAAAAwrWz7EWYfJb/6hjNRhWtn2Isw+S3/ANQwHm6AAAAAAAAAAAAAAAAFuPsM32G87+Xkfs6BYSK9vYZvsN538vI/Z0CwkAAAAAAAAAAAAAABDNz2V8burLICXf5NFxrIZq7C3xOJPQ3Wzn3Nu+NZE33xJcMt1oQ6lCt1bp6nvMwANdqLs1PZXf6gzMusLmtoL2/7w8UhSIqK+fAjsMR43e8GzdJCksdWkuoI0nstHiR5+9oVXqyrObqNkV9BRmMMo1lWx3WPJkuFGRGTIa5Mm4h1LSEkXpmjctzQZiSgARHadmmikrq11d/kOMqh4+zi7508lls51c1v3bLpraWaTSa3NnGTbWXNWyi9X7M7MWHSMXzHHY6p9dUZRFhQJEaI42lMaJFZQy1HY3QfFs0IPclcjPvF7GW5bS2ACG5/ZeopuSs2ackySJAZyZGWooo8iOUL2xJZLUs92DdUlZkZmhThpI1GaSSexlnWnWnUDTKosKurlzJEGXZzLRLUxaF+TrkvKedbQpKUmaO8W4ouZqUXMy5bERFlQAI1j6BY8xaxp5zLN11nK38wNDjrfF2a5HcjklezZGbSEOFwSRkZG2gzUrYyOPrXs22DmsVbNpr7IMfpW03d5JvIL8JUk7Oe9FSTCUOsrLu0sNOpSZtmZFx9PkW42LABDrfZfx6reopGP32RYxOrIc2C5OrpTS5E9uW8h+SqQ4+04ZuLdbS4bqOCyPfZRFsRfbFuzTRYTMwKVQ31/WOYjUFRNk28wsrGJzbcUiUS2T3NS2kqNTXdq3M+pF0KXAARMx2d4aZ19Zy80y2ff2lS5SM3jkxhqZWxFud4aIy2mEESiXsoluEtRcS67dBKcKN5HDYj966/3TaW+9eVyWvYtuSj9Zn4mY+wAAAAAAAAAAAAAAAMK1s+xFmHyW/+oYzUYVrZ9iLMPkt/9QwHm6AAAAAAAAAAAAAAAAFuPsM32G87+Xkfs6BYSK9vYZvsN538vI/Z0CwkAAAAAEDdqbtCZfoOvCyxXTSy1CK8nqiy1QDX/NElw4l6KFemvmriatk/W1bmJ5LwAcO5uq/HauTZ2s+NWV0VBuPzJjyWWWkl4qWtRkSS++Zj4Lymlaxo8jXbwEY8UTy87ZUlBRCjcOffd7vw7vh6XPfbbrvsIW7TtPKzbL9HcNjXk+nbtMkXNlFDbjrJTMKK7KStSXmnCM0vtx+JGXHdXpJV6O0V5829f1mt8ZyauY3lWc0eBxnX2mW1FH/maZCDNtCOSE+VS0ly3VsjYzMBuc24h5tDja0rbWRKSpJ7kZH4GRj+hq3e5FOpNbNctQkZDaS42nuOx2GMXZ8l7iSpMFya60e7JuklRuR1EpCyVyIyNSkElCcaiaq6zVGluV5xZ2EpMdOIvSI7Vg3UqaK5fNsoRwERFuqOMRqWRnKcUpe6Ni98A3JAazaz3moGkWP4/Lm6i2MuobWp3Jravg1nthD5ky1HVHjOM8VRe+701p2W/wCkkkqPYyKRO0pl1phukr7dLKcjX11OgY9Bmo2JbD02U1G74umxKQTqll025JLoAlRC0uFulRKLcy3I9+pHsZf/AHH6NTlY1p/ker2odHqKqsRhOndbWw6Wgt5BIgsR3IpPPT1tqPZxZqPuicVuae5VtsajM8S0Xy7Kyxqro5OoU/TvF6vFZmSHJmR4z8piE/aSSrO8XLbcMkpis7Gky348S3I9jAbvANSNOLfLNYtWtJJ2S5HY4zb1WAsZFYVEJqK22/KmSCbLk26ytREtth5KyIyUjdJINszUauHpdl+QuM49RVuRrp3tQsmyi5fydcSIcpMKC+TDRNoNomDedQlhXNTaiJCHD4mfUg3DAag4Jqxnuo1rAqTz2RUUsGqyCzlZLDrIapFlDYskR66UlLjSmkGttqQozJHBaSMySW6TTjK+0dqHkGh9zkszLCxCxxfD6matUKHEN64uZ8UnmELKQ24htk+bCTS2lKjUtzZSSQRAN0J2VUtZLlxZlvAiSYcM7GSy/JQhbEUjURvrSZ7pb3QouZ+j6J9ehj+MVzChzqmbt8au67IalxSkIn1UtuSwtST2URONmaTMj6GW/Qaiatzp87HO0tYuSkybZ2HS6csSUp48nXmW+8NCfURu26uhf8v3hlWp2Oac32vKKnPGaj6h8Cwbyg0WziW2Ir0yTxS4RmZcXEtV6tjLqRL6dTAbUANDcPzXI8Tx/AMkuF2tv9R+DZTl7NZboSuQqMl/uqzm6pBvE4uK6aVGavBsty5EZnIOrTGSzuz4UW11WlW9rm82lqDXWMQY8SEU2U2h4o5pZNZNGy45sbi1qPuy2UW57hsnk+f4vhMitYyLJKigfs3vJ4LdpOajKlu7kXBolqI1q3Unonc/SL7o74a8HDUvtO0MKZcKum8MwKXO9s7UmEbvTZbaEOu9y222nZuC4XooSW3X8MVZHmmXZfolleIZXmlvEzqbf0mLWjLTNe3EhrmvtE6cN9po+cdyO6pSe9M3k8CJRkZ9Q3cAQJHn5TM1jsMXa1Gnw8SxLGYNhZ2LkSvJ+XKfkSVJNxw45NIQliMfIkIT0WkyNJ9Tlu+z/H8bwSbmUy0jHjUWAqyVYsOE40uOSOZLQpJmSyUnbjtvy3LbfcB3KJ8Zyc9DRIaVMZbQ65HSsjcQhZqJClJ8SJRoWRGfQ+CtvAx9xohpNqxZY1qln2b5Km9qLjKcGm5JLgXNTLgt151zp9xFZ8oaQl3uo0lHNTfJJr7xW/pFv3eWan6v4Dj7r72bPXFqxpuWYWjDlTDbRCkMyIpqSySWiPi40U1KicNWxp5J4dCINwclyimwykk3OQW0GiqIvE37Cykojx2uSiSnk4sySndSkkW59TMi9Y6iNqxhEy3pamPmWPv2l3FTNq4Ldowp+fHMlGTzCCVydQZIWZKSRlslXXoYgXO9ZbXI9L+0DmcKwS/hFRVyKPHGGmmzTLnNMrS/KJzjyURyHUMJIlcf5uoyLdW47NnFWKLWzQTDGybSzhGIWFi8o9iJHdsxa9nr+B6R/wDYwGyCFpcQlaFEpKi3JRHuRkP0Q52P7SXd9nHDJ0olpRIakLhpWRltDOS75IRfe7judvvbCYwAYVrZ9iLMPkt/9QxmowrWz7EWYfJb/wCoYDzdAAAAAAAAAAAAAAAAC3H2Gb7Ded/LyP2dAsJFe3sM32G87+Xkfs6BYSAAAAACBu1N2hMv0HXhZYrppZahFeT1RZaoBr/miS4cS9FCvTXzVxNWyfratzE8l4AMRm6cRLTVCrzabNkyJNTXvwK6DslLEc31IN97oXJS1E02ktz2SST2LdRmOyk4JjU2sl1sjHqp+umSjnSYjsJpTT8g1ks3loNOynDWRK5GW+5Ee+4iHtKRZWU53o9iMXKLDFisLyRZSH6/ybkpuFFW8gyJ9pxJqS+cfYjI07GozSrYjThd3rBm0tuyyKryw4zsHPGsMq8QKHFWVqlqa3GkrkLNvvScUjyh8jaU2lDaEmaVFuZhskWCY0WUP5KWO1X1RyI/kj1x5E15W4z0+tKe48zR0L0TPboQ4NTpRhFBUS6msw6grquW+iVJgxKthph55CiWhxaEpJKlJUlKiUZbkaSMvAa60urOcz5OF5DHzo7Esmz2bUQ8R8hid25TNzZLZu8ktk9zajs973nPjsSSUkzPc9nMzyNrD8Pvb5/buKuA/Oc5eHFptSz/APRIDq3MWwbUSdV5Uuox7JpkI1Jr7pUZiW4waFmSiZe2UaTSslEfEy2UR+scnPcMr88omque4pk250SwivI25tSYz6JDKy38dltJMy9Zci9Y1+7BN1kFtpu3Av5J1MnHoMOscxVbSSeYcU0T6pz7hlyUcg3TWgkHwSgtvSWSuPy1r1IvId9rfkdUg1yNMcXabpmlI5oasJbDjz8w07bGbbPcEXTonvi8FqIw2FyPTPD8wtoVpfYpSXdnC6RZtjXMyHo/Xf62taTNPXr0Mhiy9AMYt9UMgzXJauoyibOKEiuRZ1bTy6tEdCy2acXyPdS3Fr3SSdtyLrtuICyHEsA0uyrSJ7BJzdnmxTTtr3Io005Eywpmojy5sma4Sj5tuKNsk8+nNaCRttsOz071UzuE/pNJyHPnMhcyfFZV7klU3XwkFUMIhpeRKaNpolJMnVoaPvDWhZqPikttgGzthg+OW+SV2QzqCrm39clSIVrIhNuSoqVb8iadNJqQR7nuSTLfcxwbTSvCrzHoVBY4hQ2FFCWTkWrlVjDkVhRb7KQ0pJpSfU+pEXiY1QxXHbj3PWh8KdmNleys4yuptZ0OY1Beb2dN21ks8kxyXts2pfLlySpKeKko9A8qx3WrML9zTnMEZUkmcuyWTALCkRIxsRapnyknHlOcO/75pDCXFrNwmyUrhwLctw2WXh1A4t9aqStUqRBTWPKOI2ZuRE8uMdXTq0XNeyD9EuaunUx0d/pDp5cd3Lu8Kxid5HEKKh+fVR3O4jII9myUtB8W0lv6PgRbjVhPaH1CwPCcA1CuskfyOJf4zdZLKxtdfFYZTEjxifiqQttsnUr5PRkLUajSZLMySRkORqfqJnOM1mp1BY56WUTI+AJdnxW4sRmNX21i95PFSwbTZOpbSXeK2dccUZKQoz69QlXXCgwnOY2Kop87wzDckyG5q72vny2mJKsj8kWlyKhKEvsrlJ5KaNJpWroZEXvhJ9hpJhd9kEbI7rD8dtsnZQhJXUmoYXKI0+HFxSVLSRHvsXLoIkxCkgwu0ffRY73ktVgOn9bSNSOKd46n3nnVKSRkZbk1Ejn1I/VuRkI60Zvctk4lpxSRMxlYnCmYVZZxf2MathLkrXKmNPRlES2TabVs5JNWyOJkSi4keykht+5QVbto7ZLrYi7F2MUNyYphJvLY5GrujXtuaOSlHx323Mz26iPaXG9FZ/t/p7U1eBSODiZFzisKPCVxXyTxXJipLorkaNjWnffj94QppbcZTrJqdo5Y5Dl8+otK7TyLks6DXtxENS5M15KCNSHGVmRLbZeSskmRp3Luzb3Vy42tdkyqx7VmSL3J2lw6DjUBxktlolrjyJSeJl1597Ni7GXUjJP3gE3UehzlLqxluRHYVMrEshqIVM5i7lL/AEDEZtxDbaXe+4d0ffPbt9zsZKIt9k9crb0jwVnEXsUbwvHkYu8vvHKRNUwUJa9yPkpjhwM90ke5l4kX3B39GqYqkrzsSIrA47ZySLwJ3iXP/wBdxzQGPu4HSN1VnCroEak9sYiIT0isjNNOm0hs22078DIyQg+KSMjJJdCLYfrGA44xh1fih0kGRjcBiPGjVkphLzDbbHHuS4rIyPgaEGRn1I0kfiQ78AHS5DhGO5cpCr2gq7pSI78RJ2MNt80svESXmi5pPZDhJSSk+CiSW++w6bULT97KKazTQSazHchnxEVrl3Jp25znkhKUZsmhSk8k+m5xSozSRrM+J7mR5mACDr/szErSDENLcYvItBhNQuIVixJrDlSrBpiSzI4pdS82llTiml94o23N+9PYk7dedM0HtsjzzNMiyHLW3276pax6JGqa1UNyDWpkPOuNm6p9w1uuJdJBupS3twJSUke20xgA41XWRKWtiV8CO3DgxGUR48dlPFDTaUklKUkXgRERERfeHJAAAYVrZ9iLMPkt/wDUMZqMK1s+xFmHyW/+oYDzdAAAAAAAAAAAAAAAAC3H2Gb7Ded/LyP2dAsJFe3sM32G87+Xkfs6BYSAAAAACBu1N2hMv0HXhZYrppZahFeT1RZaoBr/AJokuHEvRQr0181cTVsn62rcxPJeADpcmwfHM1TCTkNBV3yYL5SYpWcJuSUd0vBxvmk+Ki/5i2McYtOsZj5LKyeHjtNEyuQ2bar1Nc15WouOxcnSIlqLbboavAthFnahzvJcDZoLGsv36DFY6nFZDMp2okizjpWptuK6hiSlSVsd4pZOEhJuH6PD+0OgrNXb2VR6jal2GWPM0eGy7qMxhUGPEI5bdcl1CjkuLbU8TrimjdIm1NpSlSNyUW5mEiaSdnzF9HcJrq2qhV0bJGKluum5bCrI7E+Y4lsiXIWo0r3M1lz4rNZEexHyIuvIx/B/qorI817Uq2z3FbKMrnCmMU8ius4zqDSaVGzCSbjakq/sr2MvukZkeu+bauZxiVblyLTOiv5jOmkvILGqjxYjcWBYSVNtwW46kNk8SNze3N1xZqIkK6bjsckynUTG8kb0i08RY10XDMVrI8eVVe1RHIluIU2ycg5690xU9ykj7hpbilGst0mSSUG18bG6iFbOWkerhMWbkZENc1qOhLymEGZoaNZFuaEmpRknfYjUexdR1tZgldVZdkt+yRm7kDMVubHUkjbWtlLiCc++akLQgy8Nm0/f37elRPbp4CbVxh2zSw2UtyMRk0p7iXM0EfUkmrfbf1DUzMdfs10/yHPkWeQkqyJTa8dh9zEdoUV8qyYhRpq3m0lIJxk3TN5t1ZJUZHw9EtyCdsj0HxlzTbM8Vw6ppsCeyWtkV71hT1LTZoN1tTfeqQ3w7w08zMt1F19YyHDNLsP07hyI2M4vT0LclKUyfa6A1HOTsWxG6aElzPx6q38TES391lmO5RhOnzOpcyfMyZywmTMqkwq8n4TERlk1xozaGSZJa1vJURuocNKEub8uhlhumWpOomrmU4zjUbN5FdWIi5BNlZDCroZybaEzYoiVshBONLabUtJPL5Jb4LSkzJJckmkNj6HTHDsVYgs0uJ0dOzAkuTIjcCtZYTHfcbNtx1skJLgtSFKQai2M0mZGexj9r9NcPprW2tYOK0cGytUqTYzo9cy29MSrqonlkkjcI/XyM9xrpjep+rWp2qFomhXNqqOozByk7pwqr2tcgxHyblrkGpapqpLiUOqbS0hpCSU2ZmpO5nJ2vlq7aZHprp8g1Ih5dcuJszLwcgRI7kp5k/vOqbaaUXrQtZesBIyMJxtTUIkUNUbcSAusikmG1szDWSCVHR6PotKJtsjQXonwT06EOqrNGsApYM6FX4NjcCHOj+SS48aojttyGdzV3TiUoIlo3Mz4nuW5n90avXCNPs4ptbM21Yci2ljRXdjR1VZLkGTlWxGLu4qIbRHuh989nSWguazdQRHsREOCzqPqfjulGYSbPUSRRXuB41SxEVi4MSS5Y3qq1D7kZ43W1OOG6t5hvZCkq5KMyUXUgGx2FaNTMV1K1CvZNvW2eOZb5PvRnUG2uKTMZqMhvvu+UhbXBtfod0nq4fXYtjzdvCMcaQ4hFBVoQ5XoqVpTDbIlQkcuEY/R6sp5r2b96XJXTqY1/wAIfsbvWjWDNbTMrWqYx6DEpnKiN5EtiP3dcmW+aScYUsibdmckq5ekpGy+aEpQnBtK80yODpvS469mZaeRKDAYuaXFsxChnIkyZ7kh40kh5pTSWmzbXz4II1KcSRKRt6QbXTdOMakTq20ZxyjReU8Y41RZPVjTjlcjiaSS0exKQgiPbghSSMty6biPcM7OzsCNZfVdkLOTSbXJ/qqsUw644UeVIQ2wiM0banXVd00cdpZJ5nupCNz2IyVCl5rTqlbadZtkzuTvYZMxTAKe2frIlXFdN29fjyH3GV9+2s0tq3ipU2XpFyLipOx8u8ybWXObh1djFy9OLPpzmBhFZQRIsVxuc6T7CJ70hTza3DLiqSpCWlN7JbQZmo1dA2Yts+xihi3MmzyOpro9KbZWb0uc00iDzJJt9+alETfIlJMuW25KLbxIfWizXHspNoqW+rLc3ojc9soExt/nGcUpLb5cVHu2pSFklZdDNCiI+hjUWzNOYYxLUoyeLUPWlqIv1qch1slKDT/h7unUf/m++P3UvMr3Gc71x1CxfLE1thRS6XGKyhTGjvoupDTKXyhqJaTcLm5YLQXcmhRK5GZqJOxBukA1Ue1lzS6dr8qrsnKKUzUD6k4GENxI60SIjM440px9ZoN8niaafk7oWlCEpSRoV1M/nhWtuYZC5pPmC8pS/HzmxluSMMRDj+T11S3HkuG93hI7/vWjbYS4pThoNbhpJCdyAbDXuq+EYvUO2tzmNBUVbU1Va5On2jDDCJaeXKOa1KJJOlwVujfkXE+nQxkzTzb6eTbiXE9OqD3LqRGX/oZH/wBxpnLiHb9hHGITqCVY6l3EFxfLYzUq3tkyHdz+8y+4X4EiatKbeRYdoPW+MzudPCepmiNPvDmnC5P/APm7o4hH+BP3AEygAAAAAAMK1s+xFmHyW/8AqGM1GFa2fYizD5Lf/UMB5ugAAAAAAAAAAAAAAABbj7DN9hvO/l5H7OgWEivb2Gb7Ded/LyP2dAsJAAAAABA3am7QmX6DrwssV00stQivJ6ostUA1/wA0SXDiXooV6a+auJq2T9bVuYnkvABj91p7iuSX1dd2+M09rdV23kVjNgNPSIux7l3TikmpHXr6Jl1H8N6bYi1kk3IUYtSov5rRsSrVNcyUqQ2ZERoW7x5KSZERbGZl0GC6sXuTzNWdOcKxrIXccatGbOztpMaKw+8cSO202lKO+QtKDN6UyZKNJ+96kZbkcT1epOruf6k3dbjkqwiV+P5K1QtuPJqfIZTEc2/LHp3JXlZvOI75TaI7TSCI2z5GRqMg2FqNG8AoI8yPV4NjdaxNjqiSmolRHaS+wo91NLJKCJSDMi3Se5H9wcudpjh1pZU1hMxOjl2FKlCauU/WsrdgEg90Ews07tEkyLbiZbbdBqzIybI8fx/XTUWnzqy9tp+Xli9XDfahORoRpmRa5pzibHIjbcU/sSlcDSojWlazNZ5Rq5rXl31Y6i0+G5CxDVWKxrG60ijMyEs28+cpMhxW6TMzbjrZM0GexbGexH1ATTYadX82fJkM6o5ZAZddU4iJHi1BtspMzMkINcBSzSnwLkpR7F1Mz6jsKrSfCKKDbQq3Dcfr4dvv7Yx4tWw03N33375KUETnifvt/ExDGd3Oa0ud2WMxtTbWFW0eHSsktLVdZXOSe8U+ZRyTvH7tKEpjyS2NBmaS6ny9Mph0avLnJ9IcIuci7v2/saSFLsO6RwT5Q4whbmyfV6Rn09QA9ozp/IxmLjjuDY07j0V0349SuojnEZcPfdaGuHBKup9SLfqMgiY7UwLBM+NWQ485MVEEpLUdCXCjoM1IZ5EW/dpNSjJPgRmexdRrCzqjqzqVqvkcTFzmU9VR5SikYb2qzrXI7C2zlrmG6tU1Tq0d6baGG2yIu7M1GRqMuBT57qbmNvhEyDqJKgxcxzK9rote3UwVNR6WKc40PEpTJrN4ijsklZqNH1xPJCz3NQbJO4vg1Dm8e+cqMerswt1KiMWaozDVhNMm1LU0l3YnHDJttSjSRn6KDPbYhysjwmDkmQYvdPKW1YY9MclxXEbekTjDjDjav/CaXd+n9pCD9Wx6g0OWZBqpl+nrdzqGqldx6Plt19VRxoTchcNmeVdEeW2ts46VKaN81K7vjxI9iSZ7l96vtEarZ9Gwukhx7SHPkYkWQzrOhZq2JMxTkp5iMvjYuE20wpDBPOGhtxRd82REnpuG2M/TjDH8nTls3F6JzIo6SUV5Ir2TltJSXQyfNPMiIi+70GEYTpHgdFMvNSZ/1OZFNtJ72Rx8tfhR+USItpHdk3KM1fWkMtoPvCUSTLdWxEYgPVDVLUaZpnqi3dZvDxm1wuphUT9fVxI6iu7mVAZcWajeSa0MrckJbbJrgr0Vq5dOJcTU69v7rSPO8Zg5qnGaOqs4ul9TjUWLHW5ZKcTHiureW4k3SUpL7ikJaNHFDZKPmW5ANv0YDhlrZz8hRjlFMsLuD5HMtUwWVuz4ikp+tOO8d3WjSlPomZpMiLp0Ifttplh9/KqZNpilHZSagiTXPS65l1cIi22Jk1JM2yLYtuO3gQ1lXf5DUZrnT2L3fkE691GqcNr5jsGM4bMSNCQ/JbJJNpJaUJVLQk1bqIkdVdNx8bHWrNcXh2sW7zazbxWFnMzH3Mxi08WRak23DZUxGRHbjm0pxyWt1olkwro2RGRGrkQbWTcKx6ybtW5dDWSkWzjbtgl6G2spi2ySltTxGn64aSbQRGrfYkJ28CHWydK8GsMnVfyMQx6TkROtSFWjlYwuWTiDI21m6aefJJoSaT33LiW3gMa0ryHKqDQCDkOoy35GSRq6RZ2KXWmmnUpI3HUNqQ0RISpLXBJkXgZHuZn1OA4hPZRSaDUOX2RxK3UpM3K8qdKQbBWUnyZp9ivU4RkfdETyUk3v1bhkjw33CdM60LXdZthOR4rPqMVXQXEq5mRVUnfos35Ec47jqzbea2d7tThE4rme6kmZHx2PMWdLsMj5MnI2sRom8hSpa02yK1kpZKWZqWZO8ee6jUoz69TM9/EakVdt9Rmp2S0+kFnHxXCrjJ6LHYrsBpt+AichmXItDitrI2iMmG2UK4lt3ifukY5GU5Tk2oXdYFJ1Cnzah7UyLTwcqjMQWHpsWNBKwktr2Y7hfdPsqa5JbIlKRxUSiJaVBttB08xWsyiVksPGaeJkctPGRcMQGkS3i+4t4k81F0LxP1BQ6eYri1tY2lLjNPUWdkZqmzYEBph6UZnuZurSkjWe/X0jMa0ak5rd6f6u5vcV9qdtPpqDHMYhzrmPHJuLMs7BTTj7qmm2zNCUoYfUkzJPJWyeCTIi/vItTtQcaybJcLqc5dyF9N3jNTCyKfWxDciy5cha58VSGW223CREaS5txJaSeLdW+xkEuZToa5dZNgaaqfUY5guK2RXKMbgUnBb8wkSCSonkvJQ23ykE4aCZMzWgz5el0yjFaGi0pqmoL1my3NubJx56ZPdQ27Z2D3Ja9iPYlLMknxQn3qGySRcUdNfKjVXPX9RbTTRvM3JDb2Uyq9jMJ0GImXFhRKyLKloShDSWFOd9IJtClNmRJJw1Es0jh6X5nZ6tZlowq3yBWSxGJ2U5NEnyG2GlvQojqq6E44TKENmpSJhubpSRfeLYBsqxqzg8m1sKtnMsfds6+S1CmQkWjCno0h1wmmmnEEvdC1uGSEpURGaj2IjPoMqGjWNMOZdpRojCYtfaK0z7O7DMX7JKG1OIZQudYIWlLhGk1F/NCTySpJHsZkZFsMtwHWvM8+t6fCCzIocWRPv3UZ81Dipesq2vdYbQphK0HHNxS5CiU4TZo4x1KSkuW6Q24HBlX1ZBs49bJsYkexksuyWYjr6UuutNmknXEoM9zSg3EclEWxc077bkNUMA1vzDUePEqbTOiw6uraCbkcrLmIUQpFnDKxkxobyUPNrZbbUxGJ5w0tnv3rfE0EY73s4ZPe6saqQcnyiOmNeVOndS1KZ7vu9pNi87IePu/wCxuiJGM0+o1beoBsFiGf4vqDWKssWySoyWuS8cY5dPOaltE6SSUbfNtRlyJKiM0777GR+sdRrZ9iLMPkt/9QxrDiNo+3pnpvkFcXG2zXV9+1jpaL0ijOS5hLMtv7PkDKiM/wDlPYbPa2fYizD5Lf8A1DAeboAAAAAAAAAAAAAAAAW4+wzfYbzv5eR+zoFhIr29hm+w3nfy8j9nQLCQAAAAAAAcNVLXruG7ZUGMq1aYVFRONlJvoZUpKlNkvbkSDUhBmnfYzSk/UQ6xGn2LN5crKk41TpyhSO6O7KA0U00cePE3+PPbbptv4dB34AMVe0owiQq+U7h1A4q/29uDXVsGdlse5eUej9d2PqXPcf3U6XYZQsIYrMRoq5lElqaluJWstJS+0kkNOkSUl6aEkSUq8SIiIjIhk4AOqm4lR2bto5Mpq+W5axCgWC34razmRi57MumZfXGy713ZCty+uL6eke+N2Gm9xImOLr9Rsmo4PQma2viVPk8ZBFsSG+8grXxIi6clGf3xnIAMciac4xFyVGTHQVb2VkyTC8hXXsFPdTx4nyeSgldSLYyLYvUREXQcqBhePVXtX5FRVkP2qS4iv7iG2jyNLn9ITOxfWyVsXIk7b+sdyADDJ2iunlm3DRMwPGZaIaUIjJfp46yYJClrQSCNHoklTrqi28DcWZdVHv2eR6eYrmEuulX2M093JrV95CesYDUhcVW5HyaUtJmg9yLqnbwIZAADF7vSvC8mvE3VxiFDa3CUoQmwnVjD0gkoUSkETikmrYlERkW/QyIwnaWYXZ5L9UUzEKGXkHNpz22frGFy+TSkqaV3pp57oUlJpPfoaSMtthlAAOmawvHo8hmQ1RVjb7E12yadRDbJTctxKkOyEntuTq0rWlSy9IyWojM9zGD6laOzMtrV1GPzcboaGat961rLHF2rFqY+6vmqQRG62knuXJRqWSyUajNRGYlEAGNYfp9VYbpxT4SwTs2lratqpSU1fNbzCGia9M+m5mkuu2xdehEOFD0kxlenNJhN5Uwcqo6mKxEZYvIjUpK0soJDaloWk0mviRbnsXXfw3GZAAjLM9AcZzaxwpmZWVKsSxpUpwsYdq2nIUhbrJtIPgfoJJsluGRcD3NZeG3XJrXS7DLzGYeOWWI0Vhj0NSVxqmVWsuxGFJ34mhpSTQky5HsZF03P7oycAHTzcMx+ybuG5dFWykXJJTZpehtrKcSUkhJPEZfXCJJEkuW+xEReA49Xp5itHW1ldW4zT19fVyPK4ESLAaaaiP8AFSe9aQlJEhfFay5JIj2Uot+pjIAAYxb6W4XkEJ2HaYhQ2UR2YqxcjzKxl1tcpRbKfNKkmRuGXiv3x/dHDutJ8ek4wqspqeooJcaJMYqJsesa/wDZbklC0uOspTx4mo1mpRJNPPc9z67jMwARXgugFLW6V4thub12N559T0BFXFkyaFCGyjoJtKUk0649sZky0ajJWylII9i2IizC+0zxDKqmBV3WKUlxWV5pOHCn1zL7MY0lsk20LSZI2LoWxEMkABjuQab4llj1a9eYtS3LtYe8Fywr2X1RD6f0RrSfDwL3u3gQ4uZ4bKs49hPxd6ox7MZUZMNOQzany1xDBL5G2ZJdaWoupmku8IiUe+x+B5YACL9M9BazTyrwGK7NXbLwyiRT1yltE02l00JRIl8Nz+uOkki6mfBJrIj9NRn3etn2Isw+S3/1DGajCtbPsRZh8lv/AKhgPN0AAAAAAAAAAAAAAAALcfYZvsN538vI/Z0CwkedTSvtNam6JUsqpwfLJWPQJUjyp9qM00feOcSTyM1JM/BJFtvsXX7pjNPOAdoL4zbT82z9ABfuAoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4CgjzgHaC+M20/Ns/QDzgHaC+M20/Ns/QAX7gKCPOAdoL4zbT82z9APOAdoL4zbT82z9ABfuAoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4CgjzgHaC+M20/Ns/QDzgHaC+M20/Ns/QAX7gKCPOAdoL4zbT82z9APOAdoL4zbT82z9ABfuAoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4CgjzgHaC+M20/Ns/QHdZb7IHrmu4UdZnN3Txe6b2iyEtqUSuJclbqQZ7Ge5/8AcBe6AoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4CgjzgHaC+M20/Ns/QDzgHaC+M20/Ns/QAX7gKCPOAdoL4zbT82z9APOAdoL4zbT82z9ABfuAoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4CgjzgHaC+M20/Ns/QDzgHaC+M20/Ns/QAX7gKCPOAdoL4zbT82z9APOAdoL4zbT82z9ABfuAoI84B2gvjNtPzbP0A84B2gvjNtPzbP0AF+4wrWz7EWYfJb/6hij3zgHaC+M20/Ns/QHGsu3frxcV8mDN1GsZUOQ2pp1l1lg0rSZbGRlwAQKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKtTGrtnKVpyB5l+x8nZM1sERJ4cC4F0IuvHb1DFR3+cxa+JfqbrLR23i9y0ZSXl8lGo0FyTvsXge5f9gHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALfcd9i+7PULQWo1Fy63yyuhJxxi9tZLM5CkMpOMl55SUJjqWZFuoySRGfq6ioIW3U8K31w7F+r2W3Wa5JWpxCgfx+rxmoslRYDUeJVsq5SWU9H1SOalGbm+yFESOO25BI8D2Hrs/WkGNMjWOYuxpDaXml+2bRckKIjI9jY3LoZeI5HmbNBf79mP6UZ/gDI6GuuNbNQM/pbPUDJcIp8Fx6kRUM47aLr0JXJgHIcnSOP9MRKLgSF7tkTK90mZmYxzQfOsu7X95jtZl+XZDiESvwKqvFw8ZnrqpFvMlOPoclqda2X3SSYTs2kyTyd679CAPM2aC/37Mf0oz/AAA8zZoL/fsx/SjP8ATZh0u1ou11OxBzIbe0pYGm1W801ZTFO99IKwmNrkrT0SbykoQSlkkjPYi8CIhAGidrkurr+gtJcZ7lrddc47lUuzcrr2Qw/OUxaMoYNb6Vd5uglbJUlRKJJcSPipSTDs/M2aC/37Mf0oz/AAA8zZoL/fsx/SjP8AdVgNlluP6caZ545qJmFxdOanoxCS1aW63okmsO2er+7cj9G1L7tKV98aTc59eW2xF+Y6evevrWW5riVoqtuouSWFbW99nD8ODWFFlKaRHkVKIK2nd0ISazccNa+85EpG5EQdt5mzQX+/Zj+lGf4A60vYkOzeeSHj5X+TneJiFPVXFcsG+mOazQTpo7jckGsjSSj6GZGReBjtdQvqqt4HarylOoGW1NjgL5zqCHW3LrUKG61TRpSkm0XR1tayMjbcJSOqjJKVLUZ5PjuIxM57csDJJlpfRJsjTepvTjwbyXHjqdKa4k2jaQ4SVMeikzZMjQalKUaTNZmYYr5mzQX+/Zj+lGf4AeZs0F/v2Y/pRn+AM60GdyPT3XWRjOqN3l8rNL0rSVUTHbg5WO3MVD6XCOPGL/AN0fYaU2k2+KS2NR7r3LaftRc/s8Gdp0V2D5BmRT3jadXR+TbQiLj6bvfPN+ifI/e8j9E+nhuGo/mbNBf79mP6UZ/gB5mzQX+/Zj+lGf4Al3U7UXNMa1ffgaazp2fWylR/bXCJdeSa+uQaEfXSsyJBQ1KRsvu1m+a9zNLRctxJXaJz+y0q0Iz7MKeOmVa0lJKnRW3E8kd4hpRpUovWlJ+kZesiMBpxd+xUdmjHcpxvHLG8zGPdZEuQirjeXJV5Qpho3XS5JjGlPFBGfpGW/gW59BlVn7D3oJZSjeJeUwy4pT3UWxaSjoW2+xsn1PxMfGbgczTbWLs7ZP9WuT6iW9hBvZ7yrm0VJjSX/adbvOM1txYSoz2JLeyeJp6GZbj4YfkGVY7pvoDq+rUbI7/JM+yGphXVRLsVO1UhmxNROsMQ/6Ng45HySpsiMu5VyNRGYD+/M2aC/37Mf0oz/ADzNmgv8Afsx/SjP8ASF2UKG21rwzH9Y8kz7LV5BZzpUpyihW62KiG2iQ60mCcMi7tRISgkqUojcNRGfLqMJhai5R7iPFbpzJ7f2/ez9uA7YqsHfKls/VStk2VOcuRp7ku74Ge3AuO23QBw/M2aC/37Mf0oz/AAA8zZoL/fsx/SjP8AdLqJKyx7THXLPo2pGaVt9i2oy6qmbh3LiYcWKcyGg2jjHu26naS5sTqVknZJJIiIyPudbNRcu7LlprJU4vk93dxY+FVF9AcyexcsF1suTZvQHnkOvczSgkEl00GSkJNB7J23SA/fM2aC/37Mf0oz/ADzNmgv8Afsx/SjP8AdvFwXW3Tmnym9fuZMTFixK2XOKdn0rIZLkooxrjSoqnITBx1pWR792skGSy2SRpIfbTksgxnKOzZLdznK7z+UmjlN5Cxb27r7LjntSUxDrLe5JjrStBpI2iSZkrrufUBiGP+xIdnDLK72wpb/J7WD3zsfymHcsOtm404pt1PImNt0rQpJ/cNJl6h2XmbNBf79mP6UZ/gD56CaP5Q/2NsqPTTJ72Hmthc2sVopuRSTbS2xeSO8bY7xS0RnnWUrSbyUcjW5zUe/pDYjsrZRWX2C28CG/lqbOkt3q62rc2nnOsK6WlDalMd/yV3rfFaFoWS1EZL8fUQazzPYkuzlAv62kfucuTbWLbz0WIVm0pbjbXHvHNij9EJNbZGo9i3Wgt91ER9p5mzQX+/Zj+lGf4Azi5zu1p+0R2lcjYjlMssF0/rfaSKst0r5tTpay2L/ndbbSf3eCS9RDocUfyLTR/s55YzqNk2Y2GosxiFfV1tZHKhy0Sa92UqTGjn6EYmXG0bd0SS4K2Vv4gOl8zZoL/AH7Mf0oz/AHW1HsSHZwv5VpHrb/J579XJ8inNxrlhZxn+CHO6c2Y9FfBxCuJ9dlF90cTF7TKMS7L2C6yR9Q8wscycyZmEustL1+XCtWXrpUNUQ4zijRv3KjUlaS5lw35bF0l7sladQKvV7Xq5btsgdlxc4kxUxJN5KdiqQuDCc5rjqcNta91GSXFJNRJJKSMiSREEVZF7El2csSrkz7e5y6DCU+zG8ocs2uCXHXEttkoyj+iRrWlO57EW5bmQrR7cmh+O9nXtIZDguKuTnaWAxEcaVYvJdeM3Y7bit1JSkj9JR7dPAXudoxmvyns26ns98zKhu41aJ71pZLSlSY7vUjL+0lSf+xp+6Qoo7b+TT811nq8htVGq0tsQxyfLUrxN12pjLWf/dSjAa/gAAAAAAAAAAAAAAAAAAAAAAAtMqO052K8iw6lLNKa6kZE7jsGou1Q2Z0ZqYbMZDJ96lh5CHTTx2StRGotk7GWxbVZgAuCz7tqdinU6XClZHXXsyREgprEusRZsZT0RJ7pjvGy6jv2iMz9B3knqfTqY5Ge9uPsX6le0iryuue9pI/kddIrIEuuejR9iLuEuRnG1d1sRfW9+P3hTqAC4a97bfYtyJnHmpkC+Qmhge1UA4cWbFUiF0/mzimnUm6z6JbtuGpJ/c6mOdhfb27HOnb2MO47FvKxWMw5lfUkiDLWmKxKdS9IQRKcMlEpaEnurc07bJ2LoKawAXLsdvzsexsZrMfbYvk1FbelksSP5FLPu7EpSpZP8u85H9fWpfAzNHXbjx6DqLbtodie6z5zM5NXdlkDstqe+7HiTWGJEloyU287HbdSy64k0pMlrQZ7kR7in8AF0Ev2Q7sjTq/PIL6L5cXOiWWQt+Qyi8u5RkxldSc3b3ZQlP1vj4b+PUS5pVlmhnbGTW5TiON3Fw9hJt1kawbeerXo6NkrSyoyfbU836CT4r5p3Lw3MxQGLSPYl9Tf5NdJbtDdb7aysiz2soGWe/7nh3zClOO78Vcu7abdc47Fy4bbp33IN/sG0FwTTjMHspoNP58W9cS8hMp+x8pJhLqyW6TKHZKkMktREau7JO/rEm+3Mv8A6FYflx/4oxDN9Za7CNSMWxOW0SDt4c+ykz5Jussw4kVrm473ndG0vZRoSpJuIUklkrYyMt8WzDtZ4jQ6ZLzKohXuQx12UKqiRGqKwYclvyloSz3ZLj8lIMl8icSlSVdEpNSloSoJXK4llv8A+wZ/X/xx/wCKPjOlrs4UiHMxqXKiSG1MvMPHGWhxCi2UlSTd2MjIzIyPx3EXuau5FkWu8LGcferK3EYOMRsmuXruolJnGh991tphKVOsnGXwYcUfetqUnbY0kOi007VlvlB6eTcrwA8Tx3UFHLHrePcJnINZx1yWm5SDaaUypxltak8e8T02NRAOzwXsv6Z6a5PTZDjmnVnAtaY3zrXFXDjyIZPNm24hppyWpCEGlRlwJPEuhkRGRGOdjnZw03wvOGsrptOJEe5Yeekxlpm840R13fvXI8Zcg2WFq5K3U2hJnufXqO6ou0tprkcqWzEydttuPAetPLJ0V+JEkQ2TInpMeQ82lqQ0jknk40paS5EZn1HIo+0Fg+QR6d+LYWDTVxZIqK5U6lnRPLJC2XHkE13rKeaDbZcV3pbt7JP0gGNRuznp3Bz5WZxdOp0S+XO9tFKjWJtRlS/Hyg4qZRMG6Z9TWbe5n1M9xxLLsu6Y292/aytNrBcl60Rd90m1WiO3PS6TvlLbCZZNNuGtJGpSEka91ErkSjI8mu+0tp1j9i5XyruS7YIspNQUOFUzJb7suO00680220ypThoQ82o+BGXVREZmlRF/OD9pzTXUa2ra6gyJUt+yiOzoTr1dKjMSmmtu+7t51pLa1N8i5oJRrR15EWxgP4naPYhZYvk+OycFsHKfJbY7y1jeXkXlMzvGnO95FJ5I9Nho+KDJPo7bbGe/Z22A49fZNbX9ng8mws7amTj05Up1lxqRAS444TC2jfNsy5POGZ8dz5bGZkREXSx+1hpXKh2kxGTqTArq563cmu1ktuO/CaUlLsiM6pokSm0qcQRqYNZFzT90hwcq7WuCUWA53kle7ZXL2IxGpMqqRVS2JLpvcii8EuMkam3lpNKXkkbfQ1GrYjMBwKbQfTLR/GsnkwMCtYNXJpn4Nibtq5KNNfwNTjLXeSlm0jYvetcfAtvAhrqx7Ix2Soz2DOt+3yXMIaUxj5+QSj8iQqMcYy9/9c+smafrnL7vj1Ey9rLWfI6fs812RYq3Eht5BIapZdXklJMalLTJX3LndpcXHcYU2jvl8nGlEokJMkkR7n59QFvUvtjdiSbJyh5dfkLZZM4b1oxHbsGWHnTebfN1LSHiQ04brTazcbSlRqTvv1PfMNNfZIeydpBQPU2Je3lVBfkrmP8AKukvuyH17Ep1111anHFmSUlyWoz2SReBEKVQAXIr9kl7PcLXVeewLi38ntqIqS6iO07pG53LqnYryPElGnvZDaiPbcloMj9DY+DgXbU7FGmOUtZFjVVcV9qwh1qItUGY+1BQ4f1xMVpxxTcYleBk0lBGXTw6CnwAFn/Z27TnZK0uoMYn5QixuM7p5UqYmc1HnyITTzkh1bbrTDiiaS4Ta0FzJolEZHsZ+Jy2x7IH2QomqT+osdGQRcvkJJMibHiTG25Bk0bRKdYS4TLiybPiS1INRFtsfQhTKAC3bIe3h2YKrQO+0t09k2uK092h+G9tWSF+Tsy3D8scQajUo3O7cd4EZ7Eo0l6KS6V/9tjVDDtX+0BaZDgPflifkFfBgokMGypCGIrTPHifUiLhsX3iEEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALHPYu8CzvJ8cbyDDYmO2beK5U7YyK/IbN+Ch15ytXGZUhTUZ/fil98+pF147ferjHMgXdjVIUiFPlQ0LPdSWHlIIz+6exgPRPb6a57leZMZhc1eIvWEbD51EzQuWMiRAVKlSmlvc3FRkqNlTMZlJq7vlupZcdi3PDIXZ01IpNO8cr4EyikyqTNmckg4zYXEx6uhV7UdTbde1NWwp40odMn0qU1skyJBJJKSFCv1X33/AFux+dufvD6r77/rdj87c/eAvZx+S7qpqJ2sKfHLunYzyVDi43BiyZmxxiarNifWlKVOEyUma8RKJB7mgy8dyHOldlzONS8PqKHNrWixmsxrHZVPj1XjDr8xLUt6AuCmc++62yajaacXwbS2nY1mZqPYtqGPqvvv+t2Pztz94fVfff8AW7H525+8Be7m3ZXzvVvEYUXJJWL0E7HcfTS4/XVL0iXCcc7+I667JUtppSW3EwWme6SlXBDjh8lmZbZ/l+D6pZfb6b5VIrMQReYrbTJjlGVzK8jcbehORkOJleSc+8R3rh8TZIjJW3IjLc/PH9V99/1ux+dufvHe5nfWdVeKYhZjYXTBNNq8rKWs9zNJGaffH4H0/wCwC9vSjs55xiGU1eTX1hQTbqLHyec75E48TK7a0ntOtrIlN7k03HZS313URqMiIy6nwm+yJdzMCwvE5d1CixKPTa1xVybFW4t322ntRmnZaEmlO7ZE0+ZGZkozd8C6mKFPqvvv+t2Pztz94fVfff8AW7H525+8BfDWdkq5f0om4/NrKSuyGQmtqFWH1T2lygqluXHdmNMnMQaoyXG2TJMdsuHIkclmREZZDqt2ecvzO21Ot62XSLlX83GlV0Se+8hp6FWPpkuR5C0tKNrvXVyC3Qlz0VJ38TIvP79V99/1ux+dufvD6r77/rdj87c/eAvS7VuWRc4yPT/AJU6qXktXFtMoyCnrZpSDgqYppCWt90pX3ZvSkGhS0JNRJ32LwKhcdqvLLxxCkLubBSFFsaVSlmRl9zxHVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM6090fttSoMqTVzq5nyZwm3GZTq0uFuW5K2ShRbH1Iuv8AZMZvqV2ep8ArC6rTq4FRDh98uP37qlmaG917bo23UZHsW/r9Qw3RHOvqGziM4+53dbN/m0rc+iSM/RWf+FW3X7hqEs9qHOvIamLjMVzZ6Zs/K2PqTRH6KT/xKLf/AMn3wGs4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuJ7BfZi0o1I7MeL3mT6f0N1bu96lybKhpU4siV05H6/HxMbCe4m0I+KvGvmSRhXsan2omI/4nv1hs5MmR66G/LlvtxYrDanXX3lkhDaElupSlH0IiIjMzPwAQx7ibQj4q8a+ZJD3E2hHxV418ySM+xfWPAc4tCrcczjG7+xNrvyh1dtHkvG3tvz4IWZ8duu+2wQdY8BtL9uih5xjcu7dkOxEVrFtHXJW83v3rRNkvka0bHyTtuWx77AMB9xNoR8VeNfMkh7ibQj4q8a+ZJEkVupuHXOUScar8so52RReXf1EayZcltcffc2UqNadvXuXQderW7TpFomtVn2LpsVOOMlDO5jE8a21GhxJI577pUlSVFtuRkZH1IBg/uJtCPirxr5kkPcTaEfFXjXzJIkGFq5gtnjdhkUPNMelY/XK4zbVi1YXFin06OOkvig+pe+MvEh/NLrBgeSMWr9Rm2OWjNSyqRYOQrZh5MNpJGalvGlZ92kiIzM1bEWwDAPcTaEfFXjXzJI5E7sb6JWkg5E3TSglvmRJN1+LzVsRbEW5nvsRERDsOz52lML7SWJJusXsGEyCU739M9KZVPiNpeW0hbzTa1G2ThI5J38SUWxmM9zTIFYnh17eJYKSqsgPzSZNXEnDbbUvjvse2/HbfYBFXuJtCPirxr5kkPcTaEfFXjXzJI7Hs79orHddcDxywbuqJvKp9c3Pm49Bsm3pEPkW5kpvlzIi3LqoiGbuanYczQWt65llGikqpC4lhZKsmSjQ30qJKmnnOXFtZKUlJpUZGRqItuoCN/cTaEfFXjXzJIe4m0I+KvGvmSRyr7tExoOoGa4vWRKy0+prFnMgelsXkZx0n07mUVyIkzebI0GhfeGXHZZF6y35+luulZkmgeI6kZjYVGIx7mAzLfcmTEx4rK3C3JBOOqIvwbnuYDpvcTaEfFXjXzJIe4m0I+KvGvmSR9ND+0lW6swNS7eW7VVWO4nkkmmYtkT0rjSIzTbaikqdPZBErvN+h7bbdT8Rn8TVjCJ+JSspjZlj8jGIp7SLpq0YVCZPci2W8S+CeqiLqfrL7oCPPcTaEfFXjXzJIK7E2hBJP/8ApXjXzJIk7FtQMXzlye3jeSVGQOQFpblpq57Uk4yj32S4SFHwM9j2I9vAx3yven+AB5stW4Ues1WzSHDYaixI91NaZYZQSENoS+skpSkuhEREREReGwxMZlrT9mPO/l6f+0LGGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL2PY1PtRMR/wAT36wnPWP7EWcfIc79nWIM9jU+1ExH/E9+sNkcloY+U45a0stbjcWxiOw3VsmRLShxBoUaTMjIj2M9tyP8ACr/AErvME1AxPssY3pjUtv6r0N9XT7+dW1DkdyFAQSzmnJkd2klpXun+0ZK28epcp97I+ncKfg2vd9S1MBOevZvkseuulsI8pZd4mhkkOmXJBEbivAyL01fdMbWaXad1ukunmP4bUPypNZSREQo7s1aVvLQktiNZpSlJn+BJfgGUAKsNPUYfc4l2esJwbGn6/XegyyJKyVRVLrE6C02tw5zkx80FybWRp9E1HuWxbdNh8stwLG7Psy6sXUuhrpNwetL8c7B2KhUjujntINHeGXLjxWsuO+3pH90WqAArW7SmP1eG5R2r6mhrolLVu4LSyVwa9lLDJuk/wASXwSRFvtuW+3rGRab2GnmrnaZ0Ud0go4ztdQ4/Yxc5mQqZcOGph2GltmLI5NoJ1Xe8vRMj8d+ux7bsau6X1WtGm19hN3ImRaq5Y8nkPV60IfSnkSt0GtKkke6S8UmMlqa1qmqoVewpamYjKGEKcMjUaUpJJGexF12IBp77GnfYhVaXfUAmI1XanULs8sghqrVsyW0eXOm13jpoIllxcQRFyPbr06DZ3WP7EWcfIc79nWPzVLTtzU7G26hrLMkw1SJCZHtji0xEWUrilRd2a1trLgfLcy28Up69BHuJdlyVimTVlwvWnVa8TCfS+ddbXrDsSTse/B1BRkmpB+siMvwgNJNJH8HzSk7L2PaZUaWtXaC6gT8msK+nciuw65KFnM8rfNtJLS4Sk7bqVy36H6RcuLqnqjQ4F2aO0tpVdqnQ86nZxPnxqs4D584js2O43J7wkcEtGlCjJRqLfpt74t7VwAaQ5FTtl2udTGq+EgpU7RNS1pjtES33jfUgjPYt1KMkpTv49CL1CIae4xiqxHsk5VqRCVa6QV2OzIUpb8FcuDDtSSSEKktElW/vTSndJ7GkzLwMxZ4ACoi3htZJpPmVpiKChaXRtbH51j5LRLkxotd5O0bDztf9bNcdBmkzaMiLqncvAhll/hePu9mvtEZ7imo9dl9XZVUKtlwqPD149AJ9uQytDyUG4aVq4qUkzSkupnue4tLABhukuC47gOCU0HHKSBSRThR+aIMdDXeGTSSJSzSRGpW3rPcxmKven+Afo/Fe9P8ADzca0/Zjzv5en/tCxhozLWn7Med/L0/9oWMNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXsexqfaiYj/ie/WG0Q1d9jU+1ExH/E9+sNogAYRrjnDummjmbZTGP+eVNPKlRS48uT6Wld0nb17r4lt98ZuMY1J0+r9UcRkY5avyo9fIfjPunDWlK19y+2+SDNSVFxUbZJUW25pUoiMjPcg1lx/XbUXAIVleTjybUfG41REbWvI8fTjr7t5Imx4zUWJyjMKW2rvnDM1NKJJpQXM9zEuW+tGXw717GIGEVdllsKuO5sYpZCpuFDhqWtDBHIOLyU+6bTuzZN8S7tW7hFxM5BzrAa/UGLTxrJ6S2xWW0S4bRGUlJOvRnSdaSvkk90c0pUZFsfol1IYtnegdTnOT2F37fX9A9bVrdPcMU0lppuziIU4pDbpraWtBl3zpEtlTa9lmXLw2CL7ftnTF4taZNjeCFb0NVitXlc1+bcFDdQ1MS6pMZDZMOc3yS0RkW5JVy98n0eXd2XadyChs8gpbPT9tvIa+bRRYsCNeJdTKTZyFtNkpw2Uk262TS1LRspOxFssyPcZbP7NmJTanKaxDk+HX5C9WLkR4q20oYZgJYSxGZI2z4smTBEpJ8jPvHNjTuXHFdVOz5NyfUiktKSxt65u0yJm7vLeK/F7yvKHWPMREMIdbURkby0LMlIc6msz2IyIg6bUHtFZw1ieX0dXjlbRakVl9U4+y2dqcuGo7BTRMvtOnGI1GlLhmpC2i4klSvS2IlY52g9ZdUKPLbKHRoua5rGsPReXTWIIrrFiPMedeJkn3JrSHVsJRFdUfcNd4ZHvxLpvLk3sx4/LxhNcm+yGPcneNZI7lCJLK7N6e2ngh1ZraU0ZE2RIJvuuBJIiJJbDjZL2XazKsjyWzmZtl7UXJYkWFc1USVFZjzmWGjaShS0xyfSSiU4aiQ6kjNxfgR7EHTM9p5+s01z++lVUW9fwutrjXKrpSmo9zOkQmZHdtEpszZQapDJEZ8j2c6kRlseO6sauZiim7QMSPJOrVTRK3H6Q4Mklm1YzmyJLyVk024hwjmRfR5LIuKTSZGZkM3tOydjNhIsmWb7IqzHrG3g3cnG4L8dEByTFKOTe+7Bu92pMRlKm+847FuRJPYy7257PeP3VRk0JyxtmHr7Io+UOzmXWu/jzWDjGwbfJs0cEeSM7JWlZdD333ARtrXr5kONYJqVAwjHnbqPh1adZOySXb+Tut2K4yDbQwnu1qfcR3zC1mpTfVexGpXQfGi7W0OFmEHAqiJEyl6oto2KTnnr9J3MiSlSGZElqF3a1ustKNRuPOrbL624ZcttzznKey1j+U293IXkWSV1Td28S9saCDKZTBkzGFMmTiiUypzZZR2yWglkk9t9iVsoslw7RuJgmWW1vUZDeNVtlNkWL2OrdYVATKfUa3nUbtd8XJalL497wJSjMkkA4ekuql7qpKt5ycXjVeJxLOxq4to5aG5ImriylR+9QwTJETSzbc6m5yI07cVEZKEmDG9OMBr9MMKrMZq3pMmFBSskvzFJU86pa1OLWs0pSRqNS1GexF4+AyQAH4r3p/gH6PxXvT/AA83GtP2Y87+Xp/7QsYaMy1p+zHnfy9P/AGhYw0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABex7Gp9qJiP+J79YbRDV32NT7UTEf8T36w2iAAAAAAAAAAAAAAAAAAAAAAAAAfiven+Afo/Fe9P8ADzca0/Zjzv5en/tCxhozLWn7Med/L0/8AaFjDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF7Hsan2omI/4nv1htENXfY1PtRMR/xPfrDaIAABpdclp/nVPrbm+rLsa2sqO7saSrrZkgycq2I5d3FREaJW6H31bOktBc1m6giPYiIBuiA0gZ1H1Px3SjMJNnqJIor3A8apYiKxcGJJcsb1Vah9yM8branHDdW8w3shSVclGZKLqQkWgfsbvX7VHK7TMrWkaxSir612nh+RLZYWuEuXI2Jxha9km8y4lRK3NSDJRrQRISGzQDTnSTK8hiYTg+HP5gWnsWHgLWc3l6xDhm+47LfdWaEpeaUyhDaidU6ZN7ma2yI0b7n07uuWquRaV5llj+UvYhLxbAqq2XBiVUVZybl9mS9wWTzazQhaDiEpstlEay4qRsfIN3hw4d3XWFhPgxZ8WTOr1IRMjMvJW5GUtJLQTiSPdBqSZKIj23IyMug1VybWXObh1djFy9OLPpzmBhFZQRIsVxuc6T7CJ70hTza3DLiqSpCWlN7JbQZmo1dJQ7M6iumNSMrMycVf5pZmhz1mzDUmubL8G0Lci/8AEAmYBqJLybJoWe59Mx2+VEtMi1IqsQhTn4MZ1TcWPDRIlI4k2klpQSpiUmrdRcOqum4+lJqVn1pmkfCP5QpDbX1a29d9UD1fBKW7WQ6xtxxJp7kmeaJbxJJZNl0R6RKLcjDbcBqno9rjleRZ1jNPaZSxPx9j6qJ0i9ejx2E29bCkx40WQpSUkhJc3nDNbXBKiaJW3Ex0GD5Jkmu2aaLSbDOLOpcdgXuYkmCzCbSqOqYmNXtkhyOslfzaUpBmZGeyeW5LMlANywGuGNajZGrU7UKnyvOjoUs1020pnWmYDlKxWG6TUeb33HvSeaPo62+skKUZmn0S6T5i0eZExmpYsLZN9PbiNIkWqWUMlMcJBEp4kI9FBLPdXFPQt9i6AOzH4r3p/gH6PxXvT/AA83GtP2Y87+Xp/wC0LGGjMtafsx538vT/ANoWMNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXsexqfaiYj/ie/WG0Q1d9jU+1ExH/ABPfrDaIAGNytM8PnZY1lEnFKSRkzWxN3TtcyqYjYti2eNPMti8OoyQAEZYboBjGOZbfZXZ1dRf5TZXL9qzdSatopcNC0oQ2w26rksiQhtJbkZbnuexb7DK5mm+JWF7OupWL0sm5nxFV8uxer2VyJEZRbKYccNPJbZkREaDMyMvUMiABjV3plh+TKqlXGKUdqqpIirznVzLxw9ttu55JPu9ti97t4F9wcybhWPWTdq3LoayUi2cbdsEvQ21lMW2SUtqeI0/XDSTaCI1b7EhO3gQ7kAGKytKMInZIeQycOoJF+brb52rtWwqV3jZpNtfemnlySaUmR77lxLbwId3UY9V4/wCW+1dbDrfLZK5kryRhDXlD6iIlur4kXJZ8S3Ue5nsXXoOeADpmsLx6PIZkNUVY2+xNdsmnUQ2yU3LcSpDshJ7bk6tK1pUsvSMlqIzPcxgeR9nHD8t1CgX9xR0dlTQ4M5lNBLp2XY65kuSw+9NVy3SbqjjpIzNHIzUozV1EqgAxq80yw/J2qpq5xOjtm6ktq9E6uZeKGWxF9ZJST7volJejt4F9wHNMcOdXj614nRrVjqUoplKrWTOsSkiJJRvR+skRJSRcNtuJfcGSgAxSt0kwamr7iBAwzHoMG5Iys4saqYbanEe+5PJJBE5vufvt/ExlDDDcVltlltLTLaSQhtCSSlKSLYiIi8CIh/YAA/Fe9P8AAP0fiven+AB5uNafsx538vT/ANoWMNGZa0/Zjzv5en/tCxhoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9j2NT7UTEf8T36w2iGrvsan2omI/wCJ79YbRAAAAAAgjO9JtVb3tPYbmlHqH7U6b1kPubTF+8dLypzdzc+7Iu7XyJTZclGRp4dNxO4AA007VEiPU3+qdzneK22QwIOLt/UQ4mrem1kaUbT3euKUhKm48jvzZLvHeKiSlHA/Ejx6fg13iuAap6bx8TyR+3yF+hxWE9Bp5DkIqhEKDEXIOUlHdcUmqYai5ck9TMiTuog3sHHsbGNUV8qfNeRGhxWlPvPOHsltCSNSlGf3CIjMabw9HbrPtf8AIkZkmXDeZyRE2osCxSU65FrYxtuxmoVt3xxorayb4uNpbJ1SluEe/IjLHb3AZ9rhuu3tPgNtfMX1Y6tu8ucZeiXxuS5ZqkQuLqeUxMdBJcaWhOyOCEJNRkRgN0P5QaE6/GZyZji4uSLabq3ERXVE+bjKnkb7J3bI20KPdfEi22PYzIhkQ1dyfT6M/lumMzTHA0UTGP4/kVvWLXQnXIizHGGo8dhaVtoNpbqn1rNCyJSu65GR8dyia30+tpmApn4Rg+SQMjZwexrsntLOpkx593azWWmENuE4knJZodU6+bpEptBIIkq2PYg37HVWeT1tRcVNVIf2srVTiYkZCTUtZNp5OLMiL0UJI07qPYiNaE77qSRwvpLpHD077Q2UKx+hepcdjYpVwnJZMrQ3bTTflKceUs+jzqG0NEpZmai73Yz8BwbrKbbH9b9Ysmbop2R2GOYxUQMfqILKnHJCpK5LiySSSMyS48llK1+CUsbn0QewTBR6n49keoeT4VXzPKL7G48ORZNJIuLJSScNpG+/VXFrkZbdCWjr16ZWNGqHANTtEsgurS3xNu5uLvA7xdhZ4w9JtDsrlDyZLZvJ8la7lSzfdbaaI1+ikkkfodf4zXsyy6GqsKfFcakR51ZpWSXpzDC9re3akx3oyHHT6OvIVBVsRmakk8nwSZAN1cmyWuw+kkW9tI8kro5o76QaTNLZKWSeStvBJGojM/AiIzPoRjsle9P8A0jySTM1h0F1N1DjRXiuNWFx8TxOFJTs63WG55Ox6J9U8jcly1/cQZGfvRu0lHdsknkauKduR+JgPN1rT9mPO/l6f+0LGGjMtafsx538vT/2hYw0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABex7Gp9qJiP+J79YbRDV32NT7UTEf8T36w2iAAAAAAAB02YYfUZ7j0ijvonl9VIU2t2P3i2yWbbiXEbmgyPbkhJ7b7Htse5GZDuQAAAAAAAAAcVFTDbtXbNMZtNg6wiM5JJOy1tIUpSEKP1kk1rMt/DmrbxMcoAAdBnOC0upGNyaDIIzsypkmk3o7Mp6P3hEe/FSmlpUaT9ad9jLoZGXQd+ADqYuI0sEqYo9ZFYRTMnHrW22ySiGg0EjZtJdE+gRJIyLckmZF0MyPtVe9P8AAP0fiven+AB5uNafsx538vT/ANoWMNGZa0/Zjzv5en/tCxhoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7b2O7UTGMc7KOJQ7S/rq+URuqNmRIShREa+h7Gf3jGyn8sWDfCyo+eI/ePNqAD0lfyxYN8LKj54j94fyxYN8LKj54j9482oAPSV/LFg3wsqPniP3h/LFg3wsqPniP3jzagA9JX8sWDfCyo+eI/eH8sWDfCyo+eI/ePNqAD0lfyxYN8LKj54j94/per2Etq4ryqpSf3FS0F//sebMSHrz9kFz8Si/wCikB6B/wCWLBvhZUfPEfvD+WLBvhZUfPEfvHm1AB6Sv5YsG+FlR88R+8P5YsG+FlR88R+8ebUAHpK/liwb4WVHzxH7w/liwb4WVHzxH7x5tQAekr+WLBvhZUfPEfvH4rWLB+J//qyo8P72j9482wAMw1lcS7q/nK0KJaFXs40qSe5GXlC+pDDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIevP2QXPxKL/AKKRHgkPXn7ILn4lF/0UgI8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXl9m7XbQjD+z7gNNkqoKLumxmoVcrPG5MlEM3obTyHJD6I6m0JUhaVc1KJPiRmRkZFRoLauyPqe5hen+olNGwTKc2sLjGcYjxI1FUrlxnHVY7GQTb7peiykzUW6nNi477b7bANzsy1S0NwO+h0lqzUrt5tai3iQq3H3bB2VEWpSUutJjsOd4W6FGfHcyIuRkSeo+N9q/oHjGbrxOzdoI101IZhvl7SqXGivu7d2y/JSybLLiuSdkOLSrqXTqI/7OmjmR6Y60YLEu66Q+dHo9X0L9uTKlxUy0TTU5HQ/txMyIknxI9+JJPbYYBmONZZT6R61aJt6d5Hd5RmmS2kqqu2K5TlTIYnyCdblvzf6NpTKVbKSsyURsp4kZGQCdcu1y7PmCXtxUXjlNCm0spuHabY8841XuOIbW2ch1DBoaQpLqNnFqJBnyIlbpURcuk1c0HyGDk0uEmnNnHKtV3Yk/j7rC0wEpUo5TSHGEqfa2QrZbRLSfQiMzMt4Yz3TbKl6SdsOtbobeyn3LrSavhAdU5a8aiG2a2Eknd3dxC0+hv6RGXiRjsu09jVsjKM3uvaqamna0GyOA9Y+TrKOiQa2FoZU5txJzilxRIM99iUe2xGAlXCtW9CdQrdFXRs1b9g9CVZRo8nHHoqpsZJEpTkbvmEeUERGRn3XPxHV9mfVXT3tIY1Mnw8GjVM+LLltOxZOPvNsk03KdZaUl92O224pSWyUpCDNTZqNKiIyMR3g1pb645f2dUVGE5RRVuCxjtLbIL6sXBYMlVqoyI0Za/wCn7xTpGo0bp4oI9z8BIPY9lWmHUNzprfYtkFRb09zczfbKVWuJrJjD9k8+ytiVt3bhqRISfEj5FxVuRbAJr/k6xT4MU36Pa+iOmomtN83nW0eqjY3cTqmSqDYMMMMOPRHkGaTbdTtyQfToRkW5dS3IffLMJv7/AC+gtq3O7bHayvWlUukhxYrkeyIlko0urdaU4kjIuP1tST2P7vUYBY6A22d6zwM8yeyrqUqGaa6hjFo/czpbCVegmfOUXeONKLY1RkEhHqNThAMv1CRpppVhtnleU1FLVUFahLkuYdUl0mkqWlBHxbbUo/SUkuhH4jFMX1P0PzF69YrY9UUukgqtJ0SdjzsOQiIkj3kIaeYQt1rp79slJMzIt9zLfq+324bXZF1DWSFOGliKrgj3yv54x0L74jvOXL/tAapu5NS4JlWO02LYPf1z0q/qXIMi0lzW2ktRWGV+m6SO5UrkRceSiIjMz6hJNRrXoBeYhZ5XFVTfUzXRmJT9w/j7rEVSHjNLaW3VsJS64aiNBtNmpZL9E0kroPpC1l0Bm4VeZZ3lFFpKKTHi2rk+jXFegOPuIbZ79h1lLraVqcTstSCTturfZJmUe5HpxkMfsmdnd2HjE+fMwSRjN5a4yxH4zXW48YkSG0sq2NTzanO87s9jNTW3vthgGtONZXrW/qtnlJg2TVdNNr8WooVdZVLsewtXY12iS/J8kNPepQ025x5LSW5JUfgkzATQ72kuzkwuwacOE3Kr0E9KhLxGYUllky38oUycXvCY2699x7sty9LqQ73K9XNCcMsKuDYM1kiXaVSbyC1VY49YnIgqVxKQjyZhzdHUj39RdfDqODOxW2e7VWotqdPNXUTNOYEBmd5Ks2H30y55qZSvbipZJWgzQR7kSk9OpDXnRfLpuh2faPlkGIZbPsY+i0aFJqqekelzozqZrfoux0lzRsaeJmZbEZkR7AJp1M7QmkeCwtLrSsxqsyigzmxXGZs6ikdlpZYQy4tbiUMR3FLcJaEo7novqs9tm17Ugdph6JJ7R+qr0Bo2ILmV2q47RsKYNDZzHTSXdqIjRsW3omRGXgZFsLdK7T3M8F0y01zSfhdyo4uqNjmc/FqqMcufVV84pyW0Ew3ua1N+UNKWhG5lyV09ExUT2lbZN92jNU7NEaVCRNyq1kpjT2FMSGiXLdVwcbV1Qst9jSfUjIyPwARwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcfRL2TXMNBMfKuxzA8TVIdhwYk2wlHNW7MKJGRGYWtPlHBKiabSR8EpI9tzLfqNOAAWH+ey1a+BOF/mpf8cPPZatfAnC/wA1L/jivAAFh/nstWvgThf5qX/HHV5V7MXqRmuMXGPXWAYXNp7eG9AmxuM5vvWHUGhxHJMglJ3Soy3SZGW/QyMaCAAsDp/Zm9T6Cog1cDBMLYgwmERo7XCarg2hJJSnc5BmexERbmZmOZ57LVr4E4X+al/xxXgAD0y6IZvd6paNYNmU44EObkFJDtHo8eMvu2lvMocUlO7hnsRq2LcZupqeZ+jJjEX346j/AP8AsagdknVzK49FpFhFidTjONqwupeql2NbIdevkJq23ZC48tLqWWlMuHxUwtClmhKlkZEZbcvTbVbVmq0NwPIpVzRZPkOpWTxyqGp1bJjtw4kpyRJXyPypZqSiI3u0lJI4EkiV3p7qMNPO1b7Jzmbecak6T3GCYjfYvX3UqpUmR5cy5IbjyTJClKakpMj3bSZ8TL/7DqfPZatfAnC/zUv+ONQ+1W3NZ7Teq6LKQxKsE5TZlIfisKYacc8pc5KQ2pazQkz3MkmtRkXTkfiIsAWH+ey1a+BOF/mpf8cPPZatfAnC/wA1L/jivAAFh/nstWvgThf5qX/HGPn7Lrnp58nNT07wv6pk1h05Tv5//wC6G6Tpt8PKeHvyI+XHl6t9ug0QABYf57LVr4E4X+al/wAcaJaj5vL1M1DyjL7BhiNPyC0lW0hiMRk024+6p1SUcjM+JGsyLczPb1jHQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcF2Y+1N2R9N9M9PpN7mDrebV+ORoM1qexczmYb6ozbclLLSkLYaNXE0qUykiURbbmRiSqztOdjjR1eO0zGYPwfqblKtKiLKReTUwXHYqo+7feJWSUdw4pKW/eI5GaUpM9xRmJD15+yC5+JRf9FID++0hllVnnaD1KySil+X0tvkdhOhSibU33zDkha0L4rIlJ3SZHsoiMvWQjkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9I0d2ZIaYYbU686skIQktzUoz2Ii/7iQi7OOqBl/UO9+ZL/cAjkBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cAjgBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cAjgBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cA5uiWnGNaknYQ7SXYRrOPs62mK62lC2j2Iz2UhR7krx6/2i++JN1u0rxpuotMpnzp7UxmKllhpDjZNrcSkkNEZGjc9z232Pw38BgmCaOar4RldfcM4HfmTDhd62mGr642fRafD1lvt9/Y/UJF7Q+AajZjLgVVRht3Jq46SkOOtw18XHVF0Lw/spP8A+6jL1ANVAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cAjgBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cAjgBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4Pc4ao/AO9+ZL/cAjgBI/ucNUfgHe/Ml/uD3OGqPwDvfmS/3AI4ASP7nDVH4B3vzJf7g9zhqj8A735kv9wCOAEj+5w1R+Ad78yX+4YnlmE32CWDcHIaiXTTHGyeQxMaNtakGZkSiI/VuRl/2AdIAAA7nCv65UP4/H/1Ej0p0X/BK78Xb/VIeazCv65UP4/H/ANRI9KdF/wAErvxdv9UgHOAAAAAAAAAAAAAAAAAAAAAAAAAAAAGE65Xc7GtFNQLeskrh2Vfj1hLiyEbcmnW4zikLLfpuSiI/+wDNgGnGCarZfYZl2OYUnIJr0XKsSnz7tta9/bB9FYw4hx0/FRktalfhPcd+j2RbAVV0CzPDs/TTz7ByojWaaNK4709KlJKKhSHTNbqzR6JJIyPfYzIyURBtQAjXRnXyi1rcyOJX1l1j93jslEW1pMhhlGmRVOI5tqUlKlJ4rTuaTJR7kQkoAAAAAAAAAAAAAAAAAAAAAAAAAAAAUuey3/bUI+RIv+axdGKXPZb/ALahHyJF/wA1gNKAAAHc4V/XKh/H4/8AqJHpTov+CV34u3+qQ81mFf1yofx+P/qJHpTov+CV34u3+qQDnAAAAAAAAAAAAAAAAAAAAAAAAAAAMR1hxmbmukmb49WpQqxtqOdAjJcVxSbrsdaEbn6i5KLqMuHwhT41iyp6JIalNJcW0bjKyWkloWaFp3L1pUlSTLxI0mR9SAajYTolqHU5B2S7aZjJMpwWin0uRseXx1LhKchNR23CMlmTqTU3uZINRkRjFqDs1akQuzvpPjD2OcLyj1TYyOwi+XRj7ivTNkOm9yJzir0HEHwSZr67cdyMhvSACC9IdNMkxftP6/5bZ13k2PZSqgOnmd+2vyryaCpp/wBBKjWjisyL0yTv4luXUToAAAAAAAAAAAAAAAAAAAAAAAAAAAAAClz2W/7ahHyJF/zWLoxS57Lf9tQj5Ei/5rAaUAAAO5wr+uVD+Px/9RI9KdF/wSu/F2/1SHmswr+uVD+Px/8AUSPSnRf8Ervxdv8AVIBzgAAAAAAAAAAAAAAAAAAARP2q8ytsC0Byy5o5h1lkhEeKixItzgofktMOSf8A+0h1bm59C4bmIC1FqsI0MzvFZ2mBtR7mkpLm8yexhyjkOv1rVc73bk9ZmferclHHUhTm5maFGXQjG58yHHsYj0WWw3KivoNt1h5BLQ4gy2NKkn0MjLoZGI3y7s/4xaaXZFhGL1dRhEG8SlqWqoqmmkLRzSbhKbb4Eo1IJaNzPpz367bGEC5JrnqHo3DamycoTqLObwGTkFzVyIcWOzVzU+TJjLJbCEKS06t1/dLilGaWVqSaSLpyXs61qpMWlzJN5NivWsujpq1y9i1DjrdjJntIfWy1BU4gopR1qPZ5xbh+JKL3x7OY7ptiOH1k6tocWpaSunmo5cSurmY7UjcjI+8QhJEvcjMj3I/EfxSaX4ZjVbHrqjEqKqr48wrFmJCrWWWmpRFsT6UJSRE5t05kXLb1gNbsj12y/TiDqpQFfzc0vmL2JjmLSFVKHZZS34DUmQpTENkjebjIdN4yS2auKDSZqMyMR/hL0U9C3NI4ki1kxj1ViY+hV5FfjTnYTj7Ns6t1p9CHEqW0Ujfkkt+p+BkY3cj4NjcS6K4Yx+qZtydekeXtwm0v946lCHV94SeXJaWm0qPfdRNoI9ySWxWC42q8VdHj1UdwqSmYdgcJryg30smwl3vOPLmTJm2St9yQZp326ANN8pvMRyLTnVXU7PaOq1FySiyCxrmsVvbQ46KyLGkqYYjx0cHCbecQlLpKJHJxTpekRbbZfkuqGrmcaoZhjuEtSsfbxqZCqoqWTq1QVSFsMyHlzzkLOSbXF4koTGaI1EgzJe+5I2LnaUYTZ5QjJZmHUEvI0GlSbh+rYXLSafemTxp59Ni26+ocqTp9i03LI+USMap38mjI7ti6dgNKmNJ2MuKXjTzSWxmWxH6zAa4o1O1AmXFNkUfMHVVFpqa/i0ChKui+TyK5l59p81Od33prSUWQtKkrSWySJRK8R1+jGtWqWobdRqLZql1WFuMT7a0rpXtX7XtV6GnTZbim0tcxUhKyZ7xbxtoLZwuBHxIbSR8Kx6G3WtsUNYw3WSHJcFDcNtJRH3OZOOtESfQWrvXeSk7GfeL3P0j34MHSzC6x67eh4hQxHbxC27VxisZQqwQrfkl8yT9dI+R7kvffc/ugIAxjMNTWsJ0hhW2cPO5fqO007KnPV0NuPSstw3Jj5x20tFyfUng19dNaNyNZIIiNJ4fFyXINZIullU9qXaqg2mc3cmDexmq5D0qtq+/Sw4ovJu5WryhppRbNkk0rMzSrZJlt1kOB4zl1PHqb3Ham6qo6krZg2MFp9hpSS2SaULSaSMiMyLYuhGOA/pJg0qrr6x7DMeera+UqdDhuVTCmY0hSjWp5tBo2Q4alKUaiIjMzM99zAa+ZjrBmrsXLcgp8sVXuY/l0bEajFShxXTunidjtunJUps3ebveuKSTJtEhCCWfIt9pK0Etsr1AVfZfb5XKkUTl/bQqelZiRm45QmJS4zS1rJrvVr3ZWoj5kWyy3JXQxIidPMVRlqsqTjNOWTqR3arsoDXlpp248Te489tum2/gO0p6Wvx6uagVUGNWwGuRtxYbKWmkclGpWyUkRFuZmZ/fMzAcwAAAAAAAAAAAAAAAAAAAAUuey3/bUI+RIv+axdGKXPZb/ALahHyJF/wA1gNKAAAHc4V/XKh/H4/8AqJHpTov+CV34u3+qQ81mFf1yofx+P/qJHpTov+CV34u3+qQDnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApc9lv8AtqEfIkX/ADWLoxS57Lf9tQj5Ei/5rAaUAAAO5wr+uVD+Px/9RI9KdF/wSu/F2/1SHmswr+uVD+Px/wDUSPSnRf8ABK78Xb/VIBzgAAAAAAAAAAAAAAAAAAABHeedoPAdNbh2qv71TNgwwUqSxDgyZpw2T32dkdw2smEHseynOJHsfXoJEGp1Nb5Npy1rHj7eAZFeZ7k2RWMutmt1y11s2O+kkQluzj+tNtstE2hSFqJSe7VxSe/UNqa6xi3FfFnwZDUyFKaS+xIYWS23W1ESkrSouhkZGRkZeJGOEWVVSsqXjZTEKvEQk2K4ZJM1Jjms20uGe2xEakqItz3PirbwMaEI0rrnqfUXAWMTtM0zOqqqnCsbuWqx9UOFKjVjRHNKZx7qMbT0hSz9NLnFsiSR7kQmWtw2uq9cdXbzItPn8iySNRw009w/jSpLdgyxWml/u5JNqT3zrjzjKmuXNaUJLZSSLYNksXyitzKlZtql5x+A6txtDjrDjKjU24ptfoOJSotlIUXUuu25bkZGO1GlNPp0eGUunGOZzgNzmeJVWn0VuDRRKd2bGcv1KUcon20JNDLnHuibcfJKEc3dlJPcY7ZaA5JJ0n1Hay7F5uV5dTYNTYxSLcjuyeVgTDzi5EY9j71TLs1CSfTuae4X6RekQDfgYzi+oFdl2SZbTQWZRPYzNar5kh1KSZceXGakcWzJRmfFD7ZK3ItjPbr4jUXMMJn5FlLlfkmGZFkeYPZ/WRI2QPUsiQxU0EeRHUl6PK4G22TqG1d6baufJ9w3Nkp6bCdm2rnwKPN5VvXTKy4scxuJkhmZHW1ug5BojKQpRETqDjIj7LQZp8S33SZEEjwMorbPILWkjvOLsqtDDktpTDiUoS8Sjb2WaSSvckK34me23Xbch2o1AtdKp2TZLkbTmKzokLLNWIr0zuYTjCPa2BCQs5C1JItkOvxVl3hmRLN8tjPl1wpzF6LFdR63HbvCbROHO5tkN/GxitoZMpsocaDHr0KTEZbUfk7r8hTm5J7tXMjP0TMBvoOqtsqqqK1pa2dMQxPuX1xoDBpM1PuIaW6si2I9tm21qMz2Lp49SGoenWNXel2a4JbXGHZFExWP9VFpT0tXWPT11apUiMmFCcQyS0xz8nJ9RcjS2g3FI5FsOLpppg3IyLRCRqHpzLsG36e5tn1zMeXNKLcWNgzJJuWfdq8nNpK3jJTvEiPfqRp2AbtgNRcIpolXnurWQScAyDJ8esK2dNkWU3G5ES/W4+7s5UsG5wVLZ4J3aNvYmyJKCUe+42jw+or8fxOlq6mE5W1UKEzHiQnuXNhlCCShtXIzVulJER7mZ9OpmA7cAAAAAAAAAAAAAAAAAAAAUuey3/bUI+RIv+axdGKXPZb/ALahHyJF/wA1gNKAAAHc4V/XKh/H4/8AqJHpTov+CV34u3+qQ81mFf1yofx+P/qJHpTov+CV34u3+qQDnAAAAAAAAAAAAAAAAAAAAAAAOnxvEanEU2aamJ5KVlOes5Zm4tw3ZDpkbi91Ge2+xdC2IiIiIiIdwAAAAAAAAADp14jUuZezlConK9ZgLrG5ZuL9GOtxDi0Ejfj1W2gzPbf0SLfYdwAAAAAAAAAAAAAAAAAAAAAAAAAAAAApc9lv+2oR8iRf81i6MUuey3/bUI+RIv8AmsBpQAAA7nCv65UP4/H/ANRI9KdF/wAErvxdv9Uh5rMK/rlQ/j8f/USPSnRf8Ervxdv9UgHOAAAB0GOag4vmFpcVtFkVVc2FO95PZRIE1t92G51Lg6lJmaD3SotlbdUqLxIx34ijSHsxYHodmOY5NisGTGtMqkeUT1PyVOoSfNSzQ0k/ep5LUe3X1ddiIgEriOa/tC4DbZhHxmJdOv2cl6Sww4muleSPLjpUqQSJRt9wvu+CiXxWfE9iPYzIhIch9EVhx51XBptJrUr7hEW5mNGcBwSs1RzTLMNoMnZynDnMZuo2MW9RLNxjEym8G1sPt90k1SVm86pJreUskNKTwQW6jDZSP2pNMZMSymJyRSYMCvetlzHa6W3HfhtKSl1+O6pokSW0mtBGpk1kXJP3SHYV3aF0/soOSTCyAoUbHGmn7NdnDkQu5ad5dy4RPNoNxDhoUSFo5JWZbJMzEP13ZWuH9LplFMraavv5Ca6pOf8AVJZ26Cqm5TDktpo5aDOOlbbRklhsuG5I5LMiIy7PV/sy3+pV3nlsidXk9ZWFBJq4y5kmOTkeuNTimX3mSJxg1OvPmlbRqNOzavEtiCQfdNacpqnJ713Khtt2EeqVGm1E2PLKS+k1MN+TOMk79cSRmlXDZW3QzH0ybtJaf4cw27cWk+GZxEz32faSct6FHUo0pdlNpYNcVJmlREb5I34n9wxidD2fZcaz08sHoFbXOVd5Iv75HtzMtn5cgob8WJtLlI71/gl4lbucCTxIklsOh1A0C1Au7LPoNQ9jb9HmOSVlzNsLKXIRNKFHTEQ5AJtLCk8TKMvivn4OqSaCM+RBIsLtA00vU/M8OOqu0/UvDZkybFqnmvMrcU0t5bSTQwaeSW0tqSXLdw3SJBKMjHX0naixFzT7F8lv3nKp2+rDuGq+uiy7JxuHvuUlZNMc0M8TSZuOIQkt9jPoOBE0vz6rc1uZiuUDhZiuTOprVcx9MhqQqCxFYZkNdyZJbb7kj5oWszI/eEe4x5HZ8zbD5FtDw+ZQe191iNXirk+zdeTIp0w2nmu8jspbUl9JpfNRIUtrZZbmZkewCQsn7TWmuIPPN2WRmRMQo1m87EgSpTTMR/l3MlbjTSkoaVwP64oySXTcy3LeUEqJSSMjIyPqRl6xrVY9lq2b081NxWqm17UbIo9PRVhyHnN2aeJGjx3EOqJv+kMvLDIkkaT5o3UW58ZaTrLj6ZxQirsrJwnO5Iyw63Joj32/pPJeHH/xb8duu+wDEMX7SlM5Y5oxkk2NDTVWtq3CZgxXnnva6vaR5TJeSjmZbPE+klbJJR8EJI1+Oa2OtOEVUa3flZFFabqadq/mlspSmYLvPu3uJEZny7teySI1H0Lb0k7xBoj2YLvSu9upc2XV2cTNY81WXMm64tZTHJL7rTkVam91NmiSttbauBEaUrTuZrJXRYB2GzxmXpxOt8jO2sKxjjljpkZ+3pteTKgsnuX9DHXEZ4ke26UHuXpqATa92gsEYyqNjqreSqzfmNVxG3Vy1x25biSUiM7IJo2mnjIyPulrSsvWRD4Qe0fp7Y01ncMXb6qeuccZfsVVktEY3USPJzZbdU0SXXe92QTaDUtW6TSRkZGcXYzoHqPRWWCVrruLycZxjJLXIn5JzJBzLZ+Siacd1xHccW1NuS0mpPNZK25EouJJP75fgdVpV2UMSxHJcuqMWyGqbhqg5FNcNMFq9a/nCX1KUkiNBvoWrZwiJRHsZbnsAz9vtR6cOwHJSLaxNaJ7lYqD7Q2HlvlLbSXXW/Je4770G1oUo+GySUXIy3IZ1k2aVOIYZY5VayTi0tfCXPffWgyUlpKOZ+iZb8tvBO2+/Tbcak4fpJl+qektdLpINfU5DIuLCwczywuZSbJM03SZ9s4hNRWkvMPMtJ2YWTLakE2kyNJEZzz2o8Zn5ZopZV0JDkkyn1cmU02nkpyIzYR3ZJbF4/WW3D2Lx229YDiWWuOQnbUuLUeEJt88m1ZXU+pk2ZRItREWs0t+Uye7WfeKURpJCG1bm24fRKeR8bGe1lic6uS1kUewx7J2pNhDlY/FhSLR5DsJ1Dck2zjNL7xCe9aUSiIjNK99i2Vt/eR6d6g43q/kWa4CrGp7eS1kKDPi5G/IYOI7FU/3TrRstr7xJpkK5Nnw6pIyWW5iJsC05zbA9a72tw12iyK9qsdSu5vchkPRUqsrWdIlSX22WmnOZF5Mx9aNSPR7sufQzATNZ9pnF2cm08rKlmxyOJmcd6dFsqmulSmmorZJLvVd0yvp3jjSFEfHu+fJZpLbf713aLxxxWZSLM3KysoL8sbZdOPJckz5nctuLaai9wTq1kbhkkmicJaU80mZeGL4BoDkGk2c4E/SSau7x6lxhePTXbJ9yPMQ45JTIflMoQ2tCzdUhO7alI48S2UfgOtjaBZrj1vQ5XXOUFrkkDKb+9kVs6Y/HiPNzzcbZNL6WFqS60x3KerRke7iSPbZRhIjnaQ09bqaqwK7kOotJMmHEis1cxyY5Ij/ANOwcZLRvJdRse7akEr7wSO0jp4xjdLeovXp0G4Zekwm66sly5TrTSuLzhxmmlPIS2r0VqUgiQfRWxjENPOz7e4pqFR5PaWVfZOsNXllP7jm2TltYvxlF3aTSezLTDCmiUauR7kZl1MRzU9lrVHDMHua2gscWl31/hbWPSrOfLktlWSzdmPSXI6UsKN1t1yYZ7qNs0qbSo0q96Ala27QyCyS1fqFQp2HVuAHmbs5TbhOr71azi7GZkSUKajyFGRp5e96lsZH0NFrfqbGq9PaqVjWP5fnOV07+QvRIEh2ljVsRtuLu2o3PKlOOd5KJBK3QlW3gnYxH/aFwSZpJpDq3YT51VBo8opaDFozyZKkLgtkpMJxtXJBJ7pKZDzhL5F0NW6S23OS8hwzUCx1cjZ5p05hkvHZWJMU1bMs50hRxkqfU+t9tlpk0vIWnyfYu+Rv3fj1AdpiXaerMyn4JAi0syDZ5FMsoMyFYEtCqxyATqZSVOIbWytSXWyTx7xJmlZKLfwHOuu1Fg0DT/LcvrpFjd1WOwnJrkiHUzPJ5ZJM0kUeQbXdPEatiNbalJSR8lGSSMxhUXsmzIMavqW8hJ6DHxW/rXbd0jKa9b2zzTkicbZFxIi4uGRc9y5kkuhbjsLrSrUjK+zpLwCfGxGrs4setiQCgTZLkOUzGeaU6h7kwlTKXUNG3xSlziSz6qAc651oyjJMm0to8QjRaKXlEWfY2acopZhvQmIhMpcQTC1xnCUbryUJWstjL0iSZGW/ZZT2qMJo8Ay/Kq1VnkEbHYynlJhVMsmpi+ZtIQw+bPdukp0uBrbNSU9TUZERmMPpMrO37Ys2DaSqaDl9TgDEdioRPNaFyZUpx58mTUhDjjaExY/JZNkZEojNJbkQ6cuzdqArBMvrYzmOUrVnaVFlCw+NZy5FOyqLMTJlkl5bJLYTKNJEbbbRoRx3IlGtRgJhldoHEapnHW7RdtBtr6I/Mh0/tDYLnLQwpCX/AOblH74uJuJ98hJqTuoiNJGZSQIrTh1/H1NlaiXCIDa4uHN1bEKvN6atqWby35fFJNoU42fCMlHEiWvge6UnsR93Bl6g2ejMeQ5Dpq3UuTTpUqK84sq+NYKb6koyJxXBCz6kXLfbbc99wGL432hIuUdorIdN4cPlX09UuSq22Pi9MacZKSwg/BXdIkxuW3UlLNP9kwi9rrSiZW+2DeTupr1Vp27Ut2pmtsvxCU2lbjK1MkTvBTrZLJBqNHL0iTse0asdkTLcFexaTiWdyLuTV1V7DkFkhsMpORPY5m8hUaITijVMQ04s3lrMkkZpMzLZX9ar6GVuP6fxU5VfVVLg2PaaS8LalvqcNTEuX5KwTxIJHUto7SU7HyNStuPgAnfJ89iIn3uM1F1Cg5dAqSt1eXQXpMeIwpa0IdeJCmyMjNtzZHepUZIMy6FuIawjXXUbIonZ6mTE420WobDki0gN1shLjLSYjsvvmHDkmSC4EwjitK/ScM+XgQjx7I7DTvsh57nOeTItTqhqXXPJiQJLvcvKWcbyaBFaQvZSlJb4umjYlEp1zci6jM4GQY3A7QGN1FZYM3VdpRgsuPJj1KikuszXno0duOaEbn3xtxXCJv326y6dQE96ZZ+zqNj8uehgosiDaT6eWwS+ZIfiSnI6zI9i3So2+RfeUW/XcVEey3/bUI+RIv8AmsWr9nPT6y040rgwr3gWR2MqXdW5IUSkomS5C5DqCMuhkg3OG5ePDf1iqj2W/wC2oR8iRf8ANYDSgAAB3OFf1yofx+P/AKiR6U6L/gld+Lt/qkPNZhX9cqH8fj/6iR6U6L/gld+Lt/qkA5wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKXPZb/ALahHyJF/wA1i6MUuey3/bUI+RIv+awGlAAADscbntVWRVc1/l3MaU08viW58UrIz2/7ELb4HsvWlkKDGjnQ3qzZbS3y4JLfYiLcU/gAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lIeeD0s+D97+SkU8AAuH88HpZ8H738lI7i79lZ05x6vqZc3H7lsrNg5LDRcTcJvfZKlF6iV4l90VBafYseZZhW1RmaWHXOUhe+3BpJclnv6vRI/++w+mpGVFmGYTp7REiElRMRGyLYkMI9FBEXq6Fvt90zAWw+eD0s+D97+SkPPB6WfB+9/JSKeAAXD+eD0s+D97+SkPPB6WfB+9/JSKeAAXD+eD0s+D97+SkPPB6WfB+9/JSKeAAXJVPsuWl9xaRIDVHctOyXUsoW9xSglKPYtz9Rbn4j+rr2WvTbH7aXWzcbvWpcV1TTieKTLcj9R+svWRimsjNJkZHsZeBkJI1NIssxrH81bIjkSke11kZf3lovRUf31o2P8AARALP/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSHng9LPg/e/kpFPAALh/PB6WfB+9/JSK/wDtzdoGg7SmsrOXY7HlxYXtazFWzMSRLStBq38PEtjI/wDuNeAAAAAAAAAAAAAAAAAAAAAAAAciugP2thGhRUd7JkupZaRuRclqMiSW59C6mXiA44Da3zWnab+LdH6erf8AcB5rTtN/Fuj9PVv+4AapANrfNadpv4t0fp6t/wBwHmtO038W6P09W/7gBDGHK+pPTHJMhL0ZtksqWGr1pSoubyi/8pEW/qMRyN4Mz9jW7RkrE8So6vT5LzMCKt+Uft3Xp/nLqt1pPeQW/EiIty3Lr0MR1k3sa/aLw/G7a+t9P0xaqqiOzpj/ALd16+7ZaQa3FcUyDUeyUmexEZnt0IBrIAAAAAAAAAAJG0rV9UdNkuHuemc+KcyEXrKUyXIiL/Encj+8QjkbcaT+x59o7y/FsxqtPykVUgmJ7DxXdeg3YziSVvxVIJRcm1eBluW/UgGo4DcXMfYtu0UvKrVdRp6h+sckrXHWV3XI9Az3ItjkEZbb7dS9Q6fzWnab+LdH6erf9wA1SAbW+a07Tfxbo/T1b/uBh2rXYS1v0NweZl+bYWmmx6Ittt6YVtCf4qcWSEFwaeUo91KIuhfhAQGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO/wBPv6+418pxv9VI6Ad/p9/X3GvlON/qpAX3UfbKmv4Blmod9gicf09xqVYwZVq5dJckyX40pUdtMdjuUkpLqyQnktxvitSi9Ik8z6jE+3zW3NnZ1dhUY8doihsL2vaxrM4d4295I13rkeQphO8Zw09SPitB7L2UZp2PIYfZTnXPZYyrSe+t48Kdb2tlZR7Kt5PIjLdtHJ0RZktKDUaFG1yT0IzJREe2xj9vavVWLo7qSrUOHgcaMziVi005ipSVPyH/ACde7qzdQgmkGkj+tlzPc/f7F1DkYP2pMgsrDTZeZadpxOg1CabTSW0S8RYEmS5GOS3Hkt9y2bZrbQvipJrIzTsfHfpFmPaq5NC7Hmn11Pfu7xdhmntdY27GRuQrBhtWQOss7OKadN5vo22pozTu1uklEWw7vQ3SnUnU7GNArLNZeLwcFw6tgXdZDpFyHZthJKv7mMqR3iEoZJtDy1GlBr5K9ZF4dvD7L2bw9CP5NFWlBIj1eaRryonEt9tblem2TYOIkJ4KIny3cQkkbpPZG5p3MwHE1N9kFx7BMsy2vgwKG0rMTkLh2y52YQq2ydebSSn0QoLvpyDRuafSU3zWlSUcttx12tXahnajYrrHiGIYe1bUlXiDr8+5lXTcSSliZWKfbkMw1Nmp1pKHEkauafS3IiPYZfV6I6oaX5xmh4G7g9riOVXj2QrPKW5JTauTINJyktpaSaX21KSa0kpbZpNRkZmQwDt26F5zqXjuT3jcbCqqhxuufsoWRR/KUZA3FZhrVIgnxSTam3ld4k/rhJJC+qFGW5hRYAAAAAAAAAALzcB7YGQYVoZJlwNM/brHNN6aoYurI75uO64yusiSDXHZNk+a0JePkhSkFslJpWo1GlNGQvT017NGT5N2T9SaeLPqG5OpVDVu1C3nnSQwRUsON/ODJszSfNlR+gS/RMvXuRBKUftXu4vdZFA1KxA8IRWYq5mTD8a0RYk/AbcJt1KiS2jg+lS2y7tPNJmstlmMW017dsPUDOKTFzpscRY5IxJVSN0+awrVzv2mFPpZnNsJNUU1JQouSe9SSi23M9t8l1d7LsnWHOpj9nYRouNT9O5+HSFMqUctqS/KjPIeQg08TQkmDPqoj34ltsZmWQ6L41q5QzYMPPUYG/VV8E4ybKgRJ8vnPFxSh5aXEJQzuklmpKTc3UotjIi2MMC7MWtmquV9m2VmWUYxW3dkz5U7BfRftsqsTTNkIcS9yjttRUNJQRErkvklG+yT6Hrj20e05G177HOrdK9W1tZe41PpVSk0l8xdQXmpEojbW1KaSkjMjacSpBpI0mReO4nP3LGpZ6E3WkbthiUnG4Vn7ZUcp1yVzsWys/LfJLFkm+KWlJNTSjbUvctj4n1I4A7eej2c4doXq7m+TN4lCh5HDx+D7VY0b/GA5EnK4ISa20k6lSH1GbmzZkZEkkGRcgFSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7/T4t89xr5Tjf6qR0A7/T7+vuNfKcb/AFUgPThWP49dOz2q9ysnu18g4cxEY23FRnySlZtOEnfgskrQriex7LSe2xkFK9j2SVzdhULrLWA4paESoRtvNKUhZoWRKTuRmlSVJPr0NJkfUhqr2au0FppJ0tziS7m1M/k15a5Jksukh2yUWDccnn1I2JCycQaYjLR8i2NJFuRltuMLxe1lYjG7POPTcysbic9T1bVrhtVkEqDbNzZrqXl2aibVvMZRycJ1Dx8UoJS9zM9jDdaytMYqYtw/Kdrm0U8c5VglKUrXFa4GvktCSNREaUqMunUiPbccDTzLcW1SxeNkWOxnnamT1Yen08iAt1OxGS0tyGm1mgyMjJZJ4n6jMaW5JOof5NO1jc111Nc1At7yfjT1U/dSnTiMyXWKyIaoi3TQnmZGtpzhuSF8EKJBEkpS1mvYuLa3aZ4vX5TLm18ZEGoVg1FeSK2yYU48lLdl3bBl5WwhtBk627shCCUvfc9jDZxgqWfNmRmSgSJcRSUyWUcFOMmpJKSSyLqkzSZGW/iRkYiHth5RjWn/AGcs9et2HI6bKlnVsV2JVvSUpkOxnENE4pltRMpNRpT3jnFBGZEai3Ia+1lvWRNJu0TkWMZPaQ9S7vK5tE9GRfTFyKpUqxTWQFnGU8aWXTQ02ttwkktKCJKFEhJEWTeyCUZYD2QbPGK2ytrKRkFtFQ7KubJ+a+73KfLHjJTij4JNuAs+7bJKC3VsktzAUSgAALJ/YfcR09yGo1bmagUuM2UWNJpI8WRksSO6hpx9UptLaFPEZJU4vu0kkuqlcS6nsLK5+g+h9VMr4k3TvT+HLsHVMQ2H6SChyS4SDWaG0mjdaiSlSjJO57JM/AhT17HrnOO4y+qtye8r8fp5mcY/Yy5tpIQxHQ1Bj2ctPJajIur7cZJffUQs4z7UTGs91n09yPHs2YPGafFMiyB+9hT1SK1skGxDadNpKzadUhb0nqZGr60pO/iQCUbPs/aK0lfIn2Om2BQIMdBuPSZVFCbabSXipSlNkRF98xjGS4L2e8TzehxCw01xZWR3banoUKFhRS/rSXENqddWzGWllslutkbjpoSXLx8RrRPv67IOziury3JrSwp3c6pKS7zgstnSqexjpW0/ImR31rSTDLiTWyttP1pt0yJJmaEqKaK3IKvF9atV8viSXplFp5p1XxY8iZNdmLdJzyue6pT7qlLcM224pmpSjM+h7gM4xjT7s2ZtbTavHca0rvrOEakyoVZArZLzBpPZRLQhJmnY+h7kXUSjjjOLz63u6FFRJgQXFwOFcTSmo62j7tbOyOiTQaeJo6Gky22LYaSaPoxfOMc7NdVhC411fYeTV5leX1qN2K5k4L3lkZyWkiJTj773HuUqM9kKUoiJJGP500yyLU4bo9T57mt3hOCX+PWGWKsju5UOTbzpU0nmIjlgS+/NTbEjkSCcJbhmnc1EkyAb0KKmVZprVeQHYdz36Yh8O97olceZI8eO5kW+2257DF9O9RMK1WTbLxhDk+PWSlw35T1NIix1uoccaWTLrzSEPklbS0mpo1kRl1PqW8B0c3D8a7Uusd9bXlknIcOx+vbr6yXkEwlvwGIC335BsG7wfaNUgkmakqSTral9HFKUqPLm3e0t7LmhUN3Kn49kVQm5ssLrLmRU22QrlIJxaYb8f64p5t59SiZL0XTPZRkRbgN5jRSFZlXcYHtgbJyCibI702uXHnw8eO5kW+22/QfDIMExrLKxytu8eqrmucNKlxLCE0+yoyPcjNC0mR7GRGXQao2GQ4tiHaE17y+xu7BjK8LxWIusq5N7KSb0RitckOv+Td73bzRrkJSfJKkJdbUoiJxSlKl7QmLB0sx/A8XyXKr+71ByOiafecvrCZNOU5FZQchSOZqaZNJvluRcVLLY1czSagHa32g+iWL0dhc22m2CwKuvjuS5Up7HoZIZaQk1LWo+78CSRn/2H2rez3ozb10WfD0wwh+JKaQ+y6nHYmy0KIlJUX1v1kZGIy7U1zO1Qyqq0fp8auMupuLdxmsaheiNPIg7q8kiqVJfZQXfvN7qSS+XdMrLYyXuUcaJ5NM1UxXQ3HrPIb2gqaDALORkyKi1egrfdiSI1e2lxxhZKIyXHlr5JUR7oMiPY1EYbO+5q0h+KvCf/wDHYf8ADGE2+H9nei1DiYPN00xlnJZkN6dGjFg3NqQy0nk6bb6YptLUkjTuhKzVupJbbqIj1uxrV3UbGcSxGmXkVzYZZqtgFPHxlyxkOPHGsFyn0vSSMz2JbUOWw84rxV5MRq3MbEVVc012tqqvcmyZ8HAtOCbOZYPqfe72bLSnvHXFGZqcNusM1KM9z5GZ+ICnr2RS1wS67R0qTpzWRqjHCrmWDiRqZyp7uQ2txt9KozjTakLJaFJPkgj3SNYxNXasJ+VmtDcSiV5TkNP9Ubhr98fl8yVMIz/8r6RCoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOdQ2qqK8rrJDZOrhyW5BNqPYlGhRK23+/sOCACzXz4OU/FXUfpZ3+GHnwcp+Kuo/Szv8MVlAA381j9lou9Z8Tj0M7BE0TceyiWrUynt0k8l+M8l5no9GdQpJOIQo0qQZHxL1DMoPs3OVxIUdhzTStmONNpQqQ/ar7x0yLY1q4tJTufiexEW59CLwFaIALSMZ9mzurO/gw7LTaqgwn3SbckotHD7rfoSjI2/Aj23+9uIh7S3sh2Sah5jKg5HijsVNVEnQodUxaN+16HpMKRE8tNJxe+WvupSjSRvEktk+iR776KiTLwv5SNO2LxBc7/H0JiWBF75+L/8ADe++aepGf4TP1AIzAAAbO9i7ty2vY0ay9FbikPJiyJURThypa2O57jvttuKT3374/wAkbMefByn4q6j9LO/wxWUACzXz4OU/FXUfpZ3+GI/w/wBlgvsL1OzTNImGLku5W40/OqJVu2qG262y0yhxrjFJ4jJtlKdlOqT6Sj23PctCgAWa+fByn4q6j9LO/wAMZRkPsyGSY3ilHPlaaVRWlqSpCIXtm4RNx/BK1Hw33UfUvvbis7TPEWssyIvLl9zSwWzmWD59CQyjqZb/AHVeH3epn6hws7yx3NcomWi0dyysybjsF0JllPRCCLw6F47eszAb5akezEXup2n+SYhY6bQ4VffV79ZJkQLhaH0NPINtZoUplSSVxUfU0n+AffB/ZmcqwnEquiVgUa9KAyTCZ9laEUhxJGfElEzHbb9EtklxQnokt9z3M65wAWa+fByn4q6j9LO/wx1rnsz9u9kLN45pFTOWrEZURmQu4fUbTSlEpaUEaNk8jSjkZERq4IIzMkltW4ACzXz4OU/FXUfpZ3+GHnwcp+Kuo/Szv8MVlAA36e9litZ2rEbP7HT5NpYwGFsVddKvVnBqzcQlt1xhpLBH3i0pMjWtSzIlKJPEjMhijnsleRufV7I9rblFxmZR2bC3TbwyksxmUupbjMl7X92lskvuFuaFL6789+o0wABJOvOsZa2ZTT2jVI3j8Spo4NDFhNyDf2Zitd22o18U7maSLfYiIRsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyLAsvdwrI2Z5N+URFkbEuKrql9hXRaDL8HUvvkQx0AGW6kYc3ily27AX5TRWKPKq6SXUltH14mf/MnfYy8fA/WMSEnaZSms4qH8AsjPd81yaiTsajjSCSalJP/AMCiI9/udfWe5ffNtD3sE02jXU99arpcpCX47aiNllpST2TvtupZKJO5ke3UyLfbcwioB2uKYpb5xkdfQUFe/a3Ng8TEWHGTyW6s/UX3C8TMz6ERGZmREYkXtC9l/OezPewq7L4KCbmMpdjzoijXHcMy3UglbF6ST3Iy+9+EBEo/pppb7qGm0KccWokpQktzUZ+BEQyLTjE0ZvmtXSuurYZkuH3rjZFyJCUmtW2/QjMkmRGe+xn4H4CU2NLHtEStMrtjZuPICJNUlhCjSby1GlDjpGXoceh7bmW6iIlb7bhiubOI07w+PhcZSfbaZwmXbqD34ntu3H3+4kup/f8AwmIyH3nzpFpNfmSnVPyX1qcddWfVSjPczP8A7j4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJS0X1jj6a+UwptYiRDluk45LYIikI2LbY9/fpLqZFuWxqUfXfYT3k7beuuns6pww/b+zmLZRGiRv6U3e8SZJNJ7Gkz2P32xbbn4DUfFcVt84yOvoKCvftbiweJiLDjJ5LdWfqL7heJmZ9CIjMzIiF2vYa7DdR2YccTdXSWLXUSwZIpc4i5IhIPr3DG/q/wCZXioy9REREDsNdhqo7MOOJurpLFrqJYMkUucRckQkH1Nhgz8C/wCZXioy9RERFOutWiuMa9YHOxTKoKZUKQk+7dIvrkdz1OIP1GR7DOwAUkS+yZk3Zm7QUiBeNqkUjcN+TW3KU7NSG90p2M/AlkSz3L7x/f26bUrtD4/TxpVZVstZFJcQppwlFyiERkZGSj/tl94uhl6yFy2s+jGMa74JPxXKYSZUKSg+7dIvrkde3RaFeJGQop7U/ZYyjsu547UW7S5dLIUaq23Qn63Jb9RGfqWReJf/AM7BDMmQqVIdfWSErcWa1E22ltJGZ79EpIiSX3iIiL1D5gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7XFcVt84yOvoKCvftbiweJiLDjJ5LdWfqL7heJmZ9CIjMzIiHVDIcAz++0wy2uyXGrF2suILhOMvtKMvwpMvWk/WQC6nsNdhqo7MOOIurpDFrqJYMkUucRckQkH1Nhgz8C/5leKjL1EREW2A1o7F/bQoe1JiSY8hTVZm8Fsin1hqIu86f0rX3Un9z1f5bLgOpy+dZVmKXMynjtS7aPDediMPJWpDjyUGaEqJBGsyMyItkkZn6uojns0ZNqNk+ES3NSqdVRdR5im20PFs842pJOpNfFptsySTiUEbZH7wyV6ZKEuAADBNadFsX15wOdiuVQUS4MhJ927t9cjuepxB+oyMZ2MX1M1MxvR/CbPLMss2qqkr2+brznU1H/ZQhPipaj6EkupmYChftT9ljKOy7njtRbtLl0shRqrbdCfrclv1EZ+pZF4l/8AztCo2D7YXbDyTtW5sch/vanD4C1JqaMl9G0//Nd26LdUXifgkuifWZ6+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhwDP77TDLa/JcasXay3guE4y+0oy9fVJl60n6yF4nYw7Z9D2pMRSy8pqszaC2RWFWatu8/+q191J/+n+VDQyHT/UC+0uy6uyXGrF2suILhONPtHtv91Ki9aT9ZAPSyA1q7GHbPoe1JiKWXlNVmbQWyKwqzVt3n/wBVr7qT/wDT/KbdTNTMb0fwmzyzLLNqqpK9vm6851NR/wBlCE+KlqPoSS6mZgGpmpmN6P4TZ5Zllm1VUle3zdec6mo/7KEJ8VLUfQkl1MzFGHbD7YeSdq7NvKJHe1WHV7iiqKMl7k2Xh3zu3RTyi8T8EkfFPrNTth9sPJO1dm3lEjvarDq9xRVFGS9ybLw753bop5ReJ+CSPin1mrXwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQ6f6gX2l2XV2S41Yu1lxBcJxp9o9t/upUXrSfrISj2oO1/nHaotaxzI3W4FRWsoTGp4SjKOT3AicfUX9paj36n71J8S9ZnBoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//2Q==", "text/plain": [ - "" + "\n", + "🚅 Components\n", + " - embedder: SentenceTransformersTextEmbedder\n", + " - retriever: InMemoryEmbeddingRetriever\n", + " - prompt_builder: ChatPromptBuilder\n", + " - llm: OpenAIChatGenerator\n", + "🛤️ Connections\n", + " - embedder.embedding -> retriever.query_embedding (List[float])\n", + " - retriever.documents -> prompt_builder.documents (List[Document])\n", + " - prompt_builder.prompt -> llm.messages (List[ChatMessage])" ] }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [] - }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" @@ -645,10 +475,11 @@ "source": [ "from haystack.components.embedders import SentenceTransformersTextEmbedder\n", "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", - "from haystack.components.builders import PromptBuilder\n", - "from haystack.components.generators import OpenAIGenerator\n", + "from haystack.components.builders import ChatPromptBuilder\n", + "from haystack.dataclasses import ChatMessage\n", + "from haystack.components.generators.chat import OpenAIChatGenerator\n", "\n", - "template = \"\"\"\n", + "template = [ChatMessage.from_system(\"\"\"\n", "Answer the questions based on the given context.\n", "\n", "Context:\n", @@ -657,16 +488,16 @@ "{% endfor %}\n", "Question: {{ question }}\n", "Answer:\n", - "\"\"\"\n", + "\"\"\")]\n", "rag_pipe = Pipeline()\n", "rag_pipe.add_component(\"embedder\", SentenceTransformersTextEmbedder(model=\"sentence-transformers/all-MiniLM-L6-v2\"))\n", "rag_pipe.add_component(\"retriever\", InMemoryEmbeddingRetriever(document_store=document_store))\n", - "rag_pipe.add_component(\"prompt_builder\", PromptBuilder(template=template))\n", - "rag_pipe.add_component(\"llm\", OpenAIGenerator(model=\"gpt-4o-mini\"))\n", + "rag_pipe.add_component(\"prompt_builder\", ChatPromptBuilder(template=template))\n", + "rag_pipe.add_component(\"llm\", OpenAIChatGenerator(model=\"gpt-4o-mini\"))\n", "\n", "rag_pipe.connect(\"embedder.embedding\", \"retriever.query_embedding\")\n", "rag_pipe.connect(\"retriever\", \"prompt_builder.documents\")\n", - "rag_pipe.connect(\"prompt_builder\", \"llm\")" + "rag_pipe.connect(\"prompt_builder.prompt\", \"llm.messages\")" ] }, { @@ -705,29 +536,16 @@ }, "outputs": [ { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "6f4a57e2a1ea45bd98c7b93dd44f7059", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Batches: 0%| | 0/1 [00:00, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 5, 'prompt_tokens': 83, 'total_tokens': 88, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]}}" ] }, "execution_count": 8, @@ -762,7 +580,7 @@ "def rag_pipeline_func(query: str):\n", " result = rag_pipe.run({\"embedder\": {\"text\": query}, \"prompt_builder\": {\"question\": query}})\n", "\n", - " return {\"reply\": result[\"llm\"][\"replies\"][0]}" + " return {\"reply\": result[\"llm\"][\"replies\"][0].content}" ] }, { @@ -926,7 +744,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -958,18 +776,11 @@ ] }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "910f6e07a2b843158e74a297f4934f97", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Batches: 0%| | 0/1 [00:00 Date: Mon, 9 Dec 2024 00:40:00 +0100 Subject: [PATCH 3/5] update index.toml --- README.md | 4 ++-- index.toml | 13 ------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 670bc9cd..72647ca1 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,11 @@ Haystack 2.0 | [Preprocessing](./tutorials/08_Preprocessing.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/08_Preprocessing.ipynb) | [Build an Extractive QA Pipeline](./tutorials/34_Extractive_QA_Pipeline.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/34_Extractive_QA_Pipeline.ipynb) | | [DPR Training](./tutorials/09_DPR_training.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/09_DPR_training.ipynb) | [Evaluating RAG Pipelines](./tutorials/35_Evaluating_RAG_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/35_Evaluating_RAG_Pipelines.ipynb)| | [[OUTDATED] Knowledge Graph](./tutorials/10_Knowledge_Graph.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/10_Knowledge_Graph.ipynb) | [Building Pipelines with Conditional Routing](./tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb)| -| [Pipelines](./tutorials/11_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/11_Pipelines.ipynb) | [[OUTDATED] Simplifying Pipeline Inputs with Multiplexer](./tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb)| +| [Pipelines](./tutorials/11_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/11_Pipelines.ipynb) | [Retrieving a Context Window Around a Sentence](./tutorials/42_Sentence_Window_Retriever.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/42_Sentence_Window_Retriever.ipynb) | | [[OUTDATED] Seq2SeqGenerator](./tutorials/12_LFQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/12_LFQA.ipynb) | [Embedding Metadata for Improved Retrieval](./tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb)| | [Question Generation](./tutorials/13_Question_generation.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/13_Question_generation.ipynb) | [Building a Chat Application with Function Calling](./tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb)| | [Query Classifier](./tutorials/14_Query_Classifier.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/14_Query_Classifier.ipynb) | [Query Classification with TransformersTextRouter](./tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.ipynb) | -| [Table QA](./tutorials/15_TableQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/15_TableQA.ipynb) | [Retrieving a Context Window Around a Sentence](./tutorials/42_Sentence_Window_Retriever.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/42_Sentence_Window_Retriever.ipynb) | | +| [Table QA](./tutorials/15_TableQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/15_TableQA.ipynb) | | [Document Classifier at Index Time](./tutorials/16_Document_Classifier_at_Index_Time.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/16_Document_Classifier_at_Index_Time.ipynb) | | | | [Make Your QA Pipelines Talk!](./tutorials/17_Audio.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/17_Audio.ipynb) | | | | [Generative Pseudo Labeling](./tutorials/18_GPL.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/18_GPL.ipynb) | | | diff --git a/index.toml b/index.toml index 2e5a936c..1a43812d 100644 --- a/index.toml +++ b/index.toml @@ -403,19 +403,6 @@ haystack_2 = true dependencies = [] featured = true -[[tutorial]] -title = "Simplifying Pipeline Inputs with Multiplexer" -description = "Learn how to declutter the inputs of complex pipelines" -level = "intermediate" -weight = 84 -notebook = "37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb" -aliases = [] -completion_time = "10 min" -created_at = 2024-02-19 -haystack_2 = true -haystack_version = "2.3.1" -dependencies = ["transformers", "huggingface_hub>=0.23.0"] - [[tutorial]] title = "Embedding Metadata for Improved Retrieval" description = "Learn how to embed metadata while indexing, to improve the quality of retrieval results" From 0a926534934ec2d700a04987efb7306d8a364009 Mon Sep 17 00:00:00 2001 From: Amna Mubashar Date: Mon, 9 Dec 2024 12:12:51 +0100 Subject: [PATCH 4/5] Restore tutorial 37 --- README.md | 4 +- index.toml | 15 +- ...ing_Pipeline_Inputs_with_Multiplexer.ipynb | 554 ++++++++++++++++++ 3 files changed, 570 insertions(+), 3 deletions(-) create mode 100644 tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb diff --git a/README.md b/README.md index 72647ca1..670bc9cd 100644 --- a/README.md +++ b/README.md @@ -44,11 +44,11 @@ Haystack 2.0 | [Preprocessing](./tutorials/08_Preprocessing.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/08_Preprocessing.ipynb) | [Build an Extractive QA Pipeline](./tutorials/34_Extractive_QA_Pipeline.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/34_Extractive_QA_Pipeline.ipynb) | | [DPR Training](./tutorials/09_DPR_training.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/09_DPR_training.ipynb) | [Evaluating RAG Pipelines](./tutorials/35_Evaluating_RAG_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/35_Evaluating_RAG_Pipelines.ipynb)| | [[OUTDATED] Knowledge Graph](./tutorials/10_Knowledge_Graph.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/10_Knowledge_Graph.ipynb) | [Building Pipelines with Conditional Routing](./tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb)| -| [Pipelines](./tutorials/11_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/11_Pipelines.ipynb) | [Retrieving a Context Window Around a Sentence](./tutorials/42_Sentence_Window_Retriever.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/42_Sentence_Window_Retriever.ipynb) | +| [Pipelines](./tutorials/11_Pipelines.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/11_Pipelines.ipynb) | [[OUTDATED] Simplifying Pipeline Inputs with Multiplexer](./tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb)| | [[OUTDATED] Seq2SeqGenerator](./tutorials/12_LFQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/12_LFQA.ipynb) | [Embedding Metadata for Improved Retrieval](./tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/39_Embedding_Metadata_for_Improved_Retrieval.ipynb)| | [Question Generation](./tutorials/13_Question_generation.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/13_Question_generation.ipynb) | [Building a Chat Application with Function Calling](./tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb)| | [Query Classifier](./tutorials/14_Query_Classifier.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/14_Query_Classifier.ipynb) | [Query Classification with TransformersTextRouter](./tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.ipynb)| [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/41_Query_Classification_with_TransformersTextRouter_and_TransformersZeroShotTextRouter.ipynb) | -| [Table QA](./tutorials/15_TableQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/15_TableQA.ipynb) | +| [Table QA](./tutorials/15_TableQA.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/15_TableQA.ipynb) | [Retrieving a Context Window Around a Sentence](./tutorials/42_Sentence_Window_Retriever.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/42_Sentence_Window_Retriever.ipynb) | | | [Document Classifier at Index Time](./tutorials/16_Document_Classifier_at_Index_Time.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/16_Document_Classifier_at_Index_Time.ipynb) | | | | [Make Your QA Pipelines Talk!](./tutorials/17_Audio.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/17_Audio.ipynb) | | | | [Generative Pseudo Labeling](./tutorials/18_GPL.ipynb) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/deepset-ai/haystack-tutorials/blob/main/tutorials/18_GPL.ipynb) | | | diff --git a/index.toml b/index.toml index 1a43812d..f3bc0e0e 100644 --- a/index.toml +++ b/index.toml @@ -403,6 +403,19 @@ haystack_2 = true dependencies = [] featured = true +[[tutorial]] +title = "Simplifying Pipeline Inputs with Multiplexer" +description = "Learn how to declutter the inputs of complex pipelines" +level = "intermediate" +weight = 84 +notebook = "37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb" +aliases = [] +completion_time = "10 min" +created_at = 2024-02-19 +haystack_2 = true +haystack_version = "2.3.1" +dependencies = ["transformers", "huggingface_hub>=0.23.0"] + [[tutorial]] title = "Embedding Metadata for Improved Retrieval" description = "Learn how to embed metadata while indexing, to improve the quality of retrieval results" @@ -464,4 +477,4 @@ haystack_2 = true guide = true colab = false download = false -created_at = 2024-07-17 +created_at = 2024-07-17 \ No newline at end of file diff --git a/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb b/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb new file mode 100644 index 00000000..1f7f04e4 --- /dev/null +++ b/tutorials/37_Simplifying_Pipeline_Inputs_with_Multiplexer.ipynb @@ -0,0 +1,554 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "JFAFUa7BECmK" + }, + "source": [ + "# Tutorial: Simplifying Pipeline Inputs with Multiplexer\n", + "\n", + "\n", + "- **Level**: Intermediate\n", + "- **Time to complete**: 10 minutes\n", + "- **Components Used**: [Multiplexer](https://docs.haystack.deepset.ai/docs/multiplexer), [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore), [HuggingFaceAPIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapidocumentembedder), [HuggingFaceAPITextEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapitextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder), [HuggingFaceAPIGenerator](https://docs.haystack.deepset.ai/docs/huggingfaceapigenerator) and [AnswerBuilder](https://docs.haystack.deepset.ai/docs/answerbuilder)\n", + "- **Prerequisites**: You must have a [Hugging Face API Key](https://huggingface.co/settings/tokens) and be familiar with [creating pipelines](https://docs.haystack.deepset.ai/docs/creating-pipelines)\n", + "- **Goal**: After completing this tutorial, you'll have learned how to use a Multiplexer to simplify the inputs that `Pipeline.run()` get\n", + "\n", + "> As of version 2.2.0, `Multiplexer` has been deprecated in Haystack and will be completely removed from Haystack as of v2.4.0. We recommend using [BranchJoiner](https://docs.haystack.deepset.ai/docs/branchjoiner) instead. For more details about this deprecation, check out [Haystack 2.2.0 release notes](https://github.com/deepset-ai/haystack/releases/tag/v2.2.0) on Github.\n", + "\n", + "> This tutorial uses Haystack 2.0. To learn more, read the [Haystack 2.0 announcement](https://haystack.deepset.ai/blog/haystack-2-release) or visit the [Haystack 2.0 Documentation](https://docs.haystack.deepset.ai/docs/intro)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jy3ZkDzu9-CW" + }, + "source": [ + "## Overview\n", + "\n", + "If you've ever built a Haystack pipeline with more than 3-4 components, you probably noticed that the number of inputs to pass to the `run()` method of the pipeline grow endlessly. New components take some of their input from the other components of a pipeline, but many of them also require additional input from the user. As a result, the `data` input of `Pipeline.run()` grows and becomes very repetitive.\n", + "\n", + "There is one component that can help managing this repetition in a more effective manner, and it's called [`Multiplexer`](https://docs.haystack.deepset.ai/docs/multiplexer).\n", + "\n", + "In this tutorial, you will learn how to drastically simplify the `Pipeline.run()` of a RAG pipeline using a Multiplexer." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RJPsjBXZKWnb" + }, + "source": [ + "## Setup\n", + "### Prepare the Colab Environment\n", + "\n", + "- [Enable GPU Runtime in Colab](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration)\n", + "- [Set logging level to INFO](https://docs.haystack.deepset.ai/docs/setting-the-log-level)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "CcK-dK--G5ng" + }, + "source": [ + "### Install Haystack\n", + "\n", + "Install Haystack 2.0 with `pip`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0hwJTyV5HARC" + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "pip install haystack-ai \"huggingface_hub>=0.23.0\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3N_97P0OV9cx" + }, + "source": [ + "### Enable Telemetry\n", + "\n", + "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BKilNUd8V_Uc" + }, + "outputs": [], + "source": [ + "from haystack.telemetry import tutorial_running\n", + "\n", + "tutorial_running(37)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uTNEeEcBJc_4" + }, + "source": [ + "### Enter a Hugging Face API key\n", + "\n", + "Set a Hugging Face API key:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "aiHltCF7JgaV", + "outputId": "b973435d-94c1-458a-8212-c543fd45ffab" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Enter a Hugging Face API Token:··········\n" + ] + } + ], + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "if \"HF_API_TOKEN\" not in os.environ:\n", + " os.environ[\"HF_API_TOKEN\"] = getpass(\"Enter Hugging Face token:\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e57ugQB7dYsQ" + }, + "source": [ + "## Indexing Documents with a Pipeline\n", + "\n", + "Create a pipeline to store the small example dataset in the [InMemoryDocumentStore](https://docs.haystack.deepset.ai/docs/inmemorydocumentstore) with their embeddings. You will use [HuggingFaceAPIDocumentEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapidocumentembedder) to generate embeddings for your Documents and write them to the document store with the [DocumentWriter](https://docs.haystack.deepset.ai/docs/documentwriter).\n", + "\n", + "After adding these components to your pipeline, connect them and run the pipeline.\n", + "\n", + "> If you'd like to learn about preprocessing files before you index them to your document store, follow the [Preprocessing Different File Types](https://haystack.deepset.ai/tutorials/30_file_type_preprocessing_index_pipeline) tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "My_fx0lNJUVb", + "outputId": "b731efb8-14bb-4f13-ca49-d8706a777dd5" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/anakin87/.virtualenvs/tutorials/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n", + "Calculating embeddings: 100%|██████████| 1/1 [00:00<00:00, 1.13it/s]\n" + ] + }, + { + "data": { + "text/plain": [ + "{'doc_writer': {'documents_written': 5}}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack import Pipeline, Document\n", + "from haystack.document_stores.in_memory import InMemoryDocumentStore\n", + "from haystack.components.writers import DocumentWriter\n", + "from haystack.components.embedders import HuggingFaceAPIDocumentEmbedder\n", + "\n", + "documents = [\n", + " Document(content=\"My name is Jean and I live in Paris.\"),\n", + " Document(content=\"My name is Mark and I live in Berlin.\"),\n", + " Document(content=\"My name is Giorgio and I live in Rome.\"),\n", + " Document(content=\"My name is Giorgio and I live in Milan.\"),\n", + " Document(content=\"My name is Giorgio and I lived in many cities, but I settled in Naples eventually.\"),\n", + "]\n", + "\n", + "document_store = InMemoryDocumentStore()\n", + "\n", + "indexing_pipeline = Pipeline()\n", + "indexing_pipeline.add_component(\n", + " instance=HuggingFaceAPIDocumentEmbedder(\n", + " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", + " ),\n", + " name=\"doc_embedder\",\n", + ")\n", + "indexing_pipeline.add_component(instance=DocumentWriter(document_store=document_store), name=\"doc_writer\")\n", + "\n", + "indexing_pipeline.connect(\"doc_embedder.documents\", \"doc_writer.documents\")\n", + "\n", + "indexing_pipeline.run({\"doc_embedder\": {\"documents\": documents}})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e9hOmQx4L2Lw" + }, + "source": [ + "## Building a RAG Pipeline\n", + "\n", + "Build a basic retrieval augmented generative pipeline with [HuggingFaceAPITextEmbedder](https://docs.haystack.deepset.ai/docs/huggingfaceapitextembedder), [InMemoryEmbeddingRetriever](https://docs.haystack.deepset.ai/docs/inmemoryembeddingretriever), [PromptBuilder](https://docs.haystack.deepset.ai/docs/promptbuilder) and [HuggingFaceAPIGenerator](https://docs.haystack.deepset.ai/docs/huggingfaceapigenerator). Additionally, add [AnswerBuilder](https://docs.haystack.deepset.ai/docs/answerbuilder) to help you enrich the generated answer with `meta` info and the `query` input.\n", + "\n", + "> For a step-by-step guide to create a RAG pipeline with Haystack, follow the [Creating Your First QA Pipeline with Retrieval-Augmentation](https://haystack.deepset.ai/tutorials/27_first_rag_pipeline) tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "ueu5W07IWyXa", + "outputId": "51419b90-14d8-4e4a-cd24-8884053b9688" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "🚅 Components\n", + " - embedder: HuggingFaceAPITextEmbedder\n", + " - retriever: InMemoryEmbeddingRetriever\n", + " - prompt_builder: PromptBuilder\n", + " - llm: HuggingFaceAPIGenerator\n", + " - answer_builder: AnswerBuilder\n", + "🛤️ Connections\n", + " - embedder.embedding -> retriever.query_embedding (List[float])\n", + " - retriever.documents -> prompt_builder.documents (List[Document])\n", + " - prompt_builder.prompt -> llm.prompt (str)\n", + " - llm.replies -> answer_builder.replies (List[str])\n", + " - llm.meta -> answer_builder.meta (List[Dict[str, Any]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack.components.embedders import HuggingFaceAPITextEmbedder\n", + "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", + "from haystack.components.builders import PromptBuilder, AnswerBuilder\n", + "from haystack.components.generators import HuggingFaceAPIGenerator\n", + "\n", + "template = \"\"\"\n", + " <|user|>\n", + " Answer the question based on the given context.\n", + "\n", + "Context:\n", + "{% for document in documents %}\n", + " {{ document.content }}\n", + "{% endfor %}\n", + "Question: {{ question }}\n", + "<|assistant|>\n", + "Answer:\n", + "\"\"\"\n", + "pipe = Pipeline()\n", + "pipe.add_component(\n", + " \"embedder\",\n", + " HuggingFaceAPITextEmbedder(\n", + " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", + " ),\n", + ")\n", + "pipe.add_component(\"retriever\", InMemoryEmbeddingRetriever(document_store=document_store))\n", + "pipe.add_component(\"prompt_builder\", PromptBuilder(template=template))\n", + "pipe.add_component(\n", + " \"llm\",\n", + " HuggingFaceAPIGenerator(api_type=\"serverless_inference_api\", api_params={\"model\": \"HuggingFaceH4/zephyr-7b-beta\"}),\n", + ")\n", + "pipe.add_component(\"answer_builder\", AnswerBuilder())\n", + "\n", + "pipe.connect(\"embedder.embedding\", \"retriever.query_embedding\")\n", + "pipe.connect(\"retriever\", \"prompt_builder.documents\")\n", + "pipe.connect(\"prompt_builder\", \"llm\")\n", + "pipe.connect(\"llm.replies\", \"answer_builder.replies\")\n", + "pipe.connect(\"llm.meta\", \"answer_builder.meta\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5xxvPqyurZTi" + }, + "source": [ + "## Running the Pipeline\n", + "Pass the `query` to `embedder`, `prompt_builder` and `answer_builder` and run it:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "AIsphy4hJDpE", + "outputId": "4498e7c9-0ff2-424c-9ddd-535f8630572e" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'answer_builder': {'answers': [GeneratedAnswer(data=' Mark lives in Berlin, as stated in the first sentence of the context provided.', query='Where does Mark live?', documents=[], meta={'model': 'HuggingFaceH4/zephyr-7b-beta', 'finish_reason': None, 'usage': {'completion_tokens': 0}})]}}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "query = \"Where does Mark live?\"\n", + "pipe.run({\"embedder\": {\"text\": query}, \"prompt_builder\": {\"question\": query}, \"answer_builder\": {\"query\": query}})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "wrH2MGSBLvVC" + }, + "source": [ + "In this basic RAG pipeline, components require a `query` to operate are `embedder`, `prompt_builder`, and `answer_builder`. However, as you extend the pipeline with additional components like Retrievers and Rankers, the number of components needing a `query` can increase indefinitely. This leads to repetitive and increasingly complex `Pipeline.run()` calls. In such cases, using a Multiplexer can help simplify and declutter `Pipeline.run()`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ewDXDrw9N0CG" + }, + "source": [ + "## Introducing a Multiplexer\n", + "\n", + "The [Multiplexer](https://docs.haystack.deepset.ai/docs/multiplexer) is a component that can accept multiple input connections and then distributes the first value it receives to all components connected to its output. In this seeting, you can use this component by connecting it to other pipeline components that expect a `query` during runtime.\n", + "\n", + "Now, initialize the Multiplexer with the expected input type (in this case, `str` since the `query` is a string):" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "kArO87EKN3N-" + }, + "outputs": [], + "source": [ + "from haystack.components.others import Multiplexer\n", + "\n", + "multiplexer = Multiplexer(str)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "vBGC2wO5LWIL" + }, + "source": [ + "## Adding the `Multiplexer` to the Pipeline\n", + "\n", + "Create the same RAG pipeline but this time with the Multiplexer. Add the Multiplexer to the pipeline and connect it to all the components that need the `query` as an input:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "CTmnCZvgEAut", + "outputId": "a0ab0df0-32f7-4778-954a-e7b9cc8b612d" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "🚅 Components\n", + " - multiplexer: Multiplexer\n", + " - embedder: HuggingFaceAPITextEmbedder\n", + " - retriever: InMemoryEmbeddingRetriever\n", + " - prompt_builder: PromptBuilder\n", + " - llm: HuggingFaceAPIGenerator\n", + " - answer_builder: AnswerBuilder\n", + "🛤️ Connections\n", + " - multiplexer.value -> embedder.text (str)\n", + " - multiplexer.value -> prompt_builder.question (str)\n", + " - multiplexer.value -> answer_builder.query (str)\n", + " - embedder.embedding -> retriever.query_embedding (List[float])\n", + " - retriever.documents -> prompt_builder.documents (List[Document])\n", + " - prompt_builder.prompt -> llm.prompt (str)\n", + " - llm.replies -> answer_builder.replies (List[str])\n", + " - llm.meta -> answer_builder.meta (List[Dict[str, Any]])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from haystack.components.embedders import HuggingFaceAPITextEmbedder\n", + "from haystack.components.retrievers.in_memory import InMemoryEmbeddingRetriever\n", + "from haystack.components.builders import PromptBuilder, AnswerBuilder\n", + "from haystack.components.generators import HuggingFaceAPIGenerator\n", + "\n", + "template = \"\"\"\n", + " <|user|>\n", + " Answer the question based on the given context.\n", + "\n", + "Context:\n", + "{% for document in documents %}\n", + " {{ document.content }}\n", + "{% endfor %}\n", + "Question: {{ question }}\n", + "<|assistant|>\n", + "Answer:\n", + "\"\"\"\n", + "pipe = Pipeline()\n", + "\n", + "pipe.add_component(\"multiplexer\", multiplexer)\n", + "\n", + "pipe.add_component(\n", + " \"embedder\",\n", + " HuggingFaceAPITextEmbedder(\n", + " api_type=\"serverless_inference_api\", api_params={\"model\": \"sentence-transformers/all-MiniLM-L6-v2\"}\n", + " ),\n", + ")\n", + "pipe.add_component(\"retriever\", InMemoryEmbeddingRetriever(document_store=document_store))\n", + "pipe.add_component(\"prompt_builder\", PromptBuilder(template=template))\n", + "pipe.add_component(\n", + " \"llm\",\n", + " HuggingFaceAPIGenerator(api_type=\"serverless_inference_api\", api_params={\"model\": \"HuggingFaceH4/zephyr-7b-beta\"}),\n", + ")\n", + "pipe.add_component(\"answer_builder\", AnswerBuilder())\n", + "\n", + "# Connect the Multiplexer to all the components that need the query\n", + "pipe.connect(\"multiplexer.value\", \"embedder.text\")\n", + "pipe.connect(\"multiplexer.value\", \"prompt_builder.question\")\n", + "pipe.connect(\"multiplexer.value\", \"answer_builder.query\")\n", + "\n", + "pipe.connect(\"embedder.embedding\", \"retriever.query_embedding\")\n", + "pipe.connect(\"retriever\", \"prompt_builder.documents\")\n", + "pipe.connect(\"prompt_builder\", \"llm\")\n", + "pipe.connect(\"llm.replies\", \"answer_builder.replies\")\n", + "pipe.connect(\"llm.meta\", \"answer_builder.meta\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "i2wW4nbEQKhJ" + }, + "source": [ + "## Running the Pipeline with a Multiplexer\n", + "\n", + "Run the pipeline that you updated with a Multiplexer. This time, instead of passing the query to `prompt_builder`, `retriever` and `answer_builder` seperately, you only need to pass it to the `multiplexer`. As a result, you will get the same answer." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "YbIHBCKPQF4f", + "outputId": "32fb9d11-eec2-49d7-9ab1-97a90d9bbc28" + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'answer_builder': {'answers': [GeneratedAnswer(data=' Mark lives in Berlin, as stated in the first sentence of the context provided.', query='Where does Mark live?', documents=[], meta={'model': 'HuggingFaceH4/zephyr-7b-beta', 'finish_reason': None, 'usage': {'completion_tokens': 0}})]}}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pipe.run({\"multiplexer\": {\"value\": \"Where does Mark live?\"}})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kPiSU2xoKmio" + }, + "source": [ + "## What's next\n", + "\n", + "🎉 Congratulations! You've simplified your pipeline run with a Multiplexer!\n", + "\n", + "If you liked this tutorial, there's more to learn about Haystack 2.0:\n", + "- [Creating a Hybrid Retrieval Pipeline](https://haystack.deepset.ai/tutorials/33_hybrid_retrieval)\n", + "- [Building Fallbacks to Websearch with Conditional Routing](https://haystack.deepset.ai/tutorials/36_building_fallbacks_with_conditional_routing)\n", + "- [Model-Based Evaluation of RAG Pipelines](https://haystack.deepset.ai/tutorials/35_model_based_evaluation_of_rag_pipelines)\n", + "\n", + "To stay up to date on the latest Haystack developments, you can [sign up for our newsletter](https://landing.deepset.ai/haystack-community-updates) or [join Haystack discord community](https://discord.gg/haystack).\n", + "\n", + "Thanks for reading!" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "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.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From a85917a49f2ed1232e261ec9424065aae8e1fb3a Mon Sep 17 00:00:00 2001 From: Amna Mubashar Date: Fri, 13 Dec 2024 12:25:26 +0100 Subject: [PATCH 5/5] Fixes based on review --- ...g_Fallbacks_with_Conditional_Routing.ipynb | 8 ++++---- ...at_Application_with_Function_Calling.ipynb | 20 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb index 42f9171d..76536338 100644 --- a/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb +++ b/tutorials/36_Building_Fallbacks_with_Conditional_Routing.ipynb @@ -275,7 +275,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "id": "qyE9rGcawX3F" }, @@ -294,14 +294,14 @@ "\n", "routes = [\n", " {\n", - " \"condition\": \"{{'no_answer' in replies[0].content}}\",\n", + " \"condition\": \"{{'no_answer' in replies[0].text}}\",\n", " \"output\": \"{{query}}\",\n", " \"output_name\": \"go_to_websearch\",\n", " \"output_type\": str,\n", " },\n", " {\n", - " \"condition\": \"{{'no_answer' not in replies[0].content}}\",\n", - " \"output\": \"{{replies[0].content}}\",\n", + " \"condition\": \"{{'no_answer' not in replies[0].text}}\",\n", + " \"output\": \"{{replies[0].text}}\",\n", " \"output_name\": \"answer\",\n", " \"output_type\": str,\n", " },\n", diff --git a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb index 426805ab..6bc53f86 100644 --- a/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb +++ b/tutorials/40_Building_Chat_Application_with_Function_Calling.ipynb @@ -84,6 +84,8 @@ "name": "stderr", "output_type": "stream", "text": [ + "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020\n", + " warnings.warn(\n", "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n" ] @@ -147,7 +149,7 @@ { "data": { "text/plain": [ - "{'replies': [ChatMessage(content='Natural Language Processing (NLP) ist ein Teilgebiet der künstlichen Intelligenz, das sich mit der Interaktion zwischen Computern und menschlicher Sprache befasst. Es umfasst Techniken zur Verarbeitung, Analyse und Generierung von Sprache, um Maschinen das Verständnis und die Verarbeitung von textuellen und gesprochenen Informationen zu ermöglichen.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 66, 'prompt_tokens': 33, 'total_tokens': 99, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]}" + "{'replies': [ChatMessage(content='Natural Language Processing (NLP) ist ein Teilbereich der Künstlichen Intelligenz, der sich mit der Interaktion zwischen Computern und menschlicher Sprache beschäftigt. Ziel ist es, Maschinen zu ermöglichen, Text und Sprache in einer für Menschen verständlichen Weise zu verstehen, zu interpretieren und zu erzeugen.', role=, name=None, meta={'model': 'gpt-4o-mini-2024-07-18', 'index': 0, 'finish_reason': 'stop', 'usage': {'completion_tokens': 63, 'prompt_tokens': 33, 'total_tokens': 96, 'prompt_tokens_details': {'cached_tokens': 0, 'audio_tokens': 0}, 'completion_tokens_details': {'reasoning_tokens': 0, 'audio_tokens': 0, 'accepted_prediction_tokens': 0, 'rejected_prediction_tokens': 0}}})]}" ] }, "execution_count": 4, @@ -194,7 +196,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Natural Language Processing (NLP) ist ein Teilbereich der Informatik und Künstlichen Intelligenz, der sich mit der Interaktion zwischen Computern und menschlicher Sprache befasst. Das Ziel von NLP ist es, Computern zu ermöglichen, menschliche Sprache zu verstehen, zu interpretieren, zu analysieren und zu generieren, um nützliche Aufgaben wie Textübersetzung, Sentiment-Analyse oder Chatbots durchzuführen." + "Natural Language Processing (NLP) ist ein Bereich der künstlichen Intelligenz, der sich mit der Interaktion zwischen Computern und Menschen in natürlicher Sprache beschäftigt. Ziel ist es, Maschinen zu befähigen, Texte und gesprochene Sprache zu verstehen, zu interpretieren und darauf zu reagieren." ] } ], @@ -386,7 +388,7 @@ "text": [ "/Users/amna.mubashar/Library/Python/3.9/lib/python/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n", " warnings.warn(\n", - "Batches: 100%|██████████| 1/1 [00:03<00:00, 3.23s/it]\n" + "Batches: 100%|██████████| 1/1 [00:01<00:00, 1.11s/it]\n" ] }, { @@ -455,7 +457,7 @@ { "data": { "text/plain": [ - "\n", + "\n", "🚅 Components\n", " - embedder: SentenceTransformersTextEmbedder\n", " - retriever: InMemoryEmbeddingRetriever\n", @@ -539,7 +541,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Batches: 100%|██████████| 1/1 [00:01<00:00, 1.31s/it]\n" + "Batches: 100%|██████████| 1/1 [00:00<00:00, 1.96it/s]\n" ] }, { @@ -779,7 +781,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "Batches: 100%|██████████| 1/1 [00:00<00:00, 20.30it/s]\n" + "Batches: 100%|██████████| 1/1 [00:00<00:00, 16.47it/s]\n" ] }, { @@ -794,7 +796,7 @@ "import json\n", "\n", "## Parse function calling information\n", - "function_call = json.loads(response[\"replies\"][0].content)[0]\n", + "function_call = json.loads(response[\"replies\"][0].text)[0]\n", "function_name = function_call[\"function\"][\"name\"]\n", "function_args = json.loads(function_call[\"function\"][\"arguments\"])\n", "print(\"Function Name:\", function_name)\n", @@ -917,7 +919,7 @@ " while True:\n", " # if OpenAI response is a tool call\n", " if response and response[\"replies\"][0].meta[\"finish_reason\"] == \"tool_calls\":\n", - " function_calls = json.loads(response[\"replies\"][0].content)\n", + " function_calls = json.loads(response[\"replies\"][0].text)\n", " print(response[\"replies\"][0])\n", " for function_call in function_calls:\n", " ## Parse function calling information\n", @@ -938,7 +940,7 @@ " else:\n", " messages.append(response[\"replies\"][0])\n", " break\n", - " return response[\"replies\"][0].content\n", + " return response[\"replies\"][0].text\n", "\n", "\n", "demo = gr.ChatInterface(\n",