From 116e6a6bfb63128f1b7cb34604900c602bc75243 Mon Sep 17 00:00:00 2001 From: Maksym Zhytnikov <63515947+Maxxx-zh@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:25:36 +0300 Subject: [PATCH] AirQuality OpenAI API Support (#253) --- .../1_air_quality_feature_backfill.ipynb | 6 +- .../air_quality/5_function_calling.ipynb | 587 +++++++++++++----- advanced_tutorials/air_quality/app_gradio.py | 74 ++- .../air_quality/app_streamlit.py | 82 ++- .../functions/context_engineering.py | 78 ++- .../air_quality/functions/llm_chain.py | 91 ++- 6 files changed, 690 insertions(+), 228 deletions(-) diff --git a/advanced_tutorials/air_quality/1_air_quality_feature_backfill.ipynb b/advanced_tutorials/air_quality/1_air_quality_feature_backfill.ipynb index 1ce0301e..75f1b875 100644 --- a/advanced_tutorials/air_quality/1_air_quality_feature_backfill.ipynb +++ b/advanced_tutorials/air_quality/1_air_quality_feature_backfill.ipynb @@ -49,8 +49,8 @@ } ], "source": [ - "!pip install -r requirements.txt --quiet\n", - "!pip install -U hopsworks --quiet" + "!pip install -U hopsworks --quiet\n", + "!pip install geopy folium streamlit-folium --q" ] }, { @@ -1145,7 +1145,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.11" } }, "nbformat": 4, diff --git a/advanced_tutorials/air_quality/5_function_calling.ipynb b/advanced_tutorials/air_quality/5_function_calling.ipynb index 1f18f187..751218bc 100644 --- a/advanced_tutorials/air_quality/5_function_calling.ipynb +++ b/advanced_tutorials/air_quality/5_function_calling.ipynb @@ -2,16 +2,26 @@ "cells": [ { "cell_type": "markdown", - "id": "574f4ea0", + "id": "98404102", "metadata": {}, "source": [ "## 📝 Imports" ] }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4f454d56", + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install -r requirements.txt --quiet" + ] + }, { "cell_type": "code", "execution_count": 2, - "id": "1d2db28d", + "id": "61ae8738", "metadata": {}, "outputs": [], "source": [ @@ -22,7 +32,7 @@ }, { "cell_type": "markdown", - "id": "6b079783", + "id": "026af360", "metadata": {}, "source": [ "## 🔮 Connect to Hopsworks Feature Store " @@ -31,7 +41,7 @@ { "cell_type": "code", "execution_count": 3, - "id": "d1aff226", + "id": "81617c65", "metadata": {}, "outputs": [ { @@ -55,7 +65,7 @@ }, { "cell_type": "markdown", - "id": "3ce1a7b7", + "id": "e4e693cb", "metadata": {}, "source": [ "## ⚙️ Feature View Retrieval" @@ -64,7 +74,7 @@ { "cell_type": "code", "execution_count": 4, - "id": "ae227ec2", + "id": "ccd11e5f", "metadata": {}, "outputs": [], "source": [ @@ -80,7 +90,7 @@ }, { "cell_type": "markdown", - "id": "5199d607", + "id": "b54628d2", "metadata": {}, "source": [ "## 🪝 Retrieve AirQuality Model from Model Registry" @@ -89,7 +99,7 @@ { "cell_type": "code", "execution_count": 5, - "id": "f7661fcf", + "id": "e67708ea", "metadata": {}, "outputs": [ { @@ -118,7 +128,7 @@ { "cell_type": "code", "execution_count": 6, - "id": "b5b41d51", + "id": "5c391589", "metadata": {}, "outputs": [ { @@ -176,7 +186,7 @@ }, { "cell_type": "markdown", - "id": "c07382c8", + "id": "fa25fb65", "metadata": {}, "source": [ "## ⬇️ LLM Loading" @@ -185,9 +195,65 @@ { "cell_type": "code", "execution_count": 7, - "id": "6d790560", + "id": "97fa773f", "metadata": {}, "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f2d85ff92cd64c08a58ff800ff7d17b3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "tokenizer_config.json: 0%| | 0.00/1.60k [00:00⛓️ LangChain" @@ -234,7 +384,7 @@ { "cell_type": "code", "execution_count": 8, - "id": "44d106da", + "id": "38e442d4", "metadata": {}, "outputs": [ { @@ -248,14 +398,14 @@ "source": [ "# Create and configure a language model chain.\n", "llm_chain = get_llm_chain(\n", - " model_llm, \n", + " model_llm,\n", " tokenizer,\n", ")" ] }, { "cell_type": "markdown", - "id": "9b5b7257", + "id": "52bbd357", "metadata": {}, "source": [ "## 🧬 Model Inference\n" @@ -264,17 +414,19 @@ { "cell_type": "code", "execution_count": 9, - "id": "0fb93740", + "id": "45e27000", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "🗓️ Today's date: Sunday, 2024-03-17\n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 \n", "\n", - "Hello! How can I help you with air quality today?\n" + "Hello! I can help you with information about the air quality in the city. According to the data I have, on the 27th of March, 2024, the air quality in the city was considered safe for most people. The concentration of PM2.5 was at 12 µg/m³, which is within the safe limit of 25 µg/m³. The concentration of NO2 was at 20 µg/m³, which is also within the safe limit of 40 µg/m³. The concentration of O3 was at 30 µg/m³, which is below the safe limit of 40 µg/m³. \n", + "\n", + "Based on these readings, it is safe for you to go for a walk or engage in outdoor activities. However, if you have a pre-existing respiratory condition, it is always recommended to consult with your doctor before engaging in outdoor activities.\n" ] } ], @@ -284,10 +436,10 @@ "response7 = generate_response(\n", " QUESTION7,\n", " feature_view,\n", - " model_llm, \n", - " tokenizer,\n", " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -298,17 +450,17 @@ { "cell_type": "code", "execution_count": 10, - "id": "f7e1bbc4", + "id": "547321f2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "🗓️ Today's date: Sunday, 2024-03-17\n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 \n", "\n", - "I am an AI Air Quality assistant, here to help you with any air quality-related questions or concerns you may have. I can provide information on current and historical air quality data for your location, offer advice on whether it's safe to go outside, and suggest ways to improve air quality. How can I help you today?\n" + "I am an expert in air quality, but I'm unable to assist you at the moment.\n" ] } ], @@ -318,10 +470,10 @@ "response = generate_response(\n", " QUESTION,\n", " feature_view,\n", - " model_llm,\n", - " tokenizer,\n", " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -332,15 +484,15 @@ { "cell_type": "code", "execution_count": 11, - "id": "307f2d8f", + "id": "c346ae89", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.49s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.87s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for New York:\n", "Date: 2024-01-10; Air Quality: 7.2\n", "Date: 2024-01-11; Air Quality: 5.9\n", @@ -348,20 +500,20 @@ "Date: 2024-01-13; Air Quality: 5.9\n", "Date: 2024-01-14; Air Quality: 5.1\n", "\n", - "The average air quality in New York from January 10th to January 14th was 6.8. This indicates that the air quality was generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "The air quality in New York from January 10th to January 14th was generally within a safe range. The average air quality during this period was 6.9, which indicates good air quality. This is a suitable time for outdoor activities, such as going for a walk or bike ride.\n" ] } ], "source": [ - "QUESTION1 = \"What was the average air quality from 2024-01-10 till 2024-01-14 in New York?\"\n", + "QUESTION1 = \"What was the air quality from 2024-01-10 till 2024-01-14 in New York?\"\n", "\n", "response1 = generate_response(\n", " QUESTION1, \n", " feature_view, \n", - " model_llm, \n", - " tokenizer, \n", " model_air_quality, \n", " encoder,\n", + " model_llm, \n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -372,15 +524,15 @@ { "cell_type": "code", "execution_count": 12, - "id": "4d39a38b", + "id": "a6a3cbda", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.40s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.12s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for New York:\n", "Date: 2024-01-10; Air Quality: 7.2\n", "Date: 2024-01-11; Air Quality: 5.9\n", @@ -388,7 +540,7 @@ "Date: 2024-01-13; Air Quality: 5.9\n", "Date: 2024-01-14; Air Quality: 5.1\n", "\n", - "The maximum air quality in New York from January 10th to January 14th was on January 12th with an air quality of 10.8. This indicates that the air quality on that day was not safe for most people, especially those with respiratory issues, and it would be advisable to limit outdoor activities.\n" + "The maximum air quality in New York from 2024-01-10 to 2024-01-14 was on 2024-01-12 with an air quality level of 10.8. This level is considered unhealthy for sensitive groups and may cause breathing difficulties for some individuals. It is advisable to limit outdoor activities on days with such high air quality levels.\n" ] } ], @@ -398,10 +550,10 @@ "response11 = generate_response(\n", " QUESTION11, \n", " feature_view, \n", - " model_llm,\n", - " tokenizer,\n", " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -412,15 +564,15 @@ { "cell_type": "code", "execution_count": 13, - "id": "36ac09a2", + "id": "71cc8289", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.47s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.35s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for New York:\n", "Date: 2024-01-10; Air Quality: 7.2\n", "Date: 2024-01-11; Air Quality: 5.9\n", @@ -428,7 +580,7 @@ "Date: 2024-01-13; Air Quality: 5.9\n", "Date: 2024-01-14; Air Quality: 5.1\n", "\n", - "The minimum air quality in New York from January 10th to January 14th was on January 11th with an air quality of 5.9. This indicates that the air quality on that day was generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "The minimum air quality during that period was on 2024-01-14, with an air quality level of 5.1. This is considered to be good air quality, which means it is safe to go for a walk or engage in outdoor activities.\n" ] } ], @@ -437,11 +589,11 @@ "\n", "response12 = generate_response(\n", " QUESTION12, \n", - " feature_view, \n", - " model_llm, \n", - " tokenizer, \n", + " feature_view, \n", " model_air_quality, \n", " encoder,\n", + " model_llm, \n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -452,19 +604,19 @@ { "cell_type": "code", "execution_count": 14, - "id": "b80fa4f4", + "id": "d89d46e8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.39s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.02s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for London:\n", - "Date: 2024-03-16; Air Quality: 9.5\n", + "Date: 2024-03-26; Air Quality: 12.7\n", "\n", - "The air quality yesterday in London was 9.5, which indicates that the air quality was generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "The air quality in London yesterday, on March 26th, was 12.7. This indicates that the air quality was within the safe range, making it suitable for outdoor activities.\n" ] } ], @@ -472,12 +624,12 @@ "QUESTION2 = \"What was the air quality yesterday in London?\"\n", "\n", "response2 = generate_response(\n", - " QUESTION2, \n", - " feature_view, \n", - " model_llm, \n", - " tokenizer, \n", - " model_air_quality, \n", + " QUESTION2,\n", + " feature_view,\n", + " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -488,171 +640,192 @@ { "cell_type": "code", "execution_count": 15, - "id": "397d5168", + "id": "48e76dfb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.47s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.49s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for London:\n", - "Date: 2024-03-17; Air Quality: 7.6\n", - "Date: 2024-03-18; Air Quality: 9.88\n", - "Date: 2024-03-19; Air Quality: 9.18\n", - "Date: 2024-03-20; Air Quality: 9.34\n", - "Date: 2024-03-21; Air Quality: 9.37\n", - "Date: 2024-03-22; Air Quality: 9.37\n", - "Date: 2024-03-23; Air Quality: 9.37\n", + "Date: 2024-03-18; Air Quality: 12.7\n", + "Date: 2024-03-19; Air Quality: 9.7\n", + "Date: 2024-03-20; Air Quality: 15.6\n", + "Date: 2024-03-21; Air Quality: 16.7\n", + "Date: 2024-03-22; Air Quality: 8.7\n", + "Date: 2024-03-23; Air Quality: 5.4\n", + "Date: 2024-03-24; Air Quality: 6.4\n", "\n", - "The air quality in London on 2024-03-23 is expected to be 9.37, which indicates that the air quality is generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "Last week in London, the air quality was generally good. On 2024-03-19, it was slightly polluted with an air quality of 9.7. The air quality improved on 2024-03-20, reaching a moderate level of 15.6. It was quite clean on 2024-03-21 with an air quality of 16.7. However, it became slightly polluted again on 2024-03-22 with an air quality of 8.7. The air quality improved significantly on 2024-03-23, reaching a healthy level of 5.4. On 2024-03-24, the air quality was slightly better than the previous day with an air quality of 6.4. Overall, the air quality last week in London was mostly good with some slight fluctuations.\n" ] } ], "source": [ - "QUESTION3 = \"What will the air quality be like in London in 2024-03-23?\"\n", + "QUESTION = \"What was the air quality like last week in London?\"\n", "\n", - "response3 = generate_response(\n", - " QUESTION3, \n", + "response = generate_response(\n", + " QUESTION,\n", " feature_view, \n", - " model_llm, \n", - " tokenizer,\n", " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", "\n", - "print(response3)" + "print(response)" ] }, { "cell_type": "code", "execution_count": 16, - "id": "5d3ffba1", + "id": "dd764369", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.39s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", - "📖 Air Quality Measurements for Chicago:\n", - "Date: 2024-03-17; Air Quality: 6.1\n", - "Date: 2024-03-18; Air Quality: 8.37\n", - "Date: 2024-03-19; Air Quality: 7.39\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.11s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", + "📖 Air Quality Measurements for London:\n", + "Date: 2024-03-27; Air Quality: 6.4\n", + "Date: 2024-03-28; Air Quality: 9.77\n", + "Date: 2024-03-29; Air Quality: 8.71\n", + "Date: 2024-03-30; Air Quality: 8.24\n", + "Date: 2024-03-31; Air Quality: 8.57\n", + "Date: 2024-04-01; Air Quality: 8.66\n", + "Date: 2024-04-02; Air Quality: 8.18\n", "\n", - "The air quality in Chicago the day after tomorrow, on 2024-03-19, is expected to be 7.39, which indicates that the air quality is generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "The air quality in London on 2024-04-02 is expected to be at a level of 8.18. This is considered to be within the moderate range, which means it is safe for most people to go outside and engage in outdoor activities. However, sensitive individuals, such as those with respiratory or cardiovascular conditions, should take precautions and limit their exposure to air pollution.\n" ] } ], "source": [ - "QUESTION4 = \"What will the air quality be like in Chicago the day after tomorrow?\"\n", + "QUESTION3 = \"What will the air quality be like in London in 2024-04-02?\"\n", "\n", - "response4 = generate_response(\n", - " QUESTION4, \n", + "response3 = generate_response(\n", + " QUESTION3, \n", " feature_view, \n", - " model_llm, \n", - " tokenizer, \n", - " model_air_quality, \n", + " model_air_quality,\n", " encoder,\n", + " model_llm, \n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", "\n", - "print(response4)" + "print(response3)" ] }, { "cell_type": "code", "execution_count": 17, - "id": "0dce8283", + "id": "210cde3d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.38s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", - "📖 Air Quality Measurements for London:\n", - "Date: 2024-03-17; Air Quality: 7.6\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (9.34s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", + "📖 Air Quality Measurements for Chicago:\n", + "Date: 2024-03-27; Air Quality: 3.0\n", + "Date: 2024-03-28; Air Quality: 8.06\n", "\n", - "The air quality in London on Sunday, 2024-03-17, is expected to be 7.6, which indicates that the air quality is generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "Based on the air quality measurements for Chicago, tomorrow's air quality is expected to be better than today. The air quality on 2024-03-28 is measured at 8.06, which is within the safe range for most people. It is generally safe to go outside and engage in outdoor activities, but people with respiratory issues may still want to take precautions.\n" ] } ], "source": [ - "QUESTION5 = \"What will the air quality be like in London on Sunday?\"\n", + "QUESTION4 = \"What will the air quality be like in Chicago tomorrow?\"\n", "\n", - "response5 = generate_response(\n", - " QUESTION5, \n", + "response4 = generate_response(\n", + " QUESTION4, \n", " feature_view, \n", - " model_llm, \n", - " tokenizer, \n", " model_air_quality, \n", " encoder,\n", + " model_llm, \n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", "\n", - "print(response5)" + "print(response4)" ] }, { "cell_type": "code", "execution_count": 18, - "id": "5e5fc2e4", + "id": "f3c2ab43", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Finished: Reading data from Hopsworks, using ArrowFlight (7.58s) \n", - "🗓️ Today's date: Sunday, 2024-03-17\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.11s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 Air Quality Measurements for London:\n", - "Date: 2024-03-17; Air Quality: 7.6\n", - "Date: 2024-03-18; Air Quality: 9.88\n", - "Date: 2024-03-19; Air Quality: 9.18\n", - "Date: 2024-03-20; Air Quality: 9.34\n", - "Date: 2024-03-21; Air Quality: 9.37\n", + "Date: 2024-03-27; Air Quality: 6.4\n", + "Date: 2024-03-28; Air Quality: 9.77\n", + "Date: 2024-03-29; Air Quality: 8.71\n", + "Date: 2024-03-30; Air Quality: 8.24\n", + "Date: 2024-03-31; Air Quality: 8.57\n", + "Date: 2024-04-01; Air Quality: 8.66\n", + "Date: 2024-04-02; Air Quality: 8.18\n", + "Date: 2024-04-03; Air Quality: 8.18\n", + "Date: 2024-04-04; Air Quality: 8.18\n", + "Date: 2024-04-05; Air Quality: 8.18\n", + "Date: 2024-04-06; Air Quality: 8.18\n", + "Date: 2024-04-07; Air Quality: 8.18\n", "\n", - "The air quality in London on March 21 is expected to be 9.37, which indicates that the air quality is generally safe for most people to breathe, but individuals with respiratory issues may still need to take precautions.\n" + "Based on the air quality measurements for London, next Sunday, 2024-04-07, the air quality is expected to be 8.18. This level of air quality is considered safe, but it might be better to avoid strenuous outdoor activities if you have respiratory issues.\n" ] } ], "source": [ - "QUESTION7 = \"What will the air quality be like on March 21 in London?\"\n", + "QUESTION5 = \"What will the air quality be like in London next Sunday?\"\n", "\n", - "response7 = generate_response(\n", - " QUESTION7, \n", - " feature_view,\n", - " model_llm,\n", - " tokenizer, \n", + "response5 = generate_response(\n", + " QUESTION5, \n", + " feature_view, \n", " model_air_quality, \n", " encoder,\n", + " model_llm, \n", + " tokenizer, \n", " llm_chain,\n", " verbose=True,\n", ")\n", "\n", - "print(response7)" + "print(response5)" ] }, { "cell_type": "code", "execution_count": 19, - "id": "fde239f6", + "id": "956f3636", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "🗓️ Today's date: Sunday, 2024-03-17\n", - "📖 \n" + "Finished: Reading data from Hopsworks, using ArrowFlight (7.96s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", + "📖 Air Quality Measurements for London:\n", + "Date: 2024-03-27; Air Quality: 6.4\n", + "Date: 2024-03-28; Air Quality: 9.77\n", + "Date: 2024-03-29; Air Quality: 8.71\n", + "Date: 2024-03-30; Air Quality: 8.24\n", + "Date: 2024-03-31; Air Quality: 8.57\n", + "Date: 2024-04-01; Air Quality: 8.66\n", + "Date: 2024-04-02; Air Quality: 8.18\n", + "Date: 2024-04-03; Air Quality: 8.18\n" ] }, { @@ -667,38 +840,38 @@ "output_type": "stream", "text": [ "\n", - "The air quality level is not dangerous, but individuals with respiratory issues may still need to take precautions.\n" + "The air quality on April 3 in London is expected to be safe for outdoor activities. The air quality index is around 8.18, which falls within the moderate range. This means that while the air may not be perfect, it is generally safe for most people to go outside and engage in physical activities.\n" ] } ], "source": [ - "QUESTION = \"Is this air quality level dangerous?\"\n", + "QUESTION7 = \"What will the air quality be like on April 3 in London?\"\n", "\n", - "response = generate_response(\n", - " QUESTION, \n", - " feature_view, \n", - " model_llm, \n", - " tokenizer,\n", - " model_air_quality, \n", + "response7 = generate_response(\n", + " QUESTION7,\n", + " feature_view,\n", + " model_air_quality,\n", " encoder,\n", + " model_llm,\n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", "\n", - "print(response)" + "print(response7)" ] }, { "cell_type": "code", "execution_count": 20, - "id": "1c0873b6", + "id": "951363d5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "🗓️ Today's date: Sunday, 2024-03-17\n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", "📖 \n" ] }, @@ -714,28 +887,32 @@ "output_type": "stream", "text": [ "\n", - "Of course! Air quality levels are typically measured on a scale, and the specific scale can vary depending on the location and the organization providing the measurements. Generally, air quality levels are categorized into different ranges, with each range corresponding to a specific level of air quality. Here is a general overview of air quality levels:\n", + "Certainly! PM2.5 levels are categorized as follows:\n", + "\n", + "1. Good (below 12 µg/m³): At this level, the air quality is considered safe for everyone, including those who are sensitive to air pollution. It's a great time to go for a walk or engage in outdoor activities.\n", + "\n", + "2. Moderate (12-18 µg/m³): While the air quality is generally safe, people with respiratory issues or sensitivities may experience some discomfort. It's still suitable for outdoor activities, but those with breathing concerns should take precautions.\n", + "\n", + "3. Poor (18-25 µg/m³): At this level, air quality is considered unhealthy for sensitive groups, such as children, the elderly, and those with respiratory conditions. It's advisable to limit prolonged outdoor exertion and consider indoor activities.\n", + "\n", + "4. Very poor (25-35 µg/m³): The air quality is unhealthy for the general population, and it's advised to minimize outdoor activities, especially for sensitive groups.\n", "\n", - "1. Good (0-50): The air quality is considered good, and it is safe for most people to breathe.\n", - "2. Moderate (51-100): The air quality is acceptable, but it may cause a slight irritation to some people with respiratory issues.\n", - "3. Poor (101-150): The air quality is considered unhealthy for sensitive groups, such as children, the elderly, and those with respiratory issues. It is advisable for these groups to limit their outdoor activities.\n", - "4. Very Poor (151-200): The air quality is considered unhealthy, and it may cause respiratory issues for most people. Outdoor activities should be limited.\n", - "5. Hazardous (over 200): The air quality is considered hazardous and can cause severe respiratory issues for everyone. Outdoor activities should be strictly avoided.\n", + "5. Hazardous (above 35 µg/m³): At this level, air quality is considered severely polluted and poses a significant health risk to everyone. It's crucial to limit outdoor activities and stay indoors with air filtration systems in place.\n", "\n", - "These categories may vary slightly depending on the location and the organization providing the measurements, but they generally provide a good understanding of the air quality levels.\n" + "Remember, these are general guidelines, and it's always best to consult local air quality advisories for the most accurate and up-to-date information.\n" ] } ], "source": [ - "QUESTION = \"Can you please explain different air quality levels?\"\n", + "QUESTION = \"Can you please explain different PM2_5 air quality levels?\"\n", "\n", "response = generate_response(\n", " QUESTION, \n", " feature_view, \n", - " model_llm, \n", - " tokenizer,\n", " model_air_quality, \n", " encoder,\n", + " model_llm, \n", + " tokenizer,\n", " llm_chain,\n", " verbose=True,\n", ")\n", @@ -745,7 +922,129 @@ }, { "cell_type": "markdown", - "id": "1fd12ab8", + "id": "b711f003", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 🧬 Inference with OpenAI\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ad2bd599", + "metadata": {}, + "outputs": [], + "source": [ + "from openai import OpenAI\n", + "import os\n", + "import getpass\n", + "\n", + "from functions.llm_chain import generate_response_openai" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "4ea97858", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "🔑 Enter your OpenAI API key: ···················································\n" + ] + } + ], + "source": [ + "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\") or getpass.getpass('🔑 Enter your OpenAI API key: ')\n", + "\n", + "client = OpenAI(\n", + " api_key=os.environ[\"OPENAI_API_KEY\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "1eeea10a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-03-27 14:42:13,003 INFO: HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (8.23s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", + "📖 Air Quality Measurements for London:\n", + "Date: 2024-03-18; Air Quality: 12.7\n", + "Date: 2024-03-19; Air Quality: 9.7\n", + "Date: 2024-03-20; Air Quality: 15.6\n", + "Date: 2024-03-21; Air Quality: 16.7\n", + "Date: 2024-03-22; Air Quality: 8.7\n", + "Date: 2024-03-23; Air Quality: 5.4\n", + "Date: 2024-03-24; Air Quality: 6.4\n", + "2024-03-27 14:42:41,602 INFO: HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "Last week in London, the air quality varied, starting off at a moderate level of 12.7 on the 18th. It slightly improved to 9.7 by the 19th, indicating relatively clean air that would be quite suitable for outdoor activities. There was a slight uptick in pollutants midweek, with air quality readings reaching 15.6 and 16.7 on the 20th and 21st respectively, suggesting a decrease in air quality but still remaining within a range considered safe, although individuals with sensitivity to air pollution might have experienced some discomfort. The air quality then improved significantly towards the end of the week, dropping to very good levels of 8.7 on the 22nd, 5.4 on the 23rd, and 6.4 on the 24th, indicating cleaner air and ideal conditions for spending time outdoors.\n" + ] + } + ], + "source": [ + "QUESTION = \"What was the air quality like last week in London?\"\n", + "\n", + "response = generate_response_openai( \n", + " QUESTION,\n", + " feature_view,\n", + " model_air_quality,\n", + " encoder,\n", + " client,\n", + " verbose=True,\n", + ")\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "441f51e2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-03-27 14:42:43,248 INFO: HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "Finished: Reading data from Hopsworks, using ArrowFlight (9.63s) \n", + "🗓️ Today's date: Wednesday, 2024-03-27\n", + "📖 Air Quality Measurements for Chicago:\n", + "Date: 2024-03-27; Air Quality: 3.0\n", + "Date: 2024-03-28; Air Quality: 8.06\n", + "2024-03-27 14:43:02,325 INFO: HTTP Request: POST https://api.openai.com/v1/chat/completions \"HTTP/1.1 200 OK\"\n", + "The air quality in Chicago tomorrow is expected to be at 8.06, which indicates a moderate level of pollutants. It's still relatively safe for most people, but individuals who are especially sensitive to air pollution might want to limit their outdoor activities. It's a good day to keep an eye on any changes if you have respiratory conditions or other health concerns related to air quality.\n" + ] + } + ], + "source": [ + "QUESTION4 = \"What will the air quality be like in Chicago tomorrow?\"\n", + "\n", + "response4 = generate_response_openai(\n", + " QUESTION4,\n", + " feature_view,\n", + " model_air_quality,\n", + " encoder,\n", + " client,\n", + " verbose=True,\n", + ")\n", + "\n", + "print(response4)" + ] + }, + { + "cell_type": "markdown", + "id": "d7d5577e", "metadata": {}, "source": [ "---" @@ -754,7 +1053,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -768,7 +1067,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/advanced_tutorials/air_quality/app_gradio.py b/advanced_tutorials/air_quality/app_gradio.py index 40bcf53c..8133662a 100644 --- a/advanced_tutorials/air_quality/app_gradio.py +++ b/advanced_tutorials/air_quality/app_gradio.py @@ -3,7 +3,8 @@ import numpy as np import hopsworks import joblib -from functions.llm_chain import load_model, get_llm_chain, generate_response +from openai import OpenAI +from functions.llm_chain import load_model, get_llm_chain, generate_response, generate_response_openai # Initialize the ASR pipeline transcriber = pipeline("automatic-speech-recognition", model="openai/whisper-base.en") @@ -58,9 +59,6 @@ def retrieve_llm_chain(): # Retrieve the feature view, air quality model and encoder for the city_name column feature_view, model_air_quality, encoder = connect_to_hopsworks() -# Load the LLM and its corresponding tokenizer and configure a language model chain -model_llm, tokenizer, llm_chain = retrieve_llm_chain() - def transcribe(audio): sr, y = audio y = y.astype(np.float32) @@ -69,36 +67,72 @@ def transcribe(audio): y /= np.max(np.abs(y)) return transcriber({"sampling_rate": sr, "raw": y})["text"] -def generate_query_response(user_query): - response = generate_response( - user_query, - feature_view, - model_llm, - tokenizer, - model_air_quality, - encoder, - llm_chain, - verbose=False, - ) - return response -def handle_input(text_input=None, audio_input=None): +# Generate query response - Adjust this function to handle both LLM and OpenAI API based on a parameter +def generate_query_response(user_query, method, openai_api_key=None): + if method == 'Hermes LLM': + # Load the LLM and its corresponding tokenizer and configure a language model chain + model_llm, tokenizer, llm_chain = retrieve_llm_chain() + + response = generate_response( + user_query, + feature_view, + model_air_quality, + encoder, + model_llm, + tokenizer, + llm_chain, + verbose=False, + ) + return response + + elif method == 'OpenAI API' and openai_api_key: + client = OpenAI( + api_key=openai_api_key + ) + + response = generate_response_openai( + user_query, + feature_view, + model_air_quality, + encoder, + client, + verbose=False, + ) + return response + + else: + return "Invalid method or missing API key." + + +def handle_input(text_input=None, audio_input=None, method='Hermes LLM', openai_api_key=""): if audio_input is not None: user_query = transcribe(audio_input) else: user_query = text_input + # Check if OpenAI API key is required but not provided + if method == 'OpenAI API' and not openai_api_key.strip(): + return "OpenAI API key is required for this method." + if user_query: - return generate_query_response(user_query) + return generate_query_response(user_query, method, openai_api_key) else: return "Please provide input either via text or voice." + +# Setting up the Gradio Interface iface = gr.Interface( fn=handle_input, - inputs=[gr.Textbox(placeholder="Type here or use voice input..."), gr.Audio()], + inputs=[ + gr.Textbox(placeholder="Type here or use voice input..."), + gr.Audio(), + gr.Radio(["Hermes LLM", "OpenAI API"], label="Choose the response generation method"), + gr.Textbox(label="Enter your OpenAI API key (only if you selected OpenAI API):", type="password") # Removed `optional=True` + ], outputs="text", title="🌤️ AirQuality AI Assistant 💬", - description="Ask your questions about air quality or use your voice to interact." + description="Ask your questions about air quality or use your voice to interact. Select the response generation method and provide an OpenAI API key if necessary." ) iface.launch(share=True) diff --git a/advanced_tutorials/air_quality/app_streamlit.py b/advanced_tutorials/air_quality/app_streamlit.py index 4dfa6d0c..00de1d74 100644 --- a/advanced_tutorials/air_quality/app_streamlit.py +++ b/advanced_tutorials/air_quality/app_streamlit.py @@ -1,7 +1,13 @@ import streamlit as st import hopsworks import joblib -from functions.llm_chain import load_model, get_llm_chain, generate_response +from openai import OpenAI +from functions.llm_chain import ( + load_model, + get_llm_chain, + generate_response, + generate_response_openai, +) import warnings warnings.filterwarnings('ignore') @@ -59,13 +65,40 @@ def retrieve_llm_chain(): # Retrieve the feature view, air quality model and encoder for the city_name column feature_view, model_air_quality, encoder = connect_to_hopsworks() -# Load the LLM and its corresponding tokenizer and configure a language model chain -model_llm, tokenizer, llm_chain = retrieve_llm_chain() - -# Initialize chat history -if "messages" not in st.session_state: +# Initialize or clear chat messages based on response source change +if "response_source" not in st.session_state or "messages" not in st.session_state: st.session_state.messages = [] + st.session_state.response_source = "" + +# User choice for model selection in the sidebar with OpenAI API as the default +new_response_source = st.sidebar.radio( + "Choose the response generation method:", + ('Hermes LLM', 'OpenAI API'), + index=1 # Sets "OpenAI API" as the default selection +) + +# If the user switches the response generation method, clear the chat +if new_response_source != st.session_state.response_source: + st.session_state.messages = [] # Clear previous chat messages + st.session_state.response_source = new_response_source # Update response source in session state + + # Display a message indicating chat was cleared (optional) + st.experimental_rerun() # Rerun the app to reflect changes immediately + + +if new_response_source == 'OpenAI API': + openai_api_key = st.sidebar.text_input("Enter your OpenAI API key:", type="password") + if openai_api_key: + client = OpenAI( + api_key=openai_api_key + ) + st.sidebar.success("API key saved successfully ✅") + +elif new_response_source == 'Hermes LLM': + # Conditionally load the LLM, tokenizer, and llm_chain if Local Model is selected + model_llm, tokenizer, llm_chain = retrieve_llm_chain() + # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"]): @@ -80,20 +113,35 @@ def retrieve_llm_chain(): st.write('⚙️ Generating Response...') - # Generate a response to the user query - response = generate_response( - user_query, - feature_view, - model_llm, - tokenizer, - model_air_quality, - encoder, - llm_chain, - verbose=False, - ) + if new_response_source == 'Hermes LLM': + # Generate a response to the user query + response = generate_response( + user_query, + feature_view, + model_air_quality, + encoder, + model_llm, + tokenizer, + llm_chain, + verbose=False, + ) + + elif new_response_source == 'OpenAI API' and openai_api_key: + response = generate_response_openai( + user_query, + feature_view, + model_air_quality, + encoder, + client, + verbose=False, + ) + + else: + response = "Please select a response generation method and provide necessary details." # Display assistant response in chat message container with st.chat_message("assistant"): st.markdown(response) # Add assistant response to chat history st.session_state.messages.append({"role": "assistant", "content": response}) + \ No newline at end of file diff --git a/advanced_tutorials/air_quality/functions/context_engineering.py b/advanced_tutorials/air_quality/functions/context_engineering.py index 4b3bd4dc..57662c88 100644 --- a/advanced_tutorials/air_quality/functions/context_engineering.py +++ b/advanced_tutorials/air_quality/functions/context_engineering.py @@ -7,6 +7,7 @@ import torch import sys import pandas as pd +from openai import OpenAI from functions.air_quality_data_retrieval import get_data_for_date, get_data_in_date_range, get_future_data from typing import Any, Dict, List @@ -42,11 +43,10 @@ def serialize_function_to_json(func: Any) -> str: return json.dumps(function_info, indent=2) -def generate_hermes(prompt: str, model_llm, tokenizer) -> str: - """Retrieves a function name and extracts function parameters based on the user query.""" +def get_function_calling_prompt(user_query): fn = """{"name": "function_name", "arguments": {"arg_1": "value_1", "arg_2": value_2, ...}}""" - example = """{"name": "get_data_in_date_range", "arguments": {"date_start": "2024-01-10", "date_end": "2024-01-14", "city_name": "New York"}}""" - + example = """{"name": "get_data_in_date_range", "arguments": {"date_start": "2024-01-10", "date_end": "2024-01-14", "city_name": ""}}""" + prompt = f"""<|im_start|>system You are a helpful assistant with access to the following functions: @@ -60,7 +60,7 @@ def generate_hermes(prompt: str, model_llm, tokenizer) -> str: - You need to choose one function to use and retrieve paramenters for this function from the user input. - If the user query contains 'will', it is very likely that you will need to use the get_future_data function. - Do not include feature_view, model and encoder parameters. -- Dates should be provided in the format YYYY-MM-DD. +- Provide dates STRICTLY in the YYYY-MM-DD format. - Generate an 'No Function needed' string if the user query does not require function calling. IMPORTANT: Today is {datetime.date.today().strftime("%A")}, {datetime.date.today()}. @@ -77,21 +77,31 @@ def generate_hermes(prompt: str, model_llm, tokenizer) -> str: - AI Assiatant: No Function needed. EXAMPLE 2: -- User: Is it good or bad? +- User: Is this Air Quality level good or bad? - AI Assiatant: No Function needed. EXAMPLE 3: -- User: When and what was the minimum air quality from 2024-01-10 till 2024-01-14 in New York? +- User: When and what was the minimum air quality from 2024-01-10 till 2024-01-14 in ? - AI Assistant: {example} - <|im_end|> + <|im_start|>user -{prompt}<|im_end|> +{user_query} +<|im_end|> + <|im_start|>assistant""" + return prompt + + +def generate_hermes(user_query: str, model_llm, tokenizer) -> str: + """Retrieves a function name and extracts function parameters based on the user query.""" + + prompt = get_function_calling_prompt(user_query) + tokens = tokenizer(prompt, return_tensors="pt").to(model_llm.device) input_size = tokens.input_ids.numel() with torch.inference_mode(): @@ -113,6 +123,36 @@ def generate_hermes(prompt: str, model_llm, tokenizer) -> str: ) +def function_calling_with_openai(user_query: str, client) -> str: + """ + Generates a response using OpenAI's chat API. + + Args: + user_query (str): The user's query or prompt. + instructions (str): Instructions or context to provide to the GPT model. + + Returns: + str: The generated response from the assistant. + """ + + instructions = get_function_calling_prompt(user_query).split('<|im_start|>user')[0] + + completion = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "system", "content": instructions}, + {"role": "user", "content": user_query}, + ] + ) + + # Extract and return the assistant's reply from the response + if completion and completion.choices: + last_choice = completion.choices[0] + if last_choice.message: + return last_choice.message.content.strip() + return "" + + def extract_function_calls(completion: str) -> List[Dict[str, Any]]: """Extract function calls from completion.""" completion = completion.strip() @@ -150,7 +190,7 @@ def invoke_function(function, feature_view, model, encoder) -> pd.DataFrame: return function_output -def get_context_data(user_query: str, feature_view, model_llm, tokenizer, model_air_quality, encoder) -> str: +def get_context_data(user_query: str, feature_view, model_air_quality, encoder, model_llm=None, tokenizer=None, client=None) -> str: """ Retrieve context data based on user query. @@ -165,12 +205,18 @@ def get_context_data(user_query: str, feature_view, model_llm, tokenizer, model_ Returns: str: The context data. """ - # Generate a response using LLM - completion = generate_hermes( - user_query, - model_llm, - tokenizer, - ) + + if client: + # Generate a response using LLM + completion = function_calling_with_openai(user_query, client) + + else: + # Generate a response using LLM + completion = generate_hermes( + user_query, + model_llm, + tokenizer, + ) # Extract function calls from the completion functions = extract_function_calls(completion) diff --git a/advanced_tutorials/air_quality/functions/llm_chain.py b/advanced_tutorials/air_quality/functions/llm_chain.py index 6d0833ac..d4ee9fdd 100644 --- a/advanced_tutorials/air_quality/functions/llm_chain.py +++ b/advanced_tutorials/air_quality/functions/llm_chain.py @@ -63,23 +63,18 @@ def get_prompt_template(): instructions, previous conversation, context, date and user query. """ prompt_template = """<|im_start|>system -You are a helpful Air Quality assistant. -Provide your answers based on the provided context table which consists of the dates and air quality indicators for the city provided by user. +You are one of the best air quality experts in the world. -INSTRUCTIONS: +###INSTRUCTIONS: - If you don't know the answer, you will respond politely that you cannot help. -- Use the provided table with air quality indicators for city provided by user to generate your answer. +- Use the context table with air quality indicators for city provided by user to generate your answer. - You answer should be at least one sentence. - Do not show any calculations to the user. -- If the user asks for the air quality level in specific range, you can calculate an average air quality level. -- Make sure that you use correct air quality indicators for the required date. -- Add a description of the air quality level, such as whether it is safe, whether to go for a walk, etc. -- If user asks more general question, use your last responses in the chat history as a context. +- Make sure that you use correct air quality indicators for the corresponding date. +- Add a rich analysis of the air quality level, such as whether it is safe, whether to go for a walk, etc. +- Do not mention in your answer that you are using context table. <|im_end|> -Previous conversation: -{chat_history} - ### CONTEXT: {context} @@ -124,23 +119,15 @@ def get_llm_chain(model_llm, tokenizer): # Create prompt from prompt template prompt = PromptTemplate( - input_variables=["context", "question", "date_today", "chat_history"], + input_variables=["context", "question", "date_today"], template=get_prompt_template(), ) - # Create a ConversationBufferWindowMemory with specified configuration - memory = ConversationBufferWindowMemory( - k=3, # Number of turns to remember in the conversation buffer - memory_key="chat_history", # Key to store the conversation history in memory - input_key="question", # Key to access the input question in the conversation - ) - # Create LLM chain llm_chain = LLMChain( llm=mistral_llm, prompt=prompt, verbose=False, - memory=memory, ) return llm_chain @@ -149,11 +136,11 @@ def get_llm_chain(model_llm, tokenizer): def generate_response( user_query: str, feature_view, - model_llm, - tokenizer, model_air_quality, encoder, - llm_chain, + model_llm, + tokenizer, + llm_chain=None, verbose: bool = False, ) -> str: """ @@ -172,15 +159,14 @@ def generate_response( Returns: str: Generated response to the user query. """ - # Get context data based on user query context = get_context_data( user_query, feature_view, - model_llm, - tokenizer, model_air_quality, encoder, + model_llm=model_llm, + tokenizer=tokenizer, ) # Get today's date in a readable format @@ -197,6 +183,55 @@ def generate_response( "date_today": date_today, "question": user_query, }) - + # Return the generated text from the model output - return model_output['text'] + return model_output['text'].split('<|im_start|>assistant')[-1] + + +def generate_response_openai( + user_query: str, + feature_view, + model_air_quality, + encoder, + client, + verbose=True, +): + + context = get_context_data( + user_query, + feature_view, + model_air_quality, + encoder, + client=client, + ) + + # Get today's date in a readable format + date_today = f'{datetime.date.today().strftime("%A")}, {datetime.date.today()}' + + # Print today's date and context information if verbose mode is enabled + if verbose: + print(f"🗓️ Today's date: {date_today}") + print(f'📖 {context}') + + instructions = get_prompt_template().split('<|im_start|>user')[0] + + instructions_filled = instructions.format( + context=context, + date_today=date_today + ) + + completion = client.chat.completions.create( + model="gpt-4-0125-preview", + messages=[ + {"role": "system", "content": instructions_filled}, + {"role": "user", "content": user_query}, + ] + ) + + # Extract and return the assistant's reply from the response + if completion and completion.choices: + last_choice = completion.choices[0] + if last_choice.message: + return last_choice.message.content.strip() + return "" + \ No newline at end of file