From f1aa34bef235daa1f0f5d450f16c58bbf1d01b27 Mon Sep 17 00:00:00 2001 From: timothyafolami Date: Thu, 8 Aug 2024 22:06:39 +0100 Subject: [PATCH] image pipeline perfected. audio pipeline in progress --- .gitignore | 3 +- audio_experiment.ipynb | 135 ++++++++++++++++ data/documents.json | 2 +- .../__pycache__/utils.cpython-311.pyc | Bin 11516 -> 13522 bytes data_ingestion/utils.py | 73 ++++++++- image_experiment.ipynb | 152 +++++++++++------- loggings/app.log | 13 ++ requirements.txt | 4 +- vec-db/index/faiss_index_data/index.faiss | Bin 984621 -> 984621 bytes vec-db/index/faiss_index_data/index.pkl | Bin 982775 -> 985207 bytes 10 files changed, 319 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index 0fe238ac..45ea6e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ Ai indexing data -images \ No newline at end of file +images +.env \ No newline at end of file diff --git a/audio_experiment.ipynb b/audio_experiment.ipynb index e69de29b..47b27596 100644 --- a/audio_experiment.ipynb +++ b/audio_experiment.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install groq" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from groq import Groq\n", + "import os\n", + "import pandas as pd\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "client = Groq(api_key = os.getenv('GROQ_API_KEY'))\n", + "model = 'whisper-large-v3'" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Audio to Text\n", + "def audio_to_text(filepath):\n", + " with open(filepath, \"rb\") as file:\n", + " translation = client.audio.translations.create(\n", + " file=(filepath, file.read()),\n", + " model=\"whisper-large-v3\",\n", + " )\n", + " return translation.text" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "APIStatusError", + "evalue": "Error code: 413 - {'error': {'message': 'Request Entity Too Large', 'type': 'invalid_request_error', 'code': 'request_too_large'}}", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAPIStatusError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[10], line 3\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# testing the function\u001b[39;00m\n\u001b[0;32m 2\u001b[0m path \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdata/audio-1.mp3\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m----> 3\u001b[0m transcript \u001b[38;5;241m=\u001b[39m \u001b[43maudio_to_text\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[1;32mIn[6], line 4\u001b[0m, in \u001b[0;36maudio_to_text\u001b[1;34m(filepath)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21maudio_to_text\u001b[39m(filepath):\n\u001b[0;32m 3\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mopen\u001b[39m(filepath, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrb\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m file:\n\u001b[1;32m----> 4\u001b[0m translation \u001b[38;5;241m=\u001b[39m \u001b[43mclient\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43maudio\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtranslations\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mcreate\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 5\u001b[0m \u001b[43m \u001b[49m\u001b[43mfile\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mfilepath\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfile\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 6\u001b[0m \u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mwhisper-large-v3\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[0;32m 7\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 8\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m translation\u001b[38;5;241m.\u001b[39mtext\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\resources\\audio\\translations.py:103\u001b[0m, in \u001b[0;36mTranslations.create\u001b[1;34m(self, file, model, prompt, response_format, temperature, extra_headers, extra_query, extra_body, timeout)\u001b[0m\n\u001b[0;32m 98\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m files:\n\u001b[0;32m 99\u001b[0m \u001b[38;5;66;03m# It should be noted that the actual Content-Type header that will be\u001b[39;00m\n\u001b[0;32m 100\u001b[0m \u001b[38;5;66;03m# sent to the server will contain a `boundary` parameter, e.g.\u001b[39;00m\n\u001b[0;32m 101\u001b[0m \u001b[38;5;66;03m# multipart/form-data; boundary=---abc--\u001b[39;00m\n\u001b[0;32m 102\u001b[0m extra_headers \u001b[38;5;241m=\u001b[39m {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mContent-Type\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmultipart/form-data\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39m(extra_headers \u001b[38;5;129;01mor\u001b[39;00m {})}\n\u001b[1;32m--> 103\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_post\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 104\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43m/openai/v1/audio/translations\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[0;32m 105\u001b[0m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmaybe_transform\u001b[49m\u001b[43m(\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtranslation_create_params\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mTranslationCreateParams\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 106\u001b[0m \u001b[43m \u001b[49m\u001b[43mfiles\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfiles\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 107\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmake_request_options\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 108\u001b[0m \u001b[43m \u001b[49m\u001b[43mextra_headers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mextra_headers\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mextra_query\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mextra_query\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mextra_body\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mextra_body\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtimeout\u001b[49m\n\u001b[0;32m 109\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 110\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mTranslation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 111\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:1225\u001b[0m, in \u001b[0;36mSyncAPIClient.post\u001b[1;34m(self, path, cast_to, body, options, files, stream, stream_cls)\u001b[0m\n\u001b[0;32m 1211\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mpost\u001b[39m(\n\u001b[0;32m 1212\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m 1213\u001b[0m path: \u001b[38;5;28mstr\u001b[39m,\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 1220\u001b[0m stream_cls: \u001b[38;5;28mtype\u001b[39m[_StreamT] \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m 1221\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m ResponseT \u001b[38;5;241m|\u001b[39m _StreamT:\n\u001b[0;32m 1222\u001b[0m opts \u001b[38;5;241m=\u001b[39m FinalRequestOptions\u001b[38;5;241m.\u001b[39mconstruct(\n\u001b[0;32m 1223\u001b[0m method\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpost\u001b[39m\u001b[38;5;124m\"\u001b[39m, url\u001b[38;5;241m=\u001b[39mpath, json_data\u001b[38;5;241m=\u001b[39mbody, files\u001b[38;5;241m=\u001b[39mto_httpx_files(files), \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39moptions\n\u001b[0;32m 1224\u001b[0m )\n\u001b[1;32m-> 1225\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m cast(ResponseT, \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mopts\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m)\u001b[49m)\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:920\u001b[0m, in \u001b[0;36mSyncAPIClient.request\u001b[1;34m(self, cast_to, options, remaining_retries, stream, stream_cls)\u001b[0m\n\u001b[0;32m 911\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mrequest\u001b[39m(\n\u001b[0;32m 912\u001b[0m \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m 913\u001b[0m cast_to: Type[ResponseT],\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 918\u001b[0m stream_cls: \u001b[38;5;28mtype\u001b[39m[_StreamT] \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m 919\u001b[0m ) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m ResponseT \u001b[38;5;241m|\u001b[39m _StreamT:\n\u001b[1;32m--> 920\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 921\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 922\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 923\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 924\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 925\u001b[0m \u001b[43m \u001b[49m\u001b[43mremaining_retries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mremaining_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 926\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:960\u001b[0m, in \u001b[0;36mSyncAPIClient._request\u001b[1;34m(self, cast_to, options, remaining_retries, stream, stream_cls)\u001b[0m\n\u001b[0;32m 957\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEncountered httpx.TimeoutException\u001b[39m\u001b[38;5;124m\"\u001b[39m, exc_info\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[0;32m 959\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m retries \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m--> 960\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_retry_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 961\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 962\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 963\u001b[0m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 964\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 965\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 966\u001b[0m \u001b[43m \u001b[49m\u001b[43mresponse_headers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m 967\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 969\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRaising timeout error\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 970\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m APITimeoutError(request\u001b[38;5;241m=\u001b[39mrequest) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:1051\u001b[0m, in \u001b[0;36mSyncAPIClient._retry_request\u001b[1;34m(self, options, cast_to, remaining_retries, response_headers, stream, stream_cls)\u001b[0m\n\u001b[0;32m 1047\u001b[0m \u001b[38;5;66;03m# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a\u001b[39;00m\n\u001b[0;32m 1048\u001b[0m \u001b[38;5;66;03m# different thread if necessary.\u001b[39;00m\n\u001b[0;32m 1049\u001b[0m time\u001b[38;5;241m.\u001b[39msleep(timeout)\n\u001b[1;32m-> 1051\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 1052\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1053\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1054\u001b[0m \u001b[43m \u001b[49m\u001b[43mremaining_retries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mremaining\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1055\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1056\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1057\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:975\u001b[0m, in \u001b[0;36mSyncAPIClient._request\u001b[1;34m(self, cast_to, options, remaining_retries, stream, stream_cls)\u001b[0m\n\u001b[0;32m 972\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEncountered Exception\u001b[39m\u001b[38;5;124m\"\u001b[39m, exc_info\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[0;32m 974\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m retries \u001b[38;5;241m>\u001b[39m \u001b[38;5;241m0\u001b[39m:\n\u001b[1;32m--> 975\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_retry_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 976\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 977\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 978\u001b[0m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 979\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 980\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 981\u001b[0m \u001b[43m \u001b[49m\u001b[43mresponse_headers\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[0;32m 982\u001b[0m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 984\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRaising connection error\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m 985\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m APIConnectionError(request\u001b[38;5;241m=\u001b[39mrequest) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:1051\u001b[0m, in \u001b[0;36mSyncAPIClient._retry_request\u001b[1;34m(self, options, cast_to, remaining_retries, response_headers, stream, stream_cls)\u001b[0m\n\u001b[0;32m 1047\u001b[0m \u001b[38;5;66;03m# In a synchronous context we are blocking the entire thread. Up to the library user to run the client in a\u001b[39;00m\n\u001b[0;32m 1048\u001b[0m \u001b[38;5;66;03m# different thread if necessary.\u001b[39;00m\n\u001b[0;32m 1049\u001b[0m time\u001b[38;5;241m.\u001b[39msleep(timeout)\n\u001b[1;32m-> 1051\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m 1052\u001b[0m \u001b[43m \u001b[49m\u001b[43moptions\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43moptions\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1053\u001b[0m \u001b[43m \u001b[49m\u001b[43mcast_to\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcast_to\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1054\u001b[0m \u001b[43m \u001b[49m\u001b[43mremaining_retries\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mremaining\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1055\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1056\u001b[0m \u001b[43m \u001b[49m\u001b[43mstream_cls\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstream_cls\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m 1057\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[1;32mc:\\Users\\timmy_3aupohg\\anaconda3\\envs\\smog_env\\Lib\\site-packages\\groq\\_base_client.py:1018\u001b[0m, in \u001b[0;36mSyncAPIClient._request\u001b[1;34m(self, cast_to, options, remaining_retries, stream, stream_cls)\u001b[0m\n\u001b[0;32m 1015\u001b[0m err\u001b[38;5;241m.\u001b[39mresponse\u001b[38;5;241m.\u001b[39mread()\n\u001b[0;32m 1017\u001b[0m log\u001b[38;5;241m.\u001b[39mdebug(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRe-raising status error\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m-> 1018\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_make_status_error_from_response(err\u001b[38;5;241m.\u001b[39mresponse) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m 1020\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_process_response(\n\u001b[0;32m 1021\u001b[0m cast_to\u001b[38;5;241m=\u001b[39mcast_to,\n\u001b[0;32m 1022\u001b[0m options\u001b[38;5;241m=\u001b[39moptions,\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 1025\u001b[0m stream_cls\u001b[38;5;241m=\u001b[39mstream_cls,\n\u001b[0;32m 1026\u001b[0m )\n", + "\u001b[1;31mAPIStatusError\u001b[0m: Error code: 413 - {'error': {'message': 'Request Entity Too Large', 'type': 'invalid_request_error', 'code': 'request_too_large'}}" + ] + } + ], + "source": [ + "# testing the function\n", + "path = \"data/audio-1.mp3\"\n", + "transcript = audio_to_text(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " hello everyone so i'm timothy i'm doing this recording to talk about something i need about cars okay let's say very correct okay so what is a car the guy is a mechanical object designed to transport people from one location to the other so that's like how i would define a car for you right okay so what do i need about a car how do i fix something I kind of know little and that little is a much on its own right I have very good understanding of like service is very cool, a car, you know changing the oil, removing the oil filter, changing the oil filter like that like that right so I think um yeah yeah yeah yeah so what else you don't see what else should I say? alright this is good it's good enough yeah thanks bye stop\n" + ] + } + ], + "source": [ + "print(transcript)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "smog_env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/data/documents.json b/data/documents.json index e8bf3afc..bfe9f6d9 100644 --- a/data/documents.json +++ b/data/documents.json @@ -1 +1 @@ -{"doc_names": ["Car-Repair-Receipt-repair", "Car-Repair-Receipt-service", "Car-Repair-Receipt-tire", "Car-Repair-Receipt-tuning", "Car-Repair-Receipt-wash", "corolla-2020-toyota-owners-manual", "data\\dodge-challenger-auto-body-repair-after", "data\\dodge-challenger-auto-body-repair-before", "How to change engine oil and filter on TOYOTA Corolla", "How to change front brake pads on TOYOTA Corolla", "How to change rear windshield wipers on TOYOTA Corolla", "How to change spark plugs on TOYOTA COROLLA", "data\\hyundai-sonata-auto-body-repair-after", "data\\hyundai-sonata-auto-body-repair-before", "data\\IMG_1436", "data\\IMG_1437", "data\\IMG_1438", "data\\IMG_1440", "data\\IMG_1441", "data\\IMG_1442", "data\\IMG_1443", "data\\IMG_1444", "data\\pontiac-vibe-auto-body-repair-after", "data\\pontiac-vibe-auto-body-repair-before", "data\\toyota-tacoma-auto-body-repair-after", "data\\toyota-tacoma-auto-body-repair-before"], "docs_id": ["5f26879376a44a77bbc2b966b9189ca4", "51b1c6cab5f1440e9fd948b6d858e812", "1d63ef4a149d4addb0803370885d70c1", "749ea365f2244eb6b23bb17e28d9cd2e", "e6d3736c0e8f424382c2ff5298814534", "91b116993e4b4865b3dc7bceca9749f0", "77f9558bd9894daeaf9aaea4013ed20e", "d974631f67d242739343b3c32e91355c", "a18ad23b3c7641b3a61e77e0e143a265", "0b710683db314b14ae6f0e0919a12068", "136c808efffa4f8798c55e7595c768a1", "236dc9603c9c4e83840721175d3dc861", "5aa9f750dbdd403c94abb53883c0fad2", "0382e54d68a84021803b07c7cf7c3ad9", "a772d008c9bf4ee6a2026f00998f3f2c", "66afb44563f6449ca705a39c9a72440d", "59ef1e9cc81b41d3a32d5dcc069a0ace", "9991145202384596bc3f5ff666d213bd", "d7f49b6629e84ec7bfd1a0048d2ade76", "689296161d6b46e8b9e792dbdc8a155d", "ba6be2ab8ae74042a9c9da51c46b8f90", "d62daf66b833419fae17333395cd7b04", "7b109e03c62343fd8f8e23dcf6bdfd3b", "8254386611fc4feb85744f69e5120e18", "022d6bae08274a618921c49590040a1f", "719ed0e4d9a94fe39799c227eaac1e05"], "num_pages": [1, 2, 2, 2, 1, 588, 1, 1, 6, 7, 6, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} \ No newline at end of file +{"doc_names": ["Car-Repair-Receipt-repair", "Car-Repair-Receipt-service", "Car-Repair-Receipt-tire", "Car-Repair-Receipt-tuning", "Car-Repair-Receipt-wash", "corolla-2020-toyota-owners-manual", "data\\dodge-challenger-auto-body-repair-after", "data\\dodge-challenger-auto-body-repair-before", "How to change engine oil and filter on TOYOTA Corolla", "How to change front brake pads on TOYOTA Corolla", "How to change rear windshield wipers on TOYOTA Corolla", "How to change spark plugs on TOYOTA COROLLA", "data\\hyundai-sonata-auto-body-repair-after", "data\\hyundai-sonata-auto-body-repair-before", "data\\IMG_1436", "data\\IMG_1437", "data\\IMG_1438", "data\\IMG_1440", "data\\IMG_1441", "data\\IMG_1442", "data\\IMG_1443", "data\\IMG_1444", "data\\pontiac-vibe-auto-body-repair-after", "data\\pontiac-vibe-auto-body-repair-before", "data\\toyota-tacoma-auto-body-repair-after", "data\\toyota-tacoma-auto-body-repair-before"], "docs_id": ["8cb89c7137934d26bb4d277bacf3502c", "b71d912e8f2e487ebe8234e5b115261a", "85899825f6564cd2ace41045b9b3933a", "0f4160aa7a7544b9912203707684b580", "5dfef1a8108d42239dfacb145ff65b47", "cf8d2cd0ff2e41f28c6ca6e831ac11e0", "9f13671afa5b4572b3d87a974e9ae987", "b61bf58bed3441b98d017a8273592a6e", "2d60dcf3360a4d3f8bdf26ae7d7a7710", "c041d9b8d4d34845b1eec7076b9e397a", "51dfede7f1424c9a860aa6d0ac2aba9d", "9d5ae90bdb2f42c9a4a7eff8222f9299", "38e241f38b964f6598f68a7bdc7d6fd0", "3e28189980ef48e3924225a4795ee4f6", "b503ccb23a6140e4b3479cd0540bc7db", "56a6e12c0f4e492abf84ed420332dd53", "278b4e52edae4a34be3e6ac8b8c6fea0", "16b5cb0fd5994cb3b05881c2bd7f079b", "4f26ca03b4f74f0287cb4ec163d8a8fe", "74987f862eaf43008779404633d00352", "db78336a07d3424badbb508bdbc951dd", "4c993794c7244ce6a29bb4a6e36b035f", "004cceb280574f818e3f654bb70539ab", "a1f5ec1368c845e3b8f6c31fb7116238", "d193544fc7ce45139b3f1e85d9edbde2", "06c837414be84b49b84b505628927814"], "num_pages": [1, 2, 2, 2, 1, 588, 1, 1, 6, 7, 6, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} \ No newline at end of file diff --git a/data_ingestion/__pycache__/utils.cpython-311.pyc b/data_ingestion/__pycache__/utils.cpython-311.pyc index 55171dc78c788b7d1432e38b24c2625240c5ecb1..522417b79c857c6ac567cb56378a449e7e767d31 100644 GIT binary patch delta 3746 zcmZu!du&_P8NcUVU)%AG?Id;{wv*I(HaCx^)f!4d(~?3NEu)2%37WEbuFs9*#xKsj zrfo=Ocd9o3u}O4?4cZlm#wb*=3DEqTe>3%lddky63a2*yBri}Y**Muq@!d4p z1n=5pbE;*sg$SacwVVTumr%`W7R`4gaBWQ8=LqoOskCU2dQJ+HAX@GvUmuO+24bX1d3A%$+Iv}V$pQ5GbiP>^@E*?*$d4#;MK_#Y= z%tUE~W(+O;sOi~%-|l@Adm6}qa8q086^f;HbQ73wy4nNFIC9RrhN>}K!0ms8@sonF7At}RlOl}%eFtEEl*;i#_Nvem3Pe9KlX9m`P7v@tD8O*_?aq>831k&4E(NH%Ir zSNeG^GL=YbI+}@2gqK-cQI1%$Y(J2u;L%S56oe}drMUMg+p?prs*;G4Mmz5nQWk*>8VM}8j{*lKS{)Mnkyjkz+ z)vGrnuhaB;Fz?jpM6-0U*)|b!Tx=(RvFn^AI6wkj^Upq@RvpRTbq4a3P|;X(lpy?6 zcv_^=S#e&h5vYyI)J`4Lc~+z~)J2s!g8T%bwP(e+NZo*hdZ_oTG%p!4%wKIs*1~|+ z&53i;%k}4g^3oc0-Zp1j!1Uoi_Sa)vsP zSM#h z(QIN9=1DZMDV9kMKe}Z&2Br=}KbzEW6Y@H&;FyMt(U@-9a7$pR=}aP~=?ufebW~Sh z55nm4?mhTr;dI6b$1@ok*3y|=d^$XpVc{&xfRdg_$5p$PL8b$ikIZPtO()YH$!WR) zD`Yde!5+qmYjz)tY56Q#0SfmbskX8M)hs+@EuW1Z#}b8e=-EtK*G!`6c3?pPhE}a& z)y1ot)4-mAN4LXf6@<^6fEEc68qcBvWyZ4iV|)r z%bxm%vVb7f1w7?WA=F;p==ZtG0zlF6*`VNWePVp6BY*GGgWp=xl1skSvM*KgrHal~ zU+Bs4AB~^d`NYnJ(UNcI#c0X5`ISJ)ciV6Glzev;oki!>D~cb=fIPMiE51?WT>C-o zxC+Hb9>cd?sPz~2>saWXnFvmd3KvIRyS&odlDI4ElHPX7Fu0*=V1`;%;~~OND1-d6 z`+d)xjoN3dg2B8-4ga+g+>Y5yoikQ;GrysLR<+I9=ERq4ZmjR~@|+we2P)Ov#Xs|tBbOW_OOBDhc$yaOeC5_9&-P`{_L66N zQCh9@6?4VfRZzCEAb+_2%?2zSrUdD+z5zoGE%FH<|BCNz7>5(M*l>u<^G_Pa+fbMl zP{XOE4r_4fIjrNpTN%K2_*&%e!Dx{m@@*l(#UJ|SWzx@;z#%0GWGj)`41XlhMpFE7 zx1T>3*w>&VXBc38eJQw$e8TSvZiKbd!R`5D;ky$hy*G#*14AgaYH!`peSob8;MsumG+Q7aK6w_(Bk!JWn zsK03w%2Gu`OKfdF;O>x#MG~~S!3C?tum%2M$Dn8@ytZ>7fbn@= zWH_TKfh2njdMaTBeqU$zk@GOH_P}&UDc(;j(WNu=ty;q@XI^IwieCUQYai7xZX-H? zX8Fc+X7TcZOS}aVdJO>h+-kSO{qw`lfczfN{=>iMd>=gjUe`S7#P4FSBfJUlrOyD$ z>=xeN+s6Oh;ji;Ufn*m^)JnOx`T3rK`!ST*+DEW=aERVioi{c8UF2gRF}y8S>~uo! zIx~BEyX@}+$xHaY-ZNn5r@fm=T^hx+2ptG1uJ*Nmy$ic40;aU;FhzRUl;QYb$4xS0 zUBFE#noP0@l(CWO|MDFxg1cb- zRs?Us`mG4h^^EUdw1K0f$+8=Ch&DloG= delta 1880 zcmZ`(duW?g6wl48X&z0RCTY?~nxv8L>tlVbu3$IE3c9&5S?9Cu4BvWhwz{;*?w6KU zTiRkL6GZ5a4iO(4(>-wiNVGB^Y#@k#ROmz*d?NE7!T%=yrzm>POqtq-X+UW$Z8RxODKDv6x13Db011-cm-mF z`w^qO60w$7A=dE#Vm)uFV>~Pxk*-;?55;*zBzP3RT9L>&c^&eS zTX{iVkIW_>=P@)+aoZ7#mVj$)1s1JedIIQ+tR@J;pzVyK2Fa@89b1dlnILWw{LTX` z3B%6E{b6FHHXsH!oHtnn7RtV7DHw6JbZR7OMKJ6Laa@v2%&Kfo=jp<9AV^%q(iIP&>2Bz9kFY-U zF$$O6i>w9J5@h#gUNWrOKn8o1G_6CS}E8(NyYD=LU`Ri-eK5M<^x@5(Qz^}CC9J7w& z4AWB%KZU;bnoJlZqcFnFa39OSZ{htOI$XvPGzSdUXpee{beit5as9YR=k>yg4SGT! z7cz-rv!Wm{`uItGw2+gfIBr9U;Y6TJZR9EhYvGVbK8fsSNd7YXTr*YogFA5Eer0;i z-FM5~_pQ5cVcuy<#Vg$DC*?lVr>_T6u+yTbFgi2BtGnUm`o{5 z@*%h$Z(#f2cKlf25HZ>jHXVWkiM{Lsypm|fn^zM3o_SJPAXtPy6Vby*h&)U{iImL* zO#~wZ6p&IZ9a5h-E=;GG9y1DtY0u@wglT`)$W5s7HSK(IJg)^L2a&u#+(vflszszs zduA*v&}(cb(k+x|T6uQJnS6y#N#+>C|k#12W1al?l4czi*1r<{Y7)@J>}0)tv>l#1)B zRnn=Z4KMm^{2$dJp zm8Wv0E8am3gIZ(bse4Leqk2>dy&%$?w&i4e1r=9dZ_6r{fxmT*CF!y9O@g-({`EHG zJl6&J_6F$Jf|a*O>pX#Kq6=`ht@SX~mE3%pe2=!2a?SOh@1Y^CULY?MrJQ#mweCFn z4kz22oHXZGE%0jl8|Y`p_D)v0pVSW!G!pECTt{qB%~(}x)TdgRY30XE$EisnXH0fd z4O7#mXS4D_vQZsdhSd6Hr8qeaq07+|vWSA)7P!;#xt~f1vv!Ss_n9$BbOqr?X9v3o zcRN3xe*)RkNALo{O9V9pDt~JH?xd}nc6*2#AW$tp{VP?--Xe-B(X?l=6I5a(JzF^< yRa#V-siS{)^^fQHWLE5!pHZr5ZW>+~>Anq9Jrk8d53BmcvI+H2z2j3gMgIoZwZ0wz diff --git a/data_ingestion/utils.py b/data_ingestion/utils.py index a4eaff52..773e589d 100644 --- a/data_ingestion/utils.py +++ b/data_ingestion/utils.py @@ -11,6 +11,13 @@ from langchain_core.documents import Document from text_extractor import TextExtractor import os import json +import base64 +import requests +from dotenv import load_dotenv +load_dotenv() + +# OpenAI API Key +api_key = os.getenv('OPENAI_API_KEY') # loading the embedding model @@ -91,6 +98,56 @@ def load_document(document_path): else: raise ValueError(f"Unsupported document type for {document_path}") +# Function to encode the image +def encode_image(image_path): + with open(image_path, "rb") as image_file: + return base64.b64encode(image_file.read()).decode('utf-8') + +# Vision API to process the image +def process_image(image_path): + global api_key + + # Getting the base64 string + base64_image = encode_image(image_path) + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}" + } + + try: + payload = { + "model": "gpt-4o-mini", + "messages": [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What’s in this image?" + }, + { + "type": "image_url", + "image_url": { + "url": f"data:image/jpeg;base64,{base64_image}" + } + } + ] + } + ], + "max_tokens": 300 + } + + response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload) + # returning the content of the response + response = response.json()['choices'][0]['message']['content'] + except Exception as e: + response = "Image not good enough for processing" + + return response + + +# create image document def create_image_document(image_path): # getting the image name from the image path image_name = image_path.split('/')[-1].split('.')[0] @@ -100,9 +157,19 @@ def create_image_document(image_path): text = text_extractor.read_text_from_image(image_path) # removing special characters and line breaks text = ''.join(e for e in text if e.isalnum() or e.isspace() or e == '\n') - doc = Document(page_content=text, metadata=metadata) - # returning the document in a list - return [doc] + + # if the text is empty, then we will process the image with OpenAI vision model + if text == '': + text = process_image(image_path) + + # checking if there's no value error or something, we will only return the text if there isnt any error + if text != "Image not good enough for processing": + # creating a document from the text + doc = Document(page_content=text, metadata=metadata) + # returning the document + return [doc] + else: + pass # if there's an error, we will return None def save_embedded_data(embeddings, key="data"): diff --git a/image_experiment.ipynb b/image_experiment.ipynb index 9ff5adec..4e2046cd 100644 --- a/image_experiment.ipynb +++ b/image_experiment.ipynb @@ -11,17 +11,93 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from text_extractor import TextExtractor\n", - "from langchain_core.documents import Document" + "from langchain_core.documents import Document\n", + "import os\n", + "import base64\n", + "import requests\n", + "from dotenv import load_dotenv\n", + "load_dotenv()\n", + "\n", + "# OpenAI API Key\n", + "api_key = os.getenv('OPENAI_API_KEY')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Vision Model Set Up" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Function to encode the image\n", + "def encode_image(image_path):\n", + " with open(image_path, \"rb\") as image_file:\n", + " return base64.b64encode(image_file.read()).decode('utf-8')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def process_image(image_path):\n", + " global api_key\n", + "\n", + " # Getting the base64 string\n", + " base64_image = encode_image(image_path)\n", + "\n", + " headers = {\n", + " \"Content-Type\": \"application/json\",\n", + " \"Authorization\": f\"Bearer {api_key}\"\n", + " }\n", + "\n", + " try:\n", + " payload = {\n", + " \"model\": \"gpt-4o-mini\",\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"What’s in this image?\"\n", + " },\n", + " {\n", + " \"type\": \"image_url\",\n", + " \"image_url\": {\n", + " \"url\": f\"data:image/jpeg;base64,{base64_image}\"\n", + " }\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"max_tokens\": 300\n", + " }\n", + "\n", + " response = requests.post(\"https://api.openai.com/v1/chat/completions\", headers=headers, json=payload)\n", + " # returning the content of the response\n", + " response = response.json()['choices'][0]['message']['content']\n", + " except Exception as e:\n", + " response = \"Image not good enough for processing\"\n", + "\n", + " return response" + ] + }, + { + "cell_type": "code", + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -35,79 +111,41 @@ " text = text_extractor.read_text_from_image(image_path)\n", " # removing special characters and line breaks\n", " text = ''.join(e for e in text if e.isalnum() or e.isspace() or e == '\\n')\n", - " doc = Document(page_content=text, metadata=metadata)\n", - " # returning the document\n", - " return [doc]" + " \n", + " # if the text is empty, then we will process the image with OpenAI vision model\n", + " if text == '':\n", + " text = process_image(image_path)\n", + " \n", + " # checking if there's no value error or something, we will only return the text if there isnt any error\n", + " if text != \"Image not good enough for processing\":\n", + " # creating a document from the text\n", + " doc = Document(page_content=text, metadata=metadata)\n", + " # returning the document\n", + " return [doc]\n", + " else:\n", + " pass # if there's an error, we will return None" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[Document(metadata={'filename': 'IMG_1438'}, page_content='ex a\\n\\nAccidented car before repair\\n')]\n" + "[Document(metadata={'filename': 'hyundai-sonata-auto-body-repair-before'}, page_content=\"The image shows a dark-colored car with visible damage on the driver's side. The damage appears to be a dent and scratches on the door and fender area. The car is parked indoors, likely in a garage.\")]\n" ] } ], "source": [ "# testing the function\n", - "image_path = 'data/IMG_1438.jpeg'\n", + "image_path = 'data/hyundai-sonata-auto-body-repair-before.jpg'\n", "text = create_image_document(image_path)\n", "print(text)" ] }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'filename': 'IMG_1438'}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "text[0].metadata" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, diff --git a/loggings/app.log b/loggings/app.log index 35d6d571..bcb51baf 100644 --- a/loggings/app.log +++ b/loggings/app.log @@ -80,3 +80,16 @@ 2024-08-08 14:29:17,820 - INFO - Search completed 2024-08-08 14:29:17,820 - INFO - Page content: Accidented car Before repair +2024-08-08 21:55:11,668 - INFO - Loading the embeddings +2024-08-08 21:55:11,669 - INFO - Load pretrained SentenceTransformer: BAAI/bge-small-en +2024-08-08 21:55:15,477 - INFO - Embeddings loaded +2024-08-08 21:55:15,477 - INFO - Loading data from ./data +2024-08-08 21:56:51,671 - INFO - Data loaded +2024-08-08 21:56:51,671 - INFO - Creating vector store +2024-08-08 21:57:02,306 - INFO - Vector store created +2024-08-08 21:57:02,306 - INFO - Saving the vector store +2024-08-08 21:57:02,316 - INFO - Vector store saved +2024-08-08 21:58:02,266 - INFO - Receiving the search query +2024-08-08 21:58:08,023 - INFO - Searching for scratches on the door and fender area +2024-08-08 21:58:08,277 - INFO - Search completed +2024-08-08 21:58:08,277 - INFO - Page content: The image shows the front end of a car that has sustained damage. The bumper appears to be misaligned and there are visible signs of a collision, with dents and scratches on the bodywork. Parts like the grille and headlights also seem affected, indicating that the vehicle may require repairs. diff --git a/requirements.txt b/requirements.txt index 6db0dfa7..dc0a6d63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,6 @@ docx2txt docx fastapi[standard] pdfplumber -pytesseract \ No newline at end of file +pytesseract +groq +python-dotenv \ No newline at end of file diff --git a/vec-db/index/faiss_index_data/index.faiss b/vec-db/index/faiss_index_data/index.faiss index 163e591de083fb9cd7a29a3333ca6c688fac8fe2..80736afb75f7ce1168d81f9b93523a58ae867ef4 100644 GIT binary patch delta 12438 zcmWNXha=W)6o$==%#6&)CVP8-=Ru+&q#_bYOR1DpQtC}c!>EjujHHwzC5iWU9yCZa zL>g3-N*dBvMo6E(;Th+?uj`a1LuoP`i06+Nh!>0(iWiQb5-$=j8ZQ}39QFQZ8+s#_bI9`< zgw;$Xp?NHB%IZVr4j(G-h+`f0&LRclE;N|?8hZny96cR!Mleb_*98i z{-jHm4fqhpeP7^U#wdL1lqTQycd#!WwlYym>}m=(f3?y+^&`oTf^(J@~KL^*B)Pc}14H|p-G~5{# zA|E$L*#CA_BOw`rwC`ma>>RFVbS)b}VoDGZ9=r^KOV)st0UsmMSd0IKMG#SIE8Mp; z8I%6?;sWzJ;Et?cLfR+K9Iyee&7BR|o<_KYW6jKPUB~9{NQE0-2OVtgc4J=EIKy;) z<5X5(Mh8VZy5Dn*F8y~I6>IHjf2%V6dRKs4(G8~?_nn6ec?xuA(Rw^}*_giaGb8Jz zqQE3xgPfNZB+eh-!sc_OFx9M?6KVgPi*A0(ko{anvU8Z*213-57P2cfQY{5{zbi`Yne04x6m&q z>!CgEE*?H5N;}Rjq;q1*@%xQeJnwvvF9rxx4IgjiQg z6STOXgwcn+$=8eHc)DXTPWilt2<14S%X>Yz?zR=b`{~eQ6a4V__eE$ncL8defKQ(& zF5Ik~|@&B=rs%M>KZ6(DSUCjq5KI}~0Yp2nY6}0!l0=nYKf3RY!GJe{eihtBq;4eKu8^c^M zBmOZH9~FfryOQv)mKdEaxEJ=E=cgedQn)J6jx43?=ufBTaJj{rbXt$YY)t_S+^S5V zUxs{Cm<^Yn#?x5}&l$h0MYwbSa_rGGCF}b4p;urx7s3tKP}2w9Q0H#XP6=+nKNab; z>&6${9(fS&7WCnk5aGpy7ym3c%TFo2w^l3h7`syKARh>iC z>vrVKu^cG4IfERs$cMFyvv6$cF+9HE6o~Jyz@X6o;I3IId+QuSH$4UV?jP5NxSAEP z%_ccDrK(rqUe7y5_@+NG@;QZ%NBY_Csx$EMe~aMi);P%0y@YCfM)3F5CfGZ(5N5qA zXM&f%XKQ9Cg8}g)E2BlJzUeV&7LR96i0;9`Haim9Y3%3_I*sg&k|eL*#4%32KXK1; zDY|7KnOQNljcMj`xz}S-K*br$&B70QvuhbXg==T*=puZIKg$t2sUjQ?{x3iD8UBg-X(oy)+J?J_o z30pgVu{Z72n9kKN!Q}&@`+HO7e&QHRP2Aj#AG{v0MrI#w84F4AU*+nQ=yDFyW}fb= zB)CZqG9?jiBxxWUtm009>fj?_;*6k-{mU!;9RoiS{c$w<3S%SJ33dyvqI}t3Tv@o6 z%#$p~)4lI_edec7Kd{ZA<7yPOkblCAe0iM+@!z@ypi)9RI3KLyZ*ZBO_hZx++IcTAtud zaX5f(%|m#=`ZA~8@d*^q{ES7M=U=Kg{4h%>wqv8Y-J|0WXF+pb1yW zDKh#AbJ$}H=0=_;`(J*+<83@#ReuVc4=kgz89$hL?=dz%e$I1|xdI}K73rwX1jKKS z!2C@D;AB^aPc*laNbfUbiQjgVFM5VbS)pj5_>XOzQ4X&*?qxlW8RGEEQ?x>S6u528 z+sTp;gcq-D;6;1`w7g8iiynp;HdUN1f1kv>|9F5+6d7Yh3+#A$x3}PK=>%N;GoB_L z=%=>#MxdasfZqS%%&O(hA)fOrbE9OSJnU6I@5Gmv-}H~yB9c&ezYY9 z;lF{W8_2HK)h1`QnQ>_@X$H5}uMB2Z;xC2mT1 zZ?i#Lv5Ob?VGHTq*MRzpgK*Qvk6e*1hAav+PBjd^N*u?|#aFQM=+ep0eV83^{)rP` z??P?03wctL$qYZbhkC`+;Y-yiSXw;-J%-%F_{vfdbP@|0w;fwJx4N}xzBkPld?xX;MCJ9Qmi^0=X8oxO~Y(97-ug<9WKLx9! zBk({>i%k0+!d}dFBu{A$UOyN@`3^o{20s{(`*#gt-f?@J$oHmY+`ukIuYG0BeaAWI z$)`;3U0;JI3Vd<8kOlHaKC*>o$1vaQ7CAYfin)nSM4+jF&a9qImcEuFiTcuX=Bf#} zIK7b>(^Vix+7IA=o9E$#YcAWj;U2^X>Cx1iTQRfu8aX67A8&?QIOcn5k-DG$|=VfY`+FMP&`2dCn?$de># zA_;AF2NH$RBM|%BiG5=-1>bt9I-YP*|P%CdnB6zmE_Q(caL(_WPc3=_dEZR?%=<^1z!6uC4%-Xtgm(v=w%~T*l#7ae*P@e^@cgIYZ(dP zYS9o$dq$-%7OjJ)5#6kO@HBGdESR?f!_{5sg~Vx8uDORL*>TYEB9i`&aG`_G=flgh zi_la>8yeg-6xRsA!PM`mw}r$ z4I1+@8OwZa5-`;hqqV^0o$v%^Z{`m&IcJ3{!jQJEe)o`74fYZH^Rjp!Y=fmBr-pbwvJ zAhszU^qSU#>TB0^$O?m_C>8Pn+-@Ht9}*uy596J%+?*SR2W^a&r{u0sJ~QC9hsnp61F(@@j07_N#xCkjVZ>FwZH2rZnP zN7D@CDG zf%M&;O8dg!u@$$TFtv7B#FjVB;1|gVup@WL)FmL}>xZ4Hu_P165F^8$iNe z)I*w#5H&Ta0?+k{IQ6*^Y<~BODH6KIk+k}R0Wn{o@5D|bA#jo1vVJQ4=*E>M=nI!IhTLgPO5V}yJv8}q`FTIWk)i|B5AI2kKLJ3P@feK(d$ zKZR@gM)Az93Qg65}_QN4N0!5*TZ ztwWZsXq!RvFQkzlr$6J@K^w>X>G8n55VM&4(h;P)%~yg?qg1ujl@|Q5))N0&eSz;0 z!+22A3O>E-WD)}pQnf4RaP6iD%(}WC8um|;gh4{jd3D3D$6|D8vo=J=ZX`EfDN=`G zTe4NY4Pq{D1m)T{xJpNz>^-JQTKL|uU6t-stD^K;gf7<0qN~CjSVvDCI&=0De0*7lEXs6dw>{kf%|G2pcQXsyk9d&3 z38_$ZxDadpi^mXCbr^e8fNRbuk)b<mO~l+$ z_fiu|IV+IP1%F`ESy`CPHH}B5`!e`;OqTpUC`p`6oQ;M@)aA11v0y6v zR-TR#{v7(JJRP4+S0rB_-eHSv-09iFig2?~nJDRdHe+r;3a)Y6!OF{SW9s~B*=2wD z$wiYIsVQ8c^Jma#yC2S4Sqe>?R%3&)54mz<9n381hc3-KuvEO2z0h36e)fwXm-g_{ zodU`5@`*HQY+Q~LWjpYzyDHgf=>grpCm)~FY&x2?85H9K=!LH>D7+?*ev{jcE(_O^ zoJ3tVQqPSPEF8n9Pc>>Hy{_W67IQAGp0}9zZsVZk%5g^UYz}+X`^+SC0-`ioLz?AZ z@HAJ6lZy5^a7xIVW~x|G{c06@R7joj86RQU*avu7tbjZ+SVMgT7J|t+9`-aBgWujE zD7agSoHEMV%N`?#^E<%sRuR~wWI&?T7BbsWl4O@msY!0&ZiJleW3bs=gq7XBp10+) z34Js^hr~)Q2I(OLo8MuW*xZJI8GO$)4Z0v3KhUm=z&OrbyUe-NJcPiF;5E z)1%&FbNxHayl|a&n^kf;tFxG$`+|Y_TX`g7bw3(BR{_Hd^-QtSL)^$q1__QZ{dTL6 znQP)rqR*${hrwIWKbb5-czKNBa4;*A_!nfpc#@pAA0b4OvISSlFnT7-i^*CBfm_;f zL-}U9ye*x|#w8N&7U!eH!fOr5vD}I7AwewuRHqFCmoVhpI{dh%gK-Rd&m2h5W{Xlp z=!2luuwm&g_On|n&Rf$*`J9_^Y(^60wWZ?W9p=QdN0`I z>r1GF#0&P8svwq6T|$oCK7e%|;`H2xLb9X%Au5+ELv*D+`Fh5ltdu%|57iQQCKvYN zJ31HEZs-Buu?igBB10E@#gl|eS@N^b2z_rW(t%-h;y*H%JU`I|?P1(Byb@@P%Bx-J z?+axp-Na9sE`Ri_{EJ>O3n+J{0p!kaLyO;Qfngf)v78`k4_#-HN2gHf;UCqWX6M=T zlQED+|8lI(zlM0()hHNf2KN0YA-}Yc(>&!d6a3@_epFjZm6u(EV`)O9CH@W1PsRY! z15e{|?x-KkzP7gJ(9SUS(ph6_oc4?{8I__XVYQf&Is;$Vda_3MmynFZkDwz~pH$e! z(e|VXfQnoU?QQ@^)ior(pbg`ve}&^$O=+v~Bj&e_DMi(vuxhFaXJ|$*`nAT9`*KZ_ z!Oz~&d$|VTJ$VV^SCnvISQWTyUQ~ebS{cl{3AD9Nml)6ciqkr>u%?Sb>2|&P?wO$24L8_lX)ZVNpBttfDo_W>?x6*tg?6kBl@!*A7rV~B{Ez=vV?aC znk;f_0))hgr>78I7(I_z1nAPI>kfnS{&b#TYAoTqnH0FY!%Ta+*dDP;gi{H44)cd zO@i5&k;A}nf18pi?`<(Ey8}L7k;JBH{&+p|CO%Y(2H8$)61wXV?EFs|&Nn9Fw1+Ze zrn@1owtUI{`4dCFZ_)$V4Su+A_Bq=1UY5kp<)FsL`*8T5H0s=wqe4zWl<%(rVUkTS z@>UpaT`W$g-izT#y6V%4&UbiESB6gQm;MBsKI!6@& zr{mjCt*Cjv5esK2z_mz6S{tQKmG_;XxXT$UrM*e(8h+;T=pdYR+RS$Ky{F>aPGCbs z2A6Ky$WOK=Co^M0N(^hyPyd@)Pt!K2l3RxdP&zOP*EqV9?fV4i_l#QlT5t-@xyGRn zOO5EE;|p+QvIMn{R3vVEK{Q?J3#{BXiZ->YFq$1^ERCjN{n7^{_M8^EC3Bu*n=g$S zXL6XC>8&WP(ke*HNqD1s;0!6-LsB8LLoTNIY&xSL{2< z-q7(O`%f)F*>f?jYq8Ct!S97SkH-m2PbJ&;jy>JEmWG#Ok#4QFdz`#5Y z+sZ}hqkb9aSLkIFLK3jodI7B+XhP9-(sW``B`R_A&Vuc{x%4EpMVl=(%nA`nr(foc z;N9s3Z|}Y)iyl|tMzSAng!Y0*og|gN^Bqic)ls*@pZPF&1-9*sLmSfz5WV06UU1qD z-p09=mW!8S8rk8e_u;~+sq7|6dHiITLYsUh>2tB_1=5_8(vm6;?#*x~xA%_XwaXJQWit5s8x`{27ss;G#0(j64`&$OHHEX;*@k5A8iK>E zDeyXX2D!f{mdgsgG$8i;KIC3{02;d{V*alMV7EH}^0lhy(ZDVgGiqR(qk>`C4@X#Z zMgi&uZ;_Y$8Q8gZ8d;OSkZiqP$KG1uPW{lHd>cE>%PAK|Z$m$xSffN==9a^$ z2yfB}b8)n88AkBU!ra~@T-Y@Tg5S6onX?ndOy?41yfo(|6e=90@yV;`^6z)>$}@R7 zn{yvlyyT;`t|27kxHses>!51Q9o9A$;7G41W~OB`n_ut1yW#yXGAP1G&kDouQMxEy zVL~p346(e&VN9BR11m5nMfLV}F$Nop!M@@pS$KI4S3r^q)QH#FLoN?IDTDBlP_u}kt}?N%OU9o;R-!$&I_ z$w`F0AyOX$182~jk{p!zn*$vuyYX+L7wuA5Pd0r1h@25`$9GniL~wB|c@^k@eOf)h zJ)^i2gA7(Ohij90e@-nXv$qW64xtT1zRV7qlU}JHqybCnkK>#$0nCj& z50~9ND5s{19olt>Ja0l=bZag|gcpIDlq_Zi@>45Y4VWjSNjlP=V~|-Cz4z`NWRQKV z;?z*ii7_?oY77F`<+WUpf0;tl<%WPx9k3tT7EepJrPB+XB92TNV3=hJCcd|TGmR}M z`*|yjsjeeO3ghtYhh0Ql-h=SZI)ite&ardX+mhK+BI&{98;J024*8{EL~N9uV0fWd zb=+twE^vzBEte?9RSTM#7ske9$8v%ae(Px72kutl++&H-UoX|Hw(5i%rV3Q4;t3d@ z?849gX~C)JFxGQ0g_!sh;O4+t^w&BKEDI?Hn`REX$5$O1&)-6=7I!#OZ#W-Y?8J ze1*K^;(oC3*1E~&_d=!Ds< zSI^M?)P4xm=FrL|<*cf23UXY|p}Ltc-8WZ+e)1L}FGqav?-n<3)q4hhoxhpoe~sxl zSAu1k;$F7gYav}Tuo!0Z?ZS+uj+E2hfS&0}PM2cU=z@R>5b4S$ys%)L>w2Aqx?xz8 zR)WfYtsuK~0PiZCX5#rTLcr-dRzUkFvo5Y5IeB3aI(i?sh4sKk@q0|mK2<1PAVRII zx>(Q9k8JoRMe?t&9=U4+s!=G-iLNcSCNo`g$dmKIFdCDECl2Z3qu((onH>pDI_a$8 z-H*)7U7`?wGilNz<*+Zd^s9Qm5zgClUAm!Ba6$DJrZc`Iv+LJA^bKOk{aCilYk{M z^x~>;8Zc4|IYxY>pMNX;^;wLyvNy!wEm5GzUxK0a?Yv*L^GKr9Mdrhz_jscqj9d+K zBMSape4Ew?wHs}*NYorQXU3D9((|mr+e0*{;0!D*5+V!NyV0fl|6uwGZVAgNUQe!X z%Voj@H)3z%IA)1!(kr@B*!C`zE-(|NdGT)OBdbRTrB?FJ*+{}F{;33f#$jqvHZHF3 zV|IT%#D29%aWHEOg$&i_?2m>X2sGUZ54D%lVjUOG&FA7|w_X&_;LptJ3%8u;(@ZHE z{X>uZ8RrLU?i_1)l6{W}GF^$0^I8~#sDK)MD`dp3&8M=1irC-{_(6CHvN4AknMD>D zskE3ZH@d`j_pT-?k0t5m(d&@8a1d?INdWJP9y1ZmPs_@8!w#u$cv*8C4|TcFx{+h> z`_Fnh;-yYSB2r1{SArqACXT}WzJ%*1C=CB1vT?`p1H9`QdqL&q0CVti501tr6DI5) zte(FUNAIRHLpRURPwyRBv+u=dbz>=g8n*{^^Y7rBeY+r+5k@bu`xw9{OpL64V(*Dy zqA9hO=$qT}GUfIl|Is;Q7EL6MR<)Qn>oz^MBZ$|ot4#Z>hq!n|XOtB=`hlrm8cX_| zi)hU}O|tCk3{vsSnzn37rAJeK;IU7G=pwB}Z@V8Pk>_Ht%4-IV9DfPL8H+gW4`YeO z3NhR^tcuU>zT*fN`;rd(Mt1&!apuHjWqh6JPL|*7ghlzfa7L9-RkuNAdN>ayRQXAA z7dHpgBn2EolND*jBW0p~F^u_;kXe&d`;g%;a-(n8IgvmngnA?gVMX~An7D06j4mI; zX@|9Nx3&ZgO!*GW2W8;QkLk1`@h4-tc{<6AOd^684j~`^1U&Z3=7n2$GaXKcVgIn5 zqsIMqTsvKjDCTE_b-p5di2G6oHw(rAx*3qC9jn3EIe@oBR+mJ+$%lJk*FawLE_fJk zMiE6F65ORtl7ml!%DoN_pYkCx*02W>MYcau)otJF6B%lx`l!zPDP)*%XJ{tEQ0E^H$OY`k8oW4NI@hQD7_$UU9);?^L$h zVgigM45{5QiJDoumtk$1FP%^hBQ{#sz#>k!z>wj@6pN16STjIfna@OPEMhO!8 zyn!iu*^LJjYEh?qH%+SjgC|>cuvqLX9@S1EC+`{2r!J0+`Sg1@rcnt=sb}C>=K*#k zz?M1R6Hls(xV|7GC<3(~E`f*WMsj?=3ekHfQq!_83%celAh}5~oSbYcjPd2CGs<*H zcaSw|R7|F0)8|ZQ$ZFcP&Xw31+wo3J7p20ALwNFt7hAlm2{%>ok=aL1qKg9+f@NX#CONY7=Rd^x{A9$!gEr6CV3yX( z5EAn)@xp~7w_kT|Fef!P*ew52BE}W%M&U`PcTCFS@sNRr;lc%l7@qqGY*IBHS_iLTk=SQ;P2yWJDR`s6p0cyr3xl@;K{RXB-ZI4=s!C|$MkFqi<^MdE{F1L&0a5z4=Ef#1p=&_567lt{8WxErW?dn!&J zQl}<LL!*W~eWb67t6nyN{n_i-uHA#8sU_(6 z>m5%M6i~WWgKB_1d3{X>$6U_f*#UL(UsfF^hUUZ61Rz!C)iM0+ROXVC92xyl3Vw1U zP$@9!3+|R#!iLfsIM6==^5HUUs$O69E&XCJUDky4fu`h+Vm*8B$yS=PG7qy`nmtYvrsaC+jIH-nSiAlL-2g=fLznNMNQ3^zQUQHhVnrxE4*?@|1i8aWqn z4dUm!5c?)IlEM7|jZ;i$)iSOIsYugeBD1uZF==&&cUCvh#9NeBcB+t8+e-Mxccn&u zG=piJsY-Ks62yFsIgMZqNR9Ofv_DE9d(vH*fHU*4M`|Ba={SS*|GB~>tXz(U+{?_O znIhEZ%sR4gP?csM9Rj|Y-?8zHICa!N#+)72cWn0rb{BWTh^Q`>Cfn)-Fn@6zOgDbP z3;gE_$|vTK;2*iX0h=G-bX|x(%bK(yOD^zkN?F6xfva$5t|*xpPh+Bu#Hqz|YcgBM zkoIeA#*g3a$--y_G&ppFaj%x9HhW{pF++DIyYV#hN@yyC=W0>~uVlJ;l_1&YA;xgC z#%@o7ZxxEVOK>`i1B zWjQ2H5h5r5Go1vu7Ec+ZyPFUtJnW6$< z>p6hq+xppMpH5T#;wfCZc1AEY&J1OiiB2J+Z$uc!q8;RVLok)|Q>n4oHIAZJzvGLt zC1kdV65T7+Iw>6!;Z^Gfs`goqMj#lmg-94uU>`ffK%v^AWiGIDAFdDTkP5U$L^xoyd$)CRTSZzT~GF&%STNWZMwWN7+22` zCvQIp&d-ihpgg|&=Vn+WyaP()+31gJT8yZYoM z+lu!qM&Zx!Tyl7`C|i7Ea;)B7%Y^Hn!CPxLQmv6LoTE9$$eeelCVvLl$6KVFZfupI z>f3e}i0radN%j*e90KLvjXw&)vRCJsnY+#H@ z>^P5`ezrl5m^tCfO?e1Gb1vZZ0Z~}`{UQ59bsJHQ{e}N_9;8J9fiz6jfMh!+lV~|3 za;#Yvza%uF;IpIDVOBV-U9cWgtlQvy+es$lya$};EaYU4_^|f!B@kH2P@UFF8kMX` zRoXKsyD9>Iw06VQs`d1YcPX46oJBXEbKr93j22gSauS|&gECX(Jdg#NZFj^C~-5XoRiqBAT7A4{Y7SxL=M_z--U3$FxoY_n9zBX4_x=EVbZTqD2?;v)I57N#Rrldl*48HsqK&Sr;p>u9tW#k^FpvwY%T-v5h3_?qp zGv$G-PvTCtZF3|mA8khRDn@a2Zz1;AUqtDWuTU`{hf!$@i2vkZGCgBPytKH4sIK#6 zZL|td*u5L>1O=gy?ibEB5P?f)4zl7Q{kZJ|L$Y>lA!&27YaX?4fE$+P)KqjU@r?+i zpY!-h$-F_P$I*po?(2f>FU)c8nL*a)XE8W=?PSMa`r|s&1Ylm-vW~U;NLj zqUUVK&x4C;EO+`XFp89M%;%}mlqMgtL!cVg<{g44u~D{D+1x=tdKRg%&>=FX&clzi z{X~yjhT^}{q4nHdcKam})~iH-bgh|BIwGgiA|n}aR|_K#ULN6a4~H{SF(;w?Q3~5V zj;sLy)hlu(Y*a2D>JcY$Uh_Few*6wwSE{>Forp9L`cuGYM~q;Id?9;SF%}!X>eACh ViS8~jgak)16#V@J4HgKI{{dT6kK+IU delta 12438 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 X@y|}@#AEsAjn0Wf;j?}Ey~F+&+Lt$q delta 28264 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 DgNIiL