{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# LLM" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "### LLM\n", "from langchain_community.chat_models import ChatOllama\n", "# local_llm = \"llama3.1:8b-instruct-fp16\"\n", "local_llm = \"llama3-groq-tool-use:latest\"\n", "\n", "llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "llm = ChatOllama(model=local_llm, temperature=0)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# RAG" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "FAISS index loaded from faiss_index.bin\n", "Metadata loaded from faiss_metadata.pkl\n", "Using existing FAISS index and metadata.\n", "Creating FAISS retriever...\n" ] } ], "source": [ "from faiss_index import create_faiss_retriever, faiss_query\n", "retriever = create_faiss_retriever()" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from langchain.prompts import ChatPromptTemplate\n", "from langchain_core.output_parsers import StrOutputParser\n", "def faiss_query(question: str, docs, llm, multi_query: bool = False) -> str:\n", " context = docs\n", " # try:\n", " # context = \"\\n\".join(doc.page_content for doc in docs)\n", " # except:\n", " # context = \"\\n\".join(doc for doc in docs)\n", " \n", " system_prompt: str = \"你是一個來自台灣的AI助理,樂於以台灣人的立場幫助使用者,會用繁體中文回答問題。\"\n", " template = \"\"\"\n", " <|begin_of_text|>\n", " \n", " <|start_header_id|>system<|end_header_id|>\n", " 你是一個來自台灣的ESG的AI助理,請用繁體中文回答問題 \\n\n", " You should not mention anything about \"根據提供的文件內容\" or other similar terms.\n", " Use five sentences maximum and keep the answer concise.\n", " 如果你不知道答案請回答:\"很抱歉,目前我無法回答您的問題,請將您的詢問發送至 test@systex.com 以便獲得更進一步的幫助,謝謝。\"\n", " 勿回答無關資訊\n", " <|eot_id|>\n", " \n", " <|start_header_id|>user<|end_header_id|>\n", " Answer the following question based on this context:\n", "\n", " {context}\n", "\n", " Question: {question}\n", " 用繁體中文\n", " <|eot_id|>\n", " \n", " <|start_header_id|>assistant<|end_header_id|>\n", " \"\"\"\n", " prompt = ChatPromptTemplate.from_template(\n", " system_prompt + \"\\n\\n\" +\n", " template\n", " )\n", " \n", " # prompt = ChatPromptTemplate.from_template(\n", " # system_prompt + \"\\n\\n\" +\n", " # \"Answer the following question based on this context:\\n\\n\"\n", " # \"{context}\\n\\n\"\n", " # \"Question: {question}\\n\"\n", " # \"Answer in the same language as the question. If you don't know the answer, \"\n", " # \"say 'I'm sorry, I don't have enough information to answer that question.'\"\n", " # )\n", "\n", " \n", " # chain = prompt | taide_llm | StrOutputParser()\n", " chain = prompt | llm | StrOutputParser()\n", " return chain.invoke({\"context\": context, \"question\": question})" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "# docs = retriever.get_relevant_documents(question, k=10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "question = \"誰需要繳交碳費?\"\n", "docs = retriever.get_relevant_documents(question, k=50)\n", "docs" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "### Generate\n", "# llm = ChatOllama(model=local_llm, temperature=0)\n", "\n", "# docs_documents = \"\\n\\n\".join(doc.page_content for doc in docs)\n", "# generation = faiss_query(question, docs_documents, llm)\n", "# generation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Retrieval Grader" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "### Retrieval Grader\n", "\n", "from langchain_community.chat_models import ChatOllama\n", "from langchain_core.output_parsers import JsonOutputParser\n", "from langchain_core.prompts import PromptTemplate\n", "\n", "# LLM\n", "# llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "\n", "prompt = PromptTemplate(\n", " template=\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|> You are a grader assessing relevance \n", " of a retrieved document to a user question. If the document contains keywords related to the user question, \n", " grade it as relevant. It does not need to be a stringent test. The goal is to filter out erroneous retrievals. \\n\n", " Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question. \\n\n", " Provide the binary score as a JSON with a single key 'score' and no premable or explanation.\n", " <|eot_id|><|start_header_id|>user<|end_header_id|>\n", " Here is the retrieved document: \\n\\n {document} \\n\\n\n", " Here is the user question: {question} \\n <|eot_id|><|start_header_id|>assistant<|end_header_id|>\n", " \"\"\",\n", " input_variables=[\"question\", \"document\"],\n", ")\n", "\n", "retrieval_grader = prompt | llm_json | JsonOutputParser()\n", "# question = \"溫室氣體是什麼\"\n", "# # docs = retriever.invoke(question)\n", "# docs = retriever.get_relevant_documents(question, k=10)\n", "# doc_txt = docs[1].page_content\n", "# print(retrieval_grader.invoke({\"question\": question, \"document\": doc_txt}))" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "# for doc in docs:\n", "# doc_txt = doc.page_content\n", "# print(retrieval_grader.invoke({\"question\": question, \"document\": doc_txt}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hallucination Grader" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'score': 'yes'}" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "### Hallucination Grader\n", "\n", "from langchain_community.chat_models import ChatOllama\n", "from langchain_core.output_parsers import JsonOutputParser\n", "from langchain_core.prompts import PromptTemplate\n", "\n", "# LLM\n", "# llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "\n", "# Prompt\n", "prompt = PromptTemplate(\n", " template=\"\"\" <|begin_of_text|><|start_header_id|>system<|end_header_id|> \n", " You are a grader assessing whether an answer is grounded in / supported by a set of facts. \n", " Give 'yes' or 'no' score to indicate whether the answer is grounded in / supported by a set of facts. \n", " Provide 'yes' or 'no' score as a JSON with a single key 'score' and no preamble or explanation. \n", " Return the a JSON with a single key 'score' and no premable or explanation. \n", " <|eot_id|><|start_header_id|>user<|end_header_id|>\n", " Here are the facts:\n", " \\n ------- \\n\n", " {documents} \n", " \\n ------- \\n\n", " Here is the answer: {generation} \n", " Provide 'yes' or 'no' score as a JSON with a single key 'score' and no premable or explanation.\n", " <|eot_id|><|start_header_id|>assistant<|end_header_id|>\"\"\",\n", " input_variables=[\"generation\", \"documents\"],\n", ")\n", "\n", "hallucination_grader = prompt | llm_json | JsonOutputParser()\n", "\n", "question = \"誰需要繳交碳費?\"\n", "docs = retriever.get_relevant_documents(question, k=10)\n", "\n", "generation = faiss_query(question, docs, llm)\n", "# docs_documents = \"\\n\\n\".join(doc.page_content for doc in docs)\n", "\n", "hallucination_grader.invoke({\"documents\": docs, \"generation\": generation})" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "docs_documents = \"\\n\\n\".join(doc.page_content for doc in docs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Answer Grader" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "### Answer Grader\n", "\n", "# LLM\n", "llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "\n", "# Prompt\n", "prompt = PromptTemplate(\n", " template=\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|> You are a grader assessing whether an \n", " answer is useful to resolve a question. Give a binary score 'yes' or 'no' to indicate whether the answer is \n", " useful to resolve a question. Provide the binary score as a JSON with a single key 'score' and no preamble or explanation.\n", " <|eot_id|><|start_header_id|>user<|end_header_id|> Here is the answer:\n", " \\n ------- \\n\n", " {generation} \n", " \\n ------- \\n\n", " Here is the question: {question} <|eot_id|><|start_header_id|>assistant<|end_header_id|>\"\"\",\n", " input_variables=[\"generation\", \"question\"],\n", ")\n", "\n", "answer_grader = prompt | llm_json | JsonOutputParser()\n", "# answer_grader.invoke({\"question\": question, \"generation\": generation})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# SQL" ] }, { "cell_type": "code", "execution_count": 104, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/anaconda3/envs/llama3/lib/python3.12/site-packages/langchain_community/utilities/sql_database.py:123: SAWarning: Did not recognize type 'vector' of column 'embedding'\n", " self._metadata.reflect(\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 104, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from importlib import reload # Python 3.4+\n", "import text_to_sql2\n", "reload(text_to_sql2)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/anaconda3/envs/llama3/lib/python3.12/site-packages/langchain_community/utilities/sql_database.py:123: SAWarning: Did not recognize type 'vector' of column 'embedding'\n", " self._metadata.reflect(\n" ] } ], "source": [ "\n", "from text_to_sql import run\n", "from langchain_community.utilities import SQLDatabase\n", "import os\n", "URI: str = os.environ.get('SUPABASE_URI')\n", "db = SQLDatabase.from_uri(URI)\n", "\n", "def run_text_to_sql(question: str):\n", " selected_table = ['2022 清冊數據(GHG)', '2022 清冊數據(ISO)', '2023 清冊數據(GHG)', '2023 清冊數據(ISO)', '水電使用量(GHG)', '水電使用量(ISO)']\n", " # question = \"建準去年的固定燃燒總排放量是多少?\"\n", " query, result, answer = run(db, question, selected_table, llm)\n", " \n", " return answer, query" ] }, { "cell_type": "code", "execution_count": 187, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/anaconda3/envs/llama3/lib/python3.12/site-packages/langchain_community/utilities/sql_database.py:123: SAWarning: Did not recognize type 'vector' of column 'embedding'\n", " self._metadata.reflect(\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 187, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from importlib import reload # Python 3.4+\n", "import text_to_sql2\n", "reload(text_to_sql2)" ] }, { "cell_type": "code", "execution_count": 188, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/usr/local/anaconda3/envs/llama3/lib/python3.12/site-packages/langchain_community/utilities/sql_database.py:123: SAWarning: Did not recognize type 'vector' of column 'embedding'\n", " self._metadata.reflect(\n" ] } ], "source": [ "from text_to_sql2 import run, get_query, query_to_nl\n", "from langchain_community.utilities import SQLDatabase\n", "import os\n", "URI: str = os.environ.get('SUPABASE_URI')\n", "db = SQLDatabase.from_uri(URI)\n", "\n", "def run_text_to_sql(question: str):\n", " selected_table = ['104_112碳排放公開及建準資料', '水電使用量(GHG)', '水電使用量(ISO)']\n", " # question = \"建準去年的固定燃燒總排放量是多少?\"\n", " query, result, answer = run(db, question, selected_table, llm)\n", " \n", " return answer, query\n", "\n", "def _get_query(question: str):\n", " selected_table = ['104_112碳排放公開及建準資料', '水電使用量(GHG)', '水電使用量(ISO)']\n", " query = get_query(db, question, selected_table, llm)\n", " return query\n", "\n", "def _query_to_nl(question: str, query: str):\n", " answer = query_to_nl(db, question, query, llm)\n", " return answer" ] }, { "cell_type": "code", "execution_count": 193, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別2總排放量\"\n", "FROM \"104_112碳排放公開及建準資料\"\n", "WHERE \"事業名稱\" like '%建準%'\n", "AND \"類別\" = '類別2-能源間接排放'\n", "AND \"盤查標準\" = 'GHG'\n", "AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;\n" ] }, { "data": { "text/plain": [ "'SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別2總排放量\"\\nFROM \"104_112碳排放公開及建準資料\"\\nWHERE \"事業名稱\" like \\'%建準%\\'\\nAND \"類別\" = \\'類別2-能源間接排放\\'\\nAND \"盤查標準\" = \\'GHG\\'\\nAND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;'" ] }, "execution_count": 193, "metadata": {}, "output_type": "execute_result" } ], "source": [ "question = \"建準去年的類別2總排放量是多少?\"\n", "query = _get_query(question)\n", "query" ] }, { "cell_type": "code", "execution_count": 194, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[(19952.8,)]\n" ] }, { "data": { "text/plain": [ "('SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別2總排放量\"\\nFROM \"104_112碳排放公開及建準資料\"\\nWHERE \"事業名稱\" like \\'%建準%\\'\\nAND \"類別\" = \\'類別2-能源間接排放\\'\\nAND \"盤查標準\" = \\'GHG\\'\\nAND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;',\n", " '[(19952.8,)]',\n", " '根據資料,去年的類別2總排放量是 19,952.8 公噸CO2e。')" ] }, "execution_count": 194, "metadata": {}, "output_type": "execute_result" } ], "source": [ "answer = _query_to_nl(question, query)\n", "answer" ] }, { "cell_type": "code", "execution_count": 142, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "SELECT SUM(\"排放量(公噸CO2e)\") AS \"台積電2021年類別2總排放量\"\n", "FROM \"104_112碳排放公開及建準資料\"\n", "WHERE \"事業名稱\" like '%台灣積體電路製造股份有限公司%'\n", "AND \"類別\" = '類別2-能源間接排放'\n", "AND \"盤查標準\" = 'GHG'\n", "AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;\n", "[(None,)]\n" ] }, { "data": { "text/plain": [ "('根據資料,台積電去年的類別2總排放量為0公噸CO2e。',\n", " 'SELECT SUM(\"排放量(公噸CO2e)\") AS \"台積電2021年類別2總排放量\"\\nFROM \"104_112碳排放公開及建準資料\"\\nWHERE \"事業名稱\" like \\'%台灣積體電路製造股份有限公司%\\'\\nAND \"類別\" = \\'類別2-能源間接排放\\'\\nAND \"盤查標準\" = \\'GHG\\'\\nAND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;')" ] }, "execution_count": 142, "metadata": {}, "output_type": "execute_result" } ], "source": [ "question = \"台積電去年的類別2總排放量是多少?\"\n", "answer, query = run_text_to_sql(question)\n", "answer, query" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## SQL Grader" ] }, { "cell_type": "code", "execution_count": 177, "metadata": {}, "outputs": [], "source": [ "\n", "### SQL Grader\n", "\n", "from langchain_community.chat_models import ChatOllama\n", "from langchain_core.output_parsers import JsonOutputParser\n", "from langchain_core.prompts import PromptTemplate\n", "\n", "# LLM\n", "# llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "\n", "prompt = PromptTemplate(\n", " template=\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|> \n", " You are a SQL query grader assessing correctness of PostgreSQL query to a user question. \n", " Based on following database description, you need to grade whether the PostgreSQL query exactly matches the user question.\n", " \n", " Here is database description:\n", " {table_info}\n", " \n", " You need to check that each where statement is correctly filtered out what user question need.\n", " \n", " For example, if user question is \"建準去年的固定燃燒總排放量是多少?\", and the PostgreSQL query is \n", " \"SELECT SUM(\"排放量(公噸CO2e)\") AS \"下游租賃總排放量\"\n", " FROM \"104_112碳排放公開及建準資料\"\n", " WHERE \"事業名稱\" like '%建準%'\n", " AND \"排放源\" = '下游租賃'\n", " AND \"盤查標準\" = 'GHG'\n", " AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;\"\n", " For the above example, we can find that user asked for \"固定燃燒\", but the PostgreSQL query gives \"排放源\" = '下游租賃' in WHERE statement, which means the PostgreSQL query is incorrect for the user question.\n", " \n", " Another example like \"建準去年的固定燃燒總排放量是多少?\", and the PostgreSQL query is \n", " \"SELECT SUM(\"排放量(公噸CO2e)\") AS \"固定燃燒總排放量\"\n", " FROM \"104_112碳排放公開及建準資料\"\n", " WHERE \"事業名稱\" like '%台積電%'\n", " AND \"排放源\" = '固定燃燒'\n", " AND \"盤查標準\" = 'GHG'\n", " AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;\"\n", " For the above example, we can find that user asked for \"建準\", but the PostgreSQL query gives \"事業名稱\" like '%台積電%' in WHERE statement, which means the PostgreSQL query is incorrect for the user question.\n", " \n", " and so on. You need to strictly examine whether the sql PostgreSQL query matches the user question.\n", " \n", " If the PostgreSQL query do not exactly matches the user question, grade it as incorrect. \n", " You need to strictly examine whether the sql PostgreSQL query matches the user question.\n", " Give a binary score 'yes' or 'no' score to indicate whether the PostgreSQL query is correct to the question. \\n\n", " Provide the binary score as a JSON with a single key 'score' and no premable or explanation.\n", " <|eot_id|>\n", " \n", " <|start_header_id|>user<|end_header_id|>\n", " Here is the PostgreSQL query: \\n\\n {sql_query} \\n\\n\n", " Here is the user question: {question} \\n <|eot_id|><|start_header_id|>assistant<|end_header_id|>\n", " \"\"\",\n", " input_variables=[\"table_info\", \"question\", \"sql_query\"],\n", ")\n", "\n", "sql_query_grader = prompt | llm_json | JsonOutputParser()" ] }, { "cell_type": "code", "execution_count": 180, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'score': 'no'}\n" ] } ], "source": [ "from text_to_sql2 import table_description\n", "question = \"建準去年的類別一排放量\"\n", "# sql_query = \"\"\"\n", "# SELECT SUM(\"高雄總部及運通廠\" + \"台北辦事處\" + \"昆山廣興廠\" + \"北海建準廠\" + \"北海立準廠\" + \"菲律賓建準廠\" + \"Inc\" + \"SAS\" + \"India\") AS \"類別一排放量\"\n", "# FROM \"2023 清冊數據(GHG)\"\n", "# WHERE \"類別\" = '類別一-直接排放'\n", "# \"\"\"\n", "question = \"台積電去年的固定燃燒總排放量是多少?\"\n", "sql_query = \"\"\"\n", "SELECT SUM(\"排放量(公噸CO2e)\") AS \"固定燃燒總排放量\"\n", "FROM \"104_112碳排放公開及建準資料\"\n", "WHERE \"事業名稱\" like '%建準%'\n", "AND \"排放源\" = '固定燃燒'\n", "AND \"盤查標準\" = 'GHG'\n", "AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1;\n", "\"\"\"\n", "print(sql_query_grader.invoke({\"table_info\": table_description(), \"question\": question, \"sql_query\": sql_query}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Router" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'datasource': 'company_private_data'}\n" ] } ], "source": [ "### Router\n", "\n", "from langchain_community.chat_models import ChatOllama\n", "from langchain_core.output_parsers import JsonOutputParser\n", "from langchain_core.prompts import PromptTemplate\n", "\n", "# LLM\n", "# llm_json = ChatOllama(model=local_llm, format=\"json\", temperature=0)\n", "\n", "prompt = PromptTemplate(\n", " template=\"\"\"<|begin_of_text|><|start_header_id|>system<|end_header_id|> \n", " You are an expert at routing a user question to a vectorstore or company private data. \n", " Use company private data for questions about the informations about a company's greenhouse gas emissions data.\n", " Otherwise, use the vectorstore for questions on ESG field knowledge or news about ESG. \n", " You do not need to be stringent with the keywords in the question related to these topics. \n", " Give a binary choice 'company_private_data' or 'vectorstore' based on the question. \n", " Return the a JSON with a single key 'datasource' and no premable or explanation. \n", " \n", " Question to route: {question} \n", " <|eot_id|><|start_header_id|>assistant<|end_header_id|>\"\"\",\n", " input_variables=[\"question\"],\n", ")\n", "\n", "question_router = prompt | llm_json | JsonOutputParser()\n", "question = \"建準去年的類別1排放量是多少?\"\n", "# docs = retriever.get_relevant_documents(question)\n", "# doc_txt = docs[1].page_content\n", "print(question_router.invoke({\"question\": question}))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Node" ] }, { "cell_type": "code", "execution_count": 215, "metadata": {}, "outputs": [], "source": [ "# RAG + text-to-sql\n", "\n", "from pprint import pprint\n", "from typing import List\n", "\n", "from langchain_core.documents import Document\n", "from typing_extensions import TypedDict\n", "\n", "from langgraph.graph import END, StateGraph, START\n", "\n", "### State\n", "\n", "\n", "class GraphState(TypedDict):\n", " \"\"\"\n", " Represents the state of our graph.\n", "\n", " Attributes:\n", " question: question\n", " generation: LLM generation\n", " company_private_data: whether to search company private data\n", " documents: list of documents\n", " \"\"\"\n", "\n", " question: str\n", " generation: str\n", " documents: List[str]\n", " retry: int\n", " sql_query: str" ] }, { "cell_type": "code", "execution_count": 221, "metadata": {}, "outputs": [], "source": [ "def retrieve_and_generation(state):\n", " \"\"\"\n", " Retrieve documents from vectorstore\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): New key added to state, documents, that contains retrieved documents, and generation, genrating by LLM\n", " \"\"\"\n", " print(\"---RETRIEVE---\")\n", " question = state[\"question\"]\n", "\n", " # Retrieval\n", " # documents = retriever.invoke(question)\n", " # TODO: correct Retrieval function\n", " documents = retriever.get_relevant_documents(question, k=30)\n", " # docs_documents = \"\\n\\n\".join(doc.page_content for doc in documents)\n", " print(documents)\n", " generation = faiss_query(question, documents, llm)\n", " return {\"documents\": documents, \"question\": question, \"generation\": generation}\n", "\n", "def company_private_data_get_sql_query(state):\n", " \"\"\"\n", " Get PostgreSQL query according to question\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): return generated PostgreSQL query and record retry times\n", " \"\"\"\n", " print(\"---SQL QUERY---\")\n", " question = state[\"question\"]\n", " \n", " if state[\"retry\"]:\n", " retry = state[\"retry\"]\n", " retry += 1\n", " else: \n", " retry = 0\n", " print(\"RETRY: \", retry)\n", " \n", " sql_query = _get_query(question)\n", " \n", " return {\"sql_query\": sql_query, \"question\": question, \"retry\": retry}\n", " \n", "def company_private_data_search(state):\n", " \"\"\"\n", " Execute PostgreSQL query and convert to nature language.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): Appended sql results to state\n", " \"\"\"\n", "\n", " print(\"---SQL TO NL---\")\n", " print(state)\n", " question = state[\"question\"]\n", " sql_query = state[\"sql_query\"]\n", " generation = _query_to_nl(question, sql_query)\n", " \n", " # generation = [company_private_data_result]\n", " \n", " return {\"sql_query\": sql_query, \"question\": question, \"generation\": generation}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Conditional edge" ] }, { "cell_type": "code", "execution_count": 229, "metadata": {}, "outputs": [], "source": [ "### Conditional edge\n", "\n", "\n", "def route_question(state):\n", " \"\"\"\n", " Route question to web search or RAG.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " str: Next node to call\n", " \"\"\"\n", "\n", " print(\"---ROUTE QUESTION---\")\n", " question = state[\"question\"]\n", " print(question)\n", " source = question_router.invoke({\"question\": question})\n", " print(source)\n", " print(source[\"datasource\"])\n", " if source[\"datasource\"] == \"company_private_data\":\n", " print(\"---ROUTE QUESTION TO TEXT-TO-SQL---\")\n", " return \"company_private_data\"\n", " elif source[\"datasource\"] == \"vectorstore\":\n", " print(\"---ROUTE QUESTION TO RAG---\")\n", " return \"vectorstore\"\n", " \n", "def grade_generation_v_documents_and_question(state):\n", " \"\"\"\n", " Determines whether the generation is grounded in the document and answers question.\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " str: Decision for next node to call\n", " \"\"\"\n", "\n", " print(\"---CHECK HALLUCINATIONS---\")\n", " question = state[\"question\"]\n", " documents = state[\"documents\"]\n", " generation = state[\"generation\"]\n", "\n", " \n", " # print(docs_documents)\n", " # print(generation)\n", " score = hallucination_grader.invoke(\n", " {\"documents\": documents, \"generation\": generation}\n", " )\n", " print(score)\n", " grade = score[\"score\"]\n", "\n", " # Check hallucination\n", " if grade in [\"yes\", \"true\", 1, \"1\"]:\n", " print(\"---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---\")\n", " # Check question-answering\n", " print(\"---GRADE GENERATION vs QUESTION---\")\n", " score = answer_grader.invoke({\"question\": question, \"generation\": generation})\n", " grade = score[\"score\"]\n", " if grade in [\"yes\", \"true\", 1, \"1\"]:\n", " print(\"---DECISION: GENERATION ADDRESSES QUESTION---\")\n", " return \"useful\"\n", " else:\n", " print(\"---DECISION: GENERATION DOES NOT ADDRESS QUESTION---\")\n", " return \"not useful\"\n", " else:\n", " pprint(\"---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---\")\n", " return \"not supported\"\n", " \n", "def grade_sql_query(state):\n", " \"\"\"\n", " Determines whether the Postgresql query are correct to the question\n", "\n", " Args:\n", " state (dict): The current graph state\n", "\n", " Returns:\n", " state (dict): Decision for retry or continue\n", " \"\"\"\n", "\n", " print(\"---CHECK SQL CORRECTNESS TO QUESTION---\")\n", " question = state[\"question\"]\n", " sql_query = state[\"sql_query\"]\n", " retry = state[\"retry\"]\n", "\n", " # Score each doc\n", " \n", " score = sql_query_grader.invoke({\"table_info\": table_description(), \"question\": question, \"sql_query\": sql_query})\n", " grade = score[\"score\"]\n", " # Document relevant\n", " if grade in [\"yes\", \"true\", 1, \"1\"]:\n", " print(\"---GRADE: CORRECT SQL QUERY---\")\n", " return \"correct\"\n", " elif retry >= 5:\n", " print(\"---GRADE: INCORRECT SQL QUERY AND REACH RETRY LIMIT---\")\n", " return \"failed\"\n", " else:\n", " print(\"---GRADE: INCORRECT SQL QUERY---\")\n", " return \"incorrect\"\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Graph" ] }, { "cell_type": "code", "execution_count": 233, "metadata": {}, "outputs": [], "source": [ "\n", "from langgraph.pregel import RetryPolicy\n", "\n", "workflow = StateGraph(GraphState)\n", "\n", "# Define the nodes\n", "workflow.add_node(\"company_private_data_query\", company_private_data_get_sql_query, retry=RetryPolicy(max_attempts=5)) # web search\n", "workflow.add_node(\"company_private_data_search\", company_private_data_search, retry=RetryPolicy(max_attempts=5)) # web search\n", "workflow.add_node(\"retrieve_and_generation\", retrieve_and_generation, retry=RetryPolicy(max_attempts=5)) # retrieve\n", "# workflow.add_node(\"grade_generation\", grade_documents) # grade documents\n", "# workflow.add_node(\"generate\", generate) # generatae\n", "\n", "workflow.add_conditional_edges(\n", " START,\n", " route_question,\n", " {\n", " \"company_private_data\": \"company_private_data_query\",\n", " \"vectorstore\": \"retrieve_and_generation\",\n", " },\n", ")\n", "\n", "workflow.add_conditional_edges(\n", " \"retrieve_and_generation\",\n", " grade_generation_v_documents_and_question,\n", " {\n", " \"not supported\": \"retrieve_and_generation\",\n", " \"useful\": END,\n", " \"not useful\": \"retrieve_and_generation\",\n", " },\n", ")\n", "workflow.add_conditional_edges(\n", " \"company_private_data_query\",\n", " grade_sql_query,\n", " {\n", " \"correct\": \"company_private_data_search\",\n", " \"incorrect\": \"company_private_data_query\",\n", " \"failed\": END\n", " \n", " },\n", ")\n", "workflow.add_edge(\"company_private_data_search\", END)\n", "\n", "\n", "# workflow.add_edge(\"company_private_data_search\", END)\n", "\n", "app = workflow.compile()" ] }, { "cell_type": "code", "execution_count": 224, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "%%{init: {'flowchart': {'curve': 'linear'}}}%%\n", "graph TD;\n", "\t__start__([__start__]):::first\n", "\tcompany_private_data_query(company_private_data_query)\n", "\tcompany_private_data_search(company_private_data_search)\n", "\tretrieve_and_generation(retrieve_and_generation)\n", "\t__end__([__end__]):::last\n", "\tcompany_private_data_query --> company_private_data_search;\n", "\t__start__ -.  company_private_data  .-> company_private_data_query;\n", "\t__start__ -.  vectorstore  .-> retrieve_and_generation;\n", "\tretrieve_and_generation -.  not supported  .-> retrieve_and_generation;\n", "\tretrieve_and_generation -.  useful  .-> __end__;\n", "\tretrieve_and_generation -.  not useful  .-> retrieve_and_generation;\n", "\tcompany_private_data_search -.  not supported  .-> company_private_data_query;\n", "\tcompany_private_data_search -.  useful  .-> __end__;\n", "\tcompany_private_data_search -.  not useful  .-> retrieve_and_generation;\n", "\tclassDef default fill:#f2f0ff,line-height:1.2\n", "\tclassDef first fill-opacity:0\n", "\tclassDef last fill:#bfb6fc\n", "\n" ] } ], "source": [ "print(app.get_graph().draw_mermaid())" ] }, { "cell_type": "code", "execution_count": 234, "metadata": {}, "outputs": [ { "data": { "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAFoA2wDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAYHBAUIAwECCf/EAFkQAAEEAgECAgUGCAgKBwcEAwEAAgMEBQYRBxITIRQXIjFBCBVRVJXTFjJVVmGS0dIjQlJTcYGRkyQzNDZicnOUorEJGHR1srPBNThjgqGj1De0w/BEV5b/xAAaAQEBAAMBAQAAAAAAAAAAAAAAAQIDBAUG/8QANhEBAAEBBgMFBwMEAwEAAAAAAAEDAhETIVGRElLRBBQxYeEiQVNxobHwBSMzFTKBwUJDsvH/2gAMAwEAAhEDEQA/AP6poiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICItLmsvZbaZi8WxkmTlZ4hkmaXQ1Y+ePEkAIJ5PIawEFxB8wA5zcrNmbU3Qvi28srII3Pke2Njfe5x4A/rWudtOGaSDl6AI+Bss/atfFoGJmlFjKxnP3PM+PlOJg3ny9iMjsYOPLhrR+nnkrYDVcKAAMPQAHkB6Mz9i3XUY8Zmfz80Mnz8KsJ+WKH+9M/an4VYT8sUP8AemftX38FsL+SKH+7M/Yn4LYX8kUP92Z+xP2fP6Lk+fhVhPyxQ/3pn7U/CrCflih/vTP2r7+C2F/JFD/dmfsT8FsL+SKH+7M/Yn7Pn9DJ8/CrCflih/vTP2p+FWE/LFD/AHpn7V9/BbC/kih/uzP2J+C2F/JFD/dmfsT9nz+hkyKmZx+Qf2Vb1ay7+TDM15/+hWYtJc0jXb8fZYwWOlHHA7qrOR58+R45Hn5+SwXY27p7TYx8tvJYlnJlxsrnTzRt/lQPce48fzbieR+JwQGucFO3lYnPz6/nzS6EpReVS1Deqw2a8jZoJmCSORh5Dmkcgj+peq0TF2UoIiKAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICjGgcX8VYzb+HT5ew+z3f/CB7IW/o4jazyHl3Fx+J5k6jHTYeBptCk7kS4/voSAjgh0L3R/2HtBB+IIPxXRZypWpjWNs+kL7knRaTad41zRqsNnZNgxev1pn+HFNlbsdZkj+Oe1pe4AngE8BRr/rC9LOOfWXp/H/AH9V+8XOjc9SeoeK6V6de2XMixJSquij8GnF4k00kkjY442N5HLnPe0DkgefmQPNVf1N+UNndY1jUcpi9E2CCxldlrYezjslVgZZbG5w7msHpAYXyA9rHBxZyHdxbwt/ufUPSuqGnZnAa5LrXVa5NA0y6vTzlUvsw+IwPdz3EN7Qe4E8e0GjlpIIq6t0r6leqmqJMZZs3MFutXYMHrOUzEdm5HjYXM/wR1suLC/nxS3ue4AdoLj8At7c+tx0jHUblvQ9yuxzURkLYx1CKf5uZwS5s7hN2l7eDy2MvPlyORwsbP8AyitfxWU1bH43FZvabez4p+YxLMLWjeLEDfDPvkkYGEtkDgX8N4BBIPANadTen249Sdw+dc305Ox4q9gW08fhsjma7a+BveJL4k07A4tkLmuhIkiEjm9haB8VtOjfS3bNc2LpHazGGNGHXdGs4HIPNmGTw7QlqhgHa8lwe2F7gQCAOA7g+SCRdN+uGf3HrPu+pXtNy1DGYietDXuvZWDaodVEpNkiw4kyOPseG0jtLe7tPKulUnQpbD0064b9n7mFisaVsjaV6bYTka8EWKFar4Uvjskc13bxGHdzQRwTzxwpYPlCdLHEAdStQJPkAM9V8/8A7iCwEUFp9eemeRuQVKnUTU7VqeRsUMEObrPfI9x4a1rQ/kkkgAD3qdIIxqZGNzWwYVvAgrzMuVmDn2IpwSW/3rJiPgAQPgpOoxgh6Xu+z3Wg+HHHUx/JHALo2vlPB+I/wkD+kEfBSddFf++/yj7Qs+IiIudBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERARaPbN417RKUNvY83QwdaaUQRSX7DIRJIfcxvcR3O/QPNapnUKabqbNp8er50Qw0/Sn7C+qBje48dsTZO7lz/wAbkAeXH6UExUbvxSaxlbWWrwunx1vtdfhha50jHtAaJ2NH43sgNc0DnhjSPMEOgsXTXdupXTLMa91O2KLHZC/eEsdjRZ5aZr1QWEQeI8Fzu7teHEg8h/HwBVpYfGRYTEUcdA+WSGpAyux87y+RzWNDQXOPmTwPM/ErZYt8M+U+KxNz7Xno5qnFYgkr36sg7o5Yy2Rjv0gjkFfr5sp8/wCSQf3Y/YtRe0bF27ktyBtjGXZSXS2MbYfXdK4jjueGkNeePi4E+Q+gLHOkTk+W0Z5o+gTxH/nGtnDSnwtXfOOhkkUVOCB3dFBHG7jjljACvZRb8CJ/zpz39/F90n4ET/nTnv7+L7pMOnz/AElbo1SlFFvwIn/OnPf38X3SfgRP+dOe/v4vukw6fP8ASS6NUoc1r2lrgHNI4II5BWP820z/AP4sH92P2KP/AIET/nTnv7+L7pPwIn/OnPf38X3SYdPn+kl0apC3H1WuBFaEEeYIjHktXmdj8GycXjPCuZtzeRAXcsrg+6SYjza36B73e4fEjE/ARkw7bedzlyPjgsdeMQd/SYgw/wD1W6xOFoYGp6Lj6kVODuLyyJvHc4+9x+lx+JPmUupWM7+Kfp+fl6ZQi8O4aloGbwumX8/UrbFlQ+etVtSBs117nkyPHwJc8u8v6h7lNVg3cHjcldp3LePq2rlJxfVsTQtfJA4ggljiOWkgkcjjyKhFTotj9Zsb5k9SyNzAbBtcbnyXZ5X3YaloiTixHBI/tB7pO4tBDT2tHAA4Wm1am1M2p8ZRYqKq7GQ6odP9H1euMZB1U2L0ow5m9FPDieIC53EzWEFvLQWAsb7+13HmVIoOrOAm6o2On/ddj2KKmLwa+lIIJIvLktm47CW9zQQSPNwHmsRMkWDh85jthpC5ishVydRzi0WKczZYyR7x3NJHIWcgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIvOeeKrBJNNIyGGNpe+SRwa1rQOSST7gB8VW+4desHg9CZtWt0ch1GqS3fm6GDT423nyTe0D5tdx2gt4LgTxyPfygsxa/PbBi9WxFnK5nI1MTjKzQ6e7embDDECQAXPcQB5kDzPvIUZndv1nqdjZKjsHB07bR8S02dkvzpLZcHgMaPxGsbxGST5+ZCxtX6MYfB4nYMblb+U3WnnLhuWoNqsi/GPMFsbGPb2iNvDQG8H8Ue9B+8t1br18nplfDYHMbTQ2fiWDM4aBs1GrAew+NPL3DtaRI0jyPcOePcV+auH3zN5ncqefyWLx+r3IDVwkmBM0eSrhzSHTSSO9kSDu9ntHA7QfiQpxTpwY+rFWqwR1q0LQyOGFgYxjR7gAPIAfQF7IIRrnR/XsLqGG17IxzbfXxM7rVa5tDm5C0J3F5Mpke38f+EeAQBwDwOApuiICIiAiIgIiICIiAiIgIiICIiAiIgrW50C1qnoOY1XUH2+nVbJ2m3pLmqSCpOydpYe5ruCGg+GxpaAAW8jy5K98jQ6jYbYdKqYK5h8rq1eEVc/YzJk+cZiGgCeIs4Z3HtPIPly/3fRYaIK7xPWBsmZ3irntazGq4vVmGxJncnEG0blYB5MsMgPLuBG4kceQ458zwpZqW34XfNfqZzXsnWzGItBxhuVJA+N/Di1wBHxDgQR8CCFtpI2TRujka18bwWua4cgg+8EKB7z0N1Df9cxeDuUJMdj8XcF+lHhp30RBP7XLmiItHn3v55B/HcfeeUE+RQ063tzeqIzTdvY7TXU/BfrLsdH3NnHumbY57/Pk8t93kP0qNV+sGe1Dp7sO0dS9Pk1iPEXBCyHEWhlH24HOYGztbG0Fo5k4LTyQGOJ4HCC10Whw29YLOUcDZr5KCM52o29jq9p3g2LEJa13c2J/D/IPZyOOR3Dnhb5AREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQF5WpTBWlkA5LGFwB/QF6rHyP+QWf9m7/kg0f4UTfzMf8AaU/Cib+Zj/tKojrRk8lkd76d6TWzNzX8ZsM12S9exsvg2XtrwteyvHL72d5cSS3h3EZAI81pt2wl/CbP056d1Nq2KHC5yzkLF3IyZJ7sg9sELXx1mWT7bWuLi4kHu4jI7vNB0h+FE38zH/aU/Cib+Zj/ALSuNMlnthgzg0aHbM5Jjsd1CpYmPLsuH0uWpPjpZpK0k3vf2PJHc7lw4aee5oI8NjoZvX8J1utU932vxNHmjnwgnzEsoi5qRWXNl7iTO0ucW8SlwDfdweSQ7S/Cib+Zj/tKfhRN/Mx/2lcuw4q31F6mdTRf2/YsDUxlDFz0xjcvLWgpvkqve+TsB7SOWgkOBafPkH3qKQbnsXWLUNIr1pti/DB+sMzF9+Mzpw1OJj3OjZYkLI3mR73RuLY+0s4554HCDs38KJv5mP8AtKfhRN/Mx/2lci6lsGd6r3ei8OU2LMUYc1qV25k24m6+obcsbqoDnGPgtPLieWFpHJAIa5wLX92zur4TXdmymwX7uG1vbcnq2XluWXFs1F9l0FezP59rnxSNgHiEc9rn8nzKDrr8KJv5mP8AtKfhRN/Mx/2lU50Cu5PYNHm2jKWrUztkvz5apXsyucKtN7uK0TATw1vgtjfwAB3PcfitX16N21nemWJrZfJ4itlNiNa27FXH1pJYvQ7D+wuYQeCWt/o4BHBAIC9/wom/mY/7StXleqFLCZTD4669kNzLzPgpR+G93ivZG6Rw5AIbw1rjy4geXHvXIW65vZNct7ppOK2/NGpjdh1tlHKTXXTXKguTATQOmd7UjQGtcGv7vZk4dyFYW16xJp3VTpPj6OxbLJTyVvJVbkVrOWpm2GegTyAuDn8EteAWn+LwOOOAg6P/AAom/mY/7SsPI7BlLDq/oc1eo1sjXTd8JkdIwEEtB7gG8jkc8H3+XC5d6d7NsexbRrXTq9mMgcjpN23Psd/0h7ZbsEPs0BI/nl4nbNHI4O57vBdzytDqu3bB+H+gbVibexfgfteZmpN+fs96ULsL4Z3se2n4fbXaDECwtf3cABzfNB0RreNx9/qLtO3V9nymwMuRuw9jBz5AT4ui5nYJWRwgexJy32uXHze4cD3CW6rBidGw0OI13BY7CYyLksqUIGwxgn3ntaACT8T7z8VyfozJelHSvrXuGFt5W7l8TmM5DXgvZKezACyXlsronvLXPHAc6QjucOeT5lSnpTqXUuls+tZqXJOn1+zC6TKuu7XLlW3WPiJjkhhdVjbC4PLD/BuDe0kcHyQdPfhRN/Mx/wBpWp2LqnQ1NuOdlZI6rchehx1Y9j3+JYlPEbPZB45I954A+JCw8ladRx1qyyIzvhifI2JvveQCeB/TxwuR2Yy/s+jdH+oWW2vMZfLZ7bMVasUzcPzdD4kziIoq/wCKzw+A3ke0SDyTzwg7a/Cib+Zj/tKwqnUKtfyN7H1pqs92gWC3Xjk7nwF7e5geAfZJb5gH4EH4rj/Y9wz43Khues29ibrrtxgws0+TzxNW0x1oVp44ceIy0Rg9wbIXNeC3ngqY9P8AXsbhevnWfYLWTy8bMXYqWnxnJ2HQFklAPeXw9/a8N5d2Ag9gaA3jgIOnPwom/mY/7StpiMk/IxyOexrC08eyuKOmGwbNQ6o9OrjLGws1Xc6917I9jz/zhNZjbX8eGbwAwNrO4APDHkcP4IHC7M1f/ET/AOsP+SDdoiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCNbH021fbtgwuczGCp5DMYWXxsdemj5lrO555a73+8A8e7kArSU+nOZ1vMbrm8Tt+WyN3NwufRxecnE2Px1ntPaYmtaHNj57OWjnyB95KsBEFU3N/wB86ddOcHf2vUX7ls0tw1sjW0SJ0kcMXL+2djZi1zhw2PkEjgvPwHKlp6oaqOoP4DHOVW7b6KLgxTiRK6I8+03kcHyaSQDyB5kcEKUrBlweNmy0OUkx9WTJwMdFFddC0zRsP4zWv45APxAKDNBDgCDyD5ghfVVWN6DwaBpG0Ybptm7enZLM2/T4sjZ5ybacxc0u7IpnEFru13IJ97yfoC2cWd3TXNh6e67bxI2atfpTsz+zwcQR1bEULXNf4QBAbK/uaG8jjke/goLCREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBeN2N0tOdjBy5zHAD6TwvZEFYbv0ixvUbEsxuxYZmSqxytni5lMckMjfc+ORjg9jhyfNpB8ytHJ8m/VptVj12TXjJjI7fp8fdemNhljjjxWz+J4ofx5dwfzx5e5XWiCncZ8n/XMPi8RjqWvMgq4rJfPFUNsv7xc7XN8Z7y/ulcQ9wJeXc8jn3DjKv9EsPlKu11rOF8WHaePnhvpTx6VxE2EeYf7H8Gxo9jt93Pv81bCIOZ7XyTaGz9R9vzez4uLI4nJQ0IaNaHITxOayGIskZO1jmte0nt4Di8Hg8gfGabJ8nrWdttY+xktcjfJQqijCK9h9dnowPIge2N7WyRf/AA3gt9/l5lXIiCo9e6F4PVLGFmxWD9Ekw1exUocWnuFeGeRskrAHPI4Lmt4B57QAG8DyUW6jfJ+yWe0fJadrNTH4rDbDdns5ue5LJNK3xpPElkgaXEeI5xJHJDW8ggeXC6FRBUWZ0feabaNTUbeAxGKrV2QCtksZLacO3yHa5lqIBoaGjggnyPn8BB956A7r1Mm1SLa7eDydDFZkX52Y2GxjnGH0aaPhp8eVxf3yMIIczgA+fPC6VRBztf6adOOnbNb0q1i2Y92ey4u46sZbEkly/AWzCR8wcXFzexp5kdweOPMeSsTK9N4M3msJlruO8bIYWWWahN45b4L5InRPPAcA7lj3D2geOeR5+azd5yu0Udz0ivg9eq5fDWrczMxfn478dEI+WPZ7Q83O9k+RU4QV9S6fRY7OZLMVsXHDk8kyJlyy1w7phEHCMO8/4oc4D+lQuh8lrS8Xkqt+rq4hs07bb1RwvTdtSUP7+YW+J2xNLvxmMAa4eTgR5K9UQVFT6EYGhtOU2GvghHk8o17bvFp/gWO5oa8vg7/CLnAAF3ZyfiVqtf8Ak5YfQH2r+mYaDEZo15K9Wa5Ynt16wcQ4tbC6YBrCWtJbGWe4e5XkiCl6OodWWXa7rmc1SaoJGmaODBWI5HM59oNcbzg0kc8EggH4H3LX1/kq6TTzkOWr6sILcF9uUhbFfnbBFaa7uEjIRL4bTz5kBoB+IKvhEFFZH5LWl5XI3L1nVw+xas+mv7b0zGMsdweZ42CQNikLhyXsDXHk8nzPO5yHQzB5Tc27XYwnOdEbYX2Y7ckbZmNBDRLG14ZLwHEDva7gFW4iCjcD8mDT9ZymNyON1o172Nl8WjOb8z3VfZc0si7pT2RkPcDG3hh+LfIK3cDSmpRSiZnYXOBHmD/yW1RAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFBNtxWbtdU9Bu0trhxWFqNyAyGBe4B+WLoWiItHx8J3Lz9HKnaqrqB+BPr46T/Pfpn4Z9mW/B7wefA49Gb6V4vw/xfHbz8eUFqoiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICLFyWTp4enJbv2oaVWPzfNYkDGN/pJ8loD1Q1RpIOdqAjyPtH9i22KVSpF9izM/KFumUpRRX1pan+Xan6x/YnrS1P8u1P1j+xZ92r8k7SXTolSKK+tLU/y7U/WP7E9aWp/l2p+sf2J3avyTtJdOiVIor60tT/LtT9Y/sT1pan+Xan6x/Yndq/JO0l06JUiivrS1P8ALtT9Y/sT1pan+Xan6x/Yndq/JO0l06JUiivrS1P8u1P1j+xPWlqf5dqfrH9id2r8k7SXTolSw8yL7sReGKfXjyhgkFR9xjnwtm7T2GRrSCW93HIBBI54IWh9aWp/l2p+sf2J60tT/LtT9Y/sTu1fknaS6dH8xd9/6Q/rTV3ilBkaWEwWQ1q9PFaxdOGzHDZkHMborLfSD3taQSACOD8Su+/kj9Vdz61dH4Nv3XGY3FWr9yX0CPFxSxMkqNDWte5sj3nuMgl8+eC0NIHxPIvywfk64nqf8obWM/q+RrsxGyysgz9mA+VF8fHfYIPl7UQ8hx5vYeTy8LunXtz0XVcDjcLi8tSqY3HVo6laBjjxHExoa1v9QATu1fknaS6dE6RRX1pan+Xan6x/YnrS1P8ALtT9Y/sTu1fknaS6dEqRRX1pan+Xan6x/YnrS1P8u1P1j+xO7V+SdpLp0SpFFfWlqf5dqfrH9ietLU/y7U/WP7E7tX5J2kunRKkUV9aWp/l2p+sf2J60tT/LtT9Y/sTu1fknaS6dEqRRX1pan+Xan6x/YnrS1P8ALtT9Y/sTu1fknaS6dEqRRX1pan+Xan6x/YnrS1P8u1P1j+xO7V+SdpLp0SpFp8Lt+E2OaSHGZWrdnjb3vhilBe1vPHcW+/jn48cLcLTas2rE3WoulBERYgiIgIvxNPHWhfLNI2KJgLnPe4BrR8SSfco9sPUnU9TxNXKZrZcTi8dbl8Cvbt3I445pPP2GOJ4c72T5Dk+R+hBJEUVtdUdVpdQaejT5qCPbLlc2oMWQ7xJIgHEvB4444Y/3n+KVqsZ170HMY/b7tPY4J6upOezNyCGUCkW9/cHct5dx4b/xefxUE/RQA9e+n7NW1zZJNnqQ4LYrQpYu/M18cdiYlw7eXNHb5sd5u4Hl71P0BERAREQEREBERAREQEREBQTbcrm6vVPQaVLVIcrhbbcgchnntBfiS2FpiDT8PFdyw/Twp2oJtuKzdrqnoN2ltcOKwtRuQGQwL3APyxdC0RFo+PhO5efo5QTtERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBCMu70/qGa838JFj8dFYgjd+K2SWSZrn8fyu2IAH3gOcAR3O52i1Nr/9Tsp/3PS/862tF1D6q4zp3PiaU1HJZvNZZ8jaGHw9cTWrAjAMjwHOa1rGgjlznADkefmvVtZRZjyj7LKZoq0y3XbH4fHYB0utbJJns26YU9ajpM+cSITxK9zTII2sb7PtF/B7m8E8hYR+UnrcuNwVipi87fvZi/ZxUeJr02+mQXIGOdLBMxzwGOAafMntA8y4N9pa74RbCKl6nyqMBPXntWNZ2rH4+lkW4nK3bdCNsOLtOkawRzkSknzew90Ye0B7eSOVstk+UNjdd2PacSzVdoy41jwTlbuMpxSwQMkgbMHjmUPeAx3mGtLhwfLjglxQLWRVltvXrF6vjaeWr67smyYCxjmZZ2ZwlBs1SGs4Fwe5zntJ9kdxa0OcBwSPML953rviMZnsdhsZhc5tOQyGJbm6seErRyNlqud29/dJIxrfgfaI57gByTwl8CykVe6j1y1zdcnrVPHsutbsOMnydCxYhEcb/BkbHNAeXdwmYXAlvHHAPBPC3+h73Q6iYezlMXDZZQivWaMc1hrWiwYJXROlj4ceYy5juCeCePcFb4kSNFCOoXVen09zGu4l+FzGdyeefYZSq4iGJ7iYYw9/cZJGBo7T7+ePI8kKN2/lNatQ1N2cs4/NwPhzUWAuYl1IenU7chHa18Qd7QIc0gxl/cHDt7vcpfAttFTlzrDkJeo2qY2XB7TgG28bkrvzRaoUpBkTB2gM8Vthzo5G+Tmgey4TN7iODxIsZ1w1/N4vQbuPhvXPw0J+b4Io2eJE1sLpZXzAvAaIw3tdwXEOIAB5S+BYKKtdY664/YNsqa7c1rZdZv34Zpscc7QbAy8IgDIIyHuIcGkO7Xhp4+CjPSf5RFjZNR2rP7hgb2tY3C3b7X5CeOIVxFDYdG2EBk0j3TgABwDe0u5DSeQnFAvBFXmodaaGz7LXwF3X9g1XKXK8lqjDn6bYBdjYW+IYyx7x3N7mksd2uAdz2+/iwXvbGxz3uDGNHJc48AD6SrE3j9IueNh+UnLsea0CPUcdnaeEy+0QUHZ+1jo20MlV7Ju9kL3EvAc5rSHdrCQ09p45U9i66Y+Pdqeu5LW9lwQv3ZMdRy2ToNipW7DA89jHh5cO4RvLS5rQ4DyJU4oFlIqd6W9as1ue3b9i8lqeUrU8Flp6le7FFD4bY44IXiJ4E7numcXucO1nb2uYOQeQt7pPWzH7jtP4O2de2HVcvJUdeq19gpNg9Lga5rXvjLXvHLS9nLXdrh3DyS+BYqL4SGgkngDzJKp2n8qLXMjZxj6uC2SfB5XKw4fH7A2g0ULc8kwiBjeZA7sB7j3loBDT29x4BszEeIuNFXFzrvgKOg7Pt0lPJHG69lLGJtRNij8Z8sNgV3OjHfwWl55BJB4+APkvHa+vuI1fLZenFgthz9fC8fO+Rw1ET1seewSFsji9rnOaxzXubG15AI5A9yl8CzUVYS9f8NY3IazhsLnNlvuoVMo2XFQROgdVsFwZL4kkrAAO0Eg8Ehw7Q7h3GXQ656/kNNwexNr5CKPL5iPBRUJImelRXHWDA6ORoeWjscx7ncOPDWEjnyBXwN71FsS4rWLGYqQGbJYwts1Q13a4vDhywO+AeOWn4cOPPKwslvHVS30uxOZw/TenV3OzbdHb13KZqMsqQAygS+OwdryQ2I9oAI8Qj+L57LqH/mbkv9Vv/jarFWPaP47E+c/6Ze5Dsm3f39ScM7Hv12PQG1nHJssic5V0/bJ2iIj+CEYPgk93meH/AKFq8Vq3UZzd9iym7UgzJ+IzWpqWKZ4mGafFDHyBx4nc3uhPB4BMZ/lKxUXAxVTlOjuzbJ0zwmtZbqjsMOZp2jYu7Lg2R4+1eZzJxEWtDmsaA9g8v5sfSVI8h0rxuS6o4ze5MjmGZLH1DUioR3nNoOBEgL3we5z+JT5/6LfoUzRBXeD6AaLr8W7RVcM50W5ve/OR2Lc0rbXeZOW8OeQwfwsnkzt9/wCgcbev0k0qtq2L1r8FMPNgMW/xaOOs045oaz+XHvY14PDvbf7Q8/aP0qWog1rosSciLzmU/T2t8MWSGeKG+fs93v48z5fpK+sZiY2zta2m1s/+NADAJP8AW+n3n3qnuuW4TaF012XOQfOEUlWvI4WsZVhszVfI/wAP4Uz2MeGfjFpd58fFaTK9c6WAyz8EzA7FsmXqYmvlrHzXSicDBJ3jvJdI1ocDGeWc8nuHYHcO4C87+L17KUoadypjbdSCVk8UE8cb2RyMcHse1pHAc1wBBHmCOVGenl/cqeR2mLdchhblI5VxwVjGBzHmm4BzWzM8w0sJ7e7k88Hny4JrXIfKB15tfXzhaGX2y7nMeMrUx+DqtknFQ8DxpO9zGxt5Pb7TgS7kAEgr1znXDH4efE0Ydc2PLZ3IUTkvmOjSablWuD2mSdr3taz2vZA7uSQQAeEF+/ONX6zD+uE+cav1mH9cLna58ovWmU9Onx1HMZ1+1ts/NtfHVWmUyQAeLFI1729jgSQefIFju4gDlZGM651MxudnVqmqbJLmKQpOyDBXg8Oi2zG17XSv8bt9kOIcGlzuWu7Q4DlB0D841frMP64T5xq/WYf1wud6/wAojX7F2s4YrOs161cbQr7O+kBjZZnP8NvD+/v7XScMEhYGEkcO4PKzIOuGOye02MPh9f2HO16l75tt5nHUmvo17AIa9jnueHHsJ4cWNcG+fJ8igvz5xq/WYf1wvWKaOdpdG9sjQeOWnlc2dKOr2a3rdd1w2Q1jIUqmIy8tKvf7IRBGxkMLgyUiZzzI4vc4Fre3tc3zB5Cv7WP8hk/2h/5BBuF8c4NaSSAB5kn4L6vG7/kc/wDs3f8AJB+fnGr9Zh/XCfONX6zD+uFU20b5Q1HN61jr8FkDP3HUK9uNrTBFOI3SNZIS4EF4Y4N4B5I4PCjeR68YGhNl4IqOVyNqjlfmSCvRrtlkyNwQiWSOu3v8xG0nvc/sa0td5+SC/fnGr9Zh/XCfONX6zD+uFz2PlC61XwGXyGSq5bDXsVYhp2MHdp/4eZ5v8RHHGwuEhk/iljiDwfMcHjQ9S+veY13pXldjxuk5/G5KrcqVRWzVSJvDZZWtMnsz9rhwS0cOJD3s7hx3EB1F841frMP64VebnitWyfV/p1l79TJ2s9jG5EYy9Sje+lVEkDWzekvHss7mgBncRy7kBQfNda6uChwcE+sbDLsWXZNNBrdaCCW/HFE7h8knE3hMYOW+Zk8+8AefIGusfKR1ptDWbFXH5rI2M/as4+vQq1G+kw2oATLBNG57Sx4LS36B7yQ32kHRnzjV+sw/rhPnGr9Zh/XC5/o9cqeR21muQarssmQibU+cSypE+PFusNDo2WHNlPB7T3OLO8NHvK1fTXrVktp2nfaOa167i8XgcpPXZlZGwMrQQxQRPLZj4zneIe9z+Q3t7S3kg8hB0p841frMP64T5xq/WYf1wue9S+UDgttzOGpDEZ3EVs417sNk8pSENXJBrS/+CcHlwJYC9okawuaORysbV/lIa/tGqWdpbh87jdXgrvmdl79WNsL5GyiLwGNbI575C88ANaWkggO7vJB0b841frMP64XrFNHO0uje2RoPHLTyFRGodX6O07G3A28FnNXy8tZ1ytVztVsJtQtc1r3RuY97SWl7OWkhw7hyFcesf5BJ/tT/AMgg26IiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCDWv/wBTsp/3PS/862q66oa1tGM6m6vv+r4Vmzux9C3ib2H9LjrTOhmdE9ssL5CGdzXRcEOI5DvIqx8k0VepE0kp7BdxcEcBd5B7opZnPA+kgStPH0LaL1becWflH2WVG57Hb1JuepdSq2k+Nk6tC9iMhrLcrXNhkEssb4pY5nFsRcDCO5vcPJ/AJ4849p3R7cae76js+UxsNee1t+W2PK1ILTJG4yKfHurQxl3I8V3sxhxYD7TifcOV0mi1cKOcdk6SbZf6UdY8NBivEyWwbW7J42H0mIePXL6ZD+4v4b5RSeTiD7Pu8xzr5c1uVPrB10x2pad+Ek9+XHQi3JkYa0NSR2MiaHSteQ5zRzz7AcTwRwPeunlh1MNQoX716tRrV7t5zH27MULWyWHMaGMMjgOXkNAaOeeAAPcnCOU8t8nTZcbSg1u3rMXULGQarUw2GtWciyCliLbI3tsTvhee4l7nMeHsY94DA3yVhdIenmz4HddKyWXxDsfWxvT6vgLTnWYpOy5HNGSwdjySC1hcHAcccckHyV7IkWYgcg7tq+e6Y/Jz1B8b62H6mYbYLMuEpSWI3SWnW708RhZ2uPd3Q2mvIbzx2jnjg8Xrr2waF0M1XAaVktvwOGnxePhhEOSyUNeaQBvBlLXuB9twc7n4klTu3gcZkMnRyNrHVLOQoh4qW5oGvlrh/Hf4byOW93a3ngjngc+5ZMtSCZ3dJDHI73cuYCUizd4Dnfqn1Gp5vqx0hy+jmjv8lazl4jWw2Urnud6G3ub4hd2Bwa4O4c4eX9IWE7pFu2fnk2jJ4WGjms1vuHzlnEQ3IpPQKFMRxAvk5DXydsZc4M594A5XRcuAxc96ldkxtSS5SLzVsOgaZIC9va8sdxy3ub5HjjkeRWenDqK43DUctlet/TjYKtTxcRiKWXhu2PEYPCfO2sIh2k9zu7w3/ig8cefHIUQ0PoXmtT3/AHbJMuxQ4yOGzDqDCA4UDcd6RbcW+fA8fsAHH4rPoKvZFeGL7xyd016NbVhN86YZy10+GNv4N1iHY89YzEFq5lJZqr4zZB7i58fiHu4cQ8CQBrOAVtL/AEd3LP8ATbqV0xsYSKrUyWUu5jF7FJbikqWC+623DDJCD4reTyx5LeOAeCeV06inBAoXpnr+D0m7NsWf6Q630ljx1X+Ezz7tEASPIYWsfH+LGQ4jueWk8gdvmpxf6odPN9x9vW6G/wCt2bmWgkoxRU8vXlmc6Rpb7DGv5c7z8gFYEkbZWFr2h7T72uHIK8mUa0bg5teJrh5ghgBCt1w5mw+ndS58D0j1DJ6NHVr6VmqLrWbr5Ws+vZrVoJYWzRxdwk9oOaS0tBBPkD8NFjuie6szep38horL+z4famZTL7hYy0Ek2SreLI3iu0u7msayRjvDd4YaIuGhxK69RY8EChq+p9QNczvVfD4bE+FX2uezk8VtUd2JrKNh9BkTGyQk+Jy2WFvDmtcOHc/DhQ7p503yOh9Q9P2/IaLW0fD4rEXaefzGQzVaaeaZ7I3elTyd57o+6IgOLi/mXza1o5XVS+PY2Rpa5oc0+RBHIKvCIFL1i6bbLE/EV+oOsWJ8g01I4q2arOle5/shrAH8lxJ4AHnyufMhT3fSNJ6SaJsWpMrUsDt+FpRbHWyEL69yOOwGxObCD4rXObxyHNABBPJ54XXTaFZrg5teIEHkEMHkvLJYbH5ltduQo1rza07LUAswtkEUzDyyRvcD2vafMOHmPgkxMjmLdem/USPp51I0TE6eMrHnNhs5inmBk68UJgntssFhY9weJW+03ggNPHPd8D7ZXobZ1/qFulqz0hwfVCnsGSdlaOWuT1YpabpGNElefxh3eG1zS5pjD/Jx8uV1GicMCrtJ0C5rHWja8pDi4sfrU+CxOOxxgdGGA1zZDomxtPLQxr4wOQB5+XPB4rfUNSGW+Vhn4cferXtOwbzszoK7xI2vmbcXozo3EHgODIp5e33h03PxXTKwcRgcZgIZosXjqmNimldYlZUgbE2SRx5c9waBy4nzJPmU4Rq+of8Ambkv9Vv/AI2qwZ7EVWF800jIYYwXPkkcGtaB7ySfcFAN7jNnXJqcftWLksVeGP4ve57fIf0AEn6A0n4L+YnyzsD1+17PSN6nZq/m9fnk/wAEt495biX8H2eImANjeB8HtDjx73Acqdo/isfOf9Mvc/qhpfVrTuo2YzeM1fYaWet4XwfT/QH+LHCZe/sHiAdjifCfyGkkdvnxyFLl/PH/AKMDD7DgulvVjbMPjYMzPZmr08bj3WRAbFqvFLI6Nz3DhjT6TCA4/wCl9Hn15leqO669oOvZm/0vyNvPXrHg5HBYe/Dafjmcv4k8QcNkHDWnhvu7/wBC4GK0kULs9ToavVGppDtd2J81mobbc4zHk4tvAcTG6x3eT/Z/F4/jN+larEfKE03LYjcco+1dx2P1KR0eVsX6E0TY+0u5cz2eZG+wfNoPw+lBZKKFRdadEfq+E2OTa8XUwebf4eOvXbArx2X8kdrTJ2+fsny9/kpKdhxQzQw5ydMZYx+KKHpDPHLPP2vD57uPI+fHwQU7131zI7h0l3bB4iv6XlMhjLNatB3tZ4kjmENb3OIaOSfeSAtDqelZnG9Xc1mrNLw8XY1jG46OfxWHuniksGRnaDyOBIzzI4PPkTwV0G3GUZh3tijeCT7QPIK+/M9P6uxBxd0t6b9QOjcOo5uDVRsNh2rwYLK4eLI14bNOaGaSRkjHvd4b2ESuDgHc8gEc+5TPIY/ecD1Fg3+jpYy82WwMWMyODr5SBk1GeKZ8jHCWQtY9hErmu7fMFoIBXSuSr43E461esVz4FaJ80nhRukf2tBJ7WNBc48DyABJ9wCr7QPRusMGr9Q6U2w6/hpKs3Zrd6JkDLPc/iOeUcF5HaC5oDuCHMPHke4KU6f8ARvZ9Wz3S25frwzyUJc9fzMlaZvhVJrpEjI2BxDnAElvLQfxSTwCFO9O03J4/qn1Ryt6qYMXnHY8UrDZWEytjq+HIQAS5vDuR7QH0jkK+vmen9XYnzPT+rsQcXdJOhR0k4XW850c1/LSYywWHdRJV4mia4ujn8MgzeLx2AtI45BPcpj03xu99J5repxac3P4OTM2LdbYIcpDC1laxYdK7xo3/AMIZGeI4cNaQ7geY966g+Z6f1difM9P6uxBzroGE2fSuqG6VZtfNvXNhyxzEOdhuRBsHNWKMxSQlwk7u+EAFoI4fzyOCr+1j/IZP9of+QWZ8z0/q7F7160VVhZEwMaTzwPpQeq8bv+Rz/wCzd/yXsvjmh7S1w5aRwQgo3rJpdne+nuSoY0tjzkBZfxUzjx4d2B4lgPPwBewAn6HFVJtnye8g3R+ncjcLj91yuv257+ZwuRcxsWVlttcbTg5/Le8Sv72d3l7IHPkF2F8z0/q7E+Z6f1diDkXIdIbtzTqOU1fptiNF2DEbDUzUOEZPA35yjrtc0MllhBYxxE03b5uDSGknzPEh6h4/der/AEh2fFSah+DOV7qs1CpeyUEzrToZ453Nc6IubGCY+0EuP43J44XTPzPT+rsT5np/V2IOXslV3du+4PqRU0Wea4cTYwd/XH5OqLMLDMyaKaOTv8J3mxwc3vB4IPnxwtLpXSDbMXuOkbBk6ELJ5Nizmfy8Newx8eO9LrPjiiBJBkI9gEsB8y4+7zXXfzPT+rsUF2zO6xheqWh69dtX62Zzbb5x9Otz6LZEMLXy+P8A6rSC39PKCkep+m7TmOqOPyeq6pLiMpDaph+418tHHDYptc108Nqtz3Sjt72tBa7z4Ic33L0raBssec6p6rYwrn61u01mzFscFuLtqeNRZA5j4S4SFwdH5FoIIcDyOCupPmen9XYnzPT+rsQcmYLUN92qz0xwuwaxDruN0uxHct5RmQinbflhrPgibXYw97WO8QuPiBvAHHmV4w9Ds/m/kkUtCt1q1HY67WztqXHtlruljueO2OQsLgWPDQDwTx3fo4XXPzPT+rsT5np/V2IObejuk08bm5si/o3ienVqGuGMu15qks0rnH22N8AHhnAHm4gn+SF0PrH+QSf7U/8AILL+Z6f1di969aKqwsiYGNJ54H0oPVERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBg5fC0c9T9Fv122Ye4OAdyC13wc0jzafM+YIPmVoXdL8A5xJbk+SefLMXAP/ADVLEW6xWq04usWpiPKVvmES9V2A+jJ/bNz71PVdgPoyf2zc+9UtRZ96r/EneV4p1RL1XYD6Mn9s3PvU9V2A+jJ/bNz71S1E71X+JO8nFOqJeq7AfRk/tm596nquwH0ZP7ZufeqWr8PlZG5jXPa1zz2tBPBceCeB9PkCf6ineq/xJ3k4p1RX1XYD6Mn9s3PvVqLeuaLR2GngJ8pPHnbsT5q2MfsFoWJmMHLnNZ43JA+njhaSbcti64aTtNPRZMp09ytPJfNsGaz+I5ErGOAmlgjcfa4/hGgn+M3z458pzQ6eYSLYaOzXsdSyG316DKDs8+qxthzBz3dp/iBxc8kN/lce5O9V/iTvJxTqqfXOnWa6saa+fKU9n6RX25QmGKtnnXLU9NnAHf3ueyMvJd5cEt7W+ZB87QZ0swLGNaTlHkDgudmbnJ/SeJVLkTvVf4k7ycU6ol6rsB9GT+2bn3qeq7AfRk/tm596paid6r/EneTinVEvVdgPoyf2zc+9T1XYD6Mn9s3PvVLUTvVf4k7ycU6ol6rsB9GT+2bn3qeq7AfRk/tm596paid6r/EneTinVV9vofWZtvz5BsOxyY1lR8R1z50kFaWXy7JBIT4rT+MCO/g8j3cEGO6ZLiXafjsj1HxFvprmLV92MbQyO1TvjmnHPb4UomAcH9ri0e88eXI4JvJaHcdE13qFi2Y3ZcJRzlFkrZmQXoGytZI08hzeR5H9I+BI9xTvVf4k7ycU6sD1XYD6Mn9s3PvU9V2A+jJ/bNz71a2XF7jru97Hss2wuzemuxviVtWgxzfSoLMbW/4mUHl/eGu9l38Zw44AW06bdQ6nUrTsbsEGOyWEbdc+MY/NVjWtRyMc5r2OYfiCx3u55A5TvVf4k7ycU6vz6rsB9GT+2bn3qeq7AfRk/tm596paid6r/EneTinVEvVdgPoyf2zc+9T1XYD6Mn9s3PvVLUTvVf4k7ycU6ol6rsB9GT+2bn3qeq7AfRk/tm596paid6r/ABJ3k4p1aPC6Vh8Bb9KqVXm12lgns2JLEjWn3hrpHOIB4HIB8+As/M4XH7Fi7ONytGtksdZYY56luJssUrfoc1wII/pWai027dqpN9ub582Pih3S/pFqPRjB3MPpuHbhcbbuPvzV2Tyyh07msY5wMjnFo7Y2DtHAHHkPepiiLAF+XsbIxzXNDmuHBaRyCF+kQaPMaNrew06lTK6/isnVqSeNWguUo5WQv/lsa5pDXfpHmvCTpzrEu7xbi7CUztMcBrMy3h/w4iII7O76OCf7VI0QVxQ+TzoeI13bMJjsM/H0NpkdNlmwXZ+6d7ueXBxeSw+Z/E4Xhlfk5aTmdR1fWrVW+7Fa3MJ8cxmRna9jwSR3vDu548/c4lWciCGP6R64/qizqCYLX4Ssq+hiX0uTwfD4I48Lu7OeCfPjlTNEQEREBERAREQEREBERAREQFBNtyubq9U9BpUtUhyuFttyByGee0F+JLYWmINPw8V3LD9PCnagm24rN2uqeg3aW1w4rC1G5AZDAvcA/LF0LREWj4+E7l5+jlBO0REBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQERYmUsvqUJZY+O9vHHI/SAgy0UV/CK59LP1U/CK59LP1UEqRRX8Irn0s/VT8Irn0s/VQSpFFfwiufSz9VaLPVreey+HvuzGSojGTeO2rRseDDYdxxxM0DmRvHPsk8fHjngoPxkurLNwxW547pdbxOzbnrz2VJalyWSOpDYc4jtkkDeHdoa8lrD729pLSfL3r9I6OwbLp+67dCy5vOBx/o7ZqNmZlKOd7eJpI4iQPPl4BcOe13B54HGPru+4yfPbFhMQK8N/GTxyZOKGr4QE07PFDieAHuc0hxcOff5nlTbB5Ga/wCP4paezt44HHv5/Yg2qIiAiIgIiICIiAiIgIiICh3UPpNrfVGTAy56tPLYwd9mRoT1rUkD4ZWkfFhHIcB2kH4E8cHzUxRBXTtq2vTs7u+V3NmFrdO8dVF/HZKk+V1uONrP4WOaLtPcR2ucCz+UAA4/iy7U9tw+9a5Rz2v5GDLYe9H4le5Wd3MkbyQf6CCCCD5ggg8ELbEAggjkH4FQPaOl9jKZjT7mv7NkdOqa/ZMj8ViWRtpX4HFpfDLEW8cHt4BH4vc4gckEBPUUAwfU68cvucO161Y0zD6/KDBnsjbhNK/Wd3cTNeCOw+zy5p/F7m8kE8CdwTxWoI5oZGTQyND2SRuDmuaRyCCPeCg9EREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBVV1A/An18dJ/nv0z8M+zLfg94PPgcejN9K8X4f4vjt5+PKtVQTbcrm6vVPQaVLVIcrhbbcgchnntBfiS2FpiDT8PFdyw/TwgnaIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiIC1+e/9lT/APy/+ILYLFydZ9yjJCwgOdxx3e73goOUurtfD7J121PW94mjbpc2Ft26tO3OYqt7Itljb2SeYDyyJznNafpJWkz+A0fKdT9J1S9LRm6YswV+xjKTrpdQs5BlljZGlxcRI6Njn8MJPb7XAHC6X2HpjQ26h6DncXi81S7g/wBGyFds8fcPce17SOf0rEvdGcDlMFBhLmvYO3hq5Bhx09KN9eMj3FsZZ2j3n3D4oOOtUxFPeM/01wtuWbKafDt2x1cS6Sdz22cdFXkMTPE55ki7g5nmSHMb2nkeSsfBdKtVynyjdpoWsNWlxeF1/DNo40t/waEiSz2OEf4pc3s4aSOR3O495XRbOmlKJ+MczG4xjsWHCg5sDQagc3scIvZ9jlpLT28cjy9yjGWyWB1jqzgtblqRR7XtNSZ8NyGuP4WKq0vLZZeA7hve7tB5HtH3coOSdP0291Kr3c5kd11fXN7+f560tu7WsfPNCy225sVdj/TGN7S0Ma2MR9pa4Dhx5JsHUMRo+57x1DyfUezTm2fE7JLWqMyt4wOoUmdhqGFpe3sa9p7u5v4znHkldI2OjuDt7CzPz4DCTZ2PjsyclON1lvHu4lLO4cf0r5l+jmD2DLV8plNfwmSydcAQ3blOOWaIA8jte5hI8/oKChOmuq6hjvlNdVLcuOxdbYI7dGxQlexjbAE9IeM6Pnz9t5k7iPeSeV1Dqvutf/L/AOq0FzpXjMjnaubt4fE2czUb2V8jNWY+xCPPyZIW9zR5n3H4lSrC4yXHeN4pYe/jjtPPu5/ag2iIiAiIgIiICIiAiIgIiICIiAiIg1uxa5i9uwlzD5vH1srirjPDsU7cYkjkb7+C0+XvAI+ggFV7mNaymgZjp9S1TaMFqGj0S3Ey67kKocbwdx4bIJTIHeMGscGjz5LnOd3+5Woqp69WtJq2Om43Ondtvk3ChHhDTJHhZMiTwHycOb7A9rnnn4eRQWsiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKCbbis3a6p6DdpbXDisLUbkBkMC9wD8sXQtERaPj4TuXn6OVO1VXUD8CfXx0n+e/TPwz7Mt+D3g8+Bx6M30rxfh/i+O3n48oLVREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBQfYcrtFfqrqVHH69Vu6tYr23ZTMyceNRkawGFrPaB4eeQfZPu+CkG41cxe1HOVteusx2fmozx465Ixr2wWTG4RSFrgQQ15aeCCDx5gr+Puf+Wx8oLFbW+PJb1dqZbEyzVJKvoFSNjJAe17ZIWxdj3AggdwJafdwg/s0iqH5Klzfcx0Uwma6j5h+X2PLg5Ad9SGua1d4HgxdsTGDntAeeRyDIQfcFbyAiIgIiICIiAiIgIiICIiAiIgIiICIiAoP1Pyu04uXUBrOvVdgZZz9WvlTZ4/wGi7v8WyzlzfbZw3j3+/3FThV/wBW8Vdyk2kmlujNNFbZKlidj5ez52ib391Ae03uMnI8vPnt9xQWAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKCbblc3V6p6DSpapDlcLbbkDkM89oL8SWwtMQafh4ruWH6eFJ9qqZTIaxmKuEvtxeanpzRUbz42yCtOWERyFrgQ4NcQeCCDx5gr+QOy/La+UPgNosUMrvFmnlsPYmqTVvm2k1rJQSx7XsbD2vILTx3A8HzHCD+ySKmfkjZTf9i6IYfP9SMucvnsy51+HuqQ1zBVeGiFhbExrTyAZOSOf4Xj4K5kBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBRvJdQMPjLktUm7bnhPbIKFCey2N38lzo2FoP6Oef0La56y+lg8jYiPEkVaSRp+ghpIUV1KBlfV8SyNva30WNx+kktBJP6SSST8SSuyjSsWrM27fyy/JWNZZfrOxP1LOfYtv7tPWdifqWc+xbf3ay0W/Docs7+i5MT1nYn6lnPsW392nrOxP1LOfYtv7tZaJh0OWd/QyYnrOxP1LOfYtv7tPWdifqWc+xbf3ay0TDocs7+hkxPWdifqWc+xbf3aes7E/Us59i2/u1lomHQ5Z39DJies7E/Us59i2/u09Z2J+pZz7Ft/drLRMOhyzv6GTE9Z2J+pZz7Ft/dp6zsT9Szn2Lb+7WWiYdDlnf0MmJ6zsT9Szn2Lb+7XFvWv5L+I6k/KswO418VlW6VkCLexRfNFkO8eL+KGGPkicBgJb7j4jieSOe3UTDocs7+hkw29TMQxoa2jmw0DgAYS35f/AG199Z2J+pZz7Ft/drLRMOhyzv6GTE9Z2J+pZz7Ft/dp6zsT9Szn2Lb+7WWiYdDlnf0MmJ6zsT9Szn2Lb+7T1nYn6lnPsW392stEw6HLO/oZMT1nYn6lnPsW392nrOxP1LOfYtv7tZaJh0OWd/QyYnrOxP1LOfYtv7tPWdifqWc+xbf3ay0TDocs7+hkxPWdifqWc+xbf3aes7E/Us59i2/u1lomHQ5Z39DJies7E/Us59i2/u1+2dTcJ+NOMjRhH41i7jLEETB9LnuYGtA4PLnEAfErIRTDocs7+iZJEDyOR7l9UV6ZP51CKJvIirXLtSJv8iOK1LFG0fQA1jQB8AAFKlw1LGHbtWNJmCcpERFrQReFq5DSYHTP7Gk8A8E/8ljfP1H+f/4HfsQbBVT16taTVsdNxudO7bfJuFCPCGmSPCyZEngPk4c32B7XPPPw8irG+fqP8/8A8Dv2KGdSdrztOTVBq2GpZ9k2drRZY3CGeg0D3eLZj7nN5ez2eAO4+Z4aUFhotf8AP1H+f/4HfsT5+o/z/wDwO/Yg2CLBZm6Uj2sbNy5x4A7He/8AsWcgIiICIiAiIgIiICIiAsbI5GtiaUtu5MyvWiHL5HnyHnwB+kkkAAeZJACyVDNyd422axVeO+Hi1Z7D5jxGNY1ruPpAkdx/St1GnFS3FmfP6ResZvY9TcQD5VM08e8ObhbfB/8Atr56zsT9Szn2Lb+7WWi7cOhyzv6LkxPWdifqWc+xbf3aes7E/Us59i2/u1lomHQ5Z39DJies7E/Us59i2/u09Z2J+pZz7Ft/drLRMOhyzv6GTE9Z2J+pZz7Ft/dp6zsT9Szn2Lb+7WWiYdDlnf0MmJ6zsT9Szn2Lb+7T1nYn6lnPsW392stEw6HLO/oZMT1nYn6lnPsW392uLflBfJixXVf5TutbdTxmWj1LIlsmzNGJsscHwjy7W9gJ8ZoYzloPBDnH3rt1Ew6HLO/oZMKLqThoI2Rx0M1HGwBrWNwlsBoHuAHhr9es7E/Us59i2/u1lomHQ5Z39DJies7E/Us59i2/u09Z2J+pZz7Ft/drLRMOhyzv6GTE9Z2J+pZz7Ft/dp6zsT9Szn2Lb+7WWiYdDlnf0MmJ6zsT9Szn2Lb+7T1nYn6lnPsW392stEw6HLO/oZMT1nYn6lnPsW392nrOxP1LOfYtv7tZaJh0OWd/QyYnrOxP1LOfYtv7tbvB7Jj9jilfRle50Tu2WKaF8MsZ+Hcx4Dhz8OR5/Ba9Vj1n3XM9On4jNa7jsZkMrIZajo8tko8fXMZAdzJM/wAuGkchvPnyePepNGlaieCJibpnxv8ACL9IMpXiiq3YeoOex3XnVtUgyum18HkKD7FnG37r252WQNnINaIHtdGDGwlzh7my8HkBRXC9TLeUZ1xZZ6pYl7Ndjn8F+Kwz3y6y1os+3M13PpL2iMEtHkTC4Ae0F5rFfiLlTZ92wWZ+TFp2byXV3c8nSsZR8bNu03HyUL+RkDrI8J0DYyY4x2Fvm0AmFh59rzsPY6+A/wCtFqs8+H3W3nvmp/o2Rpskdr9VnbZ5M5Du1srh3N8wfxovdyCgty9maGMqW7dy9WqVajDJZnnmaxkLAOS57ieGjgHzP0KM5zrJo2ua7js/kNsxMGEyU3o9LIttsfBZk8/Zje0kOPsu938k/Qqz1XWNe1uHq5829J5aRzD5p5oL2Simi2mQ+OeGtMsgga8u90gYAJhyB2kD8ZXLZPHdGtYZguh+GmycV0tOl2L1OKDEs5m/whkgYYnE+yeGcO/hzz5hyCzbXWDVqfVGn08lvyjbLdQ3oqYqylnggPPcZe3sH+Ld5d3PktVieumOy+I3PIR61tMEerveyeKxinMlu9vf51G8/wAMD2Hjjjnlv0r2yW27czqjiqlGjh5NBdVc+/kZppReZPxJ2sjjA7S3kRck/BzvoWsxOQ6j2IN7iy+a1io+YyM1WfGVbD3Vm/wvhvttk8nO84SQzy5D/wBCD1yvygMZidE13aX6tts9bN2PR4qFfDufdrHlw7p4ueY2+wfMn4j6VaAIPPB5496p3KYfqNkem+CxdbqfSw+4QWjJks9Dr7J4rMPMnEbK7yAwgOjHdz/EP0rRZDp9Y6fdXrvULVctsOVpZm1E7LabTliFaaUsMT7ZfYkYG9jOx3Y3zJY0e78UOgEVU4vprQp095q3d72zMQbU6Tlty+4/NbHeJ7FIhgMPAk458/xGfR54eU6H6Pm+nmA03IZLOXMVhbJtQTPyU4syv5ef4WVvDnj+EPkfoH0ILedPGyVkTpGNkf8AisLhy7+gLTybzrkVPK235/Ftq4kF2QmNyPspgc8+KeeI+O0/jce4qN2tA0O71Mp9QZsc1+4VKppQZPxJgWQkPBb2c9h8pH+Zbz5+/wAgv3htC6e6/FsMVDX8ZBDsMjpcvF6J3MvucXF3jNcCHg97/I+XtFBkZXrVoeF1jF7Hd2zFQ4LKS+BRyPpLXQ2ZOSO2Nw5Djy13u+gr1tdXdRpdS6fT6bMNZuFysbkGM8CUl8IDiXd4b2DyY7yLufJegwOljEUcV8yYn5rov8SpS+b2eDXd5+1Gzs7Wn2neYA95+lbkZfGiYyh7RKR2l/hnuI+jnhBCMV191zPYjc7+MpZu+3VHviu14sZIJpnt7gWwNcB4p5Y4eX6PpXhlev8AjMTo2ubQ/VttnrZyf0eKhXw7n3Kx5cO6eLnmNvs+8/SPpVg/P1H+f/4HfsXpBlqlmVsUUvc93uHaR/6IIvY6o063VGroxwudfdsVTbGVZQJxzAA49jp+eA/2T7PHxH0rU4rrpjsvh9zyLNa2mCPV3vZPDYxLmS3e3u86ref4YHsPHHHvH0qykQVdlev+MxOja5tD9W22etnJ/R4qFfDufcrHlw7p4ueY2+z7z9I+lTiHcsDY2ezrcWZov2CtE2ebFiwz0lkbvc8x893afp4WbYy1SrK6KWXte33jtJ/9FH58PqsmxybHFRpV9lfVdTGZjpt9KER4Pb3lvJALWkA8jy/SUEsRU1Qyu5dJNCoVBNkOs2WGR8OSZzq2NsxUnA8OJkLWSOZw0ebgXl3PIHutRufouaCZS0kc8Fh5H/0QbFFr/n6j/P8A/A79ifP1H+f/AOB37EGwRa/5+o/z/wDwO/Ynz9R/n/8Agd+xBsEWv+fqP8//AMDv2J8/Uf5//gd+xBsEXjVtxXIy+F/e0Hgngjz/AK17INXtP+bGX/7HN/4Co9rP+beJ/wCyRf8AgCkO0/5sZf8A7HN/4Co9rP8Am3if+yRf+AL0aP8ADPz/ANL7ldaP1j2DqPfgv4DSDPo09uStFsFjKxxTSsY9zHTsrdhJi7mu4JeHEefb5rSUvlLWbdajsh06aPpveyjcXX2Q5BhmJdP6Oyw6r2cthdLw0O7+7gg9vBWV0t6e9ROlEGP1KhZ1q/otC081rlk2G5JlR0jn+CY2t8Nz293aJO8DgAlvKjdT5P26M1bGdNpsng/Vrj8rHcbbZ43znNVjtekxVXR9vhgh4a0yh/m1v4vKx9pGbmflOZvF0M1mIunz7uvYfY5dbsWmZiNtiSZtr0dkkULowHNc50fPc9paXEe0B3HNz3yj8lqGD3x2d05tTYNVrU7pxtfKiaG5BZkMcbmT+E0tIc14cCzyIHBIPI8sh0Iz9vpvtevsuY0Xcruh2OCR0snhtrHJRWuxx7ORJ2MI4AI7uPa481GvlSaHksfrnVjbjNVNDJ4LE4+vF3OMrZYbr3OL28Adp8ZvHDifI+Q8uZPFEXizcL1bzTd4bqez6iMFlruPmyGJNXJttw3mxFokiLyxnhyt8RhIILeDyHHhRnUflOOzO05XXMvgsdjsvVxdnKQxYvYIMm1wg48SGYxtBhlHc08EOBHdwTwvzlejm9dS85kMrueVw2GmgwF/C4aPXXTS+jyW2NbLae+RrD3AMYAxo8vP2uVpsF0F3aDLavYtQadiaWFwN/AinhjOO9s8LWifudGOT3xR8s49kOee954Ct9oSTUPlCZnYZdIkyGkjD47dKbpsNZdlmzOMwrGw2OZjYx4bXsa7teC8+Q7mtJ4GywnX6PYNd6d3KeEc7LbZkHY+XFPs8Ox7oWyG45zuz2/BMLm8cN7iW/i8rExXRnNUcD0OpSWqBl0YRDJFsj+2btx8lY+D7Hte28H2u32efj5KG9GsFjsh1o6idQsfZs3tIpGV+H7KsjmelWGRPyUkDe3uk5fXYOWA8ue8N5PPK+R0uqEd8pnMVMFlNqvaKIdJxWYsYq9lIcw2SxC2Kya5sCuYh3R8gEjv7h58BwHJnUPXXVp5mRtg2Xue4NHdqeVaOT9JNbgf0lULo2gbv1V6d7RqUVnA47RcltmWbfuF0zskYW5KR0kUcfb4fLy0t7y7yDvxSRykzoLjw3WDZdq3fbMDg9LgsVdcyHoFjJXMx4DJSYGSt8NohcS7l/DmngAFp7jyWiN9F+s09jGaLRyWMvRw7RazMLL+QzBvyV7la1N/gxeYWdzHMjlMZ8uBGGcHjlWB010K/puw7/euS1pIM/nPnKo2u5xcyL0aCLteC0AO7onHgcjgjz+Ar+X5O2Xu/J7ZpL8tWx2z1Mray2Oy1Nz3MqzPvTTxuBLQefCmLHeX8ZwHI8yzG2sdfcnY6fHb8ZrOOfiZcpZqVrWW2GLHV31YnujZadLJH7PiOY7tYA7y7TzwfKIbR8obZNs0vpjsOj4ysw5nafmnI07OSYAZIxM01xKyKRro3uic7xmfBreAQ89sv3nopfba6cWNQrYW1V02CapBhs+6RtZzHxRxsmDmMfxLGI/Ilp5EjvNp81G6XQDc8boFWlFkcBJseJ3R+2UHBs0VKwHue58UgDS6LznlaO3v4DWHk8kCTxDe9R/lKwdP8/Q1uSnghsrsfHkMhBltlgxtSqHkgRxzys5meXNfwAweQBd29wCn/SjqTjureh43aMZG6Gtb8RjoXvbIY5I5HRyN7mEtcA5juHNPDhwR71X+W6ab9T3pu84IatNmspi4cfnMPlJJ/Q++Jz3RTQTNjL+QJHNLXMAI+gqYS9SqWjVaGL2WK6/NisyS07X9dyFmmZCPa8N0UMgA5B9ku7h5c+9ZRM35j51K6o2dNzGB17BYF+z7Vm/FfVxwtNqxRwxBplmmmLXdjG9zAPZJcXAAKs+mnVHbcfpHUbOZDFNu5OnuNuq7H5bPxwVsdCIoT2+lSAtbE0k8Brf4/wCKPNSDY8dleom1a91B6czRRZjCMs4uzR2rHXcdDbrTeG9wBfCJGlrmMLXNY5p8wfcov/1fN1nw8lu9LrOQzQ3aba/mqaSc4yyx9cRCORxjL2vY7l7T2uHLR5efljN9+Qxtt+Upndr6KT7FptGpTzNHZ6eDyDGZSKxA3unh58Gdkb2yskbLGzvAaWiRzh5s4Nl9Qeqmy9P9WxGTt6xhG2rHeLzb20w0qlRw47WNsSxAyOcOePYaPZPJHlzBj8nzcLuldQsdayGAr5XPZ2nsmPkpslFaGxCa7jBIwjnw+azW97SS4OLu1vHat1snTff8/tuq7lNU1C5ncdQt4+bF3p7ElGsZZWObZrv8LuMgawNcC1vIJAcE9oWN0s6h0eq3T/DbXjoZK9XJRF4glcHOje1zmPYSPI8Oa4cjyPHK0PVrq5a6dZjVMNjcHFm8vsc80FVtvINoVmmJge4Omcx/tu5AawN5cefdwo10wuw/J70XHaVtMlzIZCpJZsR28DgchbglhlsyyMJMUD2xv9ogx9zuOAeSCFldQZbvW/U347VsLhszjHOfDkKe84y/jx3Fo8OSHvhDuWkuPIb9HDmkLK+bvMYfUP5TEGiZvHa8/H4eDZJMdFkr9TO7JWxkFMP5AibM8O8aTua8cMbxwASQHDlZ+Uo6/wBMsBuuv4PG2sbkXTxWX5rYq2MhqSxPMbo/GcHtlLnNf2lvskN55AIWpwvQneenWQw2Y1vL4XZcq7AVcJmotmMzGWX1+7wrMcjGvcHDvc0tcPMcefPmt1tfSzccltmnbXVj1XK5jF4qfH2qGTbNFRgnldG51qqGte4O9gt4dwS08dwU9oeMXymJc/jums2r6pJmrG7xXXQQT32121JKwHiCR4Y8FgPeC5v8kcNcXALZ2us20Xtjva9rehx5/L4WvWfnecyytWqWJoxIK0MroiZ3hp557WN4LeSCeBoOmXQPZNLt9Mxfv4m1BqUucE81UyMdYZcf3xOZGW8NI5Ic0uIHA4c7ny3WQ6fb9qHUPadg0Ozrtqhs5gsXKWwOnjNS1FEIvFidE13e1zWs5Y7t82+ThykcXvETu9VN01Tq31Ykoa3a2rF4qljLs1OXMNgjoM9Ge+VsDHBwdI7gnhoaD2ebuSOb91nYKm2a3is5QLnUcnUiu1y8cOMcjA9vI+B4cFXlTpbm27F1TydmzQd+FmNp1Koic8eHLFVkieXgt9lpc8EcFx49/mpf0w1m1pXTTUtevSQy3cRiKlCeSu4mN0kULGOLSQCWktPHIB4+AVi8bzpf/mtL/wB65P8A/fzqWqJdL/8ANaX/AL1yf/7+dS1aO0/z1PnP3WfGRERcyNNs/wDkkX+v/wChXMOYye27P8qSvrk0dipq+IxUGUaMfnZK3i980gE00bIx4vLovD8Fz+0N5dye7tHT2z/5JF/r/wDoVUMGi34utmS3AzVxjLOvVsSyNrneOJY7M8rnEdvb29sreDzzyD5fEhEqHyhpYeqlDTM7gqGLkyNmWpVkqZ+vcsse1jnt9IrMAdEHtYeDy7gkA8cqE3N9zd6GaQy5TC3ouqePxFquMy61EYiK5dHE4Rx9sLmvHMRBHJcSTz5ZWq/J83TXmaLRc/VvQdVzfzi67D44uZVrvFZJLMSzhkvZK53by8PcB7TAFvc10M2Ozi9pkoX8XHlrG7wbdihYMjoCImVw2Kfhoc3kwv57e7gFvn7+A+9QvlT43TttzOEo1sPefhA0X3ZPY62Nlc8sEnh14pOTM4NI5J7G8nt5JB42w6+2NovYynoOrybbPYxNfN2nT3mUY6decEwsc5zXcyvDXcM4AHbyXALCl6Zb3qm47LltRfq9ylsssd61Wzwn5o3BE2N74jG0+KxwY09riw8jyIWxy3T7c9e6mX9r0yfAyxZuhWp5WhmDNE1klfvEU0Lo2u59mRwLDxzwPa+gNh8lbM39g6JaRkcpcs38hYg7prNyZ0srz4rh7T3EkngAe/4LohUd0C0bIdNemmq6zlZq1jIY6PwppabnOic4yOd7JcAePaHvAV4oCIiAiIgIiICIiAiIgKFbf/nxq/8A2e7/APwqaqFbf/nxq/8A2e7/APwrr7L/AC/4tf8AmVhFesXU+XpVgMTkIMJLsE+Qy9TEx0oJxC8unf2AtLgQTzx5HgHnzcPeoXvXykLHTyziMJmsRgcft16CW7JRv7RFUpV6zZCxjzaliaXPeR5MbGT5O5IDeTNereh5DfamqxY+atC7FbJj8xObLnNDoYJe97W9rTy8j3A8D6SFoOovTPZZupmO33TZcLYyrMW7C3sZsHiNrWK/i+LG9kkbXOZIx5d/FIIcR5cLbN/uRX+c+Udse50OmmT0THU5G5HZbGHylOfKx+HJNDBK7wGzxxStdE7t8QTM94awce2e2w5ur+y2Op1vSMXpcF29Rx1LI3LkuX8KvC2Zz2vZz4Jc5zSw9vDfbHdz2cDny3np1uO0azpd6tNr8G567l25YwBs0WNnPhyxOi7gHSN9iXyf2nzb7gD5bXSdFz+N6obDt2afjWnL4bGUnQUJZH+HYgM5l47mN9gmZvafeeDyB8ZF4rrHfKDh0TAb1l9hqz18m3cXYKvjr+fZLV8c1oXtEc8scTa8HYXPIIPaQ8guLg1elD5X9OxjMxPLh8dYmwk9GTJvwufhyNWKhYm8J1mOeNntOicOXxOaw8efPB5WRmvk85+9HsWQpZPG1c8N3/C7CPma+Wu5vosUBgst7QQHhsoPZzxy0gk8hWFgtWz2zarn8Tv+P12KHKQvpmpgDK9ngPjLXh8kjWlzjyeOGjj9PvUjiGh3b5ROM0bIbp6Zj+/E6xDSilvC0xnj37R/g6rQ4BreGOie6RzwGiQcjgErG6SfKPpdStzn1axXxFfLCi7IwvwWfgy9d8TXtY9rpIw0xyAvZ7JbwQSQTwVqaXyZ7cPQRunTZ8S7aL8ebOfkjMjZMhHK2SJ72njuaGxxxkfyW/Sphr2Y2vT6GQy3UCjgatWNsUUEen07t+YvJIe5zRF39p9jhrWHt4JLj8LnfmLDyNiarj7U9asbliOJz46zXhhlcASGBzvIcny5PkOVS2j/ACmRsmx57XsnhcdSzOOxM2YjjxOfhycUkcTg2SKR8bQYpQXM9ktPkSQTwtvt244XrDqWc0rE2thxOSzlCxSgu2NbyVaOFzo3e06SSBjWj+lw59wPJCiWB6H7rFs2Nylytp+HqVdau62MdhDOGMbKI3MmD3Rju5fGAWFo7QSQ55PCszN+Q3+kfKCyex3dDfmNNOBw261jLiroybLDxIK5sdk0YY0MDmNeWuDnE8Dua0ngQbfOumz7rhtWy2u6/cxOlXtuxdOtsbMsIp7sPpzI3k1mtDhBJw5o5eS4EEt4KnWN6LZmrr3RDHzWce9+kNibk+2R5bN246SsfB9j2vbeD7Xb7PPx8lD6nQbqPi9T1nRo8hrNvT9cz1LI078r7DMhJUr2xMInxiMs8QNHaHB3Du0cgckjH2h0uqH3H5SuUwb89exOmNyer4TNRYC7mrOUFcstOkjieRCI3udEx8rWl3PPPPDSASpp6+tU/mNn/wD+Ry3/AOKuXeoOfoav1e2WZjcfstSfNVsqNHgyOQqWblkNiLJBSNUsll7g15PieG4sHcAWlW1a0kW/vXyxMPqWxbDTqVMRfo69M+tkH29kq0rskjADK2rUk9qbt57fMs7nAtbzwtzlflE5WbI7VHrGljYcfr2Pq5axdlyravjV56/jtEbDG4mTtDuGnhp483AkBfmh0s3zQ9q2c6lJquQ13YMrJmXfP7JxaoTzcGdrBG0tlYXAuaC5hBcRyQt83pZlG7R1YyInpCttmPqVKDA9/dE6KtJE7xR28NHc8cdpd5A+Q9ye0I1uXysMJhbeEp4ePE2reSxMGbJz+fgw8MVaYcxAPkDi+RwBPY1vAABLhyOVD5UEm1t0KPUdUdnLW2V8jJGybJMgjpyU5GMlbJI1kgcwlzuJGc88N4a7v8sHCdDN16e2dcy+rz63kcmzWMfr+ax2adM2tJJVj7WWIJWRudyO57e1zAC3j3FTj1eZ+91E6e7PfmxTXYPE5Cpko6Qkja+ex6PwYGEH2OYX89zgRyPf58PaEa6rfKPyHRyzBLsGs4yLFtghmsyjZYG2z3AeL6NVcwPnEZLhzywu7SQFkVN/3mx8prM6zXx1G3qdfD0LQL8j4boWSSTB9hrRAS95LO3wy8ACMODvaIEa6lfJ427ab/U2vip9afS3WNnOWyrZn36QZAyMV2Na3tMfczkO7x2+I49ryPOZu0DdcV1Wx24Yp2BlivYWniM5SuTzNMPgyvkMlZ7Yz3+UsjQHhnuaefMgM7xFte64W9fobhNbwmRvbLY3U67QwTsyLUUlk1oZGiKV0TBBAGFz3Atd2kPPJ7gFs8r8pS7rOK2CLN6ZLV2nCXcbXnw1bIsmZPDdmbFFNBOWNDvPvHa5rPNnBIB5WNkegewufn8pQyOMgz7d4/C7DGYyPgcz0SKu6Cxw0Ob3NEoJZ3ccsI58wsLNdA9x3N2wZ7O3sJFs+WyGFcypSkmNOpSo2mzljZHMD3yP5kPJY0c9o8hyVPaFjaF1OyWw7nnNT2LXma7nsbUr5Bkde+LkNitM6RrXtf4bCHB0TmuaW+XlwSFm77pGB6hZ7WMNsmJq5rFSSzyPqXGd8bnNiJaSPjwV409DyFfrllNzdNWOLta9VxLIQ53jiWOxPK5xHb29vbK0A93PIPkPecPrH1Ow/RyHF7hnm2pMVjfHdKylEJJnd7Qxoa0kA+bh7yAByT5BdFP/AJX6WvtKwtKfTsBa2CpnpsJjps5Ui8Ctk5KkbrUMftewyUjua323+QPHtH6Ss6pi6ePlsS1akFaSw/xJnwxtYZXck9ziB5nzPmfpK/mL1f8A+lF3LZJZ6egYmtqmOPLW3rjW2rrh8HAEeGz+jtf/AKy6u+Sd1wx7Pk4dNb+/bc07Bsli/DWsZeyXTXZRfmYGNc738AxtA+A7QPIBeSjpZFqIdvwVjY59fizWOlz1eMTTYpluM2o2EAhzoue4DhzTyRx5j6Vm4/KUstCZaNuC5ED2l9eVsjQfo5B96CnerPUH1W6Je2U42TLirNWi9DilEb5PFsRw+y4jjkeJyAeAeOORzyIfY6/u1Ofaa+8a8dds4TEMzjW07wuttVnPdGA13Yztk8Rob2kccuHDiPNSfrVo9/qP06v4DGy14Lk9mnM19tzmxgQ24ZnclrXHktjcB5e8j3e9RXqh0LsdTds2GzPegqYrKap8xsewudPDZFozsl7OO0sB7T+NySOOB70H7q9ZdmZk2YTOaQ3Xc5k8XZv4SN+VbPFZfC1rnwTPbFzDIA9pPsvbx3cE8cHL+TVt+1bz0lwGX2qrXbZtU4Zor0NvxX3A4cukfGImCI88ey0uH6ViYXp1uuzb7gtj321g42YClZrUq2BdM/0iaw1rJZ5DI1vZ7DeBGO7guJ7jwtr0K0za+nOn1tVz8mHtY3EQsqY27jpJfGniaXcOmY9oDHdvZ5Nc4c8+fuQeUvV/JW+r2Q0jEazHfZixUfkLtjKMrysjnHPiQwFpMrGD8Y9zfMEDkrDn69+D0lye7/MXPoWZdiPQfTPx+MiKXid/Z5e/v7e0/wAnn4rD6qdK9r6h7th7MDNboY3GXqtypnm+M3M1WRua6aJnDexzZOHN83AdrvNpIUcz3Q3fZtOzmlYu5rv4O28/88wXLUk4teG6+226BzBGWtIcHcSBzuQAO1vPcA3+V6+7BTfvF2logyWA1C/JUyFuPLtbYfHHDHNJJFAYuHFrJOS0vHPA4JJIGXP1uzuZ2nL4nTdOg2avj8ZRyvpcuXFTx4rTZHMaxhid7XEZ45IB58y3y5rzEa7vm5XetWva1YwOPwuV2axTu5DIGZ1uuH0qzZHRRtb2PPYR29zm8Hn3rMxGE3DW+tG84Xp63BBlLXsFS8TPyTDwmsZZZE9oiae8gB3LT28+XmPNBM63yiDt0etQaNrcmx5bM4t2ZfVuXG0Y6VZsnhHxZC1/t+KHMDWtPJY48gDlYrflIWcq7TKmD1Ca/mdhnyNKahavNrnHWqRAmjleGOBaD3e0PPgDhri7hYeC6D7J0rfq+Q0e9isnk8fg3YPIxZ4yQRW2mc2PHY6JryxwlfKe0ggtfxyCOVlaV0Gy+q7Ho2Xs5OnftY+1mslm5mh8fj2r/a4+Czg+w1wI9og8AH3ngB+sHv8A1CufKCva9awuOZhYsHj7U9ZuW59EdK+USysPo4Mru5hZ2ktHEbXAgvLRoj8s7AOyTZ46+Kl1p18URabsNb5yIMvheMKH4/h93n+N39ntdnCn2Y0faaHWdm469JiLGOv42vi8pVyUkscsbIp3yCSEsa4OcWyvHa7tHIB5+C0HTTpfvHS9tLWKL9XyOlUrb317txkwyTKrpC/wSwN7HPb3Fok7/cBy1BF+vHW7Y8h086lDTcBa+acFHNj7G0RZQVJYbTOPE9HjDe54jJAc7ub5hwb3cLp3V3F1ukSSSWckn4+wVzTuPQzqC/V+oen63e1uXWNps2r8M2VfYjtU5LDu+WLiNjmuZ38lruQR3eYdxwumNajMV6ow8EtaWnj/AFSgmSIiCF7VY9EtXZ+3v8KPv7eeOeGc8KjNJ+ULk9kZol/K6acHgdx4hoXRk22JGWDC6UMkiEY4Y4Rv7XBxJ4Hc1nPAvLbYHWp78LCA+SLsBd7uSzhUbh+iucx+g9G8HJax7rem5CtayD2yP8OVkdWeJwiPZyT3StI7g3yB/oIfin8o+zbrUtiOoyx9O7uTbjINiN9hlJdN4DJ3Vu3lsLpeGh3f3cEHt4Kyelu87vsHVrqLicpjqTtfxeUZWgnbkOZKrPRYnsa2MQDv7+7vcXPBaXlvtBoJj1XoJuLNYxvTqbJYT1c0MpHcbaZ43zlNVjs+kx1nR9vhgh4a0yB3m1v4vKnGvaPtOp9W9my9KTEWtU2OxDdtNnklZdrSx1mwlsbQ0se1xjYeS5pHLhwfJBv9j338H9/07WfQfSPwhFw+leN2+j+jxNk/F7T3d3dx7xxx8VANh+UjJibF+lV1tlnIR7U7Vqos5NtatK8VWWPFkmdGRFyHloZw4kgcHz8pT1O0jO5vZdP2fWn45+W16az/AIHlZJIoLEU8XhvHiMa9zHDhpB7SPIgqEY/pLvdDW9wrXKelbHY2TYpcrboZMWPQ3Vn1oYxGPYcWva+L3lrgQOfIn2Q2u/fKJboNjBYe/jMXR2rIUzfnoZjYIKFWpEHdnnZeCHuLuQ1rGEntcT2gKBdQOuee6l6TpTtLpejxZrZDg8k6pnhXlbNE2R5gisxRyDw5BH3ePGee0AAe2S3d6/0C3LQGajl8HlcNnNhxuGfg8jWzpmFWeuZ3TxCKQNe9hiLyxvc09zOOeFOdi6f7HtVLp3Lekw8GTwWwR5fItpCSOu6NsNiPthBBJd/Cs/G454cfLyCDIwHUIUru668KEzxpdKo70mzedPLc76xl9pzm9wcO3guJcXc8+XuUX6ddQr+99X8HkO+xSxmX6fU8yMR6S6SGGaawSTxwGucGkN7+0EgfD3LM2nppucO47vkdWsYN1LbqEFay7LPmbLSliifEHsaxpErS1w8i5nBHvPuXl0+6O7Ho2yaBkm3MXYhxmo19YzMJMndzCA9ktd3aO7mTkEPDfZPPv8kHR2sf5BJ/tT/yC261Gsf5BJ/tT/yC26DDzFN2RxF6qwgPngfECfgXNI/9VD9Stxz4GlBz2WasLILFd3k+GRrQHNcPeDz/AGjgjyIU8WmzGma/sNgT5TB43JTgdolt1I5XAfRy4E8Lro1bNmzNi34LGksVF4+qzTPzSwn2fF+6nqs0z80sJ9nxfurdi0dZ2jquT2RePqs0z80sJ9nxfup6rNM/NLCfZ8X7qYtHWdo6mT2RePqs0z80sJ9nxfup6rNM/NLCfZ8X7qYtHWdo6mT2RePqs0z80sJ9nxfup6rNM/NLCfZ8X7qYtHWdo6mT2RePqs0z80sJ9nxfup6rNM/NLCfZ8X7qYtHWdo6mT2RePqs0z80sJ9nxfup6rNM/NLCfZ8X7qYtHWdo6mT2RePqs0z80sJ9nxfuqiOo+oYKh8rbo9hq2GoV8RfxuYkt0IqzGwWHMiaWGRgHa4tJ5BI8vgmLR1naOpkv1F4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZeF69WxtWSzbnjrV42lz5ZXBrWgeZJJX31WaZ+aWE+z4v3VlY/p/q+JsssUtcxNSxG4PZLBSjY5rh7iCG8g/pTFo6ztHUyeXTyjNR1aITwyV32LNq4IpQQ9jZrEkzQ4EAg8SDkEcj3HzCkiIuKpbmpbm3Pvm9JzERFrR+JYI5wBJG2QDzAcAV5fN9X6tD/dhZCIMf5vq/Vof7sKGdSbuyYOTVBqetU82y5na1TLmYBvoePd3eNYb7TeXN4bwPP3+4qdqv8Aq3iruUm0k0t0ZporbJUsTsfL2fO0Te/uoD2m9xk5Hl589vuKCcfN9X6tD/dhPm+r9Wh/uwshEHg2jWa4EV4gR5ghg8l7oiAiIgIiICIiAiIgIiIChu8NFLOYDLTexSrmevNMfxYjKGdrnfQOWccngAuCmS/MkbJo3RyNa+N4LXNcOQQfeCFupVMO3xfmcXLGSOtcHtDmkFpHII+K+rzk6X6dK8ufqmEc4+8nHxfur8+qzTPzSwn2fF+6uvFo6ztHVcnsi8fVZpn5pYT7Pi/dT1WaZ+aWE+z4v3UxaOs7R1Mnsi8fVZpn5pYT7Pi/dT1WaZ+aWE+z4v3UxaOs7R1Mnsi8fVZpn5pYT7Pi/dT1WaZ+aWE+z4v3UxaOs7R1Mnsi8fVZpn5pYT7Pi/dT1WaZ+aWE+z4v3UxaOs7R1Mnsi8fVZpn5pYT7Pi/dVebn0p6eHq/05dYsUMLfDcl6LrUNSNsOb/gG95kYBw7wR7Y59xPkmLR1naOpkslF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZF4+qzTPzSwn2fF+6nqs0z80sJ9nxfupi0dZ2jqZPZVr1b6fzdaMda1TF5+5r85qzCfL40kvql3aGsPBHPeA4OZ3AlhcPLkFWJ6rNM/NLCfZ8X7q3+NxVLDVG1cfTr0azSS2GtE2Ng/oa0AKTXp2YngvmZvjOLvHLWTKPB/EPr38mDevk85h0OxY4z4iSQtq5umC+pYHw9rj2Hf6DuD5HjkeZ/pL8kzpLnMP076denzYHM9Pm69WyuOqXqIlyVS/ZbHakcJS0NDBJJL28DuAEYJJBK6UzOFx+xYuzjctQrZPHWWdk9O5C2aGVv0OY4EOH6CF6Y3G1MPjqtChVho0asTYK9WtGI4oY2gNaxjQAGtAAAA8gAvPYqXwuE3vCYXZN4yvTbTcp1Za8VKh1+cVjkanMI4ktTN7m8AO8nc+UTR8QtJd6c4DW+nOD0o9Gb7MRvNiGXZMdgcgXx4myTBy6aZsjXOa1wHLo/LiFx44Pn0YiCkzQ1/ZOpOP0P5s33FSadWiuw5v0izBjMk0CA+HJZ8T/C3e00ObID5iXz9/Ok1Lfel2YsbF1trb7nhrEBbh7uPyck0OIpzH0docyq+IFsh7o/bHI5lf8Ap46HWPfx9XK1JKt2tDcqyDh8FiMPY4fpafIoKKxesVtf6Yx4Kr1rcc3t0zLeubHnDVnsujd4BEdaJ/aJ2kEDy5P8OP0KRZDV9muW9LxeK6mYyLJ4IRu2eB+HqyTZmMeF3uLO7mryA/jtBH8MP5I5nOU6c6rm7WDs39cxVyzgnNfippqcbn0C0tI8E8cx8djPxePxR9AXjQ6Yari92yu4VMHVg2bKQCtdybQfFniAYA1x5444jZ+qEEWxV7K/h5ms9b2bUbHS5tdsdWOFrRZr2uIgfEn58MtJMh49/tsCgOLv/KBtUdnizmk6JkGxU4XYp2EyM9eO/K+VveGyud3sayLxCSQ0l3Z28gkiz6fQHp5j9CvaVX1PHw6resC1ZxTWu8GWUFhDyOeeeY2fH+KFM8LhqOuYelisZVio46lCyvWrQt7WRRsaGta0fAAABBA8nnt2oQ6KKvTWnlZsp4TdidHmIYG4PnwvEc0OYTZDe+U8M4J8L/SCzsZs2TudUMxrVnQ7NLA06jbFbZ3vidWuPIi5iawe01wMjx5+/wAJ30hTtEFQ4zrfQm6XZfdst0823X4cZabVkw9/DD5wl5MQ8WKJrj3x8y/jcj8R/l7Pnssr1o0vBv0OLIQW6tjdRH81V3YyRz+X+Fw2YNafBI8ZgPfxx7X0FWYiCDYvqdo2a6j5nQqWQrTbZhq7bd7HeivaYYiIyHd5YGHymj9zifa/QePmD6rdOtk1GztWP2XAza3WmFafKusRx14ZT2cMe93Aa4+IwcH+W36VNjXiMpkMTDIW9pf2jkj6OfoUfudNtSyGuW9fs6xh5sFbkE1jGuoxGvK8FpD3R9vaXAtaeSOfZH0IMr5310R41/puL7Mk0Oou8WPi0CAQYjz7YPc3jt5/GH0r3rZDDOyk1GvZonJV298taORnjRN8vNzR5ge0PM/SPpWiyvSDTM3c1e1d1yjPPq/b8yuLOBQ7ezt8IAgADw4+B/ohZFDphquL3bK7fUwdWDZsrAK13JNB8WeMBgDXHnjjiNn6oQbKPbMHLi5cmzM49+Nif4clxtphhY7y9kv54B8x5c/EfSvWXYsTA6i2XJ043X+PRA+wwGxzxx4fn7fPcPdz7x9KiFPoD08oaHe0qvqePi1W9YFqzimtd4MsoLCHkc888xs+P8ULOyHR3S8rPqk1vXac8uqBgwjntPNAM7O3w/Py48KP38/ihBvxkMNcyliiLNGfJQN75qwkY6aNvl5ub7wPMeZ+kKDbd1Qo09OfmtH131m2PTxjRU16aBzWTefcZJSe1jWkAOPnwXDkcckSSh0w1XF7tldvqYOrBs2VgFa7k2A+LPGAwBrjzxxxGz9ULL0zRde6dYOPD6xhaWCxjHF4q0IWxMLiAC53H4ziAOXHkngeaDQM0HMT9T49mk2aePW24/0dmqCjAIhM48vlfKOXOPk3gfAh3B4cQpdSwdGhVjrxVmmOMcAykyOP9Lnckn9JKzkQY/zfV+rQ/wB2E+b6v1aH+7CyEQY/zfV+rQ/3YT5vq/Vof7sLIRBj/N9X6tD/AHYT5vq/Vof7sLIRB+IoY4G9sbGxtJ54aOAv2iICIiAiIgIiICIiAiIgIiICIiAuduqX/vo9EP8AuvOf+S1dErnbql/76PRD/uvOf+S1B0SiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKqevVrSatjpuNzp3bb5NwoR4Q0yR4WTIk8B8nDm+wPa555+HkVayg/U/K7Ti5dQGs69V2BlnP1a+VNnj/AaLu/xbLOXN9tnDePf7/cUE4REQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFWe9ZXCVOtnTCld1SfK5m23KfN2eYzmPEhtdplDj8PFbwwf0KzFDNn/Db1kaV8yehfgZ23fwh8bjx+fCHovhfH/Gd3PHwQTNERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFzt1S/8AfR6If915z/yWrolU11m6QbXtHUHUN+0rM4elsWs1rlaCjn6cs1SyLDWtcXOika9hAB44B9/6OCFyoub/AF79ZOnZ7eoPRqfN0mfj5nQLQutcPiRUeRKB8eXEKUaR8sPpLvNr0GLbK+EywPY/GbAx2PnY/wDkcShrS79DSUF0IvxDNHYiZLE9ssTwHNew8tcD7iD8V+0BERAREQEREBERAREQEREBERAREQFX/VvFXcpNpJpbozTRW2SpYnY+Xs+dom9/dQHtN7jJyPLz57fcVYCqnr1a0mrY6bjc6d22+TcKEeENMkeFkyJPAfJw5vsD2ueefh5FBayIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiII/nMlZq3eyKUsb2A8ABa/57u/z5/sH7F77J/7RH+oP/VcU4bN4+H5O/SHFvvV25IbvQgNMyt8USMyri9vbzzy0Dk/Qg7P+e7v8+f7B+xarIUYsrnsTmbJlfkcUJhUkbM9rWeK0Nk5YCGv5AH4wPHw4K5NqCLpdvGyxYuLGbbtudr521g9lxt0zXW2I43y+iXIeSD2OAY1w8uWhva0lfnof08rZuTQtmobtqrMhba21dFGvYGUyzTEfSa9l0lx4kPmS7mP2XMBAaBwg6a1frMdw2fMYnG08rJVxU0tWfMvgjbSdYjc1skLHF3e5zSSCezt5a72vJbzZ96l1PWstnLck0lTGVJrszIGNMjmRsL3BoPAJ4aeOSP6Vxt+BGC1j5NvWS1iMRVx8jdpuV5JK8QaRWrZQeG08fxI2d3H0DlTbrLmKGc6qbDBjrte/Mzpbmi5laVshb3ywdnPBPv48vpQdOatuMuzYvEZSCSVtXIQw2Y2StaHhkjQ4BwHIB4PnwSp0qY6I362T6Z6PYp2IrVd2LpgSwvD2kiNgI5HxBBB+ggq50BERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUX3fpdp/Uqr6PtOs4rPxhva036jJXsH+i4juaf0ghShEHOc3yLMRq8r7PTHdtq6ZWCS4Vcdfdax5d9L60xPd5/DuAX49N+Uv00/x9HVesGMj/AI9WT5nybx8SQ7mH+ocldHog53pfLZ1PDWoqPUPXtm6X5B57AdgxkhqyO/8AhzxhzXD/AEiAFdWpb5re/UPTdaz+Nz1Tgcy462ydrf0HtJ4P6D5rb3aVfI1Za1uCK1WlHbJDMwPY8fQQfIhUttvyMelOz3/nKlr79RzQJMeU1Ww/HTRn4kCMhnP6S0oLwRc5eqHrp059rSerFbcaDPxMTv8AS8R/H6bkP8I4n9IAT/rL77oPsdTOjOeo12eTszqUjMvUI/luY0h8Tf8AW5KDo1FVvT75UHSzqe9kOA3TGS3nHt9AuSGpa7vi0RShriQfLyBVpICIiAiIgIiICIiAiIgKD9T8rtOLl1Aazr1XYGWc/Vr5U2eP8Bou7/Fss5c322cN49/v9xU4Vf8AVvFXcpNpJpbozTRW2SpYnY+Xs+dom9/dQHtN7jJyPLz57fcUFgIiICIiAiIgIiICIiAiIgIiICIiAih1/q/p1HCbNlY9gpZKrrTHPyzcXKLclPtDiWvZH3ODvZd7JHPkfoWhu9Y8llNP1bYdN0fL7VWzk4YYZHsoSU4e4gyyiX3DyJA+Pl7uUFnoobC3f3dT7Dpn683p42oBCxjZ/nR1kgclxJ8PwweeOPPz/Qo1juhdnK9PNh1Lfdyy29V81bFiWzKBSkhYHMc2KLwiOxnMYPAPny76UE52HfNc1PJ4nHZnOY/GZDLzitj6lqw1k1uQkN7Y2E8u83NHkPLuHPvWlpdW8Tl942PUcbTyVrN4Or6RY76ckNZ7iGlsbJnANc4h7fdyODz8Cs+j0u1SjR1ir8w0rbdYgbXw016IWZqLGtY0eHJJ3Oa7iOP2ueT2jzUpQVPLsfVjdelnp2C1fFaHuc1sMZQ2i36ZDHW585S6v7nkHkNI8iOD9KkF3RM7lt11vYZ9xyVCtjKvZbwOPDG0r05DgXyFzS4t9ryb5fitPkVOEQQ3E9Msdrs+WkxZfAMpfmydrxXukLp5SC8jk+Q8hw0eQ4Wtd0O1R+VlyjtewZycszLMl04yLxnyscHMkL+OS5rgHB3PIIBCsREEDxfR7XsHl7WWxuHxOPylrn0i9VoRxTzcnk972gOdyfPzK/ON6M63hs3YzOPwuIo5ewSZshWx8cdiXn390jQHHn9JU+RBDq3TbG06dypXq0oKtySWazBHVa1k75CTI57R5OLySXE+Z5PPKwMF0W1nV5mzYbB4bEStjfC2ShjooHBjy1z2gtA9lxa0ke4loJ9wVgIgiuB0CjrNaCpi69TG0YXmRlWnXbDG0lxc4hreACXEk+XmSSpUiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIggvUHoX0+6qMeNr1DE5mVw49KmrBtgD9EzeHj+pwVXf9UfI6T/CdLOqm1aO1n+Lxd2UZbGs+gCCbzH0c9xK6LRBzp+Gfyium/lntI1/qdjWe+7q140bgb/KfBMO17v8ARjWfhflrdOpMhHjNr+eOm+Zf5egbfjZKRP0kScGPj9JcFfiwM1gcZsuPkoZfHVMrRk/HrXYGzRO/pa4EFB8wewYvZsfHfw+Sp5ajJ+JZoztmid/Q5pIK2CoXN/Iq6cT5CTKaszL9OM0/z9P0/IyUT+geH5x8foDQsD8CflD9OPPAbzgOpeNZ7qO10TSuBv8AJbYg8nu/0pAg6JRc6SfK1ymixuHVHpTtWmNjBMmTx8bctjWgfEzw/i8+8At/p9y5Y0D/AKR3Kav18278IMjLs/TLK5qc0Z/B7JsdVD+yCSFna09nhNjLoyOSeXfjl/eH9MkWu17YsZtuDpZnDXoMni7sQmr26zw+OVh9xBH/APR7l5bFtWJ1So2xlr0VON5Ija8kvlI94Ywcuef0NBKys2bVuYs2YvmRtkVZTdfMMx7hBh81bYPdIyCKMO/oEkjT/aAvL1/438289+rV+/XoR+m9rn/rlblpL+KPyksh1Q6ZdWclqOw75tOWbgsi29ibGQy9iUgDl1e1GXP4bJ2O/Gb5tJcOfIr+rnr/AMb+bee/Vq/frmT5XXTfD/KTz+nZinh8rir2PsNq5SeUVg+fHF3c4M4ldzIw93YDw3+EdyfIK/0ztfw5+hcsX/o88NvEvSO1t+8bTnc/Y2OZslCvmb81r0atF3ta9okJLTI5ziePItbGV1QqixHWrBYHFUsZj9UzdShThZXrwRsqhscbGhrWj+H9wAA/qWX6/wDG/m3nv1av36f0ztfw5+hctJFVzev2NJ89dzrR9JbV/wDSdbjB9ZdazM7K8s8+IsSHtZHkovCa4/ACTzZyfcB3cn6Fhb/T+1U44rVObt/sXJyiLRbtvGC6c6ze2HZcnBiMPTZ3zWrDuAPoAHvc4nyDQCSSAASvPRvUVWfJ6+UDh/lHaxm9iwOPt0MTRyz8XA68WiaftggkdIWNJDPamLQO53k0EkF3a2fybThYs/Fg35egzNyxmWPGussFl7B5lwj57iB9PCDaIqxxfygNf23W9uymnU8pt0+tzmpYoUab4pppweDHEJQzu4PPJ93snjny5+5PP9TdgxekX9ZwOIwguysm2Cls0shnpwcsLo4vB5BlI7xyRwD2oLNWuz2xYrVcXNks1k6eHx0PHiXL9hkELOfIdz3kAf1lRqhp20R9UctsF3dJbmqWKYq09VFCOOOq/iPumM4Pe9xLH8AjyEhHwHGu1j5P2ma5oNrTZ6E2x4K3bF6zDsVh98zzAsIc4yE+XMbD2gAeR8vM8hn53rRqWv7JqmBsZCSbI7QA/FNq1pJo54/L+EMrWljW+03zLh+MFjY7ettzexbliYdHs4qDFQuGKzOSss9Gyc/B7QGN9trOe32vo59xCnVKlXxlKvTp14qlSvG2KGCBgZHGxo4a1rR5AAAAAeQAXugqebUuqO99L8fQzu20dD3A3DNcu6lX9JidX5f2ws9IHLXcOZy4c+bPLkFSHIdIcFl+p2F367Jfmz+IqGpVAuPZWaHCRrnmFpDS8iV4JPPl2/QFN0QaXX9K17U7eTtYXB47E2spObN+elVZDJblLnOL5XNAL3cvceXc/jFbpEQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQR3qNhstsXT3aMTgL3zZnb+LtVcfd8Z8Po9h8TmxSd7AXM7Xlp7mgkccjzX899X/6JrYbBiOxb9jKA972YulJa/qDnmP8At4/qX9KkQUN0o6P4b5HvTvKV8dnszsMNqSPwKGSnYIRZPcOIWNb/AAff3AvPLvKPnjyPMWvW7WXyMuSyM3pWQmHD5SCA0fBjASexg+DR/SeSSTP+u1t8mb12lyfBZFYtFvPkX8xsaf6g6T9ZV4vuv0fs1mnQitd7Vr7X3XfQnIREXvMBFSOz9cs0zYM/WwFKCWthZ3VHRTYm/akuzNaHPa2WBhji83do7u48jkgAjnYu6kbjsGXzVbBUcXj48diKWVMWYhmM/dPHI/wHNa5vaR4ZBd/F4/Fdz5cfeqczdGf5PRVuoqnqdVc9vFjXaGpVMdWuXsLDnLtjLeJJFWjkPayJrWFpc8uD/PkABvPnzwsz5OhnPS6ubIjFn5xyPiiIksD/AE2bnt58+OeeOVnYr2aluLNnwunP5XdRZi+PY2VjmPaHscOC1w5BC+oulE+6Ub1NicjV1y/KZMfY/g6MsjiXQSAE+ESfewgHt5/FI7RyHNDeDfliaT8pTqVsjsnt+nZB+AryO+b8Zr59PqVWfyyIi53cR75JACfgA3gDqnL2JKWOmtwntnqAWonc8cPjIe08/D2mhdXseJGNcOeHDkcr4r9a7NZpVLNWxF3Fff8AOPf/AJvbPGL38sv+j81et1EyO5dLtmyG1YhkT62xQ08ZefTi7oC+CdkreeeX+kVjwG8nwG8uHaAf6Yu0PXJNwbtb8HQfszIBVZln12ussiHd7DZCOWj2nc8e/nzW+RfOI+NaGN4aA0fQAvqIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiCqeu+HeYMNnGDmOnI+rOf5LJuztcf6HsY3/AOdVeun71KDJU56lqJk9adhjkieOWvaRwQf6lRO39Nsrqk75Kdexl8RySyWBpknhH8l7B7T+Pg5oJ+kD3n7D9I7dYw47PUm6Y8PPy+d5MXqj9YGV/wD9f7N+tQ//ACl9PUDKc+WgbMf091D/APKUk+fsb3uY69XZIz8eOSQNc3+lp8x/Wnz7jfyhU/v2/tX0mFb5p2jol06IJ6tc1VzOTymtbPNrFfOPbcvY2xQitOjsFga58bi/hjyGgO5728jkLfV9F8Da9ozXp3d890q1PwfB48HwhKO7u7va58X3cDjt9558t78+438oVP79v7U+fcb+UKn9+39qkdnsx4R5+/z6l0q5odFr+vR61YwG0fNmXxWJjwtmzJQbPDdgYe5vMReOxwd3EEOPHcR5hZmrU8h0lwFfX48PmduIkntvyVGKrCwumnkkLS2Sw0hw7vgOD5cce4Tr59xv5Qqf37f2p8+438oVP79v7VjHZrNib7ETE/8Az3Tl7i6UcO/ZTsB/AHZSSSO3uocj9P8AlX/94W413P2s6LBs4DJ4Lwu3tGRMB8Xnnnt8KWT3cefPHvHHPnxmfPuN/KFX6f8AHt/as3DssbLKIsJUmzDyeO6q3mJv+tKfYb/WefoBWU2Zp+1btZRrdEfYunQjw79lvU8JGC5+RlEDwPhF75Xf1Rhx/p4HxXUyhXTvp2zUIn3br47OanZ2SSR8mOFnIPhx8gHjkAlxALiAeAAAJqvh/wBV7ZZ7VUizT/ts/WZ8WXuuERF4iCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiDymqw2ePFhjl493e0Hj+1eXzXS+qQf3Tf2Iit8x7w+a6X1SD+6b+xPmul9Ug/um/sRFeKdQ+a6X1SD+6b+xPmul9Ug/um/sRE4p1H0Yym0gipACPiIx+xZHuRFJmZ8R9REUBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREH//2Q==", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from IPython.display import Image, display\n", "from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles\n", "\n", "display(\n", " Image(\n", " app.get_graph().draw_mermaid_png(\n", " draw_method=MermaidDrawMethod.API,\n", " output_file_path=\"agent_workflow.png\",\n", " )\n", " )\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Test" ] }, { "cell_type": "code", "execution_count": 288, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---ROUTE QUESTION---\n", "建準去年的類別八排放量\n", "{'datasource': 'company_private_data'}\n", "company_private_data\n", "---ROUTE QUESTION TO TEXT-TO-SQL---\n", "---SQL QUERY---\n", "RETRY: 0\n", "SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別8總排放量\"\n", "FROM \"104_112碳排放公開及建準資料\"\n", "WHERE \"事業名稱\" like '%建準%'\n", "AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1\n", "AND \"類別\" = '類別8-';\n", "---CHECK SQL CORRECTNESS TO QUESTION---\n", "---GRADE: CORRECT SQL QUERY---\n", "'Finished running: company_private_data_query:'\n", "---SQL TO NL---\n", "{'question': '建準去年的類別八排放量', 'generation': None, 'documents': None, 'retry': 0, 'sql_query': 'SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別8總排放量\"\\nFROM \"104_112碳排放公開及建準資料\"\\nWHERE \"事業名稱\" like \\'%建準%\\'\\nAND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1\\nAND \"類別\" = \\'類別8-\\';'}\n", "[(None,)]\n", "'Finished running: company_private_data_search:'\n", "('SELECT SUM(\"排放量(公噸CO2e)\") AS \"類別8總排放量\"\\n'\n", " 'FROM \"104_112碳排放公開及建準資料\"\\n'\n", " 'WHERE \"事業名稱\" like \\'%建準%\\'\\n'\n", " 'AND \"年度\" = EXTRACT(YEAR FROM CURRENT_DATE)-1\\n'\n", " 'AND \"類別\" = \\'類別8-\\';',\n", " '[(None,)]',\n", " '根據 SQL '\n", " '查詢和結果,去年建準的類別八排放量為空,可能是資料庫中沒有符合條件的資料。建議檢查資料庫中是否有符合條件的資料,或調整查詢條件以確保能夠正確查詢到相應的數據。')\n" ] } ], "source": [ "# Test\n", "\n", "inputs = {\"question\": \"建準去年的類別八排放量\"}\n", "for output in app.stream(inputs, {\"recursion_limit\": 10}):\n", " for key, value in output.items():\n", " pprint(f\"Finished running: {key}:\")\n", "pprint(value[\"generation\"])" ] }, { "cell_type": "code", "execution_count": 181, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'建準去年的類別一排放量是13.5953。'" ] }, "execution_count": 181, "metadata": {}, "output_type": "execute_result" } ], "source": [ "value[\"generation\"]" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'stop' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "Cell \u001b[0;32mIn[27], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mstop\u001b[49m\n", "\u001b[0;31mNameError\u001b[0m: name 'stop' is not defined" ] } ], "source": [ "stop" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# from RAG_strategy import multi_query_chain\n", "llm = ChatOllama(model=local_llm, temperature=0)\n", "question = \"溫室氣體是什麼\"\n", "generate_queries = multi_query_chain(llm)\n", "\n", "questions = generate_queries.invoke(question)\n", "questions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def ask_more_detail_chain(llm):\n", " # Multi Query: Different Perspectives\n", " template = \"\"\"\n", " <|begin_of_text|>\n", " \n", " <|start_header_id|>system<|end_header_id|>\n", " 你是一個來自台灣的AI助理,你的專長是根據使用者提供的文本來進一步詢問當中的細節,例如名詞解釋,請用繁體中文。 \\n\n", " You are an AI language model assistant. \n", " Your task is to generate three questions about the given user context as additional explanation.\n", " By generating three in-depth questions about the user context, your goal is to help the user realize more details. \n", " Provide these questions separated by newlines.\n", " For example:\n", " context: 建準廣興廠去年2023年一共自產發電了684,508度綠電\n", " in-depth question:什麼是綠電?\\n為何要使用綠電? \n", "\n", " output must in user's language and no preamble or explanation.\n", " <|eot_id|>\n", " \n", " <|start_header_id|>user<|end_header_id|>\n", " \n", " \n", " \n", " Original context: {question}\n", " three questions:\n", " <|eot_id|>\n", " \n", " <|start_header_id|>assistant<|end_header_id|>\"\"\"\n", " prompt_perspectives = ChatPromptTemplate.from_template(template)\n", "\n", " \n", " # llm = ChatOpenAI(temperature=0, model=\"gpt-4-1106-preview\")\n", " # llm = ChatOllama(model=\"llama3\", num_gpu=1, temperature=0)\n", "\n", " generate_queries = (\n", " prompt_perspectives \n", " | llm\n", " | StrOutputParser() \n", " | (lambda x: x.split(\"\\n\"))\n", " )\n", "\n", " return generate_queries" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "llm = ChatOllama(model=local_llm, temperature=0)\n", "question = \"建準廣興廠去年的固定燃燒排放量是多少?\"\n", "generate_queries = ask_more_detail_chain(llm)\n", "\n", "questions = generate_queries.invoke(question)\n", "questions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "llm = ChatOllama(model=local_llm, temperature=0)\n", "question = \"固定燃燒是什麼?\"\n", "docs = retriever.get_relevant_documents(question, k=10)\n", "generation = faiss_query(question, docs, llm)\n", "generation" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import datetime\n", "from typing import Literal, Optional, Tuple\n", "\n", "from langchain_core.pydantic_v1 import BaseModel, Field\n", "\n", "\n", "class SubQuery(BaseModel):\n", " \"\"\"Search over a database of tutorial videos about a software library.\"\"\"\n", "\n", " sub_query: str = Field(\n", " ...,\n", " description=\"A very specific query against the database.\",\n", " )" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[SubQuery(sub_query='查詢去年類別一的排放量')]" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from langchain.output_parsers import PydanticToolsParser\n", "from langchain_core.prompts import ChatPromptTemplate\n", "from langchain_openai import ChatOpenAI\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "\n", "system = \"\"\"You are an expert at converting user questions into database queries. \\\n", "You have access to a database of tutorial videos about a software library for building LLM-powered applications. \\\n", "\n", "Perform query decomposition. Given a user question, break it down into distinct sub questions that \\\n", "you need to answer in order to answer the original question.\n", "\n", "If there are acronyms or words you are not familiar with, do not try to rephrase them.\n", "用繁體中文.\n", "\"\"\"\n", "prompt = ChatPromptTemplate.from_messages(\n", " [\n", " (\"system\", system),\n", " (\"human\", \"{question}用繁體中文\"),\n", " ]\n", ")\n", "llm = ChatOpenAI(model=\"gpt-3.5-turbo-0125\", temperature=0)\n", "llm_with_tools = llm.bind_tools([SubQuery])\n", "parser = PydanticToolsParser(tools=[SubQuery])\n", "query_analyzer = prompt | llm_with_tools | parser\n", "\n", "query_analyzer.invoke({\"question\": \"建準去年的類別一排放量?溫室氣體是什麼?\"})" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "query_analyzer.invoke({\"question\": \"溫室氣體是什麼?建準去年的類別一排放量?\"})" ] } ], "metadata": { "kernelspec": { "display_name": "llama3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 2 }