diff --git a/.gitignore b/.gitignore
index fdb9d24e..1ecd0868 100644
--- a/.gitignore
+++ b/.gitignore
@@ -132,4 +132,5 @@ dmypy.json
*transactions*.jsonl
/examples/data/transactions*
*.DS_Store
-tmp_*
\ No newline at end of file
+tmp_*
+examples/fine-tuned_qa/local_cache/*
diff --git a/examples/fine-tuned_qa/ft_retrieval_augmented_generation_qdrant.ipynb b/examples/fine-tuned_qa/ft_retrieval_augmented_generation_qdrant.ipynb
new file mode 100644
index 00000000..25b911bc
--- /dev/null
+++ b/examples/fine-tuned_qa/ft_retrieval_augmented_generation_qdrant.ipynb
@@ -0,0 +1,1451 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Fine-Tuning OpenAI Models for Retrieval Augmented Generation (RAG) with Qdrant and Few-Shot Learning\n",
+ "\n",
+ "The aim of this notebook is to walk through a comprehensive example of how to fine-tune OpenAI models for Retrieval Augmented Generation (RAG). \n",
+ "\n",
+ "We will also be integrating Qdrant and Few-Shot Learning to boost the model's performance and reduce hallucinations. This could serve as a practical guide for ML practitioners, data scientists, and AI Engineers interested in leveraging the power of OpenAI models for specific use-cases. π€©\n",
+ "\n",
+ "## Why should you read this blog?\n",
+ "\n",
+ "You want to learn how to \n",
+ "- [Fine-tune OpenAI models](https://platform.openai.com/docs/guides/fine-tuning/) for specific use-cases\n",
+ "- Use [Qdrant](https://qdrant.tech/documentation/) to improve the performance of your RAG model\n",
+ "- Use fine-tuning to improve the correctness of your RAG model and reduce hallucinations\n",
+ "\n",
+ "To begin, we've selected a dataset where we've a guarantee that the retrieval is perfect. We've selected a subset of the [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) dataset, which is a collection of questions and answers about Wikipedia articles. We've also included samples where the answer is not present in the context, to demonstrate how RAG handles this case.\n",
+ "\n",
+ "## Table of Contents\n",
+ "1. Setting up the Environment\n",
+ "\n",
+ "### Section A: Zero-Shot Learning\n",
+ "2. Data Preparation: SQuADv2 Dataset\n",
+ "3. Answering using Base gpt-3.5-turbo-0613 model\n",
+ "4. Fine-tuning and Answering using Fine-tuned model\n",
+ "5. **Evaluation**: How well does the model perform?\n",
+ "\n",
+ "### Section B: Few-Shot Learning\n",
+ "\n",
+ "6. Using Qdrant to Improve RAG Prompt\n",
+ "7. Fine-Tuning OpenAI Model with Qdrant\n",
+ "8. Evaluation\n",
+ "\n",
+ "9. **Conclusion**\n",
+ " - Aggregate Results\n",
+ " - Observations"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Terms, Definitions, and References\n",
+ "\n",
+ "**Retrieval Augmented Generation (RAG)?**\n",
+ "The phrase Retrieval Augmented Generation (RAG) comes from a [recent paper](https://arxiv.org/abs/2005.11401) by Lewis et al. from Facebook AI. The idea is to use a pre-trained language model (LM) to generate text, but to use a separate retrieval system to find relevant documents to condition the LM on. \n",
+ "\n",
+ "**What is Qdrant?**\n",
+ "Qdrant is an open-source vector search engine that allows you to search for similar vectors in a large dataset. It is built in Rust and here we'll use the Python client to interact with it. This is the Retrieval part of RAG.\n",
+ "\n",
+ "**What is Few-Shot Learning?**\n",
+ "Few-shot learning is a type of machine learning where the model is \"improved\" via training or fine-tuning on a small amount of data. In this case, we'll use it to fine-tune the RAG model on a small number of examples from the SQuAD dataset. This is the Augmented part of RAG.\n",
+ "\n",
+ "**What is Zero-Shot Learning?**\n",
+ "Zero-shot learning is a type of machine learning where the model is \"improved\" via training or fine-tuning without any dataset specific information. \n",
+ "\n",
+ "**What is Fine-Tuning?**\n",
+ "Fine-tuning is a type of machine learning where the model is \"improved\" via training or fine-tuning on a small amount of data. In this case, we'll use it to fine-tune the RAG model on a small number of examples from the SQuAD dataset. The LLM is what makes the Generation part of RAG."
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1. Setting Up the Environment\n",
+ "\n",
+ "### Install and Import Dependencies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install pandas openai tqdm tenacity scikit-learn tiktoken python-dotenv seaborn --upgrade --quiet"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import json\n",
+ "import os\n",
+ "import time\n",
+ "\n",
+ "import pandas as pd\n",
+ "import openai\n",
+ "import tiktoken\n",
+ "import seaborn as sns\n",
+ "from tenacity import retry, wait_exponential\n",
+ "from tqdm import tqdm\n",
+ "from collections import defaultdict\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "from sklearn.metrics import confusion_matrix\n",
+ "\n",
+ "import warnings\n",
+ "warnings.filterwarnings('ignore')\n",
+ "\n",
+ "tqdm.pandas()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Set your keys\n",
+ "Get your OpenAI keys [here](https://platform.openai.com/account/api-keys) and Qdrant keys after making a free cluster [here](https://cloud.qdrant.io/login)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "openai.api_key = \"sk-xxx\"\n",
+ "os.environ[\"QDRANT_URL\"] = \"https://xxx.cloud.qdrant.io:6333\"\n",
+ "os.environ[\"QDRANT_API_KEY\"] = \"xxx\""
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Section A\n",
+ "\n",
+ "## 2. Data Preparation: SQuADv2 Data Subsets\n",
+ "\n",
+ "For the purpose of demonstration, we'll make small slices from the train and validation splits of the [SQuADv2](https://rajpurkar.github.io/SQuAD-explorer/) dataset. This dataset has questions and contexts where the answer is not present in the context, to help us evaluate how LLM handles this case.\n",
+ "\n",
+ "We'll read the data from the JSON files and create a dataframe with the following columns: `question`, `context`, `answer`, `is_impossible`.\n",
+ "\n",
+ "### Download the Data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# !mkdir -p local_cache\n",
+ "# !wget https://rajpurkar.github.io/SQuAD-explorer/dataset/train-v2.0.json -O local_cache/train.json\n",
+ "# !wget https://rajpurkar.github.io/SQuAD-explorer/dataset/dev-v2.0.json -O local_cache/dev.json"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Read JSON to DataFrame"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def json_to_dataframe_with_titles(json_data):\n",
+ " qas = []\n",
+ " context = []\n",
+ " is_impossible = []\n",
+ " answers = []\n",
+ " titles = []\n",
+ "\n",
+ " for article in json_data['data']:\n",
+ " title = article['title']\n",
+ " for paragraph in article['paragraphs']:\n",
+ " for qa in paragraph['qas']:\n",
+ " qas.append(qa['question'].strip())\n",
+ " context.append(paragraph['context'])\n",
+ " is_impossible.append(qa['is_impossible'])\n",
+ " \n",
+ " ans_list = []\n",
+ " for ans in qa['answers']:\n",
+ " ans_list.append(ans['text'])\n",
+ " answers.append(ans_list)\n",
+ " titles.append(title)\n",
+ "\n",
+ " df = pd.DataFrame({'title': titles, 'question': qas, 'context': context, 'is_impossible': is_impossible, 'answers': answers})\n",
+ " return df\n",
+ "\n",
+ "def get_diverse_sample(df, sample_size=100, random_state=42):\n",
+ " \"\"\"\n",
+ " Get a diverse sample of the dataframe by sampling from each title\n",
+ " \"\"\"\n",
+ " sample_df = df.groupby(['title', 'is_impossible']).apply(lambda x: x.sample(min(len(x), max(1, sample_size // 50)), random_state=random_state)).reset_index(drop=True)\n",
+ " \n",
+ " if len(sample_df) < sample_size:\n",
+ " remaining_sample_size = sample_size - len(sample_df)\n",
+ " remaining_df = df.drop(sample_df.index).sample(remaining_sample_size, random_state=random_state)\n",
+ " sample_df = pd.concat([sample_df, remaining_df]).sample(frac=1, random_state=random_state).reset_index(drop=True)\n",
+ "\n",
+ " return sample_df.sample(min(sample_size, len(sample_df)), random_state=random_state).reset_index(drop=True)\n",
+ "\n",
+ "train_df = json_to_dataframe_with_titles(json.load(open('local_cache/train.json')))\n",
+ "val_df = json_to_dataframe_with_titles(json.load(open('local_cache/dev.json')))\n",
+ "\n",
+ "df = get_diverse_sample(val_df, sample_size=100, random_state=42)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 3. Answering using Base gpt-3.5-turbo-0613 model\n",
+ "\n",
+ "### 3.1 Zero Shot Prompt\n",
+ "\n",
+ "Let's start by using the base gpt-3.5-turbo-0613 model to answer the questions. This prompt is a simple concatenation of the question and context, with a separator token in between: `\\n\\n`. We've a simple instruction part of the prompt: \n",
+ "\n",
+ "> Answer the following Question based on the Context only. Only answer from the Context. If you don't know the answer, say 'I don't know'.\n",
+ "\n",
+ "Other prompts are possible, but this is a good starting point. We'll use this prompt to answer the questions in the validation set. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Function to get prompt messages\n",
+ "def get_prompt(row):\n",
+ " return [\n",
+ " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n",
+ " {\n",
+ " \"role\": \"user\",\n",
+ " \"content\": f\"\"\"Answer the following Question based on the Context only. Only answer from the Context. If you don't know the answer, say 'I don't know'.\n",
+ " Question: {row.question}\\n\\n\n",
+ " Context: {row.context}\\n\\n\n",
+ " Answer:\\n\"\"\",\n",
+ " },\n",
+ " ]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 3.2 Answering using Zero Shot Prompt\n",
+ "\n",
+ "Next, you'll need some re-usable functions which make an OpenAI API Call and return the answer. You'll use the `ChatCompletion.create` endpoint of the API, which takes a prompt and returns the completed text."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Function with tenacity for retries\n",
+ "@retry(wait=wait_exponential(multiplier=1, min=2, max=6))\n",
+ "def api_call(messages, model):\n",
+ " return openai.ChatCompletion.create(\n",
+ " model=model,\n",
+ " messages=messages,\n",
+ " stop=[\"\\n\\n\"],\n",
+ " max_tokens=100,\n",
+ " temperature=0.0,\n",
+ " )\n",
+ "\n",
+ "\n",
+ "# Main function to answer question\n",
+ "def answer_question(row, prompt_func=get_prompt, model=\"gpt-3.5-turbo-0613\"):\n",
+ " messages = prompt_func(row)\n",
+ " response = api_call(messages, model)\n",
+ " return response[\"choices\"][0][\"message\"][\"content\"]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "β° **Time to run: ~3 min**, π Needs Internet Connection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Use progress_apply with tqdm for progress bar\n",
+ "df[\"generated_answer\"] = df.progress_apply(answer_question, axis=1)\n",
+ "df.to_json(\"local_cache/100_val.json\", orient=\"records\", lines=True)\n",
+ "df = pd.read_json(\"local_cache/100_val.json\", orient=\"records\", lines=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " title | \n",
+ " question | \n",
+ " context | \n",
+ " is_impossible | \n",
+ " answers | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " Scottish_Parliament | \n",
+ " What consequence of establishing the Scottish ... | \n",
+ " A procedural consequence of the establishment ... | \n",
+ " False | \n",
+ " [able to vote on domestic legislation that app... | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " Imperialism | \n",
+ " Imperialism is less often associated with whic... | \n",
+ " The principles of imperialism are often genera... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " Economic_inequality | \n",
+ " What issues can't prevent women from working o... | \n",
+ " When a personβs capabilities are lowered, they... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " Southern_California | \n",
+ " What county are Los Angeles, Orange, San Diego... | \n",
+ " Its counties of Los Angeles, Orange, San Diego... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " French_and_Indian_War | \n",
+ " When was the deportation of Canadians? | \n",
+ " Britain gained control of French Canada and Ac... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ " ... | \n",
+ "
\n",
+ " \n",
+ " 95 | \n",
+ " Geology | \n",
+ " In the layered Earth model, what is the inner ... | \n",
+ " Seismologists can use the arrival times of sei... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 96 | \n",
+ " Prime_number | \n",
+ " What type of value would the Basel function ha... | \n",
+ " The zeta function is closely related to prime ... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 97 | \n",
+ " Fresno,_California | \n",
+ " What does the San Joaquin Valley Railroad cros... | \n",
+ " Passenger rail service is provided by Amtrak S... | \n",
+ " True | \n",
+ " [] | \n",
+ "
\n",
+ " \n",
+ " 98 | \n",
+ " Victoria_(Australia) | \n",
+ " What party rules in Melbourne's inner regions? | \n",
+ " The centre-left Australian Labor Party (ALP), ... | \n",
+ " False | \n",
+ " [The Greens, Australian Greens, Greens] | \n",
+ "
\n",
+ " \n",
+ " 99 | \n",
+ " Immune_system | \n",
+ " The speed of the killing response of the human... | \n",
+ " In humans, this response is activated by compl... | \n",
+ " False | \n",
+ " [signal amplification, signal amplification, s... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
100 rows Γ 5 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " title question \\\n",
+ "0 Scottish_Parliament What consequence of establishing the Scottish ... \n",
+ "1 Imperialism Imperialism is less often associated with whic... \n",
+ "2 Economic_inequality What issues can't prevent women from working o... \n",
+ "3 Southern_California What county are Los Angeles, Orange, San Diego... \n",
+ "4 French_and_Indian_War When was the deportation of Canadians? \n",
+ ".. ... ... \n",
+ "95 Geology In the layered Earth model, what is the inner ... \n",
+ "96 Prime_number What type of value would the Basel function ha... \n",
+ "97 Fresno,_California What does the San Joaquin Valley Railroad cros... \n",
+ "98 Victoria_(Australia) What party rules in Melbourne's inner regions? \n",
+ "99 Immune_system The speed of the killing response of the human... \n",
+ "\n",
+ " context is_impossible \\\n",
+ "0 A procedural consequence of the establishment ... False \n",
+ "1 The principles of imperialism are often genera... True \n",
+ "2 When a personβs capabilities are lowered, they... True \n",
+ "3 Its counties of Los Angeles, Orange, San Diego... True \n",
+ "4 Britain gained control of French Canada and Ac... True \n",
+ ".. ... ... \n",
+ "95 Seismologists can use the arrival times of sei... True \n",
+ "96 The zeta function is closely related to prime ... True \n",
+ "97 Passenger rail service is provided by Amtrak S... True \n",
+ "98 The centre-left Australian Labor Party (ALP), ... False \n",
+ "99 In humans, this response is activated by compl... False \n",
+ "\n",
+ " answers \n",
+ "0 [able to vote on domestic legislation that app... \n",
+ "1 [] \n",
+ "2 [] \n",
+ "3 [] \n",
+ "4 [] \n",
+ ".. ... \n",
+ "95 [] \n",
+ "96 [] \n",
+ "97 [] \n",
+ "98 [The Greens, Australian Greens, Greens] \n",
+ "99 [signal amplification, signal amplification, s... \n",
+ "\n",
+ "[100 rows x 5 columns]"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 4. Fine-tuning and Answering using Fine-tuned model\n",
+ "\n",
+ "For the complete fine-tuning process, please refer to the [OpenAI Fine-Tuning Docs](https://platform.openai.com/docs/guides/fine-tuning/use-a-fine-tuned-model).\n",
+ "\n",
+ "### 4.1 Prepare the Fine-Tuning Data\n",
+ "\n",
+ "We need to prepare the data for fine-tuning. We'll use a few samples from train split of same dataset as before, but we'll add the answer to the context. This will help the model learn to retrieve the answer from the context. \n",
+ "\n",
+ "Our instruction prompt is the same as before, and so is the system prompt. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def dataframe_to_jsonl(df):\n",
+ " def create_jsonl_entry(row):\n",
+ " answer = row[\"answers\"][0] if row[\"answers\"] else \"I don't know\"\n",
+ " messages = [\n",
+ " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n",
+ " {\n",
+ " \"role\": \"user\",\n",
+ " \"content\": f\"\"\"Answer the following Question based on the Context only. Only answer from the Context. If you don't know the answer, say 'I don't know'.\n",
+ " Question: {row.question}\\n\\n\n",
+ " Context: {row.context}\\n\\n\n",
+ " Answer:\\n\"\"\",\n",
+ " },\n",
+ " {\"role\": \"assistant\", \"content\": answer},\n",
+ " ]\n",
+ " return json.dumps({\"messages\": messages})\n",
+ "\n",
+ " jsonl_output = df.apply(create_jsonl_entry, axis=1)\n",
+ " return \"\\n\".join(jsonl_output)\n",
+ "\n",
+ "train_sample = get_diverse_sample(train_df, sample_size=100, random_state=42)\n",
+ "\n",
+ "with open(\"local_cache/100_train.jsonl\", \"w\") as f:\n",
+ " f.write(dataframe_to_jsonl(train_sample))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Tip: π‘ Verify the Fine-Tuning Data**\n",
+ "\n",
+ "You can see this [cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/Chat_finetuning_data_prep.ipynb) for more details on how to prepare the data for fine-tuning.\n",
+ "\n",
+ "### 4.2 Fine-Tune OpenAI Model\n",
+ "\n",
+ "If you're new to OpenAI Model Fine-Tuning, please refer to the [How to finetune Chat models](https://github.com/openai/openai-cookbook/blob/448a0595b84ced3bebc9a1568b625e748f9c1d60/examples/How_to_finetune_chat_models.ipynb) notebook. You can also refer to the [OpenAI Fine-Tuning Docs](platform.openai.com/docs/guides/fine-tuning/use-a-fine-tuned-model) for more details."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class OpenAIFineTuner:\n",
+ " \"\"\"\n",
+ " Class to fine tune OpenAI models\n",
+ " \"\"\"\n",
+ " def __init__(self, training_file_path, model_name, suffix):\n",
+ " self.training_file_path = training_file_path\n",
+ " self.model_name = model_name\n",
+ " self.suffix = suffix\n",
+ " self.file_object = None\n",
+ " self.fine_tuning_job = None\n",
+ " self.model_id = None\n",
+ "\n",
+ " def create_openai_file(self):\n",
+ " self.file_object = openai.File.create(\n",
+ " file=open(self.training_file_path, \"r\"),\n",
+ " purpose=\"fine-tune\",\n",
+ " )\n",
+ "\n",
+ " def wait_for_file_processing(self, sleep_time=20):\n",
+ " while self.file_object.status != 'processed':\n",
+ " time.sleep(sleep_time)\n",
+ " self.file_object.refresh()\n",
+ " print(\"File Status: \", self.file_object.status)\n",
+ "\n",
+ " def create_fine_tuning_job(self):\n",
+ " self.fine_tuning_job = openai.FineTuningJob.create(\n",
+ " training_file=self.file_object[\"id\"],\n",
+ " model=self.model_name,\n",
+ " suffix=self.suffix,\n",
+ " )\n",
+ "\n",
+ " def wait_for_fine_tuning(self, sleep_time=45):\n",
+ " while self.fine_tuning_job.status != 'succeeded':\n",
+ " time.sleep(sleep_time)\n",
+ " self.fine_tuning_job.refresh()\n",
+ " print(\"Job Status: \", self.fine_tuning_job.status)\n",
+ "\n",
+ " def retrieve_fine_tuned_model(self):\n",
+ " self.model_id = openai.FineTuningJob.retrieve(self.fine_tuning_job[\"id\"]).fine_tuned_model\n",
+ " return self.model_id\n",
+ "\n",
+ " def fine_tune_model(self):\n",
+ " self.create_openai_file()\n",
+ " self.wait_for_file_processing()\n",
+ " self.create_fine_tuning_job()\n",
+ " self.wait_for_fine_tuning()\n",
+ " return self.retrieve_fine_tuned_model()\n",
+ "\n",
+ "fine_tuner = OpenAIFineTuner(\n",
+ " training_file_path=\"local_cache/100_train.jsonl\",\n",
+ " model_name=\"gpt-3.5-turbo\",\n",
+ " suffix=\"100trn20230907\"\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "β° **Time to run: ~10-20 minutes**, π Needs Internet Connection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "model_id = fine_tuner.fine_tune_model()\n",
+ "model_id"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### 4.2.1 Try out the Fine-Tuned Model\n",
+ "\n",
+ "Let's try out the fine-tuned model on the same validation set as before. You'll use the same prompt as before, but you will use the fine-tuned model instead of the base model. Before you do that, you can make a simple call to get a sense of how the fine-tuned model is doing."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "completion = openai.ChatCompletion.create(\n",
+ " model=model_id,\n",
+ " messages=[\n",
+ " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n",
+ " {\"role\": \"user\", \"content\": \"Hello!\"},\n",
+ " {\"role\": \"assistant\", \"content\": \"Hi, how can I help you today?\"},\n",
+ " {\n",
+ " \"role\": \"user\",\n",
+ " \"content\": \"Can you answer the following question based on the given context? If not, say, I don't know:\\n\\nQuestion: What is the capital of France?\\n\\nContext: The capital of Mars is Gaia. Answer:\",\n",
+ " },\n",
+ " ],\n",
+ ")\n",
+ "\n",
+ "print(completion.choices[0].message)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 4.3 Answer Using the Fine-Tuned Model\n",
+ "\n",
+ "This is the same as before, but you'll use the fine-tuned model instead of the base model.\n",
+ "\n",
+ "β° **Time to run: ~5 min**, π Needs Internet Connection"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df[\"ft_generated_answer\"] = df.progress_apply(answer_question, model=model_id, axis=1)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 5. Evaluation: How well does the model perform?\n",
+ "\n",
+ "To evaluate the model's performance, compare the predicted answer to the actual answers -- if any of the actual answers are present in the predicted answer, then it's a match. We've also created error categories to help you understand where the model is struggling.\n",
+ "\n",
+ "When we know that a correct answer exists in the context, we can measure the model's performance, there are 3 possible outcomes:\n",
+ "\n",
+ "1. β
**Answered Correctly**: The model responsded the correct answer. It may have also included other answers that were not in the context.\n",
+ "2. β **Skipped**: The model responded with \"I don't know\" (IDK) while the answer was present in the context. It's better than giving the wrong answer. It's better for the model say \"I don't know\" than giving the wrong answer. In our design, we know that a true answer exists and hence we're able to measure it -- this is not always the case. *This is a model error*. We exclude this from the overall error rate. \n",
+ "3. β **Wrong**: The model responded with an incorrect answer. **This is a model ERROR.**\n",
+ "\n",
+ "When we know that a correct answer does not exist in the context, we can measure the model's performance, there are 2 possible outcomes:\n",
+ "\n",
+ "4. β **Hallucination**: The model responded with an answer, when \"I don't know\" was expected. **This is a model ERROR.** \n",
+ "5. β
**I don't know**: The model responded with \"I don't know\" (IDK) and the answer was not present in the context. **This is a model WIN.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 193,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import seaborn as sns\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "class Evaluator:\n",
+ " def __init__(self, df):\n",
+ " self.df = df\n",
+ " self.y_pred = pd.Series() # Initialize as empty Series\n",
+ " self.labels_answer_expected = [\"β
Answered Correctly\", \"β Skipped\", \"β Wrong Answer\"]\n",
+ " self.labels_idk_expected = [\"β Hallucination\", \"β
I don't know\"]\n",
+ "\n",
+ " def _evaluate_answer_expected(self, row, answers_column):\n",
+ " generated_answer = row[answers_column].lower()\n",
+ " actual_answers = [ans.lower() for ans in row[\"answers\"]]\n",
+ " return (\n",
+ " \"β
Answered Correctly\" if any(ans in generated_answer for ans in actual_answers)\n",
+ " else \"β Skipped\" if generated_answer == \"i don't know\"\n",
+ " else \"β Wrong Answer\"\n",
+ " )\n",
+ "\n",
+ " def _evaluate_idk_expected(self, row, answers_column):\n",
+ " generated_answer = row[answers_column].lower()\n",
+ " return (\n",
+ " \"β Hallucination\" if generated_answer != \"i don't know\"\n",
+ " else \"β
I don't know\"\n",
+ " )\n",
+ "\n",
+ " def _evaluate_single_row(self, row, answers_column):\n",
+ " is_impossible = row[\"is_impossible\"]\n",
+ " return (\n",
+ " self._evaluate_answer_expected(row, answers_column) if not is_impossible\n",
+ " else self._evaluate_idk_expected(row, answers_column)\n",
+ " )\n",
+ "\n",
+ " def evaluate_model(self, answers_column=\"generated_answer\"):\n",
+ " self.y_pred = pd.Series(self.df.apply(self._evaluate_single_row, answers_column=answers_column, axis=1))\n",
+ " freq_series = self.y_pred.value_counts()\n",
+ " \n",
+ " # Counting rows for each scenario\n",
+ " total_answer_expected = len(self.df[self.df['is_impossible'] == False])\n",
+ " total_idk_expected = len(self.df[self.df['is_impossible'] == True])\n",
+ " \n",
+ " freq_answer_expected = (freq_series / total_answer_expected * 100).round(2).reindex(self.labels_answer_expected, fill_value=0)\n",
+ " freq_idk_expected = (freq_series / total_idk_expected * 100).round(2).reindex(self.labels_idk_expected, fill_value=0)\n",
+ " return freq_answer_expected.to_dict(), freq_idk_expected.to_dict()\n",
+ "\n",
+ " def print_eval(self):\n",
+ " answer_columns=[\"generated_answer\", \"ft_generated_answer\"]\n",
+ " baseline_correctness, baseline_idk = self.evaluate_model()\n",
+ " ft_correctness, ft_idk = self.evaluate_model(self.df, answer_columns[1])\n",
+ " print(\"When the model should answer correctly:\")\n",
+ " eval_df = pd.merge(\n",
+ " baseline_correctness.rename(\"Baseline\"),\n",
+ " ft_correctness.rename(\"Fine-Tuned\"),\n",
+ " left_index=True,\n",
+ " right_index=True,\n",
+ " )\n",
+ " print(eval_df)\n",
+ " print(\"\\n\\n\\nWhen the model should say 'I don't know':\")\n",
+ " eval_df = pd.merge(\n",
+ " baseline_idk.rename(\"Baseline\"),\n",
+ " ft_idk.rename(\"Fine-Tuned\"),\n",
+ " left_index=True,\n",
+ " right_index=True,\n",
+ " )\n",
+ " print(eval_df)\n",
+ " \n",
+ " def plot_model_comparison(self, answer_columns=[\"generated_answer\", \"ft_generated_answer\"], scenario=\"answer_expected\", nice_names=[\"Baseline\", \"Fine-Tuned\"]):\n",
+ " \n",
+ " results = []\n",
+ " for col in answer_columns:\n",
+ " answer_expected, idk_expected = self.evaluate_model(col)\n",
+ " if scenario == \"answer_expected\":\n",
+ " results.append(answer_expected)\n",
+ " elif scenario == \"idk_expected\":\n",
+ " results.append(idk_expected)\n",
+ " else:\n",
+ " raise ValueError(\"Invalid scenario\")\n",
+ " \n",
+ " \n",
+ " results_df = pd.DataFrame(results, index=nice_names)\n",
+ " if scenario == \"answer_expected\":\n",
+ " results_df = results_df.reindex(self.labels_answer_expected, axis=1)\n",
+ " elif scenario == \"idk_expected\":\n",
+ " results_df = results_df.reindex(self.labels_idk_expected, axis=1)\n",
+ " \n",
+ " melted_df = results_df.reset_index().melt(id_vars='index', var_name='Status', value_name='Frequency')\n",
+ " sns.set_theme(style=\"whitegrid\", palette=\"icefire\")\n",
+ " g = sns.catplot(data=melted_df, x='Frequency', y='index', hue='Status', kind='bar', height=5, aspect=2)\n",
+ "\n",
+ " # Annotating each bar\n",
+ " for p in g.ax.patches:\n",
+ " g.ax.annotate(f\"{p.get_width():.0f}%\", (p.get_width()+5, p.get_y() + p.get_height() / 2),\n",
+ " textcoords=\"offset points\",\n",
+ " xytext=(0, 0),\n",
+ " ha='center', va='center')\n",
+ " plt.ylabel(\"Model\")\n",
+ " plt.xlabel(\"Percentage\")\n",
+ " plt.xlim(0, 100)\n",
+ " plt.tight_layout()\n",
+ " plt.title(scenario.replace(\"_\", \" \").title())\n",
+ " plt.show()\n",
+ "\n",
+ "\n",
+ "# Compare the results by merging into one dataframe\n",
+ "evaluator = Evaluator(df)\n",
+ "# evaluator.evaluate_model(answers_column=\"ft_generated_answer\")\n",
+ "# evaluator.plot_model_comparison([\"generated_answer\", \"ft_generated_answer\"], scenario=\"answer_expected\", nice_names=[\"Baseline\", \"Fine-Tuned\"])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 98,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Optionally, save the results to a JSON file\n",
+ "df.to_json(\"local_cache/100_val_ft.json\", orient=\"records\", lines=True)\n",
+ "df = pd.read_json(\"local_cache/100_val_ft.json\", orient=\"records\", lines=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 194,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAABK0AAAH4CAYAAACIQh4xAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAB1G0lEQVR4nOzdd3zN5///8eeJDGLEaq1E7SARYkXMEJtqUao1a6sZe89Se8WMPWvGqqpZe8SmtT40du0tkURyfn/4Od+eojTIeZPH/XbL7XNyva/3+/16H59L6pnruo7JbDabBQAAAAAAABiIna0LAAAAAAAAAP6J0AoAAAAAAACGQ2gFAAAAAAAAwyG0AgAAAAAAgOEQWgEAAAAAAMBwCK0AAAAAAABgOIRWAAAAAAAAMBxCKwAAAAAAABgOoRUAAAA+GGaz2dYlAACAOEJoBQAA8DedOnWSu7u7Zs6caetS3ovAwEC5u7v/61dERISty3zBtWvX1Lx5c125cuWtr3X58mW5u7srODj4HVQGAADeF3tbFwAAAGAUDx8+1KZNm5QjRw4tXrxY3333nUwmk63Lei8WL178ymOOjo5xWMmb2b17t7Zt22brMgAAQBwitAIAAPj/fv75Z0lSr1691LBhQ+3du1e+vr42rur9yJcvn61LAAAA+FcsDwQAAPj/li9fLl9fXxUpUkSfffaZFi1aZHW8fv366tWrl4KCguTn56c8efKoTp06OnbsmKXPkydP1L9/f5UsWVKenp6qWLGiZsyYIUk6deqU3N3dtXHjRkv/AwcOyN3dXWPHjrW03b17V7ly5bKEaPfu3VPfvn1VtGhR5cmTR7Vr19aePXusanN3d9eECRNUo0YNeXl5acKECW/1Xvz1118qUKCA6tevb2mLiIhQ5cqVVaVKFUVERGjfvn1yd3fXzp07VbduXXl5eal8+fJauHCh1bViYmIUFBSkcuXKydPTUxUqVNC8efNeuOfKlStVvXp15c2bV35+fho1apQiIyMVHBysHj16SJL8/f3VvXt3yzlLly5VlSpV5OnpKT8/PwUGBio6Otrquhs2bFC1atXk5eWl6tWr69SpU2/13gAAgLhBaAUAACDpf//7n44fP64vv/xSkvTll19q8+bNunXrllW/9evXa/Pmzerdu7dGjx6tW7duqW3btpagZMiQIdq+fbu6deumGTNmyN/fX8OHD9fy5cuVM2dOpUuXTrt377Zc73n4dODAAUvbrl27ZGdnpxIlSigiIkINGzbU5s2bFRAQoAkTJiht2rRq2rTpC8HVlClT9Pnnn2v8+PGqUKHCvz7v06dPX/oVExMjSUqXLp26d++ukJAQLV++XJI0atQoXbx4UaNGjZKTk5PlWgEBAcqdO7cmTpyookWLasCAAVbBVf/+/TV+/HhVq1ZNU6ZMUcWKFTVkyBBNnDjR0mfBggXq1q2bPDw8NGHCBDVv3lzz5s3TDz/8ID8/P7Vq1UqSNGHCBH3//feSpKlTp6pPnz7y9fXVlClTVLduXU2bNk19+vSxXHfLli1q166d3N3dNXHiRFWqVEldunT51/cGAAAYA8sDAQAA9GyWVfLkyVWmTBlJUvXq1RUYGKhly5apZcuWln5Pnz7VjBkzlCRJEknS48eP1a1bN508eVKenp4KCQlRsWLFVKVKFUmSj4+PnJ2dlSpVKklSyZIlXwitPDw8dPToUUVERMjJyUk7duxQ/vz55eLioiVLlujUqVNasmSJ8ubNa7lG/fr1NXLkSEugJEkFCxbUd99990bP6+Hh8dL2unXrqm/fvpKkWrVqacOGDRo+fLiSJ0+uuXPnqkuXLsqZM6fVOeXKlVOvXr0kSSVKlNCNGzc0adIkffPNNzp//ryWLFmijh07qnnz5pKk4sWLy2QyaerUqfr222/l4uKiiRMnqmzZsvrhhx8s1w0PD9fatWuVNGlSZcyYUZKUK1cuubq66uHDh5o0aZK+/vpr9e7d23Ld5MmTq3fv3vruu++UPXt2TZw4UV5eXhoxYoSlPulZAAcAAIyNmVYAACDei4qK0urVq1W2bFk9efJEDx48UOLEiVWgQAEtWbLEMvtIkrJly2YJrCQpTZo0kp4FLNKzkGrJkiVq1qyZ5s+fr0uXLql169by8/OTJPn5+en8+fP666+/FBYWpmPHjqlly5aKjIzU0aNHZTabtXPnTkv/PXv26JNPPpGHh4dlNlR0dLRKly6t33//Xffv37fUkitXrjd+5mXLlr30q2nTplb9fvjhB8XExKhNmzYqXLiwGjdu/MK1qlevbvV9+fLldfPmTYWGhmrv3r0ym80qU6aM1YyuMmXKKCIiQgcPHlRoaKhu376tcuXKWV2nSZMmCg4OloODwwv3PHz4sJ48efLS60rPZqs9efJEf/zxh0qXLm11bqVKld74fQIAALbDTCsAABDvbd26Vbdv37YEN/+0Y8cOlSpVSpKUKFEiq2N2ds9+B/g82OrVq5fSpk2r1atXa9CgQRo0aJC8vb3Vv39/5cyZU76+vnJyctLu3buVOnVqOTg4qEyZMsqUKZNCQkKUOHFi3bp1yxK03Lt3Tzdv3nzlzKibN2/KxcVFkuTs7PzGz5wnT5436pcmTRr5+vpq/fr18vPze+mnKT4P7p57Pqvs/v37unfvniRZZp790/Xr15UiRQqr897E8+s+n731Tzdu3ND9+/dlNpst13/u008/feP7AAAA2yG0AgAA8d7y5cvl5uamwYMHW7WbzWa1adNGixYtsoRWr+Po6KhWrVqpVatWunr1qn777TdNmjRJnTp10tq1a5UoUSIVLlzYMoMqf/78sre3l4+Pj0JCQpQgQQJ99tlnypIliyQpadKkypQpk0aOHPnS+7m6ur7dw7/Gzp07tX79euXKlUuBgYEqV66c3NzcrPrcvXvXsnxPkm7fvi3pWQiVLFkySdKcOXOUOHHiF66fPn163blzR5Is//v36544cULe3t4vnPf8uiNHjlSmTJleOJ46dWolT55cdnZ2L+xL9jzwAgAAxsbyQAAAEK/dvHlTO3bsUJUqVeTj42P1VaRIEVWsWFHbtm3T9evXX3utJ0+eqEKFCpo5c6akZ4FM3bp1VaVKFV29etXSz8/PT/v27dOBAwfk4+MjSSpSpIiOHDmiTZs2WS1nK1y4sP766y+lSpVKefLksXzt2rVL06dPV4IECd7xO/J/Hj58qN69e6to0aKaP3++kiVLpp49e8psNlv127Rpk9X3v/76qzJkyKCMGTOqYMGCkp4FUH+v/86dOxo3bpzu3bunLFmyKEWKFPrtt9+srrNq1So1b95cUVFRlhltz+XNm1cODg66fv261XXt7e01evRoXb58WU5OTvL29taGDRusat6yZcu7fJsAAMB7wkwrAAAQr61cuVJPnz595fK1L7/8UkuXLtWSJUtee62ECRNaPv3OwcFB7u7uCg0N1YoVK6w+za9UqVIaNGiQbty4YdnAvHDhwoqIiNDvv/+uzp07W/rWqFFD8+fP13fffaeWLVtaPn1w2rRpqlev3kv3e3oTR44ceeWxzJkzy8XFRUOGDNHdu3c1d+5cJUmSRH369FHr1q01f/581a9f39J/1qxZcnJyUr58+bRhwwb99ttvlo3O3d3dVa1aNfXp00dXrlyRp6enQkNDNWbMGLm6uipTpkxKkCCB2rZtq4EDBypVqlQqU6aMQkNDNX78eNWtW1cuLi6WmVUbN25UyZIllTVrVjVt2lTjxo3To0eP5OPjo+vXr2vcuHEymUyWzeI7duyohg0bqk2bNvr6668VGhqqKVOmxOo9AwAAcYvQCgAAxGvBwcHKnj27cuTI8dLjBQoUkKurq5YuXSpXV9fXzmwaOHCgxo4dq5kzZ+rmzZtKlSqVvvrqK7Vv397Sx83NTVmzZtVff/0lT09PSc+Ws2XLlk3Xr1+3zE6Snu1TtWDBAo0aNUojRozQw4cPlSFDBnXq1Omlm6K/qa+//vqVxyZOnCgHBwcFBwerS5culqV/ZcuWVfny5TVq1CiVLFnS0r9nz55asWKFpk6dqixZsmj8+PFWId2PP/6oqVOnatGiRbp27ZpSpUqlypUrq0OHDpb3s27dunJ2dtaMGTO0ePFipU2bVs2aNVOzZs0kPdvgvmjRoho1apT27NmjoKAgdejQQZ988okWLlyo6dOny8XFRb6+vurYsaOSJk0q6dknKk6bNk2jR49WmzZt5OrqqiFDhlh9IiQAADAmk/mf87sBAACAN7Bv3z41aNBAc+fOtSxzBAAAeFfY0woAAAAAAACGQ2gFAAAAAAAAw2F5IAAAAAAAAAyHmVYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAAAAyH0Oojd/r0aZ0+fdrWZQAAAAAAAPwnhFYfucjISD169EgRERG2LgX4IEVEROjgwYOMISAWGD/A22EMAW+HMQS8HSOMHUKreCI6OtrWJQAfpOdjhzEE/HeMH+DtMIaAt8MYAt6OEcYOoRUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAUAAAAAAADDIbQCAAAAAACA4RBaAQAAAAAAwHAIrQAAAAAAAGA4hFYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAAAAyH0AoAAAAAAACGQ2gVT5hMJluXAHyQTCaTEiVKxBgCYoHxA7wdxhDwdhhDwIfPZDabzbYuAu/P8ePHJUl58uSxcSUAAAAAEH/ExJhlZ0dghg9XWFiYnJ2dbVqDvU3vjjgzffsJ/XXvsa3LAAAAAICPXrrkidW0ZG5blwF88Ait4om/7j3WxTuPbF0GAAAAAADAG2FPKwAAAAAAABgOoRUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAUAAAAAAADDIbQCAAAAAACA4RBaAQAAAAAAwHAIrQAAAAAAAGA4hFYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAA8ciSJUtUpUoV5cuXT5UqVdKCBQtkNpstx7du3aqaNWsqX758Kl26tMaPH6/IyEira4wdO1a+vr4qXbq0goODrY6ZzWbVqFFDq1evjpPnwcfL3tYFAAAAAACAuLF06VL16dNH9evXl7+/vw4cOKBBgwYpIiJCjRs31s6dO9WqVSt9+eWX6tSpk/7880+NGjVKN2/e1KBBgyQ9C7VmzpypH374Qffv31efPn2UJ08eZc+eXZK0du1axcTE6PPPP7flo+IjQGgFAAAAAEA8sXz5chUoUEC9e/eWJPn6+io0NFTz589X48aNNXXqVHl4eOjHH3+UJBUtWlR3797V5MmT1aNHDzk7O2v37t0qWrSoqlWrJulZEBYSEqLs2bMrMjJSY8aMUb9+/WQymWz2nPg4EFoBAAAAABBPRERE6JNPPrFqS548ue7duydJGjJkiKKioqyOOzg4KCYmRk+fPpUkmUwmOTk5WR2Pjo6WJC1cuFDp06dXyZIl3+NTIL5gTysAAAAAAOKJBg0aaOfOnVq1apUePnyoHTt2aMWKFfriiy8kSW5ubsqSJYsk6dGjR9qwYYNmzpypKlWqKFmyZJKkfPnyKSQkRKGhoTp69KjOnDmj/Pnz6+HDh5oyZYq6dOlis+fDx4WZVgAAAAAAxBNVqlRRSEiIunbtamkrXry4evbsadXvxo0bKlGihKRnQVZAQIDlWMWKFbVnzx5VrVpV9vb2at++vTw9PTV69GgVLlzYsrxw27ZtypUrl/r06aOUKVPGzQPio2Iy//0jAvDROX78uCRpZegTXbzzyMbVAAAAAMDHL2PKJOpTrZCty3ippk2b6uDBg2rdurW8vLx05swZBQYGqkCBApo4caJlH6oHDx7ojz/+0L179xQYGKj79+8rODhYadKksVwrMjJSCRIkUIIECXT9+nVVqlRJy5Yt086dO7V06VKNHj1aU6ZMUVRUlMaPH2+rR0YshYWFydnZ2aY1sDwQAAAAAIB44NChQ9qxY4d69uyppk2bqnDhwqpXr56GDx+uzZs3a+vWrZa+yZIlk6+vrypVqqSgoCDdvn1bS5cutbqeo6OjEiRIIEkaN26cqlatqixZsmj9+vWqVq2asmfProYNG2rz5s2WPa+A/4LlgQAAAAAAxANXr16VJOXPn9+qvWDBgpKk//3vfwoPD1emTJmUO3duy3FXV1e5uLjoxo0bL73u//73P61bt06//vqrJOn27dtKnjy5pGfh19OnT3X37l2lTp36XT8SPnLMtAIAAAAAIB54vsH6gQMHrNoPHTok6dneVaNGjdKoUaOsjj9fJuju7v7S644cOVL16tWzLB1MlSqVbt68KUm6efOmEiRIYAmxgP+CmVYAAAAAAMQDuXPnVoUKFTR06FDdv39fefPm1dmzZxUYGCgPDw+VK1dOERER6tatm/r166eKFSvq0qVLGj9+vHLkyKGaNWu+cM2QkBAdOXJEI0eOtLT5+flp4cKFyp07t+bNm6eSJUvK3p74Af8dG7F/5NiIHQAAAADilpE3Yo+MjNTkyZO1atUq3bhxQ+nTp1fZsmXVunVrJU6cWJL066+/KigoSH/++aecnZ1VtmxZderUSS4uLi9cr1atWqpYsaKaNGliaYuIiFCfPn20efNmeXh4aMSIEVYbuOPDYISN2AmtPnKEVgAAAAAQt4wcWgFvygihFXtaAQAAAAAAwHAIrQAAAAAAAGA4hFYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAAAAyH0AoAAAAAAACGQ2gFAAAAAAAAwyG0AgAAAAAAgOEQWgEAAAAAAMBwCK0AAAAAAABgOIRWAAAAAAAAMBxCKwAAAAAAABgOoRUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADMfe1gUgbqRLntjWJQAAAABAvMC/v4B3g9AqnmhaMretSwAAAACAeCMmxiw7O5OtywA+aCwPjCeePHli6xKAD1J4eLhOnDih8PBwW5cCfHAYP8DbYQwBb8fWY4jACnh7hFbxhNlstnUJwAfJbDYrPDycMQTEAuMHeDuMIeDtMIaADx+hFQAAAAAAAAyH0AoAAAAAAACGQ2gFAAAAAAAAwyG0AgAAAAAAgOEQWgEAAAAAAMBwCK0AAAAAAABgOIRWAAAAAAAAMBxCKwAAAAAAABgOoRUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAVD2blzp2rWrKm8efOqTJkymjFjhsxmsyQpKipKffv2VaFChVShQgVt27bN6twnT56oVKlSOnjwoC1KBwAAAAAA7xChFQzjyJEjatmypbJkyaLAwEB9/vnnGjFihKZNmyZJWrJkiTZu3Kgff/xRFStWVEBAgO7cuWM5f86cOcqdO7cKFChgq0cAAAAAAADvyEcTWpUpU0bu7u6WL09PT1WoUEHTp0+P0zrc3d0VHBwsSQoMDFSZMmXi9P4fssDAQOXKlUsjRoxQyZIlFRAQoCZNmmjKlCl68uSJdu/ercqVK6ts2bLq0KGD7OzsdOzYMUnS3bt3NXPmTHXs2NHGTwEAAAAAAN6Fjya0kqTGjRtr586d2rlzp9atW6d27dpp4sSJWrBggc3qWbZsmU3u/aGJjIzUvn37VK5cOav2ChUq6PHjxzp48KBMJpOcnJwkSSaTSfb29oqOjpYkTZo0SWXKlFH27NnjvHYAAAAAAPDu2du6gHfJ2dlZn3zyieV7Nzc37du3T8uXL1fdunXjvJ7EiRMrceLEcX7fD9GlS5cUFRWlTJkyWbV/9tlnkqTQ0FDly5dPwcHBatiwoY4fP66wsDB5enrq0qVLCg4O1s8//2yDygEAAAAAwPvwUc20epmECRNaXt+/f1+9e/dWiRIl5OHhIV9fX/Xu3Vvh4eGWPjNmzFDZsmXl6empMmXKaOLEiZaNwCXpt99+U40aNeTl5aVy5cpp7NixioyMfOm9/7488PLly3J3d9f69etVq1Yty/UXL15sdc7y5ctVqVIleXl5qVKlSpozZ45iYmLe5VtiSA8fPpQkJUmSxKr9eej36NEj1atXT1myZJGfn5969OihQYMGKU2aNBo7dqxq166t5MmTq3v37qpQoYL69u1r9ecKAAAAAAA+LB/VTKt/OnbsmH7++We1bdtWktS9e3ddv35dEyZMUKpUqXTo0CH17NlT2bJlU6NGjbRlyxZNnTpVY8aMUebMmXXkyBF17dpVrq6u+uKLL7R9+3Z16NBBPXr0UNGiRXXx4kUNGjRIoaGhGjdu3BvV9OOPP6pPnz7KkSOHZs2apf79+6to0aJyc3PT4sWLNXr0aPXt21deXl46ceKEBg0apOvXr6tr167v862yudcFc3Z2dkqYMKEmTJigJ0+eyMnJSSaTSb///rt27NihDRs2aOzYsbp27ZomTZqkAQMGaPz48erWrVscPQEAAAAAAHiXPqrQaurUqZo5c6YkKSoqSlFRUcqbN68+//xzSVKxYsVUqFAhubu7S5JcXV01f/58nTlzRpJ08eJFOTo6KkOGDEqfPr3Sp0+vTz/9VOnTp5ckTZkyRbVr11adOnUkSRkzZtSAAQPUsGFDXb58Wa6urq+tsVGjRvL395ckBQQEaMGCBTp69Kjc3Nw0adIktWrVSlWqVJH0bHnjo0ePNGDAALVv396yn9PHKGnSpJKkx48fW7U/evRIkvUMrL/PnhsxYoSaNGmi5MmTa/369eratauyZs2qOnXqaOTIkYRWAAAAAAB8oD6q0KpOnTqqX7++JOnp06e6cOGCxowZo7p162rp0qX69ttvtWXLFq1YsULnz5/X2bNndfnyZWXJkkWSVK1aNS1fvlwVKlRQtmzZVLRoUVWoUMESWp04cULHjh2z2lz9+dLBc+fOvVFolTVrVsvr50FNVFSU7ty5o2vXrmn06NFWs7ZiYmIUERGhy5cvW537scmYMaMSJEigCxcuWLVfvHhRkl767Nu3b9e5c+c0ZcoUSdLt27eVPHlySZKLi4tu3br1fosGAAAAAADvzUcVWrm4uFg27paeBR0uLi769ttvtXv3bi1YsED/+9//VLVqVVWuXFkeHh7q06ePpX/KlCm1atUqHT58WLt27dLOnTs1d+5ctW3bVm3atFFMTIyaNm2q6tWrv3Dvv28A/28cHR1faDObzZblcc+XHv5TunTp3uj6HyonJycVLFhQGzduVJMmTWQymSRJ69evV9KkSeXl5WXVPyYmRiNHjlSbNm2UKFEiSVKqVKl08+ZNSdLNmzeVKlWquH0IAAAAAADwznxUodXLPJ8J9fvvv2v79u1asmSJ8ubNK+nZDKeLFy/Kzc1NkrR69Wo9fPhQdevWVYECBdSuXTv17t1bv/zyi9q0aaPs2bMrNDTUKhjbt2+f5s6dq/79+8vZ2TnWdaZKlUopU6bUpUuXrK7/yy+/aOPGjRo2bFisr/2haNWqlb777ju1b99eNWvW1OHDhzVjxgx16tTJEkw9t2rVKkVEROirr76ytPn5+Wn27NlKkSKF5syZY1mGCQAAAAAAPjwf1acHhoWF6ebNm7p586Zu3LihAwcOaMiQIfr0009Vq1Yt2dvba926dbp06ZKOHz+uDh066ObNm5ZP/4uIiNCwYcO0cuVKXb58WQcOHND+/fvl7e0tSWrWrJnWr1+vCRMmKDQ0VHv27FGPHj308OHDN55p9Somk0nNmjXTvHnzNH/+fF28eFEbN25U//79lTBhwpfO0PrY+Pr6KjAwUKGhoWrdurXWrFmjrl27qlmzZlb9IiIiNG7cOAUEBMje/v9y1w4dOujTTz9VQECAMmTIoPbt28f1IwAAAAAAgHfko5ppNXPmTMtG7HZ2dkqePLkKFiyokSNHKk2aNBo6dKgCAwO1YMECffLJJ/Lz87N8aqAk1apVS/fu3dOkSZP0119/ycXFRRUqVFDnzp0lSRUrVtSYMWM0depUTZkyRcmTJ1eZMmUsx99W48aN5eTkpHnz5mno0KFKnTq1ateurXbt2r2T638IypUrp3Llyv1rHycnJ23duvWF9uTJk2vq1KnvqTIAAAAAABCXTObn6+fwUTp+/LgkKVu2bC8ssQPwemFhYTp58qRy5cr1VkuAgfiI8QO8HcYQ8HYYQ8DbCQsLs/nY+aiWBwIAAAAAAODjQGgFAAAAAAAAwyG0AgAAAAAAgOEQWgEAAAAAAMBwCK0AAAAAAABgOIRWAAAAAAAAMBxCKwAAAAAAABgOoRUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAUAAAAAAADDIbQCAAAAAACA4RBaAQAAAAAAwHAIreIJk8lk6xIAAAAAAADeGKFVPJEwYUJblwDEWnR0tK1LAAAAAADEMXtbF4C40bVrP/157rytywD+syxZM2n48AG2LgMAAAAAEMcIreKJP8+d18mTp21dBgAAAAAAwBtheSAAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAUAAAAAAADDIbQCAAAAAACA4RBaAQAAAAAAwHAIrQAAAAAAAGA4hFYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAAAAyH0AoA3oFr166pYMGC2rdvn6XN3d39lV/169e39FuwYIFKlCihYsWKaerUqS9cu02bNpo8eXKcPAcAAAAAGIW9rQsAgA/dX3/9pSZNmujhw4dW7YsXL36h74YNGzRjxgx98803kqTTp0/rhx9+UM+ePeXi4qLevXsrd+7cKlGihCTp8OHDOnLkiEaMGPH+HwQAAAAADITQCgBiKSYmRitXrtSwYcNeejxfvnxW3//1119aunSp6tatq8qVK0uS9u7dq2zZsllmXq1bt067d++2hFbDhw9X69atlShRovf3IAAAAABgQCwPBIBYOn36tPr166cvv/xSw4cPf23/oUOHysnJSR07drS0mUwmOTk5Wb53cHBQTEyMJGnTpk26c+eOatWq9e6LBwAAAACDI7QCgFhKly6dNm7cqB49eihhwoT/2vfIkSP69ddf1bFjRyVJksTSni9fPp0+fVrHjh1TaGioQkJCVKBAAUVHR2vUqFHq0KGD7O2ZFAsAAAAg/uFfQgAQS8mTJ3/jvtOnT1eGDBlUrVo1q3YvLy+1bNlSdevWldlsVp06dVS+fHktXrxYzs7OqlixoqZOnapVq1bJ1dVVffr0kZub2zt+EgAAAAAwHmZaAcB7du3aNW3evFkNGzZ86ayp1q1b69ChQzp06JB69+6tsLAwBQYGqnPnztqyZYvmzZunESNGKGvWrOrQoUPcPwAAAAAA2AChFQC8Zxs2bJDJZFKVKlVe2cfBwUGOjo6SpFmzZsnd3V2+vr5av369ypYtKw8PDzVt2lS///67rly5ElelAwAAAIDNEFoBwHu2detWFSxYUKlTp35t3zt37mjmzJmWzdpv374tFxcXSVKyZMkkSbdu3Xp/xQIAAACAQRBaAcB7ZDabdezYMeXPn/+N+k+YMEGlSpWSh4eHJClVqlSWkOrmzZuSpJQpU76fYgEAAADAQNiIHQDeo6tXr+rhw4fKli3ba/teuHBBwcHBWr16taXNz89Pffv2VcmSJbVp0yblzJlTrq6u77NkAAAAADAEQisAeI9u374t6f+W9v2b0aNHq0aNGsqYMaOlrWLFijp27Jj69OkjV1dXjRw5UiaT6b3VCwAAAABGQWgFAO+Aj4+PTp8+/UK7l5fXS9tfZty4cS+02dnZqXv37urevftb1wgAAAAAHxL2tAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAADAcQisAAAAAAAAYDqEVAAAAAAAADIfQCgAAAAAAAIZDaAUAAAAAAADDIbQCAAAAAACA4RBaAQAAAAAAwHAIrQAAAAAAAGA4hFYAAAAAAAAwHEIrAAAAAAAAGA6hFQAAAAAAAAyH0AoAAAAAAACGQ2gFAAAAAAAAwyG0AgAAAAAAHyyz2Ryn5yHuEFoBAAAAAADDOXPmjAICAlSsWDF5enqqePHi6tChg06dOmXpc/DgQTVv3vw/X3vz5s3q1q3buywX74G9rQsAAAAAAAD4u//973/6+uuvlS9fPvXu3VupUqXStWvXNH/+fNWuXVtz585Vvnz5tHTpUp07d+4/X3/27Nnvvmi8c4RWAAAAAADAUGbNmqUUKVJo2rRpsrf/v+iibNmyqlixoiZNmqSgoCAbVoi4wPJAAAAAAABgKLdu3ZLZbFZMTIxVu7Ozs3r27KlKlSqpe/fuWrFiha5cuSJ3d3cFBwdLki5fvqyuXbuqePHi8vDwkK+vr7p27aq7d+9KkurXr6+QkBCFhITI3d1d+/btU3BwsNzd3XX58mWr+5UpU0bdu3e3fL9r1y7Vrl1b3t7eKlSokFq1ahWrmV54M8y0iieyZM1k6xKAWOH/uwAAAED84+fnp23btqlOnTqqWbOmihQpoixZsshkMqlixYqSpAIFCujOnTs6ceKEJkyYoIwZMyo8PFwNGjRQihQp1K9fPyVNmlSHDx/WhAkTlDBhQg0cOFD9+vVTly5dJEn9+vVTtmzZdOXKldfWdOnSJX3//feqWbOmOnbsqAcPHmj06NFq3ry5Nm7cKDs75gW9a4RW8cTw4QNsXQIQa9HR0UqQIIGtywAAAAAQR7799lvdvHlTM2bM0MCBAyVJKVKkUPHixdWgQQN5eXkpY8aMSpkypRwdHZUvXz5J0smTJ5U2bVoNGzZMbm5ukqQiRYro6NGjCgkJkSRly5ZNSZIkkSTLeW/i2LFjevLkiVq0aKE0adJIktKmTavNmzcrLCzMck28O4RW8UBkZKTCw8OVKFEiW5cCxAqBFQAAABD/tG/fXo0aNdKOHTu0Z88e7du3T2vWrNHPP/+snj17qkGDBi+ckytXLi1cuFAxMTE6f/68Lly4oLNnz+rPP//U06dP36qevHnzysnJSV999ZUqVqyokiVLysfHR15eXm91XbzaG4dW+/fv/08XLlSo0H8uBu+P2Wy2dQkAAAAAAPwnLi4uqlq1qqpWrSpJOnHihLp06aIRI0bo888/f+k5s2bN0pQpU3Tv3j2lTp1anp6eSpQokR4+fPhWtbi6umr+/PkKCgrSsmXLNHfuXCVLlkzffvutOnToIJPJ9FbXx4veOLSqX7/+G/0BmM1mmUwmnTx58q0KAwAAAAAA8c/169dVs2ZNtW/fXrVq1bI6ljt3bgUEBKh169a6dOnSC+euWbNGQ4cOVZcuXVSjRg2lTJlS0rNZW8ePH3/lPZ/nHf/c+P3x48dW33t5eWnChAmKjIzUwYMHtXjxYk2ZMkU5c+ZUpUqVYvW8eLU3Dq3mzp37PusAAAAAAABQ6tSpZW9vr4ULF6patWpycnKyOv7nn3/KyclJn3322Qubnx88eFDJkiVT06ZNLW2PHz/WwYMHZW//fxGInZ2dVUD1fD+qa9euKWPGjJKkc+fO6d69e5Y+s2fP1pw5c7R+/Xo5OjrK19dXnp6eWrduna5evfrOnh//541Dq8KFC7/yWEREhBwdHZkKBwAAAAAA3kqCBAnUv39/tW7dWjVr1lTdunWVNWtWhYeHa9euXVqwYIHat28vFxcXJUuWTLdu3dK2bduUK1cueXl56aefftLQoUNVunRp3bhxQzNmzNCtW7fk4uJiuUeyZMl0+PBh7dmzR7lz55aPj48SJkyooUOHqn379nr8+LHGjx+v5MmTW84pUqSIRo4cqdatW6tevXpKkCCBFi1aJEdHR5UuXdoG79THL9afx/jnn3+qQ4cOKly4sLy9vXXixAkNGDBA8+bNe5f1AQAAAACAeMbPz09LlixRjhw5NGXKFDVp0kQdO3bUyZMnNWbMGDVv3lySVKNGDWXIkEGtW7fWypUrVb16dbVu3Vrr1q1Ts2bNNH78eBUsWFADBw7UvXv3dO7cOUlS3bp15eDgoGbNmmn79u1KliyZAgMDFR0drdatW2vcuHFq3bq1PD09LTXlzJlTU6ZM0aNHj9SxY0e1adNG9+7d08yZM5UlSxabvE8fO5M5Fjt0nzx5UnXr1lWqVKlUsmRJLVy4UMuWLdOKFSu0YMECDRkyRNWrV38f9eI/On78uCIjI5UrVy45OzvbuhzggxMWFqaTJ08yhoBYYPwAb4cxBLwdxhDwdsLCwmw+dt54eeDfDRs2TJ6enpo5c6YkacGCBZKk3r17KyIiQnPnziW0AgAAAAAAQKzFanngkSNH1KhRI9nb27+wj1XlypV1/vz5d1EbAAAAAAAA4qlYhVZOTk568uTJS4/du3dPjo6Ob1UUAAAAAAAA4rdYhVbFihXT+PHjde3aNUubyWTS48ePNXPmTBUtWvSdFQgAAAAAAID4J1Z7WnXp0kVff/21KlasqJw5c8pkMmno0KEKDQ2V2WzW6NGj33WdAAAAAAAAiEdiNdMqXbp0WrVqlRo2bCiz2ayMGTMqLCxMVatWVXBwsNzc3N51nQAAAAAAAIhHYjXTSpJSpEihgICAd1kLAAAAAAAAIOk/hFb79+//TxcuVKjQfy4GAAAAAAAAkP5DaFW/fn2ZTKYX2s1ms+X134+fPHnyLUsDAAAAAABAfPXGodXcuXMtr69evao+ffqoZs2aqlSpkj755BPdu3dPW7Zs0aJFizRw4MD3UiwAAAAAAADihzcOrQoXLmx5Xb9+fTVq1EidOnWy6pM/f34lTJhQs2bNUuXKld9dlQAAAAAAfARiYsyys3txFdPHel9JOn/+vCpUqKBcuXJp5cqVNqnBFrp3764rV65o3rx5/9pvxYoVWrp0qc6cOSNJyp49uxo1aqQKFSrERZn/2dWrV3X48GFVqVJFklSmTBlVr15dbdu2fef3itVG7MeOHVOrVq1eeszb21vTpk17q6IAAAAAAPgY2dmZNH37Cf1173Gc3TNd8sRqWjJ3rM599OiRpkyZ8srjRYsWVdGiRf/1GsHBwcqcObNOnjypo0ePKm/evLGq5WNjNpvVoUMH7d27V23bttXAgQNlMpm0YcMGBQQEqEOHDmrevLmty3xBt27dlCFDBkto9T7FKrRKmzatduzY8dL/Y/7666/KmDHjWxcGAAAAAMDH6K97j3XxziNbl/FGHj16pDp16sjV1fWlxw8cOPCv50dHR2vlypWqV6+eVq5cqUWLFhFa/X8LFy7Uxo0btXTpUnl4eFjaW7VqpejoaI0fP15Vq1ZV+vTpbVilbdnF5qTvvvtOs2bNUqdOnfTzzz9r165dWrVqlVq1aqVly5bp+++/f9d1AgAAAACAD8zOnTt1/fp1FStWTOXLl9e6dev04MEDqz7u7u5atmyZGjVqJC8vLxUvXlwTJkywHA8PD1evXr1UrFgx5cmTR19++aU2bNggSRo6dKg+//xzS9/79+8rV65cVnttb9myRd7e3oqIiJDZbNa0adPk7++vvHnz6osvvtDq1astffft26fcuXMrKChIPj4+qlGjhmJiYnT9+nUFBASoYMGC8vHxUcuWLXX+/HnLeWazWZMmTVLJkiWVL18+9ejRQxEREf/63ixatEh+fn5WgdVzDRs21OzZs5U6dWpJ0pMnTzR27Fj5+/srT548+uKLL7R+/XpL/+DgYJUrV04//PCDChQooO+//z7WzyJJq1evVu3ateXl5SV/f3/NmTNH0rPtokJCQrRixQqVKVPG6pyoqCj5+vpa/dk9f87ixYvr6dOn//p+vEysQqs6deqod+/e2rt3rzp37qwmTZqoW7duOnXqlEaOHKlKlSrF5rIAAAAAAOAjsnz5cmXMmFEeHh6qXLmywsPDX7qv1bBhw1S9enWtXbtW9erVU2BgoPbv3y9JGjdunE6fPq2goCD98ssvKlmypAICAnT58mWVLl1aZ86c0c2bNyVJe/bskdls1r59+yzX3rp1q4oXLy4nJyeNGTNGP/30k/r06aM1a9aoQYMG6t+/vxYsWGDpHx0drW3btmnx4sUaPHiwnjx5ovr160uS5s+fr3nz5ilFihSqXbu2rl+/LkkKCgrS9OnT1bVrVwUHBytZsmT65ZdfXvm+RERE6MyZM8qfP/9LjydNmlQFCxaUo6OjJKljx45auXKl+vTpo9WrV6ts2bJq3769Nm3aZDnn4sWLunHjhlauXKmAgIBYP8svv/yibt26qWrVqlq9erU6duyokSNHKjg4WIGBgfL29lalSpW0bNkyq5odHBxUrVo1qxBQklauXKlq1arJ3v6/L/aLVWglSfXq1dOuXbv0yy+/6KefftKvv/6q3377LU7WNAIAAAAAAGO7e/eutmzZYvmgthw5cihHjhxavHjxC32//PJLffHFF3Jzc1PLli2VLFkyHTp0SNKzMCZx4sRyc3OTm5ub2rdvrylTpsjFxUUFChSQi4uLdu3aJUnavXu3/P39dfbsWd26dUuStH37dvn7+yssLEyzZ89Wz5495efnp4wZM6pmzZpq1KiRZsyYYVVP48aNlSlTJuXKlUtr167VgwcPNGLECOXMmVM5cuTQ4MGDlSRJEi1ZskRms1nz5s1TgwYNVLVqVWXJkkU9evRQrly5Xvne3L9/X5Lk4uLy2vfx3Llz2rx5s/r16yc/Pz9lzpxZbdu2lb+//wv7jX3//fdyc3NT9uzZY/UskjRnzhxVrlxZDRo0UKZMmVSlShX16dNHCRMmVPLkyeXg4KCECRMqZcqUL9Ras2ZNXbhwQYcPH5YkhYaG6vDhw6pRo8Zrn/NlYrWn1XPnzp1TSEiIHj58qBQpUigmJkZZsmR5m0sCAAAAAICPwJo1axQVFWUJrSSpSpUqGjNmjA4cOKCCBQta2rNmzWp1btKkSRUVFSVJatasmVq2bClfX195eXmpWLFi+vzzz5U0aVJJUokSJbR79259+eWX2rVrl/r27aujR49q3759ypo1q27cuKFSpUrp7NmzioiIUKdOnWRn939zeJ4+farIyEg9efLE0pYpUybL6xMnTuj+/fsqVKiQVY0RERE6d+6c7t69q5s3bypPnjxWx/Ply6dz58699L1Jnjy5TCaT7t69+9r38fTp05KkAgUKWLUXKlRIo0ePtmr7e92xeRZJOnPmzAsTkmrXrv3aOqVnwWSePHm0cuVKeXt7a+XKlfLy8lK2bNne6Px/ilVoZTab1a9fPy1dulRms9nSbjKZVL16dQ0ZMiRWxQAAAAAAgI9DcHCwJKl69eqWtucZwk8//WQVWj1fBvd3z/t6e3tr27Zt2rVrl/bs2aOVK1dq8uTJmj59unx9feXv768hQ4bo4sWLun79ugoVKiQfHx/t27dPly9fVoECBZQiRQpdvHhRkjR27NiXTrj5ew1OTk6W1zExMcqcObMmT578wjnOzs4ymUxW9T73b8vhHB0d5enpaZlN9k8PHjxQmzZt1KZNm1dew2w2v3CPhAkTvtDvvzzL6+p+EzVr1tSYMWPUq1cvrVmzRk2bNo31tWK1PHD69Olavny52rVrp82bN+vYsWPatGmT2rRpo9WrV2v27NmxLggAAAAAAHzYTpw4oZMnT6ply5ZauXKl5WvVqlUqUaKENmzY8EazjCRp/PjxOnjwoPz9/dW7d2+tX79ebm5ulo3IS5QooXv37mnu3LnKmzevnJ2dVbRoUe3du1e//fab/P39JUlZsmSRvb29rl69qs8++8zytW3bNs2YMcNq9tXf5ciRQ1evXlXSpEkt56RPn16jRo3S/v37lSJFCqVLl04HDx60Ou/333//1+eqXbu2tm/frj/++OOFY3PnztWBAwfk6uoqd3d3SXrh+gcOHPjPM5he9yzSs1lvx48ftzrvxx9/VLt27d7oHlWrVlVERIRmzZqlW7duqWrVqv+pxr+LVXy2bNkyNW3aVK1atbK0ubq6qnXr1oqKitKSJUvUqFGjWBcFAAAAAMDHKl3yxB/1/aRns6wSJUqkxo0bv7BvU7NmzbRjxw4FBwerSZMmr73WpUuXtHr1ag0aNEgZM2bU0aNHdfXqVXl7e0v6v03LFy9erBYtWkiSfH191bNnT126dEkjRoyw9KtTp47GjRunJEmSKH/+/Nq3b59GjBhhOe9lqlWrpqCgILVr105dunRRkiRJNGnSJG3fvl3t27e3PNOwYcOUJUsWFSxYUKtWrdKxY8deWNL3d1999ZU2b96s7777Tu3bt1exYsX05MkTrV69WrNmzVK3bt2UPn16SVLp0qU1YMAAmUwmffbZZ1q7dq02b96ssWPHvvb9+6/P0rx5c7Vt21Y5c+ZUuXLldPToUf3000+WT2RMnDixrly5omvXrilt2rQv3CNp0qQqV66cJk2aJH9/fyVLluw/1fh3sQqt/vrrLxUpUuSlx3x8fDRz5sxYFwQAAAAAwMcqJsaspiVz2+S+dnamWJ07bdo0y/5R//Q8pPm7yMhIrVmzRp9//vlLNxr38fGRh4eHlixZosaNG7/2/v369dOwYcPUpUsX3bt3TxkyZFDnzp31xRdfWPqULl1ae/bssWQV6dOnV6ZMmeTo6Cg3NzdLvx49eihFihQaN26cbty4oXTp0qldu3b/uoQtadKkmj9/voYPH64mTZooOjpaHh4emjlzpmUvrrp16yomJkaTJ0/WrVu3VKJECX311VcKDQ195XXt7Ow0ceJEzZ8/X0uXLtWoUaNkb2+v7Nmza8KECZYZYpI0evRojR49Wr169dKDBw+UI0cOBQYGqly5cq99//7rs5QpU0YDBw5UUFCQxowZowwZMqhHjx768ssvJUl16tRRt27dVK1aNe3Zs+el96lRo4bWrFkT6w3YnzOZ/7no8g1UqlRJX3/99UtnU82aNUtz5szR1q1b36owvBvHjx9XZGSkcuXKZVmfCuDNhYWF6eTJk4whIBYYP8DbYQwBb4cxBLydsLCwWI+d4OBgBQYGavPmza9cdvkmYjXTqmrVqgoMDFSaNGlUsWJFmUwmmc1mrVu3ThMmTNDXX38d64IAAAAAAADw4fnjjz/0559/avz48apXr95bBVZSLEOrZs2a6cCBAwoICFCXLl2UIkUK3b17V0+fPpWPj49lHSQAAAAAAADihyNHjmj48OHy8/NTw4YN3/p6sQqtHB0dNWvWLG3fvl0hISG6f/++XFxcVKhQIZUqVeqtiwIAAAAAAMCHpW7duqpbt+47u94bh1Y9evT41+O3b9/Wr7/+ql9//VUmk0lDhgx56+IAAAAAAAAQP71xaLVixQqZTCalSZPmtWsSTabYfSIBAAAAAAAAIP2H0KpSpUraunWrIiMjVbFiRVWpUkUFChR4n7UBAAAAAAAgnnrj0GrMmDEKDw/Xb7/9pl9++UXfffedUqdOrcqVK6tKlSrKlSvX+6wTAAAAAAAA8ch/2og9UaJEqly5sipXrqxHjx5p48aN+uWXXzR79my5urqqatWqqlKlijJnzvy+6gUAAAAAAEA8EKtPD5SkJEmSqHr16qpevbru3bunjRs3at26dZoyZYpy5Mih4ODgd1knAAAAAAAA4pF/31H9DUVERCg8PFxPnjxRdHS0rly58i4uCwAAAADARyUmJiZe3Rd4G7GeaXX9+nX9+uuv+vXXX3X06FE5OzurbNmyatGihYoVK/YuawQAAAAA4KNgZ2enwCk7deXqgzi7Z4b0ydS2ZfE4u9/fRUZGKigoSD///LMuX76sRIkSycvLS82aNVORIkUkSZcvX5a/v7/mzp0rHx+fF64RGBioFStWaMuWLXFdvpXu3bvrypUrmjdvnk3riE/+U2j196DqyJEjSpQokUqXLq2mTZuqRIkScnR0fF914i2ZTCZblwB8kEwmkxIlSsQYAmKB8QO8HcYQ8PG6cvWBQi/csXUZb+TRo0eaMmXKK48XLVpURYsWfeXx3r1769ixY+revbuyZcumhw8fatGiRWrcuLFmzJghX1/f19bQuHFj1a1bN1b148P2xqHVN998o6NHj8rJyUmlSpXSuHHjVKpUKTk5Ob3P+vAOODo6KlGiRLYuA/ggJUqUSLlz57Z1GcAHifEDvJ2XjaGYGLPs7AixAMSdR48eqU6dOnJ1dX3p8QMHDvzruatXr1ZgYKD8/Pws7QMGDNCpU6e0YMGCNwqtEidOrMSJE//n2vHhe+PQ6vDhw0qQIIGyZcumO3fuaP78+Zo/f/5L+5pMJs2ZM+edFYm3N337Cf1177GtywAAAEAspUueWE1LEgQD+LDY2dlp586dKl26tOzt/y+CGD9+/CvPOXfunBo0aKBixYrpxx9/1KRJkyzLA58vJRw2bJimT5+uixcvKmfOnOrWrZsKFCgg6dkyvsjISCVPnlwrV66Uk5OTvvjiC3Xs2NGyQuz69esaOnSoduzYoQQJEsjb21vdu3dXpkyZJElms1mTJ0/WokWL9ODBA1WqVEkRERHv743CS71xaFWoUCHLa7PZ/K99X3ccce+ve4918c4jW5cBAAAAAIgnkiRJom+//Vbz5s3Thg0bVLRoURUqVEhFixZVxowZX3rOhQsX1KhRI5UsWVKDBw+Wnd3LPz9u6NCh6t27t3LlyqVp06apcePG+vnnn+Xm5iZJ2rBhg/z8/LRo0SJdunRJvXr1Unh4uAYMGKCwsDDVr19fHh4emj9/vuzs7DRr1izVrl1ba9asUZo0aRQUFKTp06dr4MCByp07txYvXqzg4GAVLlz4vb1feNEbh1ZsNAYAAAAAAP6L3r17K1++fFq+fLk2bNign3/+WZJUvHhxDRkyRGnSpLH0vXz5srp27apSpUpp0KBB/7qnX/PmzVW1alVJ0qBBg7R3714tWbJEnTp1kiQlS5ZMI0aMUKJEiZQjRw7duHFDgwcPVpcuXbRu3To9ePBAI0aMsMz+Gjx4sPbt26clS5aoTZs2mjdvnho0aGC5R48ePbRv37738h7h1WL96YEAAAAAAACvU7VqVVWtWlVPnjzR4cOHtXHjRi1ZskRt27bVkiVLLP369++vqKgopUuX7rUfQvH3Txl0cHCQp6enzpw5Y2nz8vKy2tvZ29tbUVFRCg0N1YkTJ3T//n2rFWWSFBERoXPnzunu3bu6efOm8uTJY3U8X758OnfuXKzeA8QOoRUAAAAAAHjn9u3bpy1btqhHjx6SpIQJE8rX11e+vr7KmjWrBg4cqDt3/u9TFKtXr64cOXJo6NChKleunHLkyPHKa/99fyxJio6OtlpK6ODgYHU8JiZGkpQgQQLFxMQoc+bMmjx58gvXdXZ2tgRm/9z66J/3xPv38sWhAAAAAAAAb+HRo0eaPXu2jh49+sKxpEmTKmHChEqSJImlrUqVKvr222/l6empHj16KDo6+pXXPn78uOV1ZGSk/vjjD3l4eFja/vjjD6vzDx8+rESJEilz5szKkSOHrl69qqRJk+qzzz7TZ599pvTp02vUqFHav3+/UqRIoXTp0ungwYNW9/z9999j9T4g9ogJAQAAAACIQxnSJ/uo7/dc6dKlVbhwYbVq1Upt27ZVkSJFFB0drePHj2vUqFFq1qyZ5dP8nrOzs9OgQYNUvXp1TZ8+XS1atHjptceOHavUqVPL1dVVU6ZMUXh4uGrXrm05fuXKFQ0YMEANGzbUuXPnNH78eNWrV0+JEiVStWrVFBQUpHbt2qlLly5KkiSJJk2apO3bt6t9+/aSpGbNmmnYsGHKkiWLChYsqFWrVunYsWOWTyhE3CC0AgAAAAAgjsTExKhty+I2ue+rPonvdaZNm6akSZO+9NjzUOdl7OzsFBQUpBkzZmjhwoUaPny4YmJilDVrVrVv315fffXVS8/Lnj27mjVrpgkTJsjf3/+lfb755hsNGzZMV69eVd68eTVv3jx9+umnluP58uWTnZ2dvvrqKyVNmlQNGjRQq1atJD2b5TV//nwNHz5cTZo0UXR0tDw8PDRz5kxlzZpVklS3bl3FxMRo8uTJunXrlkqUKKGvvvpKoaGhb/y+4e2ZzP9cpImPyvMpkytDn+jinUc2rgYAAACxlTFlEvWpVuj1HQFIksLCwnTy5EnlypVLzs7Oti4H78jly5fl7++vuXPnWm3G/nfdu3fXlStXNG/evDiu7uMSFhZm87HDnlYAAAAAAAAwHEIrAAAAAAAAGA57WgEAAAAAgA+Cq6urTp8+/a99hg4dGkfV4H1jphUAAAAAAAAMh9AKAAAAAAAAhkNoBQAAAAAAAMMhtAIAAAAAAIDhEFoBAAAAAADAcAitAAAAAAAAYDiEVgAAAAAAxJHo6Oh4cV+z2SwfHx+NGDHCqv3Ro0fy9PRU3rx5FRERYXWsd+/eqlKlSlyW+cbOnz8vd3d3ffnll7YuJV6xt3UBAAAAAADEFwkSJFDXrv3057nzcXbPLFkzafjwAbE699GjR5oyZcorjxctWlRFixZ9od1kMsnHx0eHDx+2at+7d6+SJUumBw8eKCQkRCVKlLAc279/v0qVKhWrOt+34OBgZc6cWSdPntTRo0eVN29eW5cULxBaAQAAAHgrR44c0ahRo3T8+HE5OzurRIkS6tq1q1KlSiVJunDhgn788UcdOHBACRIkUMWKFdWlSxclSZLEco0FCxZoypQpiomJUYMGDdSiRQure7Rp00YeHh5q1apVnD4b8D78ee68Tp48besy3sijR49Up04dubq6vvT4gQMHXnmur6+vfvzxR0VGRsrR0VGStGPHDvn6+urGjRvasWOHJbS6ffu2zp8/r549e777h3hL0dHRWrlyperVq6eVK1dq0aJFhFZxhOWBAAAAAGLt999/V4MGDZQ4cWJNmDBBnTt31q5du9S6dWtJ0oMHD9SwYUPdunVLQ4cOVadOnfTLL7+offv2lmucPn1aP/zwg5o3b65u3bpp4sSJ2rFjh+X44cOHdeTIETVq1CiuHw/AW/D19VVERIT++OMPS9vOnTtVrFgxFS9eXDt37rS079+/Xw4ODipUqJAuX74sd3d3TZ06VcWKFZO/v78ePXqke/fuacCAASpVqpS8vLxUp04d7du3z3KNwMBANWrUSEFBQSpZsqTy5MmjevXq6dy5c5Y+d+7cUUBAgAoWLCgfHx+NHDlSDRo0UGBg4CufY+fOnbp+/bqKFSum8uXLa926dXrw4IFVH3d3dy1btkyNGjWSl5eXihcvrgkTJliOh4eHq1evXipWrJjy5MmjL7/8Uhs2bJAkDR06VJ9//rml7/3795UrVy4NHDjQ0rZlyxZ5e3srIiJCZrNZ06ZNk7+/v/LmzasvvvhCq1evtvTdt2+fcufOraCgIPn4+KhGjRqKiYl5oz8zo2GmFQAAAIBYGzFihHLnzq1JkybJzu7Z78STJEmiwYMH69KlS/rll1907949BQcHK2XKlJKkNGnSqHnz5jp48KAKFCigvXv3Klu2bKpfv74kad26ddq9e7dlBsbw4cPVunVrJUqUyDYPCSBWMmXKpPTp0+vw4cPy9vZWaGioLl++rGLFiunmzZsaPXq0rl69qvTp0+vAgQPy9vaWs7Oz7ty5I0lasWKF5syZo/DwcCVKlEi1atVSVFSURowYoZQpU2ru3Llq0qSJFi5cKC8vL0nPZn45OTkpKChIUVFR6tq1qwYMGKC5c+cqJiZGLVq0UHR0tKZPny4HBwfLLNBChQq98jmWL1+ujBkzysPDQw4ODpo8ebJWrlypBg0aWPUbNmyYevfurUGDBmnt2rUaM2aMfHx8VKhQIY0bN06nT59WUFCQkiVLpqVLlyogIEDr169X6dKlNWvWLN28eVOffPKJ9uzZI7PZbBXIbd26VcWLF5eTk5NGjx6tn3/+WX379lWWLFm0f/9+9e/fXw8fPlTdunUlPZsdtm3bNi1evFjh4eGWv58/NB9m1QAAAABs7u7duwoJCdE333xj9Q+i8uXLa9u2bXJzc9POnTtVoEABS2AlScWLF1fixIm1fft2Sc/2vnFycrIcd3BwsMwK2LRpk+7cuaNatWrF0VMBeJeKFCmiQ4cOSXq2NDBHjhxKkyaNPDw8lDJlSstsqwMHDqh48eJW53777bfKli2b8uTJo507d+qPP/7QqFGjVLhwYWXLlk0DBgxQ9uzZNWPGDMs5T58+1fDhw5UzZ07lyZNHderUsdw/JCREx44d08iRI5UvXz55eHho7NixlqWLL3P37l1t2bJFlStXliTlyJFDOXLk0OLFi1/o++WXX+qLL76Qm5ubWrZsqWTJklnuffHiRSVOnFhubm5yc3NT+/btNWXKFLm4uKhAgQJycXHRrl27JEm7d++Wv7+/zp49q1u3bkmStm/fLn9/f4WFhWn27Nnq2bOn/Pz8lDFjRtWsWVONGjWyeh8kqXHjxsqUKZNy5cr15n9gBkNoBQAAACBWTp8+rZiYGKVMmVKdOnWSt7e3vL291bVrV8vSmXPnzilz5sxW5yVIkECurq4KDQ2VJOXLl0+nT5/WsWPHFBoaqpCQEBUoUEDR0dEaNWqUOnToIHt7FokAHyJfX19LcPN8aaD0LKwuWrSo9u3bp4cPH+r06dMvbOj+2WefWV6fOXNGSZMmVY4cOSxtJpNJBQsW1JkzZyxtqVOnlouLi+X7pEmTKioqSpJ04sQJubi4KEuWLFb9//l31N+tWbNGUVFRltBKkqpUqaKzZ8++sJ9X1qxZrb7/+72bNWumU6dOydfXV998840mT56sjBkzKmnSpLK3t1eJEiW0e/duSdKuXbtUu3ZtffLJJ9q3b59OnTqlGzduqFSpUjp79qwiIiKs/s719vbWtGnTdOXKFT158sRy/0yZMr3yuT4U/M0PAAAAIFaeL+Hp2bOnSpYsqUmTJun8+fMaPXq0Ll26pIULF+rhw4dKnDjxC+cmTpxYjx49kiR5eXmpZcuWqlu3rsxms+rUqaPy5ctr8eLFcnZ2VsWKFTV16lStWrVKrq6u6tOnj9zc3OL0WQHEjq+vr27fvq1z584pJCTEakld8eLFNWbMGB06dEjJkiWTh4eH1bkJEya0vDabzS+9vtlstgq1/23WVIIECf7z3k7BwcGSpOrVq79Qy08//aSCBQv+672f9/X29ta2bdu0a9cu7dmzRytXrtTkyZM1ffp0+fr6yt/fX0OGDNHFixd1/fp1FSpUSD4+Ptq3b58uX76sAgUKKEWKFLp48aIkaezYsVbh28tq+PsM1g8VM60AAAAAxMrzGQQeHh4aPHiwZQZB//79dejQIe3ateuV/9CUns2SeK5169Y6dOiQDh06pN69eyssLEyBgYHq3LmztmzZonnz5mnEiBHKmjWrOnTo8L4fDcA78sknnyh79uz66aefFBMTY7V3VLFixXT9+nWtX79evr6+/7rvkru7ux4+fGg1q8psNuvgwYPKli3bG9WSM2dOPXz40Gpj9rt37+rChQsv7X/ixAmdPHlSLVu21MqVKy1fq1atUokSJbRhwwbdvXv3je49fvx4HTx4UP7+/urdu7fWr18vNzc3rV+/XpJUokQJ3bt3T3PnzlXevHnl7OysokWLau/evfrtt9/k7+8vScqSJYvs7e119epVffbZZ5avbdu2acaMGR/s3lWv8nE9DQAAAIA483wGVenSpa3an2+gfuLECSVJkkSPHz9+4dxHjx4padKkVm0ODg6WWQKzZs2Su7u7fH19tX79epUtW1YeHh5q2rSpfv/9d125cuV9PBKA96BIkSJavny5ChUqZDX759NPP1WOHDm0bt06y7LBVylevLhy5cqlTp06KSQkROfOndPAgQN15swZNWzY8I3q8PHxUd68edW1a1cdOXJEp06dUufOnRUeHm4Voj8XHBysRIkSqXHjxpa9rJ5/NWvWTJGRkZaZWK9z6dIl9evXT3v27NGVK1e0fv16Xb16Vd7e3pKeLSUsWLCgFi9eLF9fX0nPZqlduHBBR48etYRWSZMmVZ06dTRu3DitWrVKly5d0rJlyzRixAh9+umnb1TL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",
+ "text/plain": [
+ "