diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1632ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Jupyter Notebook +.ipynb_checkpoints +*.ipynb + +# Virtual Environment +venv/ +env/ +ENV/ +.env + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# Testing +.coverage +htmlcov/ +.pytest_cache/ +.tox/ + +# Distribution +*.tar.gz +*.whl + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local \ No newline at end of file diff --git a/notebooks/lesson_2_baseline.ipynb b/notebooks/lesson_2_baseline.ipynb index 6f03352..ad1f2f2 100644 --- a/notebooks/lesson_2_baseline.ipynb +++ b/notebooks/lesson_2_baseline.ipynb @@ -1,131 +1,110 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, - "metadata": { - "vscode": { - "languageId": "shellscript" - } - }, - "outputs": [], + "cell_type": "markdown", + "id": "0c9a132e", + "metadata": {}, "source": [ - "! pip install langchain-anthropic langgraph langchain_core" + "# Lesson 2: Baseline Email Assistant\n", + "\n", + "This lesson builds an email assistant that:\n", + "- Classifies incoming messages (respond, ignore, notify)\n", + "- Drafts responses\n", + "- Schedules meetings\n", + "\n", + "We'll start with a simple implementation - one that uses hard-coded rules to handle emails.\n", + "\n", + "![Memory Course App](img/memory_course_email.png)\n", + "\n", + "First, install the prerequisite packages:" ] }, { "cell_type": "code", "execution_count": 1, + "id": "01b255f0", "metadata": {}, "outputs": [], "source": [ - "import os, getpass\n", - "\n", - "def _set_env(var: str):\n", - " if not os.environ.get(var):\n", - " os.environ[var] = getpass.getpass(f\"{var}: \")\n", - "\n", - "_set_env(\"ANTHROPIC_API_KEY\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Baeline app" + "%%capture stderr\n", + "%pip install -e ..\n", + "%pip install -U -q langchain-anthropic langgraph langchain" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 3, + "id": "bc3d445b", + "metadata": { + "lines_to_next_cell": 2 + }, + "outputs": [], + "source": [ + "import os\n", + "from getpass import getpass\n", + "\n", + "if not os.environ.get(\"ANTHROPIC_API_KEY\"):\n", + " os.environ[\"ANTHROPIC_API_KEY\"] = getpass(\"ANTHROPIC_API_KEY: \")" + ] + }, + { + "cell_type": "markdown", + "id": "0dc3e26f", + "metadata": {}, + "source": [ + "### Define Triage\n", + "\n", + "The triage step is the \"first line of defense\" against incoming emails. It helps the assistant determine if the email should be responded to, ignored, or notified." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6f5f75bc", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Classification: respond\n", + "Reason: 1. This is a direct question from a team member (Alice) about technical documentation\n", + "2. The question is specific and requires John's expertise as the senior software engineer\n", + "3. The issue relates to API documentation for authentication service, which is important for the team's work\n", + "4. Missing API endpoints in documentation could lead to confusion or implementation issues\n", + "5. As team lead, John should provide clarity on whether these endpoints were intentionally omitted or need to be documented\n", + "6. A timely response would help maintain documentation accuracy and team productivity\n" + ] } ], "source": [ - "import sys\n", - "sys.path.append('..')\n", - "import src.memory_course.prompts\n", - "import src.memory_course.utils\n", - "import src.memory_course.schemas\n", + "from memory_course.schemas import Router\n", + "from langchain.chat_models import init_chat_model\n", + "from memory_course.prompts import triage_system_prompt, triage_user_prompt\n", "\n", - "# Reload the module in case any changes were made\n", - "import importlib\n", - "importlib.reload(src.memory_course.prompts)\n", - "importlib.reload(src.memory_course.utils)\n", - "importlib.reload(src.memory_course.schemas)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "# LLM \n", - "from langchain_anthropic import ChatAnthropic\n", - "llm = ChatAnthropic(model=\"claude-3-5-sonnet-latest\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Email Assistant\n", + "llm = init_chat_model(\"anthropic:claude-3-5-sonnet-latest\")\n", "\n", - "![Memory Course App](img/memory_course_email.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define Triage \n", + "# We'll use structured output to generate classification results\n", + "llm_router = llm.with_structured_output(Router)\n", "\n", - "Create and test logic for triage." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "from pydantic import BaseModel, Field\n", - "from typing import Literal\n", - "from src.memory_course.schemas import Router\n", + "# Example user profile\n", + "profile = {\n", + " \"name\": \"John\",\n", + " \"full_name\": \"John Doe\",\n", + " \"user_profile_background\": \"Senior software engineer leading a team of 5 developers\",\n", + " \"triage_rules\": {\n", + " \"ignore\": \"Marketing newsletters, spam emails, mass company announcements\",\n", + " \"notify\": \"Team member out sick, build system notifications, project status updates\",\n", + " \"respond\": \"Direct questions from team members, meeting requests, critical bug reports\",\n", + " },\n", + "}\n", "\n", - "# Define and augment LLM with structured output\n", - "llm_router = llm.with_structured_output(Router)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "# - From user profile memory - \n", - "full_name = \"John Doe\"\n", - "name = \"John\"\n", - "profile = \"John is a senior software engineer who leads a team of 5 developers\" \n", - "\n", - "# - From triage examples in memory - \n", - "triage_no = \"- Marketing newsletters\\n- Spam emails\\n- Mass company announcements\"\n", - "triage_notify = \"- Team member out sick\\n- Build system notifications\\n- Project status updates\"\n", - "triage_email = \"- Direct questions from team members\\n- Meeting requests from stakeholders\\n- Critical bug reports\"\n", - "\n", - "# - From email parsing during app ingestion - \n", - "email_thread = \"\"\"\n", + "# Example incoming email\n", + "email = {\n", + " \"from\": \"Alice Smith \",\n", + " \"to\": \"John Doe \",\n", + " \"subject\": \"Quick question about API documentation\",\n", + " \"body\": \"\"\"\n", "Hi John,\n", "\n", "I was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?\n", @@ -135,188 +114,139 @@ "- /auth/validate\n", "\n", "Thanks!\n", - "Alice\"\"\"\n", - "author = \"Alice Smith \"\n", - "to = \"John Doe \"\n", - "subject = \"Quick question about API documentation\"" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Router(classification='respond')" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from src.memory_course.prompts import triage_system_prompt, triage_user_prompt\n", + "Alice\"\"\",\n", + "}\n", "\n", - "# Format the system prompt\n", - "triage_system_prompt_formatted = triage_system_prompt.format(\n", - " full_name=full_name,\n", - " name=name,\n", - " user_profile_background=profile,\n", - " triage_no=triage_no,\n", - " triage_notify=triage_notify,\n", - " triage_email=triage_email\n", + "system_prompt = triage_system_prompt.format(\n", + " full_name=profile[\"full_name\"],\n", + " name=profile[\"name\"],\n", + " user_profile_background=profile[\"user_profile_background\"],\n", + " triage_no=profile[\"triage_rules\"][\"ignore\"],\n", + " triage_notify=profile[\"triage_rules\"][\"notify\"],\n", + " triage_email=profile[\"triage_rules\"][\"respond\"],\n", ")\n", "\n", - "# Format the user prompt\n", - "triage_user_prompt_formatted = triage_user_prompt.format(\n", - " author=author,\n", - " to=to,\n", - " subject=subject,\n", - " email_thread=email_thread)\n", + "user_prompt = triage_user_prompt.format(\n", + " author=email[\"from\"],\n", + " to=email[\"to\"],\n", + " subject=email[\"subject\"],\n", + " email_thread=email[\"body\"],\n", + ")\n", "\n", - "# Mock the email input \n", - "messages = [{\"role\": \"system\", \"content\": triage_system_prompt_formatted},\n", - " {\"role\": \"user\", \"content\": triage_user_prompt_formatted}]\n", - "\n", - "# Test\n", - "llm_router.invoke(messages)\n" + "# Test classification\n", + "result = llm_router.invoke(\n", + " [\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": user_prompt},\n", + " ]\n", + ")\n", + "print(f\"Classification: {result.classification}\\nReason: {result.reasoning}\")" ] }, { "cell_type": "markdown", + "id": "f97a1bf5", "metadata": {}, "source": [ - "### Define Tools \n", + "### Define Tools\n", "\n", - "Define tools that the agent can use." + "Define tools that the agent can use. These are place-holder tools for the purpose of testing the LLM." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, + "id": "11ec23f0", "metadata": {}, "outputs": [], "source": [ - "# Define tools \n", - "# TODO: Replace with actual tools\n", "from langchain_core.tools import tool\n", "\n", + "\n", "@tool\n", "def write_email(to: str, subject: str, content: str) -> str:\n", - " \"\"\"Write and send an email.\n", - "\n", - " Args:\n", - " to: recipient email address\n", - " subject: email subject line\n", - " content: email body content\n", - " \"\"\"\n", - " # Dummy response - in real app would send email\n", + " \"\"\"Write and send an email.\"\"\"\n", + " # Placeholder response - in real app would send email\n", " return f\"Email sent to {to} with subject '{subject}'\"\n", "\n", "\n", "@tool\n", "def schedule_meeting(\n", - " attendees: list[str],\n", - " subject: str,\n", - " duration_minutes: int,\n", - " preferred_day: str\n", + " attendees: list[str], subject: str, duration_minutes: int, preferred_day: str\n", ") -> str:\n", - " \"\"\"Schedule a calendar meeting.\n", - "\n", - " Args:\n", - " attendees: list of attendee email addresses\n", - " subject: meeting subject/title\n", - " duration_minutes: length of meeting in minutes\n", - " preferred_day: preferred day for the meeting\n", - " \"\"\"\n", - " # Dummy response - in real app would check calendar and schedule\n", + " \"\"\"Schedule a calendar meeting.\"\"\"\n", + " # Placeholder response - in real app would check calendar and schedule\n", " return f\"Meeting '{subject}' scheduled for {preferred_day} with {len(attendees)} attendees\"\n", "\n", "\n", "@tool\n", "def check_calendar_availability(day: str) -> str:\n", - " \"\"\"Check calendar availability for a given day.\n", - "\n", - " Args:\n", - " day: the day to check availability for\n", - " \"\"\"\n", - " # Dummy response - in real app would check actual calendar\n", - " return f\"Available times on {day}: 9:00 AM, 2:00 PM, 4:00 PM\"\n", - "\n", - "# Augment the LLM with tools\n", - "tools = [write_email, schedule_meeting, check_calendar_availability]\n", - "tools_by_name = {tool.name: tool for tool in tools}\n", - "llm_with_tools = llm.bind_tools(tools)" + " \"\"\"Check calendar availability for a given day.\"\"\"\n", + " # Placeholder response - in real app would check actual calendar\n", + " return f\"Available times on {day}: 9:00 AM, 2:00 PM, 4:00 PM\"" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 6, + "id": "167a1474", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "[{'name': 'check_calendar_availability',\n", - " 'args': {'day': 'Tuesday'},\n", - " 'id': 'toolu_01X8hF5kgJrWJBsT6m7Lkug6',\n", - " 'type': 'tool_call'}]" - ] - }, - "execution_count": 38, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "John, you have three available time slots on Tuesday:\n", + "- 9:00 AM\n", + "- 2:00 PM\n", + "- 4:00 PM\n", + "\n", + "Would you like me to schedule any meetings during these times?\n" + ] } ], "source": [ - "# Prompt for agent\n", - "from src.memory_course.prompts import agent_prompt\n", + "# Create agent\n", + "from memory_course.prompts import agent_system_prompt\n", + "from langgraph.prebuilt import create_react_agent\n", "\n", - "full_name = \"John Doe\"\n", - "name = \"John\"\n", - "agent_prompt_formatted = agent_prompt.format(full_name=full_name, name=name)\n", - "\n", - "# Mock the user's request\n", - "messages = [{\"role\": \"system\", \"content\": agent_prompt_formatted},\n", - " {\"role\": \"user\", \"content\": \"What is my availability on Tuesday?\"}]\n", + "agent = create_react_agent(\n", + " \"anthropic:claude-3-5-sonnet-latest\",\n", + " tools=[write_email, schedule_meeting, check_calendar_availability],\n", + " prompt=agent_system_prompt.format(**profile),\n", + ")\n", "\n", "# Test the agent\n", - "result = llm_with_tools.invoke(messages)\n", - "result.tool_calls" + "response = agent.invoke(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": \"What's my availability on Tuesday?\"}]}\n", + ")\n", + "response[\"messages\"][-1].pretty_print()" ] }, { "cell_type": "markdown", + "id": "fb4d4c67", "metadata": {}, "source": [ "### Build agent\n", "\n", - "Combine triage with tool calling agent \n", + "Combine triage with tool calling agent\n", "\n" ] }, { "cell_type": "code", - "execution_count": 39, - "metadata": {}, - "outputs": [], - "source": [ - "# Prompt for agent\n", - "from src.memory_course.schemas import State\n", - "from src.memory_course.utils import parse_email" - ] - }, - { - "cell_type": "code", - "execution_count": 58, - "metadata": {}, + "execution_count": 7, + "id": "55cf741a", + "metadata": { + "lines_to_next_cell": 2 + }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAUkAAAGMCAIAAADYzcf4AAAAAXNSR0IArs4c6QAAIABJREFUeJzt3XdAE+f7APA3kzDCCns5WCqoOOuqCuKmxYF7K1YrrtZq3WKtWKu1rqp1WzfixoqIoiKCDLHiAHEge2SQkD1/f1x/KV9kBLjcJcf7+Qsuyfs+gTy59+7ee16SRqMBEAQRDhnvACAI0guY2xBETDC3IYiYYG5DEDHB3IYgYoK5DUHERMU7AIhoOKVyYZVSJFDKxGq5VI13ODqhm5IpFJK5JcXciurUloF3OOggwevbECqK8iQfsoUfskUu7RgyqdrMkmrFohnLp8vElMKrkIsFSqVC8+mNuJ2febvO5p16WwIS3pG1AMxtqKVK3kuexHJsHOn2ribt/M2ZNkY/GPz4UvThpejTa1G3QJtugdZ4h9NMMLehFkmMruCVy/t9ZUeYoayWRgOSb7Bz0qtHznZ29TK+dwdzG2omYZXy3PaCUXOd3bxN8Y5FjyRC1d0z5W06mnUdZGQ7cJjbUHNIxeoLOwqmrPIwMW0Vl1qSrrJtnel+fSzxDqQJYG5DTcYrV9w8XDxzQ1u8A8HUw5hKCo00INQO70B01Sq+dCF0nf/10/S1rSuxAQCDwuylQlVOejXegegK5jbUNPGnyyet8CBT8I4DD8HTHAtyxOxiOd6B6ATmNtQEuZnVgARYLnS8A8GNX1/LpKuVeEehE5jbUBM8ucnu/xUL7yjw5OplSqGRPr0R4x1I42BuQ7p681TQub+1uRUWU1NUKtXz58/xennDBoTa56QL9NQ4imBuQ7rKzazGbILKli1boqKi8Hp5w2ydaOUFUgFHoaf20QJzG9KJQq4p+yTFbJqKTCZr3guRa7rNfrmO2vtbfMgW6bWLljP6qb8QNj69Fvv1sdJHy48fP963b19RUZGLi0tYWNikSZMiIyPv3r0LAOjZsycA4MaNGy4uLs+fPz969Cgy0vbz81u+fHnHjh0BAAkJCatXr965c+fp06dfvXo1a9as8vLyz1+ObsxeXZkvkqrQbRN1MLchnXDLZXQG+qM8sVj8448/tm/ffv369e/evausrAQAzJ07t7y8vLi4+KeffgIA2NnZAQBKSkpkMll4eDiZTL506dLSpUtv3rzJYPx7jLB9+/aIiIhvv/3Ww8NDKpV+/nJ0MVnUoveGfjoN5jakExFfae9mgnqzXC5XJpMFBQWNHDlSu9HDw8Pa2prD4QQEBGg3jhw5ctSoUcjPnTp1Wrhw4fPnz/v06YNsmTRpUkhIiPbJn78cXeZMilig0lPjaIG5DelELFCZW6L/aXF1de3SpcuxY8dMTU3HjRtHp9d75ZxEIiUmJp45c+bjx49mZmYAAA6Ho320d+/eqMfWEBIwtaBIhCpTC8OdxAPPpUE6IVNJFCr6nxYSibR3796QkJDdu3ePGzfu2bNn9T3z6NGjK1eu7NSp065du5YvXw4AUKv/K+qCZDuWGGZktWHvuWFuQzoxYZCFVXq56mNhYbF69erLly9bWFh8//33YvG/x7E172KSyWQnTpwYM2bMihUrAgICOnfu3Giz+r4JilehMLc03J02zG1IV2aWFJFAqY+WketVrq6ukydPFgqFJSUlAABTU1MOh6PdM0skEplMhpwYBwBUVVXV2m/XUuvlqJMIVQxzioFXXILH25BObBzoVZXo77cVCsX48eOHDh3q6el56dIlCwsLNzc3AED37t1v3LgRFRUVEBBgaWk5cOBALy+vCxcusFgsoVB4+PBhMpn87t27+pr9/OXohi2uVnv4Yn0U0FSUyMhIvGOAjICZBTUxuqJboA26zYpEooKCgsTExPv379vb20dGRiK57eXlxefz4+Linj17Zm1t3bt37+7duycnJ0dHR3/69GnJkiVt2rS5fPnytGnTPn36lJCQMHHiRGvr/+qifP5ydMN+mcxnmFNcvQy64AyszQDpKnpX4eAJDg7u6F8JMzoXdhQET3W0czXoPwUck0O68unBLP0gbSC3MzIyfvjhh8+3M5nM6uq6SxosW7Zs7NixqIZZm1AorHnpu6YuXbq8ePHi8+3h4eHTp0+vr0GRQGVuTTPwxIb7bahp/vj+3aKdXqR6zsBKpVIul9ukBq2srMzNzdEJrh5qtbqsrKxJL7G0tLSwsKjv0YRz5a5eph17G3rtNJjbUBNkJVaJBEojqhmGOl654u/jJdPWtME7kMbBa2BQE3QLtOaWyWVi41gJSB9eJvMHjLHHOwqdwNyGmiZwosP5HQV4R4GP9Hgu3ZTcpqOhX/1CwNyGmoZpQx0cZn/tQDHegWDt5RNBRaHsi5G2eAeiK3i8DTUHu1j++HrlmEWueAeCkZdPBNxS2cDxxjEaR8D9NtQcdq70boE2JyI/igXEP/ZOusauKJQaV2LD/TbUIiK+8v7FCktbWr+vWDQTAu4n3qQJntzkfDGC5d/f0K94fQ7mNtRS2Y/5T2LZ3YNsndsxiLHun4Cj+JAtyvtHaGNP6/+1nSHfpN0AmNsQOl4+EeRlVVcUSjv3t1arNeaWVKYNDZCM49NFpZGruQpxtUouVRe+FSsV6vb+Fn59rWwcaXiH1nwwtyE0KWTqTzmSaq5CXK1UyjViIcrlC3g8XkVFha+vL7rNWlhT1UqNmSXFworq4MFgORNh4RSY25AxSUpKunz58u7du/EOxAgQ8PwHBEEwtyGIsGBuQ8aERqM5OjriHYVxgLkNGROFQlFeXo53FMYB5jZkTMhksqkpES6hYwDmNmRM1Gq1RCLBOwrjAHMbMiYUCsXKSi9LDhIPzG3ImKhUKj6fj3cUxgHmNmRMaDSak5MT3lEYB5jbkDFRKBRNLWzYasHchiBigrkNGRMKhYL9kp1GCuY2ZExUKpV2oU+oYTC3IWNCoVCYTCbeURgHmNuQMVGpVPUtPwTVAnMbgogJ5jZkTKhUqp1d612xqElgbkPGRKlUstlsvKMwDjC3IYiYYG5DxoRKpcLaDDqCuQ0ZE6VSCWsz6AjmNgQRE8xtyJjQaDRnZ2e8ozAOMLchY6JQKEpLS/GOwjjA3IYgYoK5DRkTWMNYdzC3IWMCaxjrDuY2BBETzG3ImMD65LqDuQ0ZE1ifXHcwtyFjAu8D0x3MbciYwPvAdAdzG4KICeY2ZEwoFIqlpSXeURgHmNuQMVGpVAKBAO8ojAPMbciYwHlpuoO5DRkTOC9NdzC3IWMC1/rTHcxtyJjAtf50B3MbMiYUCsXGxgbvKIwDSaPR4B0DBDViwoQJcrlco9FIJBKZTGZjY4P8fPfuXbxDM1xUvAOAoMb16dPn3LlzJBIJ+RVZ7s/HxwfvuAwaHJNDRmDmzJmurq41tzAYjNDQUPwiMgIwtyEjYG9vP3DgwJpbXFxcxo4di19ERgDmNmQcpk+frt11m5iYhIWF0el0vIMyaDC3IePg5OQ0bNgw5Gc3Nze4024UzG3IaEyYMMHDw4PBYIwfP55Go+EdjqGD58lbu6pKBa9coVKp8Q5EF2ZD+kzLysrq4jns3T9CvINpHAmQzK0odi4mVDoJh97h9e1Wq+itJCOBJ+Aq3DuYC6uUeIdDQBQKqZqnkIlUXt0s+oWwMO4d5nYrVZYve3i5cugMV5oJDruU1ubFQ55Mogia5IBlp/B4uzXilMoTzpePCneDiY2NLoNsGBa0R1cqsewU5nZrlHGX1/crTPchUOcBNtwyRVWFArMeYW63Rp9yRNZ28OIw1ihUEqdMjll3MLdbHZlYw7Sm0U3hvx5r1o50YRXcb0N6QyJpqnnYfcIgLaUcqJTYnbqGuQ1BxARzG4KICeY2BBETzG0IIiaY2xBETDC3IYiYYG5DEDHB3IYgYoK5DUHEBHMbgogJ5jYEERPMbUgnQqHwbV5Ow8/58OHd16GBj5MfYBUUalQqVXb2c7yjQBnMbUgn4d9Mvn37esPPoVKpFhZMKsX4ivDt+G3Lrt1ReEeBMuP7N0C4kMsbuvFYo9GQSCQPj7bnzt5oastFRQVubh4ti+5/wmjGC+UyGcY9YgDmNtS4yVNDeDzuteuXrl2/5OjodOFcLJ9fNWZc8MIFy/Le5SYnP/D27jBqZOj2XzcDAHb8+kfPHl9UVJQfO3Hg6dNkkUjo7t5m6pQ5wUNGIK1xOOx9+3dkZj6l0mg9enzx6NG9Pw+eadfOEwBw/UZM9KUzbHaFk5PLkKARkybOMDExaSCwPXu3P3x074fv1x849HtxceHOHQd6dO8dH3/r7PkTJSVFLJbd6FFjp02dQyaTlUrl0OF95ocvnjplNvLaNeuW8/lVB/af/OXXyMQHdwEAgUN6AgDOnb3h7ORSXzCfv/G9u49i8k9oMpjbUOMiN/266sfFAV17TAibRquxmseZM8dCQyf8tvMQhUKxtrL5Zv6Sw0f2IQ8pVcqcnFehX4dZWVo/enx/a9R6V1f3jh38VCrV2nXLuTzOsmWruVz2kaP7uwX0RBL75KnDl2LOjBs7uU2b9oWF+Rej/yoqLli7+qeGYxOJhMdOHFi+bLVUKunerdedO7G//Bo5ZMiIeXMXvX6dffzEQQDAjOnzGmhh+tS5lRXlpaXFa1b/BABg2do1GkzNN47C31c/YG5Djevg24lKpbJYdp07B9Tc3qlT5/B5Edpfu3bprv3Zxdn15PFLyHh15MjQseODk5MfdOzg9+bNy7d5OZs2/jJ4UDAAoKAg/3bcDblcLhDwz547vn7d1kEDhyAtsFj2v+/etjjiB0umZQOxyeXyH75f37GjPzJCPnr8j86dA9av/RkAMPDLoOpqwYWLp8aPm9LAAkNubh5WVtZcHkf77tjsyvqCqfONGyaY21Dzde/eu4FH371/e/LUn7m5r5ET0VwuBwBQUVkOAHBxcUOe4+bmoVarJRJxZuZTpVK5NWr91qj1yENIdW12ZUXDuc1gMJDERg7d2ezKSRNnaB/t1avv37evFxUXtG/npfv7aiAYFsuu0TduIGBuQ83HYJjW99CzrPQfVy/pFtBz1cpN5mbmGyNXqjVqAICrqzsAIDv7uY93BwDAmzcv7ezsraysOVw2ACBq624He8ea7Wi/Bepjamqm/VkoEgIArK1ttVuYTEskJ5uU2w0EIxIJG37jhgPmNqSrJi1Tcfr0URcXt6itu6lUKgDA9P+TwdenY6+efQ4f2VteXlrF5yU/ebh+3VZtEgIAPDzaNjtCJBX5/CrtFh6PizTe6Nnsmu8OlWBwB69vQzoxZZhyOGzdn88XVHl5+iCJLZfLxRKxWv3vkmNLFq90c/MoLPpkbWWzf98J5MC7W7deJBLp6rWL2hYkEklTg2Sx7JwcndPSkrVbHj5MYDAYXl6+FAqFybRkc/6t/q/RaCoqyrRPYzBMuVyONkJUgsEd3G9DOuncudu9+3Hnzp9kMi39OnVBDjsbEBDQ886dm3/fvm7JtLp0+Wx1tSD/43uNRqNSqRYtnjUhbLqrqzuJRKquFgiFQgsLCzdX93FjJ1++cn7t+u8G9B/M4bCvXY/eFrUHGbrrbvasBb/8Grlj55Zevfo+e5b2OPnBrJnfmJqaAgB69+p7N/5W9269bG1Y0ZfOFBTke/9/4127dL8dd2PX71Gd/QOYTMt+/QaiEgy+YG5DOlnwzVIul336zFFrK5tFi75vNLfnzv6Wy2Hv27+DybQMGT1uYtj0Xbujsp5ndO/Wq2ePPqfPHFUq/11dkGnB3LvnWNu27SMWfe/g4Hj16sX09BQWy+7LAYH2dk1e/GT48BCpTHop5mz83Vt2LPtv5i+ZPGkm8lDEohUymeyX7ZvMzS2+/ipMKpMKBHzkoaFDR+W+fR1/91ZKatKI4V/16zcQlWDwBdf6a3XkEvXJn/KnrG6PVwAqlQq5LKzRaEpKi8PnT544Yfqc2QvxigczGfEcKxa5e5ANNt3B/TaEKZlMtmjxLAcHp65dutNo9OzsLKlU6unpU9/zU1Mfb922vs6H9u890aZNO30Ga9xgbkOYIpFIw4aOvn//zomTh+h0ert2Xps2/jLwy6D6nh8Q0PPwn+fqfMjoBskYg7kNYYpOp0+aOKPm9JKGMRgMZHY31FTwGhgEERPMbQgiJpjbEERMMLchiJhgbkMQMcHchiBigrkNQcQEcxuCiAnmNgQRE8xtCCImmNutDplCtnVuqDAwpCc0ExLDDLu6qDC3Wx0qHUiFSj5bgXcgrU7Je7GNY73lVlEHc7s18unOrPhkfEWCjJpSrqFQgFNbBmY9wtxujXqPsH33nF+YK8Y7kFbk7pni/l/bYbm+EKy70kppNODib4Xt/ZnmNjRbRxP4MdAHEgmIBEo+W5F5lz1usZudK3YDcpjbrd0/j/hFeWIAAKekoaX8hMJqMpliZmbWwHP0R6lUKhQKpJ4huoTCagtzC6C3nSmVTjIxIzu1Me0ZbGNihvUYGeY21IgXL17k5uZOmDABrwCOHz8ukUgiIvSyRs/s2bNPnjypj5ZxB3MbqltCQsKGDRtSUlLwDgS8evWKxWI5OTnpr4uqqipra2v9tY8LeC4Nqu39+/cAADab/fDhQ7xjAQAAPz8/vSY2AGDbtm0cDkevXWAP5jb0n6qqqqlTpxYWFgIAJk+e3MDal1havXq1tpi5nmzfvv3w4cN67QJ7cEwOAQDA27dvfXx8Xr9+TaFQfH198Q7nPyUlJQsWLLh58yY23RUWFrq7u2PTl77B/TYEjh49um/fPgBAp06dDCqxAQBmZma//vorZt0dP36cx+Nh1p1ewf126yWRSFJTUwMDA7Oysrp164Z3OIYiISEhODgY7yhQAPfbrVRhYeHQoUNZLBYAwJATe+vWrbdu3cKyx+Dg4Js3b1ZUVGDZqT7A3G51rl69iqzv8fjx4y5duuAdTiPy8/OxPwD+6quvNm/e/Pr1a4z7RRcck7cukZGRDAZj9erVeAcC6R3M7VYhJycnPz9/xIgRFRUVDg5wGS1dXb58edCgQXZ2jSxIbJjgmJzgNBrN+/fvt2zZEhAQAAAwrsR++/btli1bcAxg/Pjx33zzTXFxMY4xNBvMbSI7duyYQCCwsrI6e/asvqd26UNOTo5arcY3hitXrri6uuIbQ/PAMTlh7dixw9zcfNGiRXgHQgQbNmzAdwTRDDC3iebjx48JCQnz588XiUTm5uZ4h9MiIpGITqfTaDS8AwFVVVWrVq0yrnmpcExOKCKRaOXKlcjUC2NPbABAeHj4x48f8Y4CAACsra2NK7FhbhNHbGxsUVERhUKJiYlp164d3uGgo7Ky0qBmd7948eLSpUt4R6ErmNtEcOLEiYyMDDc3NwYDu1J7GEhISNBHuZVm69KlC5/PP378ON6B6AQebxs3ZPJzQUGBh4cH3rFAhgXut42VXC4fOnQosqMmZGI/evRox44deEdRt9jYWJFIhHcUjYC5bZSKi4tlMtnFixcHDBiAdyz68unTJyaTiXcUdevTp8+4cePwjqIRcExuZD5+/Dhx4sSEhAQrKyu8Y2nVZDKZVCo15P8C3G8bmY8fPz59+tSQP1JoqaqqkslkeEdRLxMTk7KysqKiIrwDqRfMbeOQnp4eFhYGAAgKCiKTW8V/beHChUjlNoPl6+u7ePFigw2SincA+FCr1WKxMa2YIxQKT548KRQKa26k0WgmJkRekdPZ2RnvEBpx4cKFly9fGtRFeK1WerytUqmMomatUqmUy+X1LehhampqsGebWg+VSqXRaKhUg9tNtorRnfESCoV4rdSDL7VabfgXmRAUCmXOnDkGWKQF5raBUigUyDRmvAPBx7Nnz1asWIF3FLratWsXUqnKoMDcNjgajYbD4VAoFLwDwRObzTaiaif29vbr1q3DO4raYG4bFo1Go1KpbG1ta50Mj4uLGzVqFJfLxS80TAUFBW3YsAHvKJrm0KFDtU524gvmtgERiUTIWRkSlkuwGySNRmN0fwQfH5/NmzfjHcV/DO7knoHA/rOlUCgoFEoruXbdqG3btvXs2TMkJATvQJogKCioc+fOUqnUQO7Gg7n9r6SkpG3btm3YsOHy5ctv374NCwubOXOmVCo9derUgwcP5HK5m5vbuHHjBg0aBAAoKirav39/bm4uk8ns1atXREQEmUyeMGGCj4+PVCr98OGDpaXlkCFDpk6dilwa4XK5R44cycjIUKlUnTp1mjdvHnKL9bVr1x4+fDh27NhTp05xuVwvL6+lS5dqL5a+f//+0KFDeXl5NjY2bm5ueP+FsGb4F7c/Z21tzefzYW4bogMHDsyaNWvGjBmurq5qtXrz5s3l5eWTJk2ytrb+559/tm/fLpVKhw8fvmfPnqKiogULFojF4hcvXmh3tkVFReHh4SwWKy0tLTo6WiQSffvtt1KpdM2aNQKBYO7cuSYmJpcuXVq7du2RI0csLCwAALm5udHR0UuXLlWpVPv27du1a9fvv/+OrPvx448/Wlpazp49m0KhnDt3Du+/DaYiIyPxDqE5aDTa9u3bR44cGRQUhHcsMLf/11dffaVdCyopKenVq1cnTpxAFtYZPHiwVCq9fv368OHDy8vLPT09R4wYAQCoeT/Ql19++eWXXyKL5gkEgtu3b0+bNi05ObmwsDAqKgqpIuzn5zd37twbN25MnToVedVPP/1ka2sLAPj666+PHDkiEAgsLS2PHTtGJpN37dqFXAYjk8l//PEHTn8VHBQVFTk5ORnghJBGrVq16sKFCzC3DQ6Sfoj09HSlUjl37lztFpVKhRQhCwoKio6OPnjw4OTJk21sbOpsqmfPnnFxce/fv3/x4oW5ubm2ZUdHR3d397dv3yLTzpDpZchDSPFwDodDp9OfPXs2evRo7fXt1nZJbPLkyXfv3jXG3La3t1+yZAneUQCY27XVrODD4/FsbW23bdtW8wnIp23WrFnW1tYXL16Mj4+fO3fuV1999XlTyLeARCIRi8W1bttiMplcLpfNZtf67CK/qtVqHo+nVCodHR318BaNgFwud3R0NKhqSk1SUVGRkJCgHZfhBZ6VrZeFhQWfz3dwcHCvATnBQyKRxowZc+zYsT59+hw8ePDVq1efvxyZr25nZ8disaqrq2s+xOPxzM3NG5ibgXwXVFVV6eedGTo6nX758mW8o2g+BweHBw8eZGZm4hsGzO16BQQEqFSqv//+W7tFIpEgPyD3FZuZmc2YMQMA8O7du1qv1Wg08fHxFhYW7u7uHTt2rK6uzsnJQR76+PFjSUmJn59fA12bmZm5uLgkJSUhM09bG7VaLZVK8Y6iRaKionC/RQ+OyesVFBQUFxd37Ngx5MzZhw8fUlJSDh06xGAwtm3bZmZm1r179/T0dACAt7c38pJHjx7Z2tqamJgkJSW9ePFi7ty5pqamgYGB0dHR27ZtmzJlColEOnfunJWV1ejRoxvufdq0aTt27FixYsXQoUPJZPL169cxedMG4cWLF/v37z969CjegTSfnZ0d7nNmYW7Xi0aj/fzzzydOnHj48OHt27ddXFxGjRqFHBL7+vomJCQ8efKExWItXbq0U6dOyEtYLFZCQkJxcbGdnd28efPGjx+PHEX//PPPR44cOXLkiFqt9vPzW7BgQX1n4LQCAwOFQuGVK1eOHz/u4eHRoUMHQy7xgS6JROLj44N3FC31999/azSaRr/E9Qfev42aCRMmDB8+PDw8vIHnqNVqFGeewfu3DVlRUVFERASOAy54vI0dPp+P+6qURkEmk2lPbRgvNze3ixcv4vgfh7mNEaVSaWpqaowXbLF3/vz5Y8eO4R0FCkgkEo7lHOFHDTUNrxQFs1p3ZDLZGFcL/xyPxwsPD4+NjcWld/iBw4JMJiORSHQ6He9AjMPMmTPxDgEdTk5O/v7+nz59atOmDfa9w3NpeqfRaLhcLjIpHV1EPZfG5XJNTEwIsMYwvuDxtt5pNJpGr3hBNf36668pKSl4R4GO6upq5N4B7LXSMTmFQsF9agFUHzMzM8IUgWQymYGBgenp6diXkWmlY3LMxMfHP3/+fNWqVXgHAuHmwIEDQ4cO1U5exEwr3W9jJikpadKkSXhHYWSEQiGDwSDMlYVFixbh0i/cb0MGZ8qUKZs3bybAtFMEj8crKCjo2rUrxv3Cc2l6VFFR8fktYlCj5HI5ka4XUiiU5cuXY98vzG09OnjwoAEuJWP4Ll++3LZtW7yjQI2lpeWwYcOwX3+OIIc0hkkoFPbu3RvvKIwPwfbbAIA1a9Zg3yk83oYMzuDBg2NjY5E6sMSAVObo0KEDlp3CMbm+CIVCba0VqElMTU1xL1qCrqKiopMnT2LcKcxtfXn06NHZs2fxjsIo3b59m0aj4R0Fmrp3795wFS19gGNyfUlOTlapVAMHDsQ7EOPD4XD0Mf2+tYH7bX3p378/TOxmkEqloaGheEeBvosXL2K8yifMbX0pKCjg8Xh4R2F81Gq1vb093lGgLzExEePzLzC39eXgwYNIFVSoSczMzK5evYp3FOibMmUKxmf+4fVtfWnXrh2yBhDUJGq1WigUWlpa4h0IypAVYLEEz6VBhqWsrAzHOkT6k5OTU15ejmWGwzG5viQlJbWeiuIoUqvVRJq1osVms69cuYJlj3C/jbKhQ4eSSCQSiSQQCJDCpiQSydLSsuFKiRDhsdnshISEyZMnY9YjPN5GmYWFRWFhIfIzspqXRqMZMGAA3nFBOLOzs8MyseGYHH1Dhw6ttaVNmzawPIPu3r17h3EOYOb8+fNYdgdzG2WTJ0+uWbBWo9EEBAQQpswANgg2mVzr8OHDAoEAs+5gbqPM1tY2ODhY+6uLi8uUKVNwjcjIeHl5nTp1Cu8o9GLu3LlYnt6CuY2+CRMmILtuZKeNfRE8o6ZUKjGem4mZGTNmWFlZYdYdzG302dnZDRkyBFlWYsaMGXiHY2Rev369ZMkSvKPQi9u3b2vPs2KgtZwn57MVWHY3etiEB3fT/P39HW3bYdk1jUY2s6Jg1p0+0Gg0os7ne/jwIZVKdXd3x6Y7gl/fruYqk2M57/+p9vC14JbhtqIiZsx5L50fAAAgAElEQVStqfxKecfelv2+gvdIGpzHjx/b29v7+vpi0x2Rc7uqUnllf9GQqS7W9nSyce/MmkAiVBXligpyqkO/dcV8KQsUyGQyPp9P1F03lgh7vF3NU17ZXzTh+7a2Tq0osQEAphYU7x6WngFW1w4U4x1Lc2RnZ2/cuBHvKPQiLS0tMzMTs+4Im9spsZygKS54R4Gbtn4Wdq6muZnGd8KZwWC4ubnhHYVevHr1Css1DAl7Lu39C2Hvka16XMewoJR+lPj2MLL7Lvz9/f39/fGOQi969eqF5dwVYuZ2NVfl5m1GoRnh4SZ6WE50XpkU7yiaTCKR8Hg8FxcCjrkw/s4i6phcwykl/lnxhqmUQMhT4h1Fk/3zzz9bt27FOwq9yMvLe/ToEWbdETW3IWNlamrq6uqKdxR68f79+/j4eMy6I+aYHDJeXbt2xX7JS2x4eXlh2R3MbciwiMViPp/v7OyMdyDo8/LywjK94ZgcMixZWVnbtm3DOwq9KCkpuX//PmbdwdyGDAuDwSDkThsAUFhYGBMTg1l3cEwOGZYePXr06NED7yj0wtXV9fOyPPoDcxtCgVAoFIvFaLWmVqvJZNRGlDY2Ns1eOZDNZqvVarQiodPp/fv3r6ioQKtBRH1z7+GYHDIscrmcqLUZ1Gq1TIbdtAuY25BhIZFIKO60DYparZZKsZspCMfkkGGh0WgEW3xbi0wmY1nmkZhfkJBRI2pNATKZzGAwsOsOs54M3IOHCYFDehYU5CO/zpk38acta/AKhs+vChzS8/qNmDpjIzCRSPTmzZvq6mrtljt37kyZMkV7/kmtVp86dWr69OmTJk1KS0vDPsJa8TQJPN6GWq+IiIh79+6RatSLodPpZmZm2iPwuLi4mJiY8ePHr1ixws/PD/sIa8XTJGq1WiKR6CGousHjbciAyOVyMpnMZDK1WwIDAwMDA7W/ZmRkdO3adezYsTgFWDuemvh8fq3ga9F9TK7RaEgtLogFc7txee9yl383f8O6qCPH9hcU5Ds6OE2bNpfL5dy4GSMUVnfr1uuH79dbW9s03Eh5ednR43+kp6eIxSJPT5+JE6YHDh5aUVF+7MSBp0+TRSKhu3ubqVPmBA8ZgdXb0q+ffvrJzc2NQqHExcUplcpevXpFRESYm5sjFcjPnDmTkJAgEAjc3d2nT5/et29fAMDs2bOrqqpiY2NjY2MdHBxOnjy5a9euhIQEAMCNGzeoVGpISAhytXnUqFELFy6USqVnz549ffq0drHuHTt2vHnz5vjx43p6U7XiAQAkJCRER0dXVla2adOGRCI5OjquXr362rVrDx8+HDt27KlTp3g8nqen59KlS93d3ZHcvnfvXnR0dGlpqa2t7YgRIyZOnEgmk/l8/pQpU+bNm/f+/fvU1FRPT88dO3YAAG7dunXlyhUOh+Po6Dh48OBx48bpfjYOjsl1IhaLd+/9Zf68xdt/2Uc3Mfl1x09P05I3rIv6/rt1z56l/XFwV8Mv53DYEUtmZ2SkTp40c8V369q382KzKwAASpUyJ+dV6Ndh3y5YbmlptTVq/ZucV1i9J727cuVKeXl5ZGTkggULHj9+fOHCBWT73r17L1++PGLEiJUrVzo6Om7ZsuXly5cAgLVr1zKZzD59+kRGRq5duxYA8PXXXwcFBWkbXL9+vbu7u6en54YNG3r16hUcHKxSqbR3RCsUirS0NL0ucF0rnpSUlF27dvn7+69atYpGo+Xm5o4ZMwZ5KDc398qVK0uXLl2/fj2bzd61axcyJo+Li/vtt988PT1//PHHL7/88q+//oqOjtY2eOHCBQcHh6ioqG+++QYAcPbs2ePHjw8cOHDZsmUDBgyIiYnZt2+f7tHC/bauFi5Y3qfPAADAxAnTt/+6+btla9q18/QHXTMznz5NS274tX+dPlJVxTt+9KKHR1sAwPDhIch2F2fXk8cvIaOvkSNDx44PTk5+0LEDDoeR+uDq6rpy5UoSieTr65ucnJyZmTlv3rzCwsKEhIQpU6ZMnz4dADBgwIDw8PCzZ89u27bNx8eHQqHY2Nh06NAB2RV7eXl5eHhoG+zTp09MTAyDwUD288gE1Xv37oWEhAAAnj17JhKJBg8erL93VCue2NjYNm3aLF26FADg4+MzY8aMtLS0Dh06II9u2rTJxsYG+UY4cuQIsmbz6dOn/fz8Vq1aBQDo37+/UCi8dOlSaGgo8pIOHTrMnj0b+ZnD4Vy8eHHVqlXaRWBZLNb+/fsXLFjQwLC/JpjbujKh/zsWotHoAAAanY78am/vwOdXNfzap2nJ3bv1QhK7lnfv35489Wdu7msAgEql4nI5eogdHyYmJtqDRkdHxzdv3gAAkF10v379kO0kEql79+41746iUCjaMXajgoODt23bVlhY6O7u/vjx43bt2tVcaFHf2Gy2tvYTi8ViMBg1Z9RpD62ROaEcDodCofB4vAkTJmif07179zt37hQXF9vb2wMAAgICtA9lZWUplcodO3Ygg3PtpUEOhwNzGyMkUuM13nk8bo/uX3y+/VlW+o+rl3QL6Llq5SZzM/ONkSvVGtRmLxsUKpWqUqmQq1wAAGtra+1DTCZTIpGIxWIzM7OmNtunTx9LS8t79+5NmzYtNTV14sSJaAfeEGdn57y8PLlcTqfTP378KJVK27dv//nTkCNztVotl8s/f+/IdwSS2zXPtHG5XABAZGSknZ1drU51DA/mNhYsLJhcXh075NOnj7q4uEVt3Y38+00ZpnhEhykWiwUAqK6uRn4AAPB4PCqVqj1FpFKpqqurddw10Wi0wMDAe/fudejQQSQS6fVg+3NhYWFr1qxZs2ZNQEDA/fv3fXx8ai7h+jnkLfP5fO2WqqoqbYbXot3Y7DWG4Lm0f9FpdACAQMDX/lpdjVq52e7dej17llZaVqLdolQqAQB8QZWXpw+S2HK5XCwRI+eBqVQaAEAbQK3YjFqHDh1IJJJ22olcLk9PT+/YsSOFQkF2XFwut0nz0oKDgzkczpEjR/z8/DBejaRTp06hoaFqtbq0tDQsLGz79u3Iv7I+1tbWdnZ2GRkZ2i1JSUkmJiZ17u27du1KIpFu3Lih3dLUa+Mwt//Vrr0XmUz+fc+2rOcZAAAvL9+MzKd/HNilUKCwUt+M6eFUKnXxkjlnz524HXdjy89rd+/5BQAQENAz9enjv29ff/z4wcofI6qrBfkf32s0GnNzc1cXt+hLZ27GXvk8NqPm7OwcHBx89uzZCxcuPHjwYNOmTTweT7tEub+/f2ZmZlxc3O3bt/PzdZqH5+np6e7uXlpaqtezaHW6evXqP//8Exoa2rt3bysrq5KSkoafTyaTJ0+enJmZuWfPnqSkpH379qWkpISFhZma1jFec3Fx+frrr58+fRoZGXnnzp0LFy6Eh4e/e/dO9/DgmPxfzk4uP67c9NeZo6mpj7sF9AyfF1FdLYiLuzFr5jctb9zDo+2+Pcf/PLznzNljNCrN3aPt2DGTAABzZ3/L5bD37d/BZFqGjB43MWz6rt1RWc8zunfrtW7d1n37d9yJj/0qZFyt2NB4u3hatGiRmZnZjRs3hEJhmzZtNm3apD2HNGfOHC6Xe+HCBSsrq/nz57dtW8fZx8916NChtLRUez4ZM97e3levXv3111+1W0aOHNnAAsNkMnnUqFEajebq1av37t1jsVhz5swJCwur7/nffPONvb39zZs3nz17Zmtr269fP+2BjC6IudZfNVd5eV/R+OU6fTKIquyjJDuJO24JFvWAUazNoFAopFKpjsfbiC1btqhUqsjIyDof1WttBpVKhRxNyOXy48ePx8bGXrt2rb6RuUajkcvlqN8KVt+RCNxvo0MoFE6ZFlLnQwu+WRYyGrc5kkZHo9Hovr9JTExMTEzMzMyMiorSc1x1uHfv3qlTpwYOHOjk5MTj8Z48eeLh4dHAIbdKpZJIJJjd5glzGx1mZmaH/zxX50OWTCvMwzFidDpd991sfHy8QqHYsmULLiXNPTw8/Pz8EhMTq6urbW1t+/TpM3ny5Aaej/H92zC30UEmk52dCLiEFS50v00C32rH3t7eP/74o+7PJ5PJdZ420xN4nhwyLAqFgsD10pDpK9iAuQ0ZFo1Gg2JpUYOCHG9j1h0ck0MoaPnNxlo0Gg0580w8ZDIZy1JwMLchFJibmyP3ZhNMrbncxgWOySHD8uzZs+3bt+MdhV6UlJQ8fPgQs+5gbkOGRSQSlZaW4h2FXuTm5t68eROz7uCYHDIs3bp1q/PeCQJwdXUdMmQIZt3B3IYMi4WFhYWFBd5R6IWPj4+Pjw9m3cExOWRYsrKytJVGCOb9+/c1b/DUN5jbkGERCoXFxcV4R6EXqamp2sqNGCDsmJzlgt3EXcNEopIsWca3sBaBj7fbt29fs5SivhEzt5m21JJ3YoVMTTNpvQMTTrGUzjC+t0/g421teVZsGN//Xkde3Zi8Cuzm7hogqVDl6ml8BdgIfH37+fPnTSqc0kKEze0vx9jdPU3MwzZdvHpSJeLLPbsa31wxAl/fvnbtWk5ODmbdEbPuCkIqUh3f9HHIFBdLO7qFNTGPPj7HKZUV54nFfPnQ6Y54x9IcEolEIBA4Ohpl8A27fPmyv7+/r68vNt0RObcBAGqV5vE1zoeXQis7ekVBk2/BUavVarW64eKVOrZDIpFaeEOFtnxPAyzt6CQS6NjbsutAWBCitSN4bmspZE17m0+ePDl//vy7d+/odPqRI0daWBz3xIkTGo1m7ty5LWkkPT399evXs2bNauA5VBqJZOSHWenp6bdv3964cSPegaDv7t27vXr1qrn2gF61lpEqzUTXfSZSBOvjx48ikYhEInl6erq6t3R8OGTooHfv3ukeQ536Dejt6MxqYSOGTyqVIktqEM/u3buPHTuGWXetJbd1cePGjbNnz5aUlIjFYhKJhKyf7u3t3fKWPT09PT09UWkHADB37lz9LUOLuy+++KLmslhEMmzYMCxvGjXyARyqdu/enZeXJ5FItAfGGo0GrTMfBw8eRKUdAEBUVNTevXvRas3Q0On0JhUwNiLLli1r+bkb3cHc/s/9+/drrb1kZWWFyv4WGerruFBGo5ycnJD1mQUC1FY1MhwpKSlNKjBoLPh8/tWrV7HsEeb2/7h+/bqjo6O2XpeZmRla8x8jIiKQQT4qkAUfv/vuO7Qq/hsOpVIpk8nwjgJ9ubm58fHxWPbYWs6T60goFIaEhDx48ODLL7+USCTe3t7nz5/HO6iG3L17t2/fvkSapInWdUdDk5OTU1RU1PBCn+iC++3/MWfOHOQ0VVJSEpPJRHFdyKdPnx4+fBit1rSGDh364cOHp0+fot4yXshkMvESG1m0DMvEhrn9Pw4cODB37lztIDwxMXHPnj1oNW5paamn+/u6dOly6tQpwtT0Tk1NXb9+Pd5RoO/Jkydv377FskeY2/+6detWWVnZyJEj9dS+r6+v/j6yBw4cEAqFbDZbT+1jiahrD/z1118Yn/uEx9sAKUC5bdu2ffv24R1Ii3z48CEtLa3hJakMn0wmk0gkmE3ewszJkyfDwsKwPDMC99sAWeh43bp1+u7lxIkTZWVl+mu/ffv2hYWFRUVF+usCAyYmJsRLbADA7NmzMT7lCXMbREZGLly40MnJSd8dZWdn5+bm6rWLlStXqtVqLG8kRF16evpPP/2EdxQoY7PZ169fx7jT1p7b8fHxdnZ2ISF1L52NruXLl3fq1EnfvSBLQP/222/67khPCDmfPDU1NSsrC+NOW/XxNpvNnjZt2p07d/AOBH3nzp2bOHGiMV5MIuTxdlpampmZmb+/P5adturcnjx58pYtW1C5G0QXCQkJZWVl06dPx6Y7lUr18OHDoKAgbLqDDE3rHZMfOXJkzJgxmCU2MtrMy8vDrDsKheLq6hoZGYlZj6hIS0sj3s3b+/btUygUGHfaSnP7n3/+SUlJwfhyUb9+/aZOnYplj76+vv369cOyx5aTyWQEuwfm/fv3SUlJWK7Oi2ilY/Lhw4efPXvWqFdg1Z1QKBQIBC4uLngHohOlUqlQKExNja9Ca30KCwu5XG7Xrl0x7rc17rd/+eWX8PBw7BM7Ly/vr7/+wrhTpOL369evV69ejX3XzUClUomU2AAAd3d37BO7NeZ2VlaWQqGYMGEC9l2Xl5dnZmZi3y8AIDg4eN26dUZRG/jJkycrV67EOwo0bd68uaqqCvt+W11ub9iwYf78+bh03alTJ6SmAi6YTKZcLk9NTcUrAB2pVCrsTzvpT35+fnZ2Ni6X9FrX8fahQ4eoVGp4eDjegeDmxo0bWVlZmzZtwjuQesnlcplMRpiySmw2WyaTubq6Yt91K8rtoqKiiIgI7Kf+aaWkpOTn50+ZMgWvAKBWxfjmLTXb4cOHN2/e3LzXKpVKlUrVwgDYbDaFQtFfwSA6na7j8gZZWVkCgWDQoEF6iqQlMjIy7ty5g8GtOxiQSqXh4eFnzpzBpffWktsPHjwQi8XNLo4rkUgkkiYvS1JLz549kZp4LWynPnZ2djrmdrdu3ZAa7LNnz9ZTMM0mkUgqKyvxjgId9+7dw3G94dYyJg8JCTl69Gizb/aqrq5ueW7rm52dHYrlFvFCpPXApFIplUrFa1a/0X8UdHHmzJkhQ4ZgcBdnw8RisbaCqoG4c+fOy5cv8Y7if5iamhIjsZH69jjerkP83NZoNA8fPvzuu+/wDgRIJBJD268OHz48JiYmJSUF70D+k5mZuW3bNryjQMGZM2cOHTqEYwDEP94+fPhwr1698I4CIOUQ8Q6hDoZ2M4lYLC4vL8c7ChT8888/y5YtwzEA4h9v9+zZMyMjo4WNNHC8/fz58+fPn+t4UgpZON7Ly0u75c6dOydPntyzZ0/L6yW35Hh7//79kydPNoQJ9lKpVCwW29ra4h2I0TOsISLqDh8+rO9ZaPHx8Q8ePGj0K1IqlcpksoiIiFqLS9DpdDMzM9zH6osXL166dKkhnC9kMBgESOznz5/jPvogeG6npKQsWLBAf+1XV1enpKTweLxXr141/EypVEqhUORyea3tgYGBx44dM4Qd5rlz5wzhJo2UlJS1a9fiHUWLFBUVbdq0CfczgkQ+3o6Oju7QoYNeu0hMTGzbtq2Njc39+/drVsypqKg4derUs2fPxGJx+/btx44d279//3nz5lVVVcXGxsbGxjo4OJw8eXLXrl0JCQnIVFDkhGpOTs6xY8fy8vIYDMYXX3wRHh6OzL6cMGFCRERESkpKWlqaubn5qFGj9HEreHl5+aVLlxYvXox6y7pTqVSffwMal9LS0p07d+IdBaAY2qkUFEVGRi5btszGxqblTcnlcqVS+fn2ffv2DR48uH379teuXRszZgyFQgEAcLnc5cuXl5SUjB8/ftCgQQqFgk6nd+rUqVOnTsnJyT179ly6dOngwYNZLJa9vb1MJvv48eOUKVPIZPKnT59WrVplaWk5e/Zsb2/vW7duvXr1Cllo5tKlS48fPx40aNDMmTMpFMqFCxe8vb1rzVI2MzPTce5KfSwsLEgk0vHjx3Gcsubi4jJo0CDsKxmgyNXVlcVi4R0Fcffbz549s7a21uusoLy8vA8fPmzYsMHExGTPnj3p6elIkZNz587x+fwDBw4gK/4GBwcLhUKFQuHj40OhUGxtbf38/JAWvLy8PDw8tA1euHCBRCJt2bIFKWTNZDJ37tyZnZ3duXNnZGX2SZMmIXXI79y58+zZs969e6P+pnr16oXvZQUcJ3ug4tSpU97e3oZQ7oawx9uXLl3S903a8fHxvr6+jo6O1tbWHTp0uH//PrI9IyOja9eu2qW8NRqNQqHQZUeUnZ3dtWtXbYX67t27I98gyK/IurxIITQWi8XhcPTztgAAIC4uDvuauwijvr6dn59/8+ZNQ0hswua2TCbLzMwcNmyY/rqQSqUPHjzo27evUqlUKpW9e/dOT09HFrKqqqqyt7fXPpNEIul4XCAWi62srLS/IkfadeYwlUpt+b0rDRgxYsT169ezs7P110V9jPr6toeHx6VLl/CO4l9GPPhpQGJioj7GqzUlJyeLRKKTJ0+ePHlSu/Hx48cjRowwNzfn8XjajWq1uuYlrgaulrFYrOrqau2vSLEOvNbWxutETLdu3XC8v6IlqqqqKisrsayc2zBi5va9e/f0tyInIj4+3svLq+Yp5f3799+/f3/EiBFdu3Z98uRJWVmZk5OTTCaTy+WmpqbIMSSDwWhg0YyOHTs+ffpUKpUiw+/Hjx8j1Vr0+kYaIBKJbt68iXE1WAsLC7y+zlooLCwsJiYG7yj+Q8wxeWJiol5r7peUlGRnZ/fr18+nht69e798+bKiomLKlCkUCmXFihUXL16Mj4//448//vjjD+SF/v7+6enp0dHRt2/fzs/Pr9XspEmTpFLpxo0bExMTo6OjT5w40bVr1y5duujvjTTM3NzcxcUF46n4WVlZO3bswLJHVGRlZe3evduglkMhYG4nJSXpu7YJMrfsiy++qLkRuT07MTHR3d39t99+a9++/YULF06fPl1RUaHNzzlz5nTp0uXChQvR0dElJSW1mnV1dd2yZYtCodi9e/eVK1cCAwPXr1/fwstaLTRw4MCtW7diOV9NKBQWFxdj1h1aunXrhvGSQI0i4HzynTt3urm5oTuSbN792xKJhMFgYJac+rt/+/nz525ubthMnjPG+7cXLly4ceNGQ6sAT8D9dmZmJnL1CF9SqVSlUuG710VLQEBAWFgYchVA34zu/u3o6OjQ0FBDS2wC5rZQKCwpKfHx8cE7EEClUo30nFCd4uLiPn36hEFHqamp69evx6AjtEycOFHfJ26bh2i5/fz58759++IdBUByG+8Q0MRgMFxdXTG48qxQKLAZIKBi+/bthlZLR4touf3y5cuad0fjQqlU4rKOhL5ZW1vv3r271j2qqOvbt+/PP/+s1y7Q8u233wYGBuJ+f259DDSsZnv79i3ukwdkMlnN6WVEsm3bNmRtAP11YSzHMiqVat++ffqeItUShBo3IrOv9ZHbDAZD9zuTDLN2ElpCQkL02v7z58/v3r1r4EuCVVRU5OfnG3JiEy239bcYLY1G0yW38/PzY2Nj8b3/GQMxMTFsNnvhwoX6aFwkEhUWFuqjZbSw2eyZM2fGxcXhHUgjCDUmz8/PrzWfBGOLFy82wGr+qAsLC1Mqla9fv9ZH4126dMG3hGCjuFxubGws3lE0jlD77eLiYnzPThvFvxwV+hubMJlMQ17oLzs7u127dkZxEYRQ++3S0lK8phC8fv3aoKp8YyA3N/f8+fOoN5udnb1r1y7Um0XF6tWry8vLjeJUH9Fyu6SkxNnZGft+c3JyoqKiDOS6OmZ8fX3z8/OvXbuGbrNCofDjx4/otomKsrKyH374ASlxZRSMYGihO41Go612giVTU9PTp09j3y/u1qxZg3qb3bt3N4RphbUUFBTIZDLcL682CaH222/evMH+UC0vL0/3BTSJh81m37t3D8UGTUxMDKGQYE3Xrl3766+/jCuxiZbbVVVVGN9AGxkZmZOTY25ujmWnBsXOzu7169c1i8+0UHp6uj6GA80mk8mCg4ONa4o7glBj8qqqKlQqFuuoqKho9uzZbdu2xaxHw7RkyZJXr15py8W0kFKpNJz55GVlZfn5+X369ME7kOYgzn5boVC4urqi8vHSBZ/PVygUMLERfn5+aP3l/f39ly9fjkpTLZScnBwVFWWkiU2o3BaJRHot61tTTk5OREREu3btsOnOKBw8eBCVkTmTyfT09EQjohYRCASdO3feu3cv3oE0H3FyWywWY7OclVKpFIvFZ86cwaAvI/Ltt99++vSJzWa3sJ3Xr1/v378fpaCaKT4+XiqVGvt9AcTJbYlEou/VvxBcLtcQ6roYoE2bNrW87hKfz8/NzUUpouaoqKhITExs+ZLJuCNObsvl8tLSUn33YpgVNgxHXFxccnJyS1ro3LkzjjeBFRUVCQQC413YpCbi5LZKpdL3LN/Hjx9HR0cT4Btdf0aMGLFnz56WnPiwsLCouUYalvbs2aNQKHCv7YEW4uS2RqPRaw29rKwsf39/Q76NwUBER0e3ZPJJamoqLveBCYVCGxsbIp0fJU5uK5VKFM+Tz58/v+av3333XXV1tUFVljdkeXl5ZWVlzXutRqPBvgJZRkYGjUabOXMmxv3qFXFyW6PRoDXxs6Kiory8vEePHsivYrF4+/btAwcORKXx1sDb2zs0NLTOFcsb1alTJ4yLW4wfP75du3YmJiZYdooB4uQ2iURyc3NDpakXL15UVVWRSKQePXqEhIQUFBTQ6XRUWm49zpw58+DBg2a80MrKytfXVw8R1UGlUj158uS3334ztBnsqCBObqvVarTWmnn48KFYLEa+L8rKyjZv3oxKs62Kt7d3826HzMnJOXTokB4iqi0jI6O8vLxPnz5EnVxInNxGi0qlevHiRc0teXl5EydOxC8iY1VVVfXNN98041UYrPtdXFx85MgRFxcXg61A3HKEfWPNlpmZKRKJam5BRgQYL1VLANbW1tOmTbtz506TXtWzZ8+dO3fqLSgAAHj37l11dfWff/6p115wR6j7wFDx9OlTZOUAjUZDpVIdHR07deo0atQoeC6tGQYNGtTUl1CpVL3OU1izZk1ERARap2YMGczt2p48eaLRaFxcXNzc3IYMGRIUFGRra4t3UEasVl3nsWPHXr16tYHnp6ennzp1Sh9TylUqFYfDGTt2bGtI7MbX6K0olGUlVpV/kkqEzbmegSWNRqNWayiUlh5lKBVKEplEJpP1VErFsQ1DodC07WTeayh2t5rj6+eff/bz8xs7duyAAQOoVOrGjRuDgoJqPSckJKSsrEz7aSSRSMhFzYyMjOZ1un79+qysrFu3biG/vn37tqCgYPDgwUZRohQVDb3Pj6/EqX9zug6y7fylralFa/mL6JsGAFvsWJQAAA8dSURBVF6ZrKpSfuqn/Jkb2raGWkzr168fNmzY1q1bAQBkMrnO0gvTp0/ft29fzdWISCRSs6d/FhcXZ2dna+fPCIXCTZs26aMqqyGrN2NfpwpyM0Uh3+BQWpDwHDwYDh4MG3uTk5vz50QS8wKMVkhISGlpqXYQpFKp6lwMdNy4cdevX8/Ly9NuodPpYWFhzev0xo0bJSUlyAyFK1eumJiYtLbErvc8uVSsfvtMGDwdh3rArYe9h0lAICstjod3IHo0cODAmomNKCkp+fyZdDo9NDS05hwhd3f35uW2UqmMi4tDhvckEmnixIlOTk7NCt+41Z3bpR8lJHIrGCzizcaR/uGlodQG04dHjx716NGj1nROHq/ur7Px48e3adMG+dnExKTZO+1bt27VLBGhUqk+P7xvDerObQFb6dQGixomrRzLyYRGJ/h36OHDh1etWuXs7IzsvUkkUn2lDmk0WmhoKPJF4Orq2uzcvnz5slQqrblFIBAMHTq0ea0Zr7pzWyZRyWVY34vTGpFA6UepDs8zbqGhoTExMePGjWOxWBqNBpk+UCfkApWJicnEiRObd50iLS2trKwMOc2u0WisrKycnZ27desWGhrasjdhfODZb6hFuGVycbVKXK1UyDRyqaqBZ47oszCg7ZhHjx7x+fysxHrPMozs+21GRoYna0gDz2lA7NWX7W2DOjjRLS0t3dt4uLjYt/V0cXC2srJrdXf71H19Oy2OK5OCgEA4Z0PvTkW+W/y78RX6+PRGnJspzH8tsrAxUcjVVDqFbkZXKQxrrEcik1VKhVquVilUVDqJSgPeARZeXS0sWa1il9Yq3iSEorwsYdJ1Dt2MbmZj2r63NdWEgndEuhLxpLkvxG+fV9o6UAaPt6ObEvxmCpjbkK5EAtXNI2UKJdmtsxPdzPg+OeY2DHMbBgCAW1h9bFN+9yDbL0YQuZAOwb+6ILQU5EjORH2ydrN17+JgjIldk607s2Ngm+ICzbWDei+MiyOY21DjivJkD65yfAe1YVgS54yUrbsl2dTieGQ+3oHoC8xtqBG5mcL7MRyPAAJOUrR0NHPycTj1cwHegegFzG2oIexi+ZNYrkeAHotD48vMxsTWw+baIQIOzmFuQ/VSq8CdMxXterviHYh+Me3NANX0KeEm9sPchup1/2Ilw8Yc7yiwYO3KfP6AJxE2NPfG6MDchupWzVN+fCViuRv3Wpa6c/CyfXgFozWesQFzG6pbxt0qRx8CVu2uj40rk89R8SoUeAeCGkPMbaVSOX3m2IOHduMdSBOUlZWWltVxW7Lxep1axbQ3wzuKOrA5hT9s+CLrRTz6TVNpeVnEueXWEHObRCIxmZYMBgPvQHRVXFI0dfrXubmv8Q4ENQU5YksH09ZQ76kmpp3Z+xfEyW1DnGBEoVAO/nGqzodQXPQLRSqlsuGSkkanIEdiYWeBdxRYM7M24XwkiatVZkyjmSTfADRz+/qNmOhLZ9jsCicnlyFBIyZNnGFiYpL3LnfJ0rm/RO09fHTf+/dvHR2dF8xf2r//oDc5rxZFzFrx/bqQ0WORl588dfjc+RP7955Y8O10AMD0aXPnzV3E51eNGRe8cMGyvHe5yckPvL077N19VKlUnjh56E58LJ9f1aZNu9mzFgzoPxgAUF9fAICYy+ceJd0fNnT0qb8O8/lVnp4+8+YuSki4nZz8gEqjDRs6+pv5SygUCgBAKpUePfbHvftxcrnM3a3NxIkzggKHIS3cT4yfEDbt2LE/OFy2t3eHH75f7+HRtrSsZNacMADA5p9WbwZg+PCQ1asiUfyr4qI0X2pqr6+51k/SLj9MPscXVNjauHTrMmxw/+k0mklxSe7+o/Pnzfj97/gDJWVvbaydRw9b7N/x35rwQhHv+t+/v8p5RKOaeLbroafAAABqDaiqVBAjt1Ebk588dfjwkb1BgcNW/rBx8KDgi9F//fb7VuQhmUy2ecvqsPFTd+867OTo/HPUOj6/qmMHP28v3/i7t7Qt3E34e9CgYA+Ptlt+2lmr0OyZM8ecHJ1/23koYtEKAMDO336+GH06ZPTYdWt/dnJy2bDxhxcvshroC3koO/v5/ft3IjduX/3j5oKCjytXRdDp9J07D44JnRh96UzcnZvIEiLr1n+XkvJo2tQ53y1f6+Xlu+XntX/fvo608ObNy+jo0ytWrP9p887KivJt2zcBAFi2duvW/gwAmDN74d7dR6dPnYvWnxRH4molTT83eMXfP3Lrzv6AzkMnjlnfxW/Ig6QzMde3IQ8pFLIzF9cN7Df527kHbaydzl3aIBJVAQAUSvmfJ5e8evNwYL+po4cv5vL0eF6DSqeIBIZerltH6Oy32ezKs+eOr1+3ddDAIcgWFsv+993bFkf8gPy6ZPFKZO8XHr54wcLp/7x4NvDLoNGjx+7e80tZWamTk/OrVy9KSorW/LiZwWAM6D+41sC7U6fO4fMikJ8LCvLvxMfOnBE+e9YCAMCggUOmzxx78tSfu3471EBfyEMbN2yztrbx8+uSlv4kNfXxd8vXkEgkX5+O8fGxz56ljR415lHS/RfZWefP3rSzswcABA8ZIZGIL185P2rkv1U7tv78u60tCwAwbtzkAwd/5wv4VpZWPt4dAAAeHm07dw5A5e+JO4lQRaWjn9t8QeW9RyenhW3p4v/vf8SKaXf55vbQUd8jv44ZvSKg81AAwKihi3YfnPU+P6uLX2By6qXSsrxvZu3z8eoNAGjr3vnXvZNQjw1BplHEfJjbNWRmPlUqlVuj1m+NWo9sQY4/2ZUVyK+mjH+rrzk6OiPfBQCAIUEjDv25O+He7enT5sbfvdW+vZe/f9c62+/evbf2539ePAMADBgQiPxKIpF69exzN+Fv7RPq7AtBp/9blI9Oo9NoNO03iJ29A7J7T019rFQqp07/WvsSlUplbv7fkSfjfxvnsCutLK1a8JczSBpAN6WQKeif18h7n6ZSKc/GbDwbs/G/zgDgV//7OaHT/v3z2lg7AwAE1ZUAgJdvHjo7eiGJDQAgk/U4YKZQKWq1wZ3QaR50cpvDZQMAorbudrD/n4nHLi5uH/Pf19xCo9IAAGq1CgBgYWERFDg84d7tSRNnJD64O2/uovra12YUAEAkEgIAbKz/qwljaWklFotrLdBXq6+GIeW1AAA8HofFstu183/WiKXUtRIF0rhKh8aNDwmQgEYhU9FNUT7VKqhmAwDmTd9lbeVQczvL1q2s/H8+J1TKf/+7Kn6ZqzNGK3IrZQpTJkHudUPnn8dk/jt7ycOjaZX0R48e+/ft66fPHFUqFcFDRuryEjs7BwCAQMBHhs0AAC6XQ6VSUblmxmRaVlXxHB2da5XdbW1MmVSlHnLb1PTfz4mDfRM+JxbmNkIRRpO9VXKVuaUhXjxqBnTOpXXr1otEIl29dlG7RSKR6PLCTh39vTx9zpw9HjxkpLm5TlOXO3b0J5FIqU8fI7/K5fLUp4/9/LogZ7lbqHv33iqV6sbNGO0WXd6IiQkDGZ+3PAAD4eDOUMrRH5J4t+9JIpEeP43WbpHJG//zujr7Fha/rqj8hHo8n6ObUpjWBMltdN6Gm6v7uLGTL185v3b9dwP6D+Zw2NeuR2+L2oOcZGrY6NFj9+zd/tVX43Xsy9XFbfiwkJOn/lSpVC4ubrduXeVyOWvXbGnxmwAAgKHBo27GXjn0557SshIf7w7v3r19nJx48nhMw4MCBwdHF2fX6JgzDFNTgYA/buxkY9/tu7QzyXwgsnRAeV6aHct9QJ9JSSkXjp9Z4ddxUHU1O/lpzLwZu9xcGvqcBH45M+P53weOLxzYd7Il0+7Zi6Yt6K07uVgp4kmtHWh6ah9jqH1FRSz63sHB8erVi+npKSyW3ZcDAu3tHHR4HQgeMjIp6b63VxMOqJYvW21ubnH12sXqakG7tp5RP//evVuvFsT+HxqNtmP7H0eO7rt//05s7BU3N4+vvwprdOVHEom0fn3Urzs27/9jp4ODU+DgYU5Oxl3JoH1n88RLFa7AHvWWvx653NrK4XHqpdx3qZZMO/9Og60sG/mc2LHc5s/cE3tn7537R6ytHDt3HPz23VPUAwMACCpF7TsTZ8YOrGGMM4OtYXzrWDnJjGlmbdwDkCYpf1v5ZYiViydBVtQhyKEFhLquAy0TYzhm9ZdSuhm392nm9c+3uzl3KCrNqfMlS+YfdXRoh1aEf9898CTt8ufbaVQThVJW1yvAhh9umpjUfaAh4kmBUkmYxIa5DdXLzdvUzIIk5EgsWHV/3AO/nNn/izqW7NJeUPxco8PvJhnUf1qfnmM+365UKqjUuo+Z6fR6U7fyA3fEdPSPQXAEcxuq18CxdokxvPpy28LcGpjjWd/b3MzK3AydiUNCjtS1HcOprdHceqgLQ7zHEzIQ9m4mPt1My3LZOjzXiCnlqpLXFUOnEWqnDXMbakTAICt7J3Llh3pX3iSA96lFM9Z64B0F+mBuQ40YMtnetQ258j3RyoACANRKzdukgtkb25paEOGmzlpgbkON6xdi4+RGKn1dgXcgaJLw5blJnyb/4G5C0EX/iPmuINR9OYbVbaDFh9QiXlE13rG0lFysLH5ZoZFWf/urp6UtYU8nE/aNQajr0IvZzt/88XVOXnIhy93Kwt4M9ZtJ9K2aLZFVS6srhAPH2nl2Jc4UtDoZ2f8GwpeJKXnIZPveXJusB/x3/5RqAIlpZ0GiAKoJlW5K1agNrGgcmaSWqxQylVKmopDV5R8Ezu3NOvdh+va0wzsyLMDchpqMaUsdOI41cByLWyYv/SjlVSiEVVKSiiTgyvEO7X/QTCgkErBkUm3aU20cTdt966DPsg4GB+Y21Hy2TnRbJ4JUMiCeunObSidrgIGNrwjKzpWh0QDDq8sMGb26z5ObW1E4pYY1viKkaq5CLlXBxIb0oe7ctnMxMbjzIkQk4CjbdGgVC2VC2Ks7t1nOdKYt9fkDLubxtC4PY0r7joY3yUN6Ue/teACABzGVGg05YLAtlQ5HjSjjV8rjT5eELXW3ZLWmU7cQhhrKbQBAZgIvO5lPIpMIOeEWF1Z29A/Z1e38zPuFsCxZBCnNBRmgRnIbAKDRAAFHQZiFVHBHJpPtXOhwKATpW+O5DUGQMYL3ikAQMcHchiBigrkNQcQEcxuCiAnmNgQRE8xtCCKm/wMzyl30r+bKrwAAAABJRU5ErkJggg==", + "image/png": "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", "text/plain": [ "" ] @@ -327,206 +257,80 @@ ], "source": [ "from langgraph.graph import StateGraph, START, END\n", + "from langgraph.types import Command\n", + "from typing import Literal\n", "from IPython.display import Image, display\n", - "from langchain_core.messages import HumanMessage, ToolMessage, SystemMessage\n", + "from memory_course.schemas import State\n", + "from memory_course.utils import parse_email\n", "\n", - "# Nodes\n", - "def triage_router(state: State) -> dict:\n", - " \"\"\"Route the email to the appropriate classification based on content analysis.\n", - " \n", - " Args:\n", - " state (State): Contains email information and formatting variables\n", - " \n", - " Returns:\n", - " dict: Update state with routing decision\n", + "\n", + "def triage_router(state: State) -> Command[Literal[\"response_agent\", \"__end__\"]]:\n", + " \"\"\"Analyze email content to decide if we should respond, notify, or ignore.\n", + "\n", + " The triage step prevents the assistant from wasting time on:\n", + " - Marketing emails and spam\n", + " - Company-wide announcements\n", + " - Messages meant for other teams\n", " \"\"\"\n", - " \n", - " # Parse the email\n", - " author, to, subject, email_thread = parse_email(state['email_input'])\n", - "\n", - " # - From user profile memory - \n", - " full_name = \"John Doe\"\n", - " name = \"John\"\n", - " profile = \"John is a senior software engineer who leads a team of 5 developers\" \n", - "\n", - " # - From triage examples in memory - \n", - " triage_no = \"- Marketing newsletters\\n- Spam emails\\n- Mass company announcements\"\n", - " triage_notify = \"- Team member out sick\\n- Build system notifications\\n- Project status updates\"\n", - " triage_email = \"- Direct questions from team members\\n- Meeting requests from stakeholders\\n- Critical bug reports\"\n", - "\n", - " # Format the prompts\n", + " author, to, subject, email_thread = parse_email(state[\"email_input\"])\n", " system_prompt = triage_system_prompt.format(\n", - " full_name=full_name,\n", - " name=name,\n", - " user_profile_background=profile,\n", - " triage_no=triage_no,\n", - " triage_notify=triage_notify,\n", - " triage_email=triage_email\n", + " full_name=profile[\"full_name\"],\n", + " name=profile[\"name\"],\n", + " user_profile_background=profile[\"user_profile_background\"],\n", + " triage_no=profile[\"triage_rules\"][\"ignore\"],\n", + " triage_notify=profile[\"triage_rules\"][\"notify\"],\n", + " triage_email=profile[\"triage_rules\"][\"respond\"],\n", " )\n", - " \n", + "\n", " user_prompt = triage_user_prompt.format(\n", - " author=author,\n", - " to=to,\n", - " subject=subject,\n", - " email_thread=email_thread\n", + " author=author, to=to, subject=subject, email_thread=email_thread\n", " )\n", - " \n", - " # Create message list\n", - " messages = [\n", - " {\"role\": \"system\", \"content\": system_prompt},\n", - " {\"role\": \"user\", \"content\": user_prompt}\n", - " ]\n", - " \n", - " # Get classification\n", - " result = llm_router.invoke(messages)\n", "\n", - " # See the agent if we want to response\n", + " result = llm_router.invoke(\n", + " [\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": user_prompt},\n", + " ]\n", + " )\n", + " update = None\n", + " goto = END\n", " if result.classification == \"respond\":\n", - " message = HumanMessage(content=f\"Respond to the email {state['email_input']}\")\n", - " return {\"routing_decision\": result.classification, \"messages\": [message]}\n", - " \n", - " else:\n", - " return {\"routing_decision\": result.classification}\n", - "\n", - "def llm_call(state: State):\n", - " \"\"\"Process the current state with the LLM to determine and execute next actions.\n", - " \n", - " This function takes the current state and uses the LLM to decide whether to:\n", - " 1. Call a tool (write_email, schedule_meeting, check_calendar_availability)\n", - " 2. Respond directly to the user\n", - " \n", - " Args:\n", - " state (State): Current workflow state containing:\n", - " - messages: List of previous messages in the conversation\n", - " - other context variables\n", - " \n", - " Returns:\n", - " dict: Updated state with new messages, including any tool calls\n", - " the LLM decided to make\n", - " \"\"\"\n", - "\n", - " # Format the messages\n", - " full_name = \"John Doe\"\n", - " name = \"John\"\n", - " agent_prompt_formatted = agent_prompt.format(full_name=full_name, name=name)\n", - " messages = [SystemMessage(content=agent_prompt_formatted)] + state[\"messages\"]\n", - "\n", - " # Update the messages with the LLM call\n", - " return {\"messages\": [llm_with_tools.invoke(messages)]}\n", - "\n", - "def tool_node(state: dict):\n", - " \"\"\"Executes tool calls based on the LLM's decisions.\n", - " \n", - " This function processes any tool calls that were requested by the LLM in the previous state.\n", - " It supports multiple tools:\n", - " - write_email: Send emails to recipients\n", - " - schedule_meeting: Schedule calendar meetings\n", - " - check_calendar_availability: Check available time slots\n", - " \n", - " Args:\n", - " state (dict): Current workflow state containing:\n", - " - messages: List of messages including any tool_calls from the LLM\n", - " \n", - " Returns:\n", - " dict: Updated state with tool execution results added as new messages\n", - " Each tool result includes:\n", - " - role: \"tool\"\n", - " - content: The tool's response\n", - " - tool_call_id: ID linking to the original tool call\n", - " \"\"\"\n", - "\n", - " result = []\n", - " for tool_call in state[\"messages\"][-1].tool_calls:\n", - " tool = tools_by_name[tool_call[\"name\"]]\n", - " observation = tool.invoke(tool_call[\"args\"])\n", - " result.append(ToolMessage(content=observation, tool_call_id=tool_call[\"id\"]))\n", - " return {\"messages\": result}\n", - "\n", - "# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call\n", - "def should_continue(state: State) -> Literal[\"environment\", END]:\n", - " \"\"\"Decide if we should continue the loop or stop based upon whether the LLM made a tool call.\n", - " \n", - " This function examines the last message in the state to determine if the LLM requested\n", - " any tool calls. If it did, the workflow should continue to the environment node to\n", - " execute those tools. If not, the workflow should end as the LLM has provided a direct\n", - " response to the user.\n", - " \n", - " Args:\n", - " state (State): Current workflow state containing:\n", - " - messages: List of messages, where the last message may contain tool_calls\n", - " \n", - " Returns:\n", - " Literal[\"environment\", END]: \n", - " - \"environment\": If the LLM made a tool call and we should continue processing\n", - " - END: If no tool calls were made and we should end the workflow\n", - " \"\"\"\n", - "\n", - " messages = state[\"messages\"]\n", - " last_message = messages[-1]\n", - " # If the LLM makes a tool call, then perform an action\n", - " if last_message.tool_calls:\n", - " return \"Action\"\n", - " # Otherwise, we stop (reply to the user)\n", - " return END\n", - "\n", - "def route_after_triage(state: State) -> Literal[\"respond\", \"ignore\", \"notify\"]:\n", - " \"\"\"Determine next step based on email classification.\n", - " \n", - " Args:\n", - " state (State): Contains the triage classification\n", - " \n", - " Returns:\n", - " str: Next step in the workflow\n", - " \"\"\"\n", - " classification = state[\"routing_decision\"]\n", - " if classification == \"respond\":\n", " print(\"📧 Classification: RESPOND - This email requires a response\")\n", - " return \"respond\"\n", - " elif classification == \"ignore\":\n", + " goto = \"response_agent\"\n", + " update = {\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": f\"Respond to the email {state['email_input']}\",\n", + " }\n", + " ]\n", + " }\n", + " elif result.classification == \"ignore\":\n", " print(\"🚫 Classification: IGNORE - This email can be safely ignored\")\n", - " return \"ignore\"\n", - " elif classification == \"notify\":\n", + " elif result.classification == \"notify\":\n", " print(\"🔔 Classification: NOTIFY - This email contains important information\")\n", - " return \"notify\"\n", " else:\n", - " raise ValueError(f\"Invalid classification: {classification}\")\n", + " raise ValueError(f\"Invalid classification: {result.classification}\")\n", + " return Command(goto=goto, update=update)\n", + "\n", "\n", "# Build workflow\n", - "agent_builder = StateGraph(State)\n", - "\n", - "# Add nodes\n", - "agent_builder.add_node(\"llm_call\", llm_call)\n", - "agent_builder.add_node(\"triage_router\", triage_router)\n", - "agent_builder.add_node(\"environment\", tool_node)\n", - "\n", - "# Add edges to connect nodes\n", - "agent_builder.add_edge(START, \"triage_router\")\n", - "agent_builder.add_conditional_edges(\"triage_router\", route_after_triage, {\n", - " \"respond\": \"llm_call\",\n", - " \"ignore\": END,\n", - " \"notify\": END,\n", - "})\n", - "agent_builder.add_conditional_edges(\n", - " \"llm_call\",\n", - " should_continue,\n", - " {\n", - " # Name returned by should_continue : Name of next node to visit\n", - " \"Action\": \"environment\",\n", - " END: END,\n", - " },\n", + "agent = (\n", + " StateGraph(State)\n", + " .add_node(triage_router)\n", + " .add_node(\"response_agent\", agent)\n", + " .add_edge(START, \"triage_router\")\n", + " .compile()\n", ")\n", - "agent_builder.add_edge(\"environment\", \"llm_call\")\n", - "\n", - "# Compile the agent\n", - "agent = agent_builder.compile()\n", "\n", "# Show the agent\n", - "display(Image(agent.get_graph(xray=True).draw_mermaid_png()))\n" + "display(Image(agent.get_graph(xray=True).draw_mermaid_png()))" ] }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 8, + "id": "12ae5d5c", "metadata": {}, "outputs": [ { @@ -566,7 +370,7 @@ "Marketing Team\n", "---\n", "To unsubscribe, click here\n", - "\"\"\"\n", + "\"\"\",\n", "}\n", "\n", "response = agent.invoke({\"email_input\": email_input})" @@ -574,7 +378,8 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 9, + "id": "ebca8e98", "metadata": {}, "outputs": [ { @@ -587,33 +392,30 @@ "Respond to the email {'author': 'Alice Smith ', 'to': 'John Doe ', 'subject': 'Quick question about API documentation', 'email_thread': \"Hi John,\\n\\nI was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?\\n\\nSpecifically, I'm looking at:\\n- /auth/refresh\\n- /auth/validate\\n\\nThanks!\\nAlice\"}\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "[{'citations': None, 'text': \"I'll help draft a response to Alice regarding her question about the API documentation. I'll use the write_email function to send a professional acknowledgment and let her know John will look into this.\", 'type': 'text'}, {'id': 'toolu_01BabQWXFAbuZjg2BtuTcv7i', 'input': {'to': 'alice.smith@company.com', 'subject': 'Re: Quick question about API documentation', 'content': \"Hi Alice,\\n\\nThank you for bringing this to John's attention regarding the missing endpoints in the API documentation. I'm John's assistant, and I've made note of your inquiry about the /auth/refresh and /auth/validate endpoints.\\n\\nI'll make sure John reviews this and gets back to you with clarification about whether these endpoints should be included in the documentation.\\n\\nBest regards,\\nJohn's Assistant\"}, 'name': 'write_email', 'type': 'tool_use'}]\n", + "[{'citations': None, 'text': \"I'll help draft and send a response to Alice regarding her API documentation question. Since the email should be a continuation of the thread, I'll use the same subject.\", 'type': 'text'}, {'id': 'toolu_01VDxukMeEntJ19K6ZGJebk8', 'input': {'to': 'alice.smith@company.com', 'subject': 'Re: Quick question about API documentation', 'content': \"Hi Alice,\\n\\nThanks for bringing this to John's attention. I'm John's assistant, and I'll make sure he reviews this right away.\\n\\nI've added this to his priority list, and he will get back to you with clarification about the /auth/refresh and /auth/validate endpoints as soon as possible. Would you like me to schedule a quick meeting between you and John to discuss this in detail?\\n\\nBest regards,\\nExecutive Assistant to John Doe\"}, 'name': 'write_email', 'type': 'tool_use'}]\n", "Tool Calls:\n", - " write_email (toolu_01BabQWXFAbuZjg2BtuTcv7i)\n", - " Call ID: toolu_01BabQWXFAbuZjg2BtuTcv7i\n", + " write_email (toolu_01VDxukMeEntJ19K6ZGJebk8)\n", + " Call ID: toolu_01VDxukMeEntJ19K6ZGJebk8\n", " Args:\n", " to: alice.smith@company.com\n", " subject: Re: Quick question about API documentation\n", " content: Hi Alice,\n", "\n", - "Thank you for bringing this to John's attention regarding the missing endpoints in the API documentation. I'm John's assistant, and I've made note of your inquiry about the /auth/refresh and /auth/validate endpoints.\n", + "Thanks for bringing this to John's attention. I'm John's assistant, and I'll make sure he reviews this right away.\n", "\n", - "I'll make sure John reviews this and gets back to you with clarification about whether these endpoints should be included in the documentation.\n", + "I've added this to his priority list, and he will get back to you with clarification about the /auth/refresh and /auth/validate endpoints as soon as possible. Would you like me to schedule a quick meeting between you and John to discuss this in detail?\n", "\n", "Best regards,\n", - "John's Assistant\n", + "Executive Assistant to John Doe\n", "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: write_email\n", "\n", "Email sent to alice.smith@company.com with subject 'Re: Quick question about API documentation'\n", "==================================\u001b[1m Ai Message \u001b[0m==================================\n", "\n", - "I've sent a professional acknowledgment email to Alice that:\n", - "1. Acknowledges receipt of her question\n", - "2. Shows we understand the specific endpoints she's asking about\n", - "3. Assures her that John will review and respond\n", - "4. Sets appropriate expectations for follow-up\n", + "I've sent an initial response to acknowledge Alice's email and let her know that John will address her concerns about the API documentation. I've also offered to schedule a meeting if needed, which we can arrange once Alice responds with her preference. This ensures the matter is being handled while giving John time to review the specific API documentation concerns.\n", "\n", - "This allows us to keep the communication channel open while giving John time to review the API documentation and provide a detailed response about these specific endpoints.\n" + "Would you like me to proactively schedule a meeting between John and Alice to discuss this matter in more detail?\n" ] } ], @@ -631,7 +433,7 @@ "- /auth/validate\n", "\n", "Thanks!\n", - "Alice\"\"\"\n", + "Alice\"\"\",\n", "}\n", "\n", "response = agent.invoke({\"email_input\": email_input})\n", @@ -639,9 +441,21 @@ " m.pretty_print()" ] }, + { + "cell_type": "markdown", + "id": "38e501e9", + "metadata": {}, + "source": [ + "### What We Learned\n", + "\n", + "Running these examples shows the code works, but it reveals some limitations: all the instructions and preferences are hard-coded in the prompts. We would like a more flexible approach.\n", + "The next lesson explores what happens when we let the assistant learn from these interactions." + ] + }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, + "id": "87dd01b5-06cc-412b-a4ff-c430de81d7b0", "metadata": {}, "outputs": [], "source": [] @@ -649,7 +463,7 @@ ], "metadata": { "kernelspec": { - "display_name": "memory-course-env", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -663,9 +477,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.1" + "version": "3.11.2" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/src/memory_course/examples.py b/src/memory_course/examples.py index 5184cb5..56a6581 100644 --- a/src/memory_course/examples.py +++ b/src/memory_course/examples.py @@ -1,3 +1,3 @@ example_input = """ . """ -example_output = """ . """ \ No newline at end of file +example_output = """ . """ diff --git a/src/memory_course/prompts.py b/src/memory_course/prompts.py index 40be91c..fd449fb 100644 --- a/src/memory_course/prompts.py +++ b/src/memory_course/prompts.py @@ -57,4 +57,4 @@ Please determine how to handle the below email thread: From: {author} To: {to} Subject: {subject} -{email_thread}""" \ No newline at end of file +{email_thread}""" diff --git a/src/memory_course/schemas.py b/src/memory_course/schemas.py index 052832b..71fae90 100644 --- a/src/memory_course/schemas.py +++ b/src/memory_course/schemas.py @@ -1,17 +1,22 @@ from pydantic import BaseModel, Field -from typing import List, TypedDict, Literal, Annotated -import operator +from typing_extensions import TypedDict, Literal, Annotated +from langgraph.graph import add_messages + + class Router(BaseModel): + """Analyze the unread email and route it according to its content.""" + + reasoning: str = Field( + description="Step-by-step reasoning behind the classification." + ) classification: Literal["ignore", "respond", "notify"] = Field( - None, description="The classification of an email: 'ignore' for irrelevant emails, " "'notify' for important information that doesn't need a response, " - "'respond' for emails that need a reply" + "'respond' for emails that need a reply", ) + class State(TypedDict): email_input: str routing_decision: str - messages: Annotated[list, operator.add] - - + messages: Annotated[list, add_messages] diff --git a/src/memory_course/utils.py b/src/memory_course/utils.py index e69a16a..470c096 100644 --- a/src/memory_course/utils.py +++ b/src/memory_course/utils.py @@ -1,13 +1,13 @@ def parse_email(email_input: dict) -> dict: """Parse an email input dictionary. - + Args: email_input (dict): Dictionary containing email fields: - author: Sender's name and email - to: Recipient's name and email - subject: Email subject line - email_thread: Full email content - + Returns: tuple[str, str, str, str]: Tuple containing: - author: Sender's name and email @@ -19,5 +19,5 @@ def parse_email(email_input: dict) -> dict: email_input["author"], email_input["to"], email_input["subject"], - email_input["email_thread"] + email_input["email_thread"], )