From 16c2ea2225215103b6fafc31e5ef253c11fcb03d Mon Sep 17 00:00:00 2001 From: laz4rz Date: Fri, 29 Mar 2024 20:50:04 +0100 Subject: [PATCH 01/12] change completion to update_cost to reduce integration complexity --- src/openai_cost_tracker.py | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/openai_cost_tracker.py b/src/openai_cost_tracker.py index ec4c018..0007bd5 100644 --- a/src/openai_cost_tracker.py +++ b/src/openai_cost_tracker.py @@ -3,7 +3,7 @@ from pathlib import Path from time import strftime from typing import List, Dict -from openai import OpenAI, AzureOpenAI +from openai.types.chat.chat_completion import ChatCompletion from constants import DEFAULT_LOG_PATH @@ -55,28 +55,16 @@ def __init__( self.experiment_name = experiment_name self.cost_upperbound = cost_upperbound self.filename = f"{experiment_name}_cost_" + strftime("%Y%m%d_%H%M%S") + ".csv" - - if client.value == ClientType.OPENAI.value: - self.client = OpenAI(**client_args) - elif client.value == ClientType.AZURE.value: - self.client = AzureOpenAI(**client_args) + - def chat_completion(self, messages: List[Dict], api_args: Dict = {}) -> str: - """Use the chat completion endpoint to get a response from the model - and update the cost tracker with the cost of the completion. + def update_cost(self, response: ChatCompletion) -> None: + """Extract the number of input and output tokens from a chat completion response + and update the cost. Args: - messages (list): A list of messages to send to the model. Every message is a dictionary with the key 'role' and the value 'content'. - api_args(Dict, optional): The parameters to pass to the api endpoint. Defaults to {}. - Returns: - str: The model's response. + response: ChatCompletion object from the model. """ - answer = self.client.chat.completions.create( - model=self.model, - messages=messages, - **api_args - ) - self.cost += self.__get_answer_cost(answer) + self.cost += self.__get_answer_cost(response) self.__validate_cost() path = Path(self.log_folder, self.filename) path.parent.mkdir(parents=True, exist_ok=True) @@ -86,7 +74,6 @@ def chat_completion(self, messages: List[Dict], api_args: Dict = {}) -> str: csvwriter = csv.writer(file) csvwriter.writerow(FILE_HEADER) csvwriter.writerow([self.experiment_name, self.model, self.cost]) - return answer.choices[0].message.content def __get_answer_cost(self, answer: Dict) -> float: """Calculate the cost of the answer based on the input and output tokens. @@ -114,4 +101,4 @@ def get_current_cost(self) -> float: Returns: float: The current cost. """ - return self.cost \ No newline at end of file + return self.cost From 409f505ddd68d3626878b8c6145fe611ab91cc54 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Fri, 29 Mar 2024 21:04:09 +0100 Subject: [PATCH 02/12] remove leftover attributes, update docstring --- src/openai_cost_tracker.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/openai_cost_tracker.py b/src/openai_cost_tracker.py index 0007bd5..f76d335 100644 --- a/src/openai_cost_tracker.py +++ b/src/openai_cost_tracker.py @@ -7,11 +7,6 @@ from constants import DEFAULT_LOG_PATH -"""Clients supported.""" -class ClientType(Enum): - OPENAI = 1, - AZURE = 2 - """Every cost is per million tokens.""" COST_UNIT = 1_000_000 @@ -26,14 +21,12 @@ class ClientType(Enum): class OpenAICostTracker: def __init__( self, - client: ClientType, model: str, input_cost: float, output_cost: float, experiment_name: str, cost_upperbound: float = float('inf'), log_folder: str = DEFAULT_LOG_PATH, - client_args: Dict = {} ): """Initialize the cost tracker. @@ -59,7 +52,7 @@ def __init__( def update_cost(self, response: ChatCompletion) -> None: """Extract the number of input and output tokens from a chat completion response - and update the cost. + and update the cost. Saves experiment costs to file, overwriting it. Args: response: ChatCompletion object from the model. From 9ceaf806995a324b28de36c4d808155c8e83c30a Mon Sep 17 00:00:00 2001 From: laz4rz Date: Sat, 30 Mar 2024 14:14:45 +0100 Subject: [PATCH 03/12] add changes proposal and motivation --- changes_proposal.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 changes_proposal.md diff --git a/changes_proposal.md b/changes_proposal.md new file mode 100644 index 0000000..4374bd2 --- /dev/null +++ b/changes_proposal.md @@ -0,0 +1,39 @@ +1. cost tracker handles completion creation +Change: separating completion and cost tracking, by changing the main functionality from `chat_completion` to `update_cost` +Motivation: + - bulletproofs us from changes in how the completion is created, we only care about response structure + - allows easier integration, user only has to initialize tracker object and call `update_cost(response)`, + otherwise each chat completion call would have to be rewritten + +2. costs are calculated across all log files +Change: + - static `total_cost` that will calculate total spending from logs + - static `experiment_cost(experiment_name=self.experiment_name)` gets you total cost of specific experiment + - defaulting to current experiment_name in tracker object + - if object not initialized, experiment_name has to be provided + - `cost` that gets you costs for current run of this tracker object + +3. log file just acumulates total cost +Change: + - add breakdown of responses/input token per response/output token per response/cost per response + - maybe change log file format to json, so that we can better handle logs, for example: + ``` + { + "experiment_name" + "model": + "run_datetime": + "logs": + { + "0": { # maybe datetime of response? + "num_of_input_tokens": + "num_of_output_tokens": + "other": # additional info? message? thread? prompt? + } + } + "total": { + "cost": # something else? + } + } + ``` + +3. WIP \ No newline at end of file From 3b2a5dd0de4e2136999391181e1ba61745e561f7 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Sat, 30 Mar 2024 14:19:26 +0100 Subject: [PATCH 04/12] add units to plot --- src/openai_cost_tracker_viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openai_cost_tracker_viz.py b/src/openai_cost_tracker_viz.py index b46b0f7..6b1e628 100644 --- a/src/openai_cost_tracker_viz.py +++ b/src/openai_cost_tracker_viz.py @@ -92,6 +92,6 @@ def plot_cost_by_day(path: str = DEFAULT_LOG_PATH, last_n_days: int = None) -> N plt.bar(cost_by_day.keys(), cost_by_day.values(), width=0.5) plt.xlabel('Day') - plt.ylabel('Cost') + plt.ylabel('Cost [$]') plt.title('Cost by day') plt.show() \ No newline at end of file From b855913462b8534e632c75b003dc111d0fb2b9d9 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Sat, 30 Mar 2024 14:21:55 +0100 Subject: [PATCH 05/12] modify demo to work with update_cost, behavior replicated --- demo.ipynb | 117 ++++++++++++++++++++--------------------------------- 1 file changed, 44 insertions(+), 73 deletions(-) diff --git a/demo.ipynb b/demo.ipynb index b70bca7..ce6ba86 100644 --- a/demo.ipynb +++ b/demo.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 166, + "execution_count": 64, "metadata": {}, "outputs": [ { @@ -28,45 +28,11 @@ }, { "cell_type": "code", - "execution_count": 167, + "execution_count": 65, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Defaulting to user installation because normal site-packages is not writeable\n", - "Requirement already satisfied: openai==1.13.3 in /home/drudao/.local/lib/python3.10/site-packages (from -r requirements.txt (line 1)) (1.13.3)\n", - "Requirement already satisfied: matplotlib in /home/drudao/.local/lib/python3.10/site-packages (from -r requirements.txt (line 2)) (3.8.3)\n", - "Requirement already satisfied: pydantic<3,>=1.9.0 in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (2.6.4)\n", - "Requirement already satisfied: httpx<1,>=0.23.0 in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (0.27.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai==1.13.3->-r requirements.txt (line 1)) (1.7.0)\n", - "Requirement already satisfied: tqdm>4 in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (4.66.2)\n", - "Requirement already satisfied: sniffio in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (1.3.1)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (4.10.0)\n", - "Requirement already satisfied: anyio<5,>=3.5.0 in /home/drudao/.local/lib/python3.10/site-packages (from openai==1.13.3->-r requirements.txt (line 1)) (4.3.0)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (4.42.1)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (1.1.1)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /usr/lib/python3/dist-packages (from matplotlib->-r requirements.txt (line 2)) (2.4.7)\n", - "Requirement already satisfied: packaging>=20.0 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (23.1)\n", - "Requirement already satisfied: numpy<2,>=1.21 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (1.26.4)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (1.4.5)\n", - "Requirement already satisfied: pillow>=8 in /usr/lib/python3/dist-packages (from matplotlib->-r requirements.txt (line 2)) (9.0.1)\n", - "Requirement already satisfied: cycler>=0.10 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (0.11.0)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /home/drudao/.local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 2)) (2.8.2)\n", - "Requirement already satisfied: exceptiongroup>=1.0.2 in /home/drudao/.local/lib/python3.10/site-packages (from anyio<5,>=3.5.0->openai==1.13.3->-r requirements.txt (line 1)) (1.1.3)\n", - "Requirement already satisfied: idna>=2.8 in /usr/lib/python3/dist-packages (from anyio<5,>=3.5.0->openai==1.13.3->-r requirements.txt (line 1)) (3.3)\n", - "Requirement already satisfied: httpcore==1.* in /home/drudao/.local/lib/python3.10/site-packages (from httpx<1,>=0.23.0->openai==1.13.3->-r requirements.txt (line 1)) (1.0.4)\n", - "Requirement already satisfied: certifi in /usr/lib/python3/dist-packages (from httpx<1,>=0.23.0->openai==1.13.3->-r requirements.txt (line 1)) (2020.6.20)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /home/drudao/.local/lib/python3.10/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai==1.13.3->-r requirements.txt (line 1)) (0.14.0)\n", - "Requirement already satisfied: pydantic-core==2.16.3 in /home/drudao/.local/lib/python3.10/site-packages (from pydantic<3,>=1.9.0->openai==1.13.3->-r requirements.txt (line 1)) (2.16.3)\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /home/drudao/.local/lib/python3.10/site-packages (from pydantic<3,>=1.9.0->openai==1.13.3->-r requirements.txt (line 1)) (0.6.0)\n", - "Requirement already satisfied: six>=1.5 in /usr/lib/python3/dist-packages (from python-dateutil>=2.7->matplotlib->-r requirements.txt (line 2)) (1.16.0)\n" - ] - } - ], + "outputs": [], "source": [ - "!pip install -r requirements.txt" + "# !pip install -r requirements.txt" ] }, { @@ -78,13 +44,14 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "import pathlib\n", + "import openai\n", "\n", "# Add the src directory to the path\n", "sys.path.insert(0, str(pathlib.Path('src')))" @@ -92,14 +59,14 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "from constants import DEFAULT_LOG_PATH, Models, MODELS_COST\n", "from openai_cost_tracker_viz import OpenAICostTrackerViz\n", "from openai_cost_tracker_utils import OpenAICostTrackerUtils\n", - "from openai_cost_tracker import ClientType, OpenAICostTracker" + "from openai_cost_tracker import OpenAICostTracker" ] }, { @@ -111,18 +78,18 @@ }, { "cell_type": "code", - "execution_count": 170, + "execution_count": 68, "metadata": {}, "outputs": [], "source": [ "# Export the proper environment variables based on the client you are using.\n", "\n", "# OpenAI API Key\n", - "os.environ[\"OPENAI_ORGANIZATION\"] = OpenAICostTrackerUtils.get_api_key(path='openai_organization.txt')\n", - "os.environ[\"OPENAI_API_KEY\"] = OpenAICostTrackerUtils.get_api_key(path='openai_api_key.txt')\n", + "# os.environ[\"OPENAI_ORGANIZATION\"] = OpenAICostTrackerUtils.get_api_key(path='openai_organization.txt')\n", + "os.environ[\"OPENAI_API_KEY\"] = OpenAICostTrackerUtils.get_api_key(path='/Users/mikolajboronski/.openai')\n", "\n", "# Azure OpenAI API Key\n", - "os.environ[\"AZURE_OPENAI_KEY\"] = OpenAICostTrackerUtils.get_api_key('azure_openai_key.txt')" + "# os.environ[\"AZURE_OPENAI_KEY\"] = OpenAICostTrackerUtils.get_api_key('azure_openai_key.txt')" ] }, { @@ -134,11 +101,11 @@ }, { "cell_type": "code", - "execution_count": 171, + "execution_count": 69, "metadata": {}, "outputs": [], "source": [ - "experiment_name = \"Demo\"\n", + "experiment_name = \"Demo2\"\n", "messages = [\n", " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", " {\"role\": \"user\", \"content\": \"Who won the euro 2020?\"},\n", @@ -149,12 +116,11 @@ }, { "cell_type": "code", - "execution_count": 172, + "execution_count": 70, "metadata": {}, "outputs": [], "source": [ "# Azure OpenAI usage\n", - "client_type = ClientType.AZURE\n", "model = Models.AZURE_3_5_TURBO\n", "client_args = {\n", " \"azure_endpoint\": \"https://your_key.openai.azure.com/\",\n", @@ -167,12 +133,12 @@ }, { "cell_type": "code", - "execution_count": 173, + "execution_count": 71, "metadata": {}, "outputs": [], "source": [ "# OpenAI usage\n", - "client_type = ClientType.OPENAI\n", + "client = openai.OpenAI(api_key=os.getenv(\"OPENAI_API_KEY\"))\n", "model = Models.TURBO_3_5\n", "client_args = {}\n", "input_cost = MODELS_COST[model.value][\"input\"]\n", @@ -188,42 +154,41 @@ }, { "cell_type": "code", - "execution_count": 174, + "execution_count": 72, "metadata": {}, "outputs": [], "source": [ "# Create the OpenAICostTracker object\n", "cost_tracker = OpenAICostTracker(\n", " experiment_name = experiment_name,\n", - " client = client_type,\n", " model = model.value,\n", " input_cost = input_cost,\n", " output_cost = output_cost,\n", " log_folder = log_folder,\n", - " cost_upperbound = cost_upperbound,\n", - " client_args = client_args\n", + " cost_upperbound = cost_upperbound\n", ")" ] }, { "cell_type": "code", - "execution_count": 175, + "execution_count": 73, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "'Italy won the UEFA Euro 2020. They defeated England in a penalty shootout in the final to claim the title.'" - ] - }, - "execution_count": 175, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Italy\n" + ] } ], "source": [ "# Run the chat completion\n", - "cost_tracker.chat_completion(messages, api_args={\"max_tokens\": 100})" + "response = client.chat.completions.create(model=model.value, messages=messages, max_tokens=1, temperature=0)\n", + "\n", + "print(response.choices[0].message.content)\n", + "\n", + "cost_tracker.update_cost(response)" ] }, { @@ -235,14 +200,14 @@ }, { "cell_type": "code", - "execution_count": 176, + "execution_count": 74, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Total cost: 0.000943 (USD)\n" + "Total cost: 1.4e-05 (USD)\n" ] } ], @@ -253,15 +218,14 @@ }, { "cell_type": "code", - "execution_count": 177, + "execution_count": 75, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "gpt-3.5-turbo: 0.000845 (USD)\n", - "gpt-35-turbo-0125: 9.9e-05 (USD)\n" + "gpt-3.5-turbo: 1.4e-05 (USD)\n" ] } ], @@ -272,12 +236,12 @@ }, { "cell_type": "code", - "execution_count": 178, + "execution_count": 76, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlEAAAHHCAYAAACfqw0dAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA3XUlEQVR4nO3de1xVdb7/8Td3SAU0FURJyNHULJm8IFpZyYiJJY2dQYfEzMQueHTM8ailZqcZGqcc0yzGqdTxcjSdslJjjkHqpAwqZuXdvJSNgRcEFBXU/f390XHNbyfevoEb7fV8PPaDB9/1WWt91qrH4u3aa3+3lzHGCAAAAFfE29MNAAAAXIsIUQAAABYIUQAAABYIUQAAABYIUQAAABYIUQAAABYIUQAAABYIUQAAABYIUQAAABYIUQBwEbNmzZKXl5c2bNjgkf3fc889uueeezyybwAXR4gC4FG7d+/WkCFDdPPNNyswMFDBwcHq0qWLXn31VZ08ebLK93fixAk9//zzWrlyZZVvG8BPi6+nGwDw07Vs2TL9x3/8hwICApSamqo2bdqooqJCn376qX77299qy5YtmjFjRpXu88SJE5o4caIkcYcHwI9CiALgEXv37lXfvn3VtGlT5eTkqFGjRs6yp59+Wl999ZWWLVvmwQ4B4OJ4Ow+AR0yaNEnHjx/XW2+95RagzvnZz36mYcOGOb+fOXNG//3f/61mzZopICBAUVFRGjt2rMrLy93W27BhgxISElS/fn0FBQUpOjpajz32mCRp3759atCggSRp4sSJ8vLykpeXl55//vlL9nvixAkNGTJEN954o4KDg5WamqqjR486ywcMGKD69evr9OnT563bvXt33XLLLZfcx4wZM9SsWTMFBQWpY8eO+sc//nFeTUVFhcaPH6927dopJCREtWrV0l133aVPPvnEqTHGKCoqSr179z5v/VOnTikkJERDhgy5ZD8ALo4QBcAjPvzwQ918883q3LnzZdU//vjjGj9+vO644w796U9/UteuXZWRkaG+ffs6NQcPHlT37t21b98+jR49WtOmTVNKSor++c9/SpIaNGigN954Q5L00EMPac6cOZozZ45++ctfXnL/6enp2rZtm55//nmlpqZq3rx5SkpKkjFGktS/f38dOXJEf//7393WKygoUE5Ojh555JGLbv+tt97SkCFDFB4erkmTJqlLly568MEHtX//fre60tJSvfnmm7rnnnv0hz/8Qc8//7wOHTqkhIQEbdq0SZLk5eWlRx55RB999JGKiorc1v/www9VWlp6yX4AXAYDAFdZSUmJkWR69+59WfWbNm0ykszjjz/uNj5y5EgjyeTk5BhjjHnvvfeMJLN+/foLbuvQoUNGkpkwYcJl7XvmzJlGkmnXrp2pqKhwxidNmmQkmffff98YY8zZs2dNkyZNTHJystv6kydPNl5eXmbPnj0X3EdFRYVp2LChiYmJMeXl5c74jBkzjCTTtWtXZ+zMmTNuNcYYc/ToURMWFmYee+wxZ2zHjh1GknnjjTfcah988EETFRVlXC7XZR0/gAvjThSAq660tFSSVKdOncuqX758uSRpxIgRbuPPPPOMJDnPToWGhkqSli5dWunbaj9GWlqa/Pz8nN+ffPJJ+fr6Or15e3srJSVFH3zwgY4dO+bUzZs3T507d1Z0dPQFt71hwwYdPHhQTzzxhPz9/Z3xRx99VCEhIW61Pj4+To3L5VJRUZHOnDmj9u3ba+PGjU5dixYtFBsbq3nz5jljRUVF+uijj5SSkiIvLy/LMwHgHEIUgKsuODhYktzCxsV8/fXX8vb21s9+9jO38fDwcIWGhurrr7+WJHXt2lV9+vTRxIkTVb9+ffXu3VszZ84877kpG82bN3f7vXbt2mrUqJH27dvnjKWmpurkyZN67733JEk7duxQfn6++vfvf8njq2wffn5+uvnmm8+rnz17tm6//XYFBgbqxhtvVIMGDbRs2TKVlJS41aWmpmrNmjXO9hctWqTTp09fsh8Al4cQBeCqCw4OVkREhDZv3nxF613q7omXl5cWL16s3Nxcpaen61//+pcee+wxtWvXTsePH/8xLV+W1q1bq127dpo7d64kae7cufL399evfvWrKtvH3Llz9eijj6pZs2Z66623lJWVpRUrVui+++6Ty+Vyq+3bt6/8/Pycu1Fz585V+/btL+shdwCXRogC4BG9evXS7t27lZube8napk2byuVyadeuXW7jhYWFKi4uVtOmTd3GO3XqpN/97nfasGGD5s2bpy1btmjBggWSLh3ELuSH+z5+/Li+++47RUVFuY2npqYqJydH3333nebPn6/ExETVrVv3ksdX2T5Onz6tvXv3uo0tXrxYN998s9599131799fCQkJio+P16lT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d+mee+7RW2+9pU8++UQ7duy44HpjxoxRSUmJ89q/f/9V7BoAAFxNHgtR9evXl4+PjwoLC93GCwsLFR4eXuk64eHhF60/9/NSNT98cP3MmTMqKipyaho1aiRfX1+1aNHCqWnVqpUk6ZtvvrngMQUEBCg4ONjtBQAArk8eC1H+/v5q166dsrOznTGXy6Xs7GzFxcVVuk5cXJxbvSStWLHCqY+OjlZ4eLhbTWlpqfLy8pyauLg4FRcXKz8/36nJycmRy+VSbGysJKlLly46c+aMdu/e7dTs3LlTktS0adMfc9gAAOA64evJnY8YMUIDBgxQ+/bt1bFjR02ZMkVlZWUaOHCgJCk1NVWNGzdWRkaGpO+nHejatateeeUVJSYmasGCBdqwYYNmzJghSfLy8tLw4cP14osvqnnz5oqOjta4ceMUERGhpKQkSd/fUerRo4cGDx6szMxMnT59Wunp6erbt68iIiIkff+g+R133KHHHntMU6ZMkcvl0tNPP61f/OIXbnenAADAT5dHQ1RycrIOHTqk8ePHq6CgQDExMcrKynIeDP/mm2/k7f3vm2WdO3fW/Pnz9dxzz2ns2LFq3ry5lixZojZt2jg1o0aNUllZmdLS0lRcXKw777xTWVlZCgwMdGrmzZun9PR0devWTd7e3urTp4+mTp3qLPf29taHH36ooUOH6u6771atWrV0//3365VXXrkKZwUAAFwLPDpP1PWOeaIA4HvME4Xq8JOdJwoAAOBaRogCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwYBWiXnjhBZ04ceK88ZMnT+qFF1740U0BAADUdFYhauLEiTp+/Ph54ydOnNDEiRN/dFMAAAA1nVWIMsbIy8vrvPHPP/9c9erV+9FNAQAA1HS+V1Jct25deXl5ycvLSy1atHALUmfPntXx48f1xBNPVHmTAAAANc0VhagpU6bIGKPHHntMEydOVEhIiLPM399f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DVPXrl31yiuvKDExUQsWLNCGDRs0Y8YMSZKXl5eGDx+uF198Uc2bN1d0dLTGjRuniIgIJSUlSZJatWqlHj16aPDgwcrMzNTp06eVnp6uvn37Op/Ma9WqlVufGzZskLe3t9q0aXOVzgwAAKjJPB6ikpOTdejQIY0fP14FBQWKiYlRVlaW82D4N998I2/vf98w69y5s+bPn6/nnntOY8eOVfPmzbVkyRK3cDNq1CiVlZUpLS1NxcXFuvPOO5WVlaXAwECnZt68eUpPT1e3bt3k7e2tPn36aOrUqVfvwAEAwDXN4/NEXc+YJwoAvsc8UagOP+l5ogAAAK5VhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALhCgAAAALNSJETZ8+XVFRUQoMDFRsbKzWrVt30fpFixapZcuWCgwM1G233ably5e7LTfGaPz48WrUqJGCgoIUHx+vXbt2udUUFRUpJSVFwcHBCg0N1aBBg3T8+HFn+cqVK9W7d281atRItWrVUkxMjObNm1d1Bw0AAK5pHg9RCxcu1IgRIzRhwgRt3LhRbdu2VUJCgg4ePFhp/dq1a9WvXz8NGjRIn332mZKSkpSUlKTNmzc7NZMmTdLUqVOVmZmpvLw81apVSwkJCTp16pRTk5KSoi1btmjFihVaunSpVq9erbS0NLf93H777frb3/6mL774QgMHDlRqaqqWLl1afScDAABcM7yMMcaTDcTGxqpDhw567bXXJEkul0uRkZEaOnSoRo8efV59cnKyysrK3MJMp06dFBMTo8zMTBljFBERoWeeeUYjR46UJJWUlCgsLEyzZs1S3759tW3bNrVu3Vrr169X+/btJUlZWVnq2bOnvv32W0VERFTaa2JiosLCwvT2229f1rGVlpYqJCREJSUlCg4OvqLzAgDXk6jRyzzdAq5D+15KrJbtXu7fb4/eiaqoqFB+fr7i4+OdMW9vb8XHxys3N7fSdXJzc93qJSkhIcGp37t3rwoKCtxqQkJCFBsb69Tk5uYqNDTUCVCSFB8fL29vb+Xl5V2w35KSEtWrV+/KDxQAAFx3fD2588OHD+vs2bMKCwtzGw8LC9P27dsrXaegoKDS+oKCAmf5ubGL1TRs2NBtua+vr+rVq+fU/NA777yj9evX689//vMFj6e8vFzl5eXO76WlpResBQAA1zaPPxN1Lfjkk080cOBA/eUvf9Gtt956wbqMjAyFhIQ4r8jIyKvYJQAAuJo8GqLq168vHx8fFRYWuo0XFhYqPDy80nXCw8MvWn/u56Vqfvjg+pkzZ1RUVHTefletWqUHHnhAf/rTn5SamnrR4xkzZoxKSkqc1/79+y9aDwAArl0eDVH+/v5q166dsrOznTGXy6Xs7GzFxcVVuk5cXJxbvSStWLHCqY+OjlZ4eLhbTWlpqfLy8pyauLg4FRcXKz8/36nJycmRy+VSbGysM7Zy5UolJibqD3/4g9sn9y4kICBAwcHBbi8AAHB98ugzUZI0YsQIDRgwQO3bt1fHjh01ZcoUlZWVaeDAgZKk1NRUNW7cWBkZGZKkYcOGqWvXrnrllVeUmJioBQsWaMOGDZoxY4YkycvLS8OHD9eLL76o5s2bKzo6WuPGjVNERISSkpIkSa1atVKPHj00ePBgZWZm6vTp00pPT1ffvn2dT+Z98skn6tWrl4YNG6Y+ffo4z0r5+/vzcDkAAPB8iEpOTtahQ4c0fvx4FRQUKCYmRllZWc6D4d988428vf99w6xz586aP3++nnvuOY0dO1bNmzfXkiVL1KZNG6dm1KhRKisrU1pamoqLi3XnnXcqKytLgYGBTs28efOUnp6ubt26ydvbW3369NHUqVOd5bNnz9aJEyeUkZHhBDhJ6tq1q1auXFmNZwQAAFwLPD5P1PWMeaIA4HvME4Xq8JOeJwoAAOBaRYgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACwQIgCAACw4OvpBmAnavQyT7eA69C+lxI93QIAXDO4EwUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGCBEAUAAGChRoSo6dOnKyoqSoGBgYqNjdW6desuWr9o0SK1bNlSgYGBuu2227R8+XK35cYYjR8/Xo0aNVJQUJDi4+O1a9cut5qioiKlpKQoODhYoaGhGjRokI4fP+5W88UXX+iuu+5SYGCgIiMjNWnSpKo5YAAAcM3zeIhauHChRowYoQkTJmjjxo1q27atEhISdPDgwUrr165dq379+mnQoEH67LPPlJSUpKSkJG3evNmpmTRpkqZOnarMzEzl5eWpVq1aSkhI0KlTp5yalJQUbdmyRStWrNDSpUu1evVqpaWlOctLS0vVvXt3NW3aVPn5+frjH/+o559/XjNmzKi+kwEAAK4ZXsYY48kGYmNj1aFDB7322muSJJfLpcjISA0dOlSjR48+rz45OVllZWVaunSpM9apUyfFxMQoMzNTxhhFRETomWee0ciRIyVJJSUlCgsL06xZs9S3b19t27ZNrVu31vr169W+fXtJUlZWlnr27Klvv/1WEREReuONN/Tss8+qoKBA/v7+kqTRo0dryZIl2r59+2UdW2lpqUJCQlRSUqLg4OAfdZ5+KGr0sirdHiBJ+15K9HQLuE5xzUJ1qK5r1uX+/fbonaiKigrl5+crPj7eGfP29lZ8fLxyc3MrXSc3N9etXpISEhKc+r1796qgoMCtJiQkRLGxsU5Nbm6uQkNDnQAlSfHx8fL29lZeXp5Tc/fddzsB6tx+duzYoaNHj/7IIwcAANc6X0/u/PDhwzp79qzCwsLcxsPCwi54t6egoKDS+oKCAmf5ubGL1TRs2NBtua+vr+rVq+dWEx0dfd42zi2rW7fueb2Vl5ervLzc+b2kpETS94m2qrnKT1T5NoHq+H8VkLhmoXpU1zXr3HYv9WadR0PU9SYjI0MTJ048bzwyMtID3QBXLmSKpzsAgMtX3desY8eOKSQk5ILLPRqi6tevLx8fHxUWFrqNFxYWKjw8vNJ1wsPDL1p/7mdhYaEaNWrkVhMTE+PU/PDB9TNnzqioqMhtO5Xt5//fxw+NGTNGI0aMcH53uVwqKirSjTfeKC8vr0rXQfUqLS1VZGSk9u/fX+XPpQFAVeOaVTMYY3Ts2DFFRERctM6jIcrf31/t2rVTdna2kpKSJH0fPLKzs5Wenl7pOnFxccrOztbw4cOdsRUrViguLk6SFB0drfDwcGVnZzuhqbS0VHl5eXryySedbRQXFys/P1/t2rWTJOXk5Mjlcik2NtapefbZZ3X69Gn5+fk5+7nlllsqfStPkgICAhQQEOA2FhoaesXnBVUvODiYCxKAawbXLM+72B0oh/GwBQsWmICAADNr1iyzdetWk5aWZkJDQ01BQYExxpj+/fub0aNHO/Vr1qwxvr6+5uWXXzbbtm0zEyZMMH5+fubLL790al566SUTGhpq3n//ffPFF1+Y3r17m+joaHPy5EmnpkePHubnP/+5ycvLM59++qlp3ry56devn7O8uLjYhIWFmf79+5vNmzebBQsWmBtuuMH8+c9/vgpnBVWlpKTESDIlJSWebgUALolr1rXF4yHKGGOmTZtmbrrpJuPv7286duxo/vnPfzrLunbtagYMGOBW/84775gWLVoYf39/c+utt5ply5a5LXe5XGbcuHEmLCzMBAQEmG7dupkdO3a41Rw5csT069fP1K5d2wQHB5uBAweaY8eOudV8/vnn5s477zQBAQGmcePG5qWXXqraA0e144IE4FrCNeva4vF5ooDqVF5eroyMDI0ZM+a8t1oBoKbhmnVtIUQBAABY8PjXvgAAAFyLCFEAAAAWCFEAAAAWCFEAAAAWCFGoNhkZGerQoYPq1Kmjhg0bKikpSTt27HCrOXXqlJ5++mndeOONql27tvr06eM2U/znn3+ufv36KTIyUkFBQWrVqpVeffXVC+5zzZo18vX1dSZa/f9Nnz5dUVFRCgwMVGxsrNatW+e2fMiQIWrWrJmCgoLUoEED9e7d2+07HC+3l/Lycj377LNq2rSpAgICFBUVpbfffvtyTxsAD/mpXrOmT5+uVq1aKSgoSLfccov++te/Xu4pg2dnWMD1LCEhwcycOdNs3rzZbNq0yfTs2dPcdNNN5vjx407NE088YSIjI012drbZsGGD6dSpk+ncubOz/K233jL/+Z//aVauXGl2795t5syZY4KCgsy0adPO29/Ro0fNzTffbLp3727atm3rtmzBggXG39/fvP3222bLli1m8ODBJjQ01BQWFjo1f/7zn82qVavM3r17TX5+vnnggQdMZGSkOXPmzBX18uCDD5rY2FizYsUKs3fvXrN27Vrz6aefVsUpBVCNforXrNdff93UqVPHLFiwwOzevdv8z//8j6ldu7b54IMPquq0XtcIUbhqDh48aCSZVatWGWO+nxXez8/PLFq0yKnZtm2bkWRyc3MvuJ2nnnrK3HvvveeNJycnm+eee85MmDDhvAtSx44dzdNPP+38fvbsWRMREWEyMjIuuJ/PP//cSDJfffXVZffy0UcfmZCQEHPkyJELrgPg2vBTuGbFxcWZkSNHutWMGDHCdOnS5YLbwL/xdh6umpKSEklSvXr1JEn5+fk6ffq04uPjnZqWLVvqpptuUm5u7kW3c24b58ycOVN79uzRhAkTzquvqKhQfn6+2368vb0VHx9/wf2UlZVp5syZio6OVmRk5GX38sEHH6h9+/aaNGmSGjdurBYtWmjkyJE6efLkBbcBoGb6KVyzysvLFRgY6FYTFBSkdevW6fTp0xfcDr5HiMJV4XK5NHz4cHXp0kVt2rSRJBUUFMjf3/+8L2kOCwtTQUFBpdtZu3atFi5cqLS0NGds165dGj16tObOnStf3/O/U/vw4cM6e/aswsLCLrmf119/XbVr11bt2rX10UcfacWKFfL397/sXvbs2aNPP/1Umzdv1nvvvacpU6Zo8eLFeuqppy58cgDUOD+Va1ZCQoLefPNN5efnyxijDRs26M0339Tp06d1+PDhC58gSCJE4Sp5+umntXnzZi1YsMB6G5s3b1bv3r01YcIEde/eXZJ09uxZ/frXv9bEiRPVokWLH91nSkqKPvvsM61atUotWrTQr371K506deqyepG+v/B6eXlp3rx56tixo3r27KnJkydr9uzZ3I0CriE/lWvWuHHjdP/996tTp07y8/NT7969NWDAAEnf3/3CJXj6/URc/55++mnTpEkTs2fPHrfx7OxsI8kcPXrUbfymm24ykydPdhvbsmWLadiwoRk7dqzb+NGjR40k4+Pj47y8vLycsezsbFNeXm58fHzMe++957ZuamqqefDBBy/Yd3l5ubnhhhvM/PnzL6uXc9ts1qyZ29jWrVuNJLNz584L7gtAzfFTumadU1FRYfbv32/OnDnjPGx+9uzZC9bje8RMVBtjjNLT0/Xee+8pJydH0dHRbsvbtWsnPz8/ZWdnO2M7duzQN998o7i4OGdsy5YtuvfeezVgwAD97ne/c9tGcHCwvvzyS23atMl5PfHEE7rlllu0adMmxcbGyt/fX+3atXPbj8vlUnZ2ttt+KuvfGKPy8vLL6kWSunTpogMHDuj48ePO2M6dO+Xt7a0mTZpcxlkD4Ck/xWvWOX5+fmrSpIl8fHy0YMEC9erViztRl8OTCQ7XtyeffNKEhISYlStXmu+++855nThxwql54oknzE033WRycnLMhg0bTFxcnImLi3OWf/nll6ZBgwbmkUcecdvGwYMHL7jfyj7psmDBAhMQEGBmzZpltm7datLS0kxoaKgpKCgwxhize/du8/vf/95s2LDBfP3112bNmjXmgQceMPXq1XM+Unw5vRw7dsw0adLEPPzww2bLli1m1apVpnnz5ubxxx+vilMKoBr9FK9ZO3bsMHPmzDE7d+40eXl5Jjk52dSrV8/s3bu3Cs7o9Y8QhWojqdLXzJkznZqTJ0+ap556ytStW9fccMMN5qGHHjLfffeds3zChAmVbqNp06YX3G9lFyRjjJk2bZq56aabjL+/v+nYsaP55z//6Sz717/+Ze6//37TsGFD4+fnZ5o0aWJ+/etfm+3bt19xL9u2bTPx8fEmKCjINGnSxIwYMcLtIgygZvopXrO2bt1qYmJiTFBQkAkODja9e/d22wYuzssYY6r/fhcAAMD1hTc8AQAALBCiAAAALBCiAAAALBCiAAAALBCiAAAALBCiAAAALBCiAAAALBCiAAAALBCiAEDSo48+Ki8vL3l5ecnPz09hYWH6xS9+obffflsul8vT7QGogQhRAPB/evTooe+++0779u3TRx99pHvvvVfDhg1Tr169dObMGU+3B6CGIUQBwP8JCAhQeHi4GjdurDvuuENjx47V+++/r48++kizZs2SJE2ePFm33XabatWqpcjISD311FM6fvy4JKmsrEzBwcFavHix23aXLFmiWrVq6dixY1f7kABUI0IUAFzEfffdp7Zt2+rdd9+VJHl7e2vq1KnasmWLZs+erZycHI0aNUqSVKtWLfXt21czZ85028bMmTP18MMPq06dOle9fwDVhy8gBgB9/0xUcXGxlixZct6yvn376osvvtDWrVvPW7Z48WI98cQTOnz4sCRp3bp16ty5s/bv369GjRrp4MGDaty4sT7++GN17dq1ug8DwFXEnSgAuARjjLy8vCRJH3/8sbp166bGjRurTp066t+/v44cOaITJ05Ikjp27Khbb71Vs2fPliTNnTtXTZs21d133+2x/gFUD0IUAFzCtm3bFB0drX379qlXr166/fbb9be//U35+fmaPn26JKmiosKpf/zxx51nqGbOnKmBAwc6IQzA9YMQBQAXkZOToy+//FJ9+vRRfn6+XC6XXnnlFXXq1EktWrTQgQMHzlvnkUce0ddff62pU6dq69atGjBggAc6B1DdfD3dAADUFOXl5SooKNDZs2dVWFiorKwsZWRkqFevXkpNTdXmzZt1+vRpTZs2TQ888IDWrFmjzMzM87ZTt25d/fKXv9Rvf/tbde/eXU2aNPHA0QCobtyJAoD/k5WVpUaNGikqKko9evTQJ598oqlTp+r999+Xj4+P2rZtq8mTJ+sPf/iD2rRpo3nz5ikjI6PSbQ0aNEgVFRV67LHHrvJRALha+HQeAFSDOXPm6De/+Y0OHDggf39/T7cDoBrwdh4AVKETJ07ou+++00svvaQhQ4YQoIDrGG/nAUAVmjRpklq2bKnw8HCNGTPG0+0AqEa8nQcAAGCBO1EAAAAWCFEAAAAWCFEAAAAWCFEAAAAWCFEAAAAWCFEAAAAWCFEAAAAWCFEAAAAWCFEAAAAW/h/hXOEIe57R9QAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAzwElEQVR4nO3de1xU5b7H8e8AMpg6o6ZcJAI1rewChkpobjNR8pZ22amZkGVqadvklEkXkS5iNyOVMrtopablTq0wOgqlXeh4xCg1rczrUcE7I6igzDp/dJx9ZoMKCgwsP+/Xa/0xz3ouv8Uf+n2t9awZi2EYhgAAAEzCy9MFAAAAVCXCDQAAMBXCDQAAMBXCDQAAMBXCDQAAMBXCDQAAMBXCDQAAMBXCDQAAMBXCDQAAMBXCDYA6ae7cubJYLFq7dq1H1r/55pt18803e2RtAGdHuAFQrj///FOjRo1Sq1at5OfnJ5vNpi5duuj111/X8ePHq3y9Y8eOafLkyfrmm2+qfG4AFxcfTxcAoPZJT0/X3//+d1mtVsXFxenaa69VSUmJvvvuOz3++OPauHGjZs+eXaVrHjt2TMnJyZLEHREAF4RwA8DNtm3bNHjwYIWGhiorK0tBQUGuc2PGjNGWLVuUnp7uwQoB4Ox4LAXAzUsvvaTCwkK9++67bsHmtCuuuELjxo1zfT516pSee+45tW7dWlarVWFhYXryySdVXFzsNm7t2rWKjY1Vs2bNVL9+fbVs2VL333+/JGn79u1q3ry5JCk5OVkWi0UWi0WTJ08+Z73Hjh3TqFGjdOmll8pmsykuLk6HDx92nY+Pj1ezZs108uTJMmN79eqlK6+88pxrzJ49W61bt1b9+vXVqVMnffvtt2X6lJSUaNKkSYqMjJTdbleDBg3UtWtXff31164+hmEoLCxMAwYMKDP+xIkTstvtGjVq1DnrAXB2hBsAbj7//HO1atVKnTt3rlD/ESNGaNKkSbrhhhv02muvqVu3bkpJSdHgwYNdffbt26devXpp+/btmjhxombMmKGhQ4fqxx9/lCQ1b95cb775piTp9ttv14cffqgPP/xQd9xxxznXHzt2rDZt2qTJkycrLi5O8+fP18CBA2UYhiRp2LBhOnjwoL766iu3cXl5ecrKytK999571vnfffddjRo1SoGBgXrppZfUpUsX3Xbbbdq1a5dbP4fDoXfeeUc333yzXnzxRU2ePFn79+9XbGyscnNzJUkWi0X33nuvvvzySx06dMht/Oeffy6Hw3HOegBUgAEA/6egoMCQZAwYMKBC/XNzcw1JxogRI9zaH3vsMUOSkZWVZRiGYSxZssSQZPz3f//3Gefav3+/IclISkqq0Npz5swxJBmRkZFGSUmJq/2ll14yJBnLli0zDMMwSktLjcsuu8wYNGiQ2/hp06YZFovF2Lp16xnXKCkpMfz9/Y2IiAijuLjY1T579mxDktGtWzdX26lTp9z6GIZhHD582AgICDDuv/9+V9tvv/1mSDLefPNNt7633XabERYWZjidzgpdP4Az484NABeHwyFJatSoUYX6L1++XJKUkJDg1v4f//EfkuTam9O4cWNJ0hdffFHu46ELMXLkSNWrV8/1+aGHHpKPj4+rNi8vLw0dOlSfffaZjh496uo3f/58de7cWS1btjzj3GvXrtW+ffs0evRo+fr6utrvu+8+2e12t77e3t6uPk6nU4cOHdKpU6fUoUMHrVu3ztWvbdu2ioqK0vz5811thw4d0pdffqmhQ4fKYrGc518CwGkXdbhZvXq1+vfvrxYtWshisWjp0qXVut7kyZNdewlOH1dddVW1rglUhs1mkyS3EHA2O3bskJeXl6644gq39sDAQDVu3Fg7duyQJHXr1k133nmnkpOT1axZMw0YMEBz5swpsy/nfLRp08btc8OGDRUUFKTt27e72uLi4nT8+HEtWbJEkvTbb78pJydHw4YNO+f1lbdGvXr11KpVqzL933//fV1//fXy8/PTpZdequbNmys9PV0FBQVu/eLi4vT999+75v/kk0908uTJc9YDoGIu6nBTVFSk8PBwpaWl1dia11xzjfbu3es6vvvuuxpbGzgXm82mFi1aaMOGDZUad667DRaLRYsXL1Z2drbGjh2r3bt36/7771dkZKQKCwsvpOQKadeunSIjIzVv3jxJ0rx58+Tr66u77767ytaYN2+e7rvvPrVu3VrvvvuuMjIytGLFCt1yyy1yOp1ufQcPHqx69eq57t7MmzdPHTp0qNDmZgDndlGHm969e+v555/X7bffXu754uJiPfbYYwoODlaDBg0UFRV1wV8w5uPjo8DAQNfRrFmzC5oPqGr9+vXTn3/+qezs7HP2DQ0NldPp1B9//OHWnp+fryNHjig0NNSt/cYbb9QLL7ygtWvXav78+dq4caMWLlwo6dwB6Uz+fe3CwkLt3btXYWFhbu1xcXHKysrS3r17tWDBAvXt21dNmjQ55/WVt8bJkye1bds2t7bFixerVatW+vTTTzVs2DDFxsYqJiZGJ06cKDNv06ZN1bdvX82fP187duzQ999/z10boApd1OHmXMaOHavs7GwtXLhQv/zyi/7+97/r1ltvLfMPXWX88ccfatGihVq1aqWhQ4dq586dVVgxcOEmTJigBg0aaMSIEcrPzy9z/s8//9Trr78uSerTp48kKTU11a3PtGnTJEl9+/aVJB0+fNj19tJpERERkuR6NHXJJZdIko4cOVKpemfPnu22j+fNN9/UqVOn1Lt3b7d+Q4YMkcVi0bhx47R169YKvZXUoUMHNW/eXLNmzVJJSYmrfe7cuWXq9Pb2liS36/yv//qvM4bEYcOG6ddff9Xjjz8ub29vt7fLAFwgT+9ori0kGUuWLHF93rFjh+Ht7W3s3r3brV+PHj2MxMTE81pj+fLlxscff2z8/PPPRkZGhhEdHW1cfvnlhsPhuJDSgSq3bNkyw8/Pz2jSpIkxbtw44+233zbS0tKMoUOHGr6+vsbIkSNdfePj4w1Jxt13322kpaW5Pg8cONDV57XXXjPatGljTJgwwXjrrbeMV155xbjyyisNm83m9rZSu3btjMDAQCMtLc346KOPjPXr15+xxtNvS1133XVG165djRkzZhhjx441vLy8jJtuuqnct4769etnSDIaN25snDhxokJ/i7feesuQZHTp0sWYPn26MX78eKNx48ZGq1at3N6Weu+99wxJxm233Wa89dZbxsSJE43GjRsb11xzjREaGlpm3uLiYuPSSy81JBm9e/euUC0AKoZw83/+Pdx88cUXhiSjQYMGboePj49x9913G4ZhGJs2bTIknfV44oknzrjm4cOHDZvNZrzzzjvVfXlApf3+++/Ggw8+aISFhRm+vr5Go0aNjC5duhgzZsxwCwYnT540kpOTjZYtWxr16tUzQkJCjMTERLc+69atM4YMGWJcfvnlhtVqNfz9/Y1+/foZa9eudVvzhx9+MCIjIw1fX99zvhZ+OtysWrXKGDlypNGkSROjYcOGxtChQ42DBw+WO+bjjz82JLmFs4p44403jJYtWxpWq9Xo0KGDsXr1aqNbt25u4cbpdBpTpkwxQkNDDavVarRv39744osvjPj4+HLDjWEYxsMPP2xIMhYsWFCpegCcncUw/u1e8UXKYrFoyZIlGjhwoCRp0aJFGjp0qDZu3Oi63Xxaw4YNFRgYqJKSEm3duvWs855+Y+JMOnbsqJiYGKWkpFzwNQA4u2XLlmngwIFavXq1unbt6ulyNH78eL377rvKy8tzPZYDcOH4bakzaN++vUpLS7Vv374z/iPo6+t7Qa9yFxYW6s8//2QjIVBD3n77bbVq1Uo33XSTp0vRiRMnNG/ePN15550EG6CKXdThprCwUFu2bHF93rZtm3Jzc9W0aVO1bdtWQ4cOVVxcnF599VW1b99e+/fvV2Zmpq6//nrXRsnKeOyxx9S/f3+FhoZqz549SkpKkre3t4YMGVKVlwXg35x+KSA9PV2vv/66R78ob9++fVq5cqUWL16sgwcPuv1OF4Aq4unnYp709ddfl7tPJj4+3jCMv756fdKkSUZYWJhRr149IygoyLj99tuNX3755bzWGzRokBEUFGT4+voawcHBxqBBg4wtW7ZU4RUBKI8ko2HDhsYDDzxgnDx50qO1nP53x9/f35gxY4ZHawHMij03AADAVPieGwAAYCqEGwAAYCoX3YZip9OpPXv2qFGjRvz6LgAAdYRhGDp69KhatGghL6+z35u56MLNnj17FBIS4ukyAADAedi1a5cuu+yys/a56MJNo0aNJP31x7HZbB6uBgAAVITD4VBISIjr//GzuejCzelHUTabjXADAEAdU5EtJWwoBgAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApkK4AQAApuLRcLN69Wr1799fLVq0kMVi0dKlSys89vvvv5ePj48iIiKqrT4AAFD3eDTcFBUVKTw8XGlpaZUad+TIEcXFxalHjx7VVBkAAKirPPrDmb1791bv3r0rPW706NG655575O3tXam7PQAAwPzq3J6bOXPmaOvWrUpKSvJ0KQAAoBby6J2byvrjjz80ceJEffvtt/LxqVjpxcXFKi4udn12OBzVVR4AAKgF6ky4KS0t1T333KPk5GS1bdu2wuNSUlKUnJxcjZW5C5uYXmNrAQBQG22f2tej69eZx1JHjx7V2rVrNXbsWPn4+MjHx0fPPvusfv75Z/n4+CgrK6vccYmJiSooKHAdu3btquHKAQBATaozd25sNpvWr1/v1vbGG28oKytLixcvVsuWLcsdZ7VaZbVaa6JEAABQC3g03BQWFmrLli2uz9u2bVNubq6aNm2qyy+/XImJidq9e7c++OADeXl56dprr3Ub7+/vLz8/vzLtAADg4uXRcLN27Vp1797d9TkhIUGSFB8fr7lz52rv3r3auXOnp8oDAAB1kMUwDMPTRdQkh8Mhu92ugoIC2Wy2Kp+fDcUAgItddWworsz/33VmQzEAAEBFEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpEG4AAICpeDTcrF69Wv3791eLFi1ksVi0dOnSs/b/9NNP1bNnTzVv3lw2m03R0dH66quvaqZYAABQJ3g03BQVFSk8PFxpaWkV6r969Wr17NlTy5cvV05Ojrp3767+/fvrp59+quZKAQBAXeHjycV79+6t3r17V7h/amqq2+cpU6Zo2bJl+vzzz9W+ffsqrg4AANRFHg03F8rpdOro0aNq2rTpGfsUFxeruLjY9dnhcNREaQAAwEPq9IbiV155RYWFhbr77rvP2CclJUV2u911hISE1GCFAACgptXZcLNgwQIlJyfr448/lr+//xn7JSYmqqCgwHXs2rWrBqsEAAA1rU4+llq4cKFGjBihTz75RDExMWfta7VaZbVaa6gyAADgaXXuzs1HH32k4cOH66OPPlLfvn09XQ4AAKhlPHrnprCwUFu2bHF93rZtm3Jzc9W0aVNdfvnlSkxM1O7du/XBBx9I+utRVHx8vF5//XVFRUUpLy9PklS/fn3Z7XaPXAMAAKhdPHrnZu3atWrfvr3rNe6EhAS1b99ekyZNkiTt3btXO3fudPWfPXu2Tp06pTFjxigoKMh1jBs3ziP1AwCA2sejd25uvvlmGYZxxvNz5851+/zNN99Ub0EAAKDOq3N7bgAA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", "text/plain": [ "
" ] @@ -290,6 +254,13 @@ "# Visualize the cost by day\n", "OpenAICostTrackerViz.plot_cost_by_day(path=DEFAULT_LOG_PATH)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -308,7 +279,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.12.2" } }, "nbformat": 4, From e41a43dc9cb46396c9c855fb4bffa21012363f1d Mon Sep 17 00:00:00 2001 From: laz4rz Date: Sat, 30 Mar 2024 14:22:02 +0100 Subject: [PATCH 06/12] update proposal --- changes_proposal.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/changes_proposal.md b/changes_proposal.md index 4374bd2..49d5ff5 100644 --- a/changes_proposal.md +++ b/changes_proposal.md @@ -36,4 +36,14 @@ Change: } ``` -3. WIP \ No newline at end of file +3. model has to be provided in form of enum +Change: + - we can just infer it from `response.model` + - removes possible problems with choosing the right enum or forgetting to change it while changing the model for experiment + +4. datetime strftime format +Change: + - change strftime format to `strftime("%Y-%m-%d_%H:%M:%S")`, makes it more readable + - we could possibly infer the datetime and do plots with datetime instead of str + +5. WIP \ No newline at end of file From 0d6f0b193444d4781b71281cd1d907d75ea51891 Mon Sep 17 00:00:00 2001 From: Laz4rz <62252332+Laz4rz@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:30:00 +0100 Subject: [PATCH 07/12] Update changes_proposal.md --- changes_proposal.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/changes_proposal.md b/changes_proposal.md index 49d5ff5..5106e33 100644 --- a/changes_proposal.md +++ b/changes_proposal.md @@ -1,11 +1,11 @@ -1. cost tracker handles completion creation +1. ✅ cost tracker handles completion creation Change: separating completion and cost tracking, by changing the main functionality from `chat_completion` to `update_cost` Motivation: - bulletproofs us from changes in how the completion is created, we only care about response structure - allows easier integration, user only has to initialize tracker object and call `update_cost(response)`, otherwise each chat completion call would have to be rewritten -2. costs are calculated across all log files +2. ⌛ costs are calculated across all log files Change: - static `total_cost` that will calculate total spending from logs - static `experiment_cost(experiment_name=self.experiment_name)` gets you total cost of specific experiment @@ -13,7 +13,7 @@ Change: - if object not initialized, experiment_name has to be provided - `cost` that gets you costs for current run of this tracker object -3. log file just acumulates total cost +3. ⌛ log file just acumulates total cost Change: - add breakdown of responses/input token per response/output token per response/cost per response - maybe change log file format to json, so that we can better handle logs, for example: @@ -36,14 +36,14 @@ Change: } ``` -3. model has to be provided in form of enum +4. ⌛ model has to be provided in form of enum Change: - we can just infer it from `response.model` - removes possible problems with choosing the right enum or forgetting to change it while changing the model for experiment -4. datetime strftime format +5. ⌛ datetime strftime format Change: - change strftime format to `strftime("%Y-%m-%d_%H:%M:%S")`, makes it more readable - we could possibly infer the datetime and do plots with datetime instead of str -5. WIP \ No newline at end of file +6. WIP From 1a733320e4cae2d7d1d5dff144d6401c63b612d2 Mon Sep 17 00:00:00 2001 From: Laz4rz <62252332+Laz4rz@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:32:10 +0100 Subject: [PATCH 08/12] Update changes_proposal.md --- changes_proposal.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changes_proposal.md b/changes_proposal.md index 5106e33..3b49e3f 100644 --- a/changes_proposal.md +++ b/changes_proposal.md @@ -1,11 +1,14 @@ 1. ✅ cost tracker handles completion creation + Change: separating completion and cost tracking, by changing the main functionality from `chat_completion` to `update_cost` + Motivation: - bulletproofs us from changes in how the completion is created, we only care about response structure - allows easier integration, user only has to initialize tracker object and call `update_cost(response)`, otherwise each chat completion call would have to be rewritten 2. ⌛ costs are calculated across all log files + Change: - static `total_cost` that will calculate total spending from logs - static `experiment_cost(experiment_name=self.experiment_name)` gets you total cost of specific experiment @@ -14,6 +17,7 @@ Change: - `cost` that gets you costs for current run of this tracker object 3. ⌛ log file just acumulates total cost + Change: - add breakdown of responses/input token per response/output token per response/cost per response - maybe change log file format to json, so that we can better handle logs, for example: @@ -37,11 +41,13 @@ Change: ``` 4. ⌛ model has to be provided in form of enum + Change: - we can just infer it from `response.model` - removes possible problems with choosing the right enum or forgetting to change it while changing the model for experiment 5. ⌛ datetime strftime format + Change: - change strftime format to `strftime("%Y-%m-%d_%H:%M:%S")`, makes it more readable - we could possibly infer the datetime and do plots with datetime instead of str From 075c2d55799db06304ed43a131ee34bfa0d36148 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Tue, 2 Apr 2024 13:23:09 +0200 Subject: [PATCH 09/12] PR ready --- changes_proposal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes_proposal.md b/changes_proposal.md index 3b49e3f..6d76c37 100644 --- a/changes_proposal.md +++ b/changes_proposal.md @@ -1,4 +1,4 @@ -1. ✅ cost tracker handles completion creation +1. ✅ cost tracker handles completion creation - PR ready Change: separating completion and cost tracking, by changing the main functionality from `chat_completion` to `update_cost` From 04ae3d79b0b1199cd807de1c65677b7eb66111b1 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Tue, 2 Apr 2024 13:42:19 +0200 Subject: [PATCH 10/12] clean demo --- demo.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo.ipynb b/demo.ipynb index ce6ba86..5f88d0e 100644 --- a/demo.ipynb +++ b/demo.ipynb @@ -85,11 +85,11 @@ "# Export the proper environment variables based on the client you are using.\n", "\n", "# OpenAI API Key\n", - "# os.environ[\"OPENAI_ORGANIZATION\"] = OpenAICostTrackerUtils.get_api_key(path='openai_organization.txt')\n", - "os.environ[\"OPENAI_API_KEY\"] = OpenAICostTrackerUtils.get_api_key(path='/Users/mikolajboronski/.openai')\n", + "os.environ[\"OPENAI_ORGANIZATION\"] = OpenAICostTrackerUtils.get_api_key(path='openai_organization.txt')\n", + "os.environ[\"OPENAI_API_KEY\"] = OpenAICostTrackerUtils.get_api_key(path='openai_api_key.txt')\n", "\n", "# Azure OpenAI API Key\n", - "# os.environ[\"AZURE_OPENAI_KEY\"] = OpenAICostTrackerUtils.get_api_key('azure_openai_key.txt')" + "os.environ[\"AZURE_OPENAI_KEY\"] = OpenAICostTrackerUtils.get_api_key('azure_openai_key.txt')" ] }, { @@ -105,7 +105,7 @@ "metadata": {}, "outputs": [], "source": [ - "experiment_name = \"Demo2\"\n", + "experiment_name = \"Demo\"\n", "messages = [\n", " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", " {\"role\": \"user\", \"content\": \"Who won the euro 2020?\"},\n", From 055cfec2393d3d6ef4c56744c7dd3eaaab16db71 Mon Sep 17 00:00:00 2001 From: laz4rz Date: Tue, 2 Apr 2024 13:55:19 +0200 Subject: [PATCH 11/12] modify the strftime date format for better readability --- src/openai_cost_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openai_cost_tracker.py b/src/openai_cost_tracker.py index f76d335..16b40d8 100644 --- a/src/openai_cost_tracker.py +++ b/src/openai_cost_tracker.py @@ -47,7 +47,7 @@ def __init__( self.output_cost = output_cost self.experiment_name = experiment_name self.cost_upperbound = cost_upperbound - self.filename = f"{experiment_name}_cost_" + strftime("%Y%m%d_%H%M%S") + ".csv" + self.filename = f"{experiment_name}_cost_" + strftime("%Y-%m-%d_%H:%M:%S") + ".csv" def update_cost(self, response: ChatCompletion) -> None: From c94fd91c1205435feceb85ed353f106ae6ed668a Mon Sep 17 00:00:00 2001 From: laz4rz Date: Tue, 2 Apr 2024 13:56:06 +0200 Subject: [PATCH 12/12] update the status of changes_proposal.md --- changes_proposal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes_proposal.md b/changes_proposal.md index 6d76c37..60dd0bc 100644 --- a/changes_proposal.md +++ b/changes_proposal.md @@ -46,7 +46,7 @@ Change: - we can just infer it from `response.model` - removes possible problems with choosing the right enum or forgetting to change it while changing the model for experiment -5. ⌛ datetime strftime format +5. ✅ datetime strftime format - PR ready Change: - change strftime format to `strftime("%Y-%m-%d_%H:%M:%S")`, makes it more readable