diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 1 - Get Gen AI recommendation.md b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 1 - Get Gen AI recommendation.md new file mode 100644 index 00000000..02617e29 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 1 - Get Gen AI recommendation.md @@ -0,0 +1,390 @@ +# User Story: Get Generative AI recommendations - Step-by-Step +⏲️ _Est. time to complete: 30 min._ ⏲️ + +## User Story +*As a user, I want to receive AI-generated recommendations on how to complete a task when I click on it.* + +## 🎯Acceptance Criteria: +- The web app should leverage AI to analyze task name and provide relevant recommendations. +- Recommendations should be displayed in a separate section on the task details page. +- Recommendations could include suggested steps, related resources, or tips to complete the task effectively as well as a link to the resource. +- The interface should look something like this: + + ![Recommendations](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/outcome-S05-F01-US01.png) + +## 🎓Know Before You Start +no resources at this time + +## 📋Steps + +In order to complete this user story you will need to complete the following tasks: + +### Open Visual Studio Code +Open Visual Studio Code and open the source code the folder with your completed solution from the previous user story if you prefer you can use the starting reference application from [here](/Track_2_ToDo_App/Sprint-04%20-%20Voice%20To%20Text/src/app-s04-f01-us01/) + +
+ +### Setup the AI SDK + +#### 1. Install the OpenAI Python library +The first thing we need to do is to install the OpenAI and Semantic Kernel Python libraries. Open a terminal window in Visual Studio Code and run the following command: + +```bash +pip install openai +pip install semantic-kernel==0.9.5b1 +``` + +### Recommendation Engine +The first step in completing this user story is to create the recommendation engine that will generate AI recommendations based on the task name. The recommendation engine will interact with the AI service to generate recommendations and return them to the web application. + +#### 1. Create a Service Enumeration Module +This recommendation engine will support both OpenAI and Azure OpenAI protocols. So in order to make working with that easier we are going to setup a quick enumeration module that allows us to see which service we are using. To do this we will need to create a new Python file called `services.py` in the same folder as your `app.py` file. Add the following code to the `services.py` file: + +```python +from enum import Enum + +class Service(Enum): + OpenAI = "openai" + AzureOpenAI = "azureopenai" +``` + +This code defines an enumeration class called `Service` that contains the different AI services that can be used for generating recommendations. For this example, we will be using the Azure OpenAI service. + +
+ +#### Create the Recommendation Engine + +#### 1. Create the Recommendation Engine Module +We need to create the recommendation engine that will generate AI recommendations based on the task name. To do this, we need to create a new Python file called `recommendation_engine.py` in the same folder as your `app.py` file. Add the following code to the `recommendation_engine.py` file: + +```python +import json +import asyncio +import semantic_kernel as sk +from services import Service +from openai import AzureOpenAI +from dotenv import dotenv_values +``` + +This code imports the necessary libraries and modules for the recommendation engine. The `semantic_kernel` module is used to calculate the semantic similarity between the task name and the recommendations. The `Service` class is used to interact with the Azure OpenAI API. The `AzureOpenAI` class is used to generate the AI recommendations. The `dotenv_values` function is used to load environment variables from a `.env` file. + +
+ +#### 2. Load Configuration and Select AI Service +We now need to have the recommendation engine module load the configuration file and tell the recommendation engine which AI service to use. Add the following code to the `recommendation_engine.py` file right after the imports: + +```python +config = dotenv_values(".env") + +#uses the USE_AZURE_OPENAI variable from the .env file to determine which AI service to use +#False means use OpenAI, True means use Azure OpenAI +selectedService = Service.AzureOpenAI if config.get("USE_AZURE_OPENAI") == "True" else Service.OpenAI +``` + +This code loads the configuration file from the `.env` file and determines which AI service to use based on the `USE_AZURE_OPENAI` variable in the configuration file. If the variable is set to `True`, the Azure OpenAI service is selected. If the variable is set to `False`, the OpenAI service is selected. + +
+ +#### 3. Create the Recommendation Engine Class +Next, we will create the recommendation engine class that will generate the AI recommendations. Add the following code to the end of the `recommendation_engine.py` file: + +```python + +deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() + +class RecommendationEngine: + + def __init__(self): + + self.client = AzureOpenAI(azure_endpoint = endpoint, + api_key=api_key, + api_version="2024-02-15-preview" + ) +``` + +This code defines the `RecommendationEngine` class, which initializes the AI service based on the selected service in the configuration file. If the selected service is Azure OpenAI, an instance of the `AzureOpenAI` class is created with the Azure OpenAI key from the configuration file. If the selected service is OpenAI, an exception is raised since the OpenAI service is not implemented yet. + +
+ +#### 4. Create the Get Recommendations Method +Next, we will create a method in the `RecommendationEngine` class to generate AI recommendations based on the task name. Add the following code to the `recommendation_engine.py` file as a method of the `RecommendationEngine` class: + +```python +async def get_recommendations(self, keyword_phrase): + prompt = f"""Please return 5 recommendations based on the input string: '{keyword_phrase}' using correct JSON syntax that contains a title and a hyperlink back to the supporting website. RETURN ONLY JSON AND NOTHING ELSE""" + system_prompt = """You are an administrative assistant bot who is good at giving + recommendations for tasks that need to be done by referencing website links that can provide + assistance to helping complete the task. + + If there are not any recommendations simply return an empty collection. + + EXPECTED OUTPUT: + Provide your response as a JSON object with the following schema: + [{"title": "...", "link": "..."}, + {"title": "...", "link": "..."}, + {"title": "...", "link": "..."}] + """ + + message_text = [{"role":"system","content":system_prompt}, + {"role":"user","content":prompt},] + + response = self.client.chat.completions.create( + model = deployment, + messages = message_text, + temperature=0.14, + max_tokens=800, + top_p=0.17, + frequency_penalty=0, + presence_penalty=0, + stop=None + ) + + result = response.choices[0].message.content + print(result) + + try: + recommendation = json.loads(result) + except Exception as e: + print(f"Error loading recommendations: {e}") + recommendation = [{"title": "Sorry, unable to recommendation at this time", "link": ""}] + + return recommendation +``` + +> [!NOTE] +> That when copy/pasting code from the browser into Visual Studio Code, the indentation may not be correct. Make sure to check the indentation of the code after pasting it into Visual Studio Code. + +This code defines the `get_recommendations` method, which generates AI recommendations based on the task name. The method constructs a prompt for the AI model to generate recommendations based on the input keyword. The response from the AI model is parsed to extract the recommendations in JSON format. If an error occurs during parsing, a default error message is returned. + +There are a few things to note in the code above: +- The `system_prompt` variable contains the instructions for the AI model on how to generate recommendations. This text is used to guide the AI model in generating the recommendations. For this scenario we are using the following system_prompt + +```text +You are an administrative assistant bot who is good at giving recommendations +for tasks that need to be done by referencing website links that can provide +assistance to helping complete the task. +``` + +- The `prompt` variable contains the input string that the AI model will use to generate recommendations. The `keyword_phrase` parameter is used to construct the prompt and should be the task name. For this scenario we are using the following prompt: + +```text +Please return 5 recommendations based on the input string: '{keyword_phrase}' +using correct JSON syntax that contains a title and a hyperlink back to the +supporting website. RETURN ONLY JSON AND NOTHING ELSE +``` +- The combination of the `system_prompt` and `prompt` variables is used to create the `message_text` variable, which is passed to the AI model to generate recommendations. +- Tweaking these prompts to get the best results from the AI model is a key part of the process and is known as **prompt engineering**. +- The `temperature`, `max_tokens`, `top_p`, `frequency_penalty`, and `presence_penalty` parameters are used to control the behavior of the AI model and the quality of the recommendations. These parameters can be adjusted to fine-tune the recommendations generated by the AI model. + +
+ +#### 5. Create configuration settings for Azure OpenAI +We now need to add the `.env` file to our project. This file will contain all of the keys and secrets needed to properly configure the Azure OpenAI service. Create a `.env` file in the same folder as your `app.py` file. This file will be used to store the configuration settings for the Azure OpenAI service and should look something like this: + +```text +USE_AZURE_OPENAI=True +AZURE_OPENAI_DEPLOYMENT_NAME= +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +``` + +This code sets the `USE_AZURE_OPENAI` variable to `True` to use the Azure OpenAI service. The `AZURE_OPENAI_DEPLOYMENT_NAME`, `AZURE_OPENAI_API_KEY`, and `AZURE_OPENAI_ENDPOINT` variables are used to configure the Azure OpenAI service. Replace ``, ``, and `` with the appropriate values for your Azure OpenAI service (you will be given these by one of the coaches). The rest of the keys you can ignore for now. + +
+ +#### 6. Build Unit Test for Recommendation Engine +Given that this functionality is complex we will want to be able to test the code outside of the larger application. A quick way to do this is to add a test function in the same file and if the developer "runs" this file instead of the app.py file, they will get the recommendation engine test function to run. To do this we need to add the following code to the end of the file `recommendation_engine.py` to test the recommendation engine: + +```python +async def test_recommendation_engine(): + engine = RecommendationEngine() + recommendations = await engine.get_recommendations("Buy a birthday gift for mom") + count = 1 + for recommendation in recommendations: + print(f"{count} - {recommendation['title']}: {recommendation['link']}") + count += 1 + +if __name__ == "__main__": + asyncio.run(test_recommendation_engine()) +``` + +This code defines a test function `test_recommendation_engine` that creates an instance of the `RecommendationEngine` class and generates AI recommendations based on the input keyword phrase "Buy a birthday gift for mom". The recommendations are printed to the console. The test function is executed when the file is run as the main program. + +
+ +#### 7. Test the Recommendation Engine +Go to the terminal window in Visual Studio Code and run the following command to test the recommendation engine: + +```bash +python recommendation_engine.py +``` +You should see results that are similar to this: +![Recommendation Engine Test](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/recommendation_engine_test-S5-F1-US1-01.png) + +Feel free to modify the input keyword phrase in the `test_recommendation_engine` method to test different recommendations. + +
+ +### Integrate Recommendation Engine into Web Application +Now that we have tested that our recommendation engine is working properly we will integrate the recommendation engine into the web application. + +#### 1. Install Flask Asynchronous Support +First, we need to make sure that we have asynchronous support in our flask application. Open a terminal window in Visual Studio Code and run the following command: + +```bash +pip install 'flask[async]' +``` + +This command installs the asynchronous support for Flask, which allows us to use asynchronous functions in our web application. + +
+ +#### 2. Update the Database Model +We need to update our model to hold the recommendations. Open the `database.py` file in the source folder of your application. Add the following code right under the `name` instance variable to create the additional column + +```python +recommendations = [] +``` + +This code adds a new transient column to the `Todo` model called `recommendations`. This column will hold the collection of recommendations that come back from the recommendation engine, but it will not be stored in the database. + +
+ +#### 3. Import Recommendation Engine into Application +We now need integrate the recommendation engine into the application. Open the `app.py` file in the source folder of your application. Add the following module import to the `app.py` file right after the `from database...` statement to make the recommendation engine accessible to the flask application. + +```python +from recommendation_engine import RecommendationEngine +``` + +
+ +#### 4. Create Recommendation Route to handle recommendations +Now that we have tested that our recommendation engine is working properly we will integrate the recommendation engine into the web application. Open the `app.py` file in the source folder of your application. Add the following code right after the `remove_todo()` function to the `app.py` file to create the backend functionality that the web app will use to get AI recommendations based on the task name: + +```python +# Show AI recommendations +@app.route('/recommend/', methods=['GET']) +async def recommend(id): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name) + + return render_template('index.html') +``` + +This code defines a new route `/recommend/` that takes the task ID as a parameter. The route initializes the recommendation engine, retrieves the task from the database based on the ID, and generates AI recommendations based on the task name. The recommendations are stored in the task object and rendered in the `index.html` template. + +
+ +### Updating the Web Application FrontEnd + +#### 1. Update the User Interface to Display Recommendations +To display the AI recommendations in the web application, we need to update the user interface to show the recommendations when a user clicks on a task. We will add a button to each task that will trigger the AI recommendations and display them in a separate tab on the right side of the task list. To accompish this, we will make several changes to the `index.html` file in the `templates` folder of your application. +- We will add a button to each task that will trigger the AI recommendations for each task +- We will move our add button into the same col as the list of tasks, so that the add button stays aligned with the task list as the recommendation tab may vary in size. +- We will adjust the width of the task list to leave room on the right for the recommendations +- We will display the recommendations in a separate tab to the right of the list + +For the sake of making these changes easier in this step-by-step, instead of walking through each change we will simply ask you to replace the code in the `index.html` file with the code provided below. This will allow you to see the changes in the web application and understand how the changes were made. + +Open the `index.html` file in the `templates` folder of your application. Replace the entire contents of the `index.html` file with the following code: + +```html + + + + My To-Do List + + + + + + +
+
+
+
+

My To-Do List

+
+
+
+
+
+
    + {% for todo in g.todos %} +
  1. +
    +
    {{ todo.name }}
    +
    + + + + +
  2. + {% endfor %} +
+
+
+
+
+
+ + + + + + +
+ +
+
+ {% if g.todo != None %} +
+
+
+
AI Recommendations for "{{ g.todo.name }}"
+ {% for recommend in g.todo.recommendations %} + {{ recommend.title }} + {% endfor %} +
+
+
+
+ {% endif %} +
+
+
+ + +``` + +
+ +#### 2. Run the Application +Now let's run the application to test the AI recommendations. Open a terminal window in Visual Studio Code and run the following command: + +```bash +python app.py +``` +the application should start and you should be able to see the AI recommendations when you click on the 'Recommendations' button for a task. The recommendations should be displayed in a separate tab on the right side of the task list and look something like this: +![outcome](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/outcome-S05-F01-US01.png) + +
+🎉 Congratulations! You have now added AI recommendations to your web app, allowing users to get additional information on how to complete their tasks. + +
+ +> [!NOTE] +> 📄For the full source code for this exercise please see [here](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/src/app-s05-f01-us01/). + + +
+ +[🔼 Back **Workshop** Instructions ](/Track_2_ToDo_App/Workshop-Format.md) | [🔼 Back to **Hackathon** Sprint 5 ](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/README.md) | [**◀ Previous user story** (in previous sprint)](/Track_2_ToDo_App/Sprint-04%20-%20Voice%20To%20Text/Feature%201%20-%20Add%20Voice/User%20Story%201%20-%20Add%20Voice.md) | [**Next user story** ▶](User%20Story%202%20-%20Cache%20recommendations%20in%20DB.md) diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 2 - Cache recommendations in DB.md b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 2 - Cache recommendations in DB.md new file mode 100644 index 00000000..98cd2bf2 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 2 - Cache recommendations in DB.md @@ -0,0 +1,104 @@ +# User Story: Cache Recommendations in the Database - Step-by-Step +⏲️ _Est. time to complete: 30 min._ ⏲️ + +## User Story +*As a user, I want to be able to quickly go back and see the recommendations that were last presented for a given to-do item* + +## 🎯Acceptance Criteria: +- The web app should store the recommendations in the database when they are generated. +- The recommendations should be displayed when the user clicks on the recommendations button for the task again. + +## 🎓Know Before You Start +no resources at this time + +## 📋Steps + +In order to complete this user story you will need to complete the following tasks: + +### Open Visual Studio Code +Open Visual Studio Code and open the source code the folder with your completed solution from the previous user story if you prefer you can use the starting reference application from [here](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/src/app-s05-f01-us01/) + +### Update the database to store the recommendations in the database + +#### 1. Update the Database Model to Store recommendations +The first thing we will need to do is add the recommendations to the database. Open the `database.py` file and add the following code right under the `recommendations = []` instance variable to create the additional column + +```python +recommendations_json = db.Column(db.JSON) +``` + +This code adds a new column to the `Todo` model called `recommendations_json` that will store the AI recommendations in JSON format. Note we will still keep the recommendations as a list in the `recommendations` instance variable so that it is easy for the UI to work with. + +
+ +### Update the Application Backend + +#### 1. Update the `app.py` to handle saving and retrieving recommendations +We now need to update the `app.py` to handle saving and retrieving recommendations. We need to make two changes to this file. (A) import the json module and (B) Replace the recommend route in the `app.py` file with the following code: + +A. At the top of the file add the following import statement: + +```python +import json +``` +B. We will then need to replace the `recommend` route with the following code: + +```python +# Show AI recommendations +@app.route('/recommend/', methods=['GET']) +async def recommend(id): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + + if g.todo: + try: + #attempt to load any saved recommendation from the DB + if g.todo.recommendations_json is not None: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + return render_template('index.html') + except ValueError as e: + print("Error:", e) + + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name) + + # Save the recommendations to the database + try: + g.todo.recommendations_json = json.dumps(g.todo.recommendations) + db.session.add(g.todo) + db.session.commit() + except Exception as e: + print(f"Error adding and committing todo: {e}") + return + + return render_template('index.html') +``` + +This code updates the `/recommend/` route to check if the task has saved recommendations in the database. If the recommendations are found, they are loaded from the database and displayed. If the recommendations are not found, the AI recommendations are generated and saved to the database. + +#### 2. Delete the `todos.db` file +Before we can test this change, we will need to delete the `todos.db` file in your directory. This is because we have added a column to the database and to keep things simple we will just start fresh versus trying to update the database schema. This approach works fine when you are in development, but if this was a production system you would want to update the database schema and migrate the data. When you do this any items saved in the database will be lost. + +> [!WARNING] +> If you do not delete the `todos.db` file you will get an error when you run the app. The error will be something like `sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: todos.recommendations_json`. This error is because the database schema does not match the model. + +
+ +#### 3. Run the Application +Now let's see this functionality in action. Start the web app by running the following command in the terminal: + +```bash +python app.py +``` + +
+🎉 Congratulations! You have now saved the AI recommendations to the database. This will allow users to quickly go back and see the recommendations that were last presented for a given to-do item. + +
+ +> [!NOTE] +> 📄For the full source code for this exercise please see [here](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/src/app-s05-f01-us02/). + + +
+ +[🔼 Back **Workshop** Instructions ](/Track_2_ToDo_App/Workshop-Format.md) | [🔼 Back to **Hackathon** Sprint 5 ](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/README.md) | [**◀ Previous user story**](User%20Story%201%20-%20Get%20Gen%20AI%20recommendation.md) | [**Next user story** ▶](User%20Story%203%20-%20Refresh%20Recommendations.md) diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 3 - Refresh Recommendations.md b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 3 - Refresh Recommendations.md new file mode 100644 index 00000000..8d8f60cd --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/Feature 1 - Get Generative AI recommendation/User Story 3 - Refresh Recommendations.md @@ -0,0 +1,183 @@ +# User Story: Allow the user to refresh the recommendations - Step-by-Step +⏲️ _Est. time to complete: 30 min._ ⏲️ + +## User Story +*As a user, I want to be able to tell the system to re-generate the recommendations on how to complete a task when I click on it* + +## 🎯Acceptance Criteria: +- The web app should provide a user interface element to refresh the recommendations for a task. +- The system should re-generate the recommendations when the user clicks on the refresh button. +- The new recommendations should be displayed in the recommendations section. +- The new recommendations should be stored in the database and overwrite the old recommendations. +- The AI recommendation engine should not have any reuse old recommendations in the new recommendation list. +- The interface should look something like this: + + ![Refresh Recommendations](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/outcome-S05-F01-US03.png) + +## 🎓Know Before You Start +no resources at this time + +## 📋Steps + +In order to complete this user story you will need to complete the following tasks: + +### Open Visual Studio Code +Open Visual Studio Code and open the source code the folder with your completed solution from the previous user story if you prefer you can use the starting reference application from [here](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/src/app-s05-f01-us02/) + +
+ +### Update the recommendation engine to allow for refreshing recommendations + +#### 1. Update get_recommendations method +The first thing that we will need to do is update the recommendation engine to enable it to re-generate new recommendations. Open the `recommendation_engine.py` file in the source folder of your application. We will need to replace the `get_recommendations` method with the following code: + +```python +async def get_recommendations(self, keyword_phrase, previous_links_str=None): + prompt = f"""Please return 5 recommendations based on the input string: '{keyword_phrase}' using correct JSON syntax that contains a title and a hyperlink back to the supporting website. RETURN ONLY JSON AND NOTHING ELSE""" + system_prompt = """You are an administrative assistant bot who is good at giving + recommendations for tasks that need to be done by referencing website links that can provide + assistance to helping complete the task. + + If there are not any recommendations simply return an empty collection. + + EXPECTED OUTPUT: + Provide your response as a JSON object with the following schema: + [{"title": "...", "link": "..."}, + {"title": "...", "link": "..."}, + {"title": "...", "link": "..."}] + """ + + if previous_links_str is not None: + prompt = prompt + f". EXCLUDE the following links from your recommendations: {previous_links_str}" + + message_text = [{"role":"system","content":system_prompt}, + {"role":"user","content":prompt},] + + response = self.client.chat.completions.create( + model= deployment, + messages = message_text, + temperature=0.14, + max_tokens=800, + top_p=0.17, + frequency_penalty=0, + presence_penalty=0, + stop=None + ) + + result = response.choices[0].message.content + print(result) + + try: + recommendation = json.loads(result) + except Exception as e: + print(f"Error loading recommendations: {e}") + recommendation = [{"title": "Sorry, unable to recommendation at this time", "link": ""}] + + return recommendation +``` + +The only changes that we made to this function are as follows: +![Recommendation Engine Changes](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/Recommendation_engine_changes-S05-F01-US03.png) +- We added the `previous_links_str` parameter to the function. This parameter will be used to pass in any previous recommendations that we want to exclude from the new recommendations. +- We updated the prompt to include the `previous_links_str` if it is passed in. You will notice that we simply append some additional prompt text to the exiting prompt to exclude previous recommendations where `{previous_links_str}` is replaced with the string of the previous recommendations. + + ```text + . EXCLUDE the following links from your recommendations: {previous_links_str} + +
+ +### Updating Web Application Backend to handle refreshing recommendations + +#### 1. Update Recommend Route +We now need to update the backend route to handle the refresh of the recommendations. Open the `app.py` file and replace the `recommend` route with the following code: + +```python +@app.route('/recommend/', methods=['GET']) +@app.route('/recommend//', methods=['GET']) +async def recommend(id, refresh=False): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + + if g.todo and not refresh: + try: + #attempt to load any saved recommendation from the DB + if g.todo.recommendations_json is not None: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + return render_template('index.html') + except ValueError as e: + print("Error:", e) + + previous_links_str = None + if refresh: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + # Extract links + links = [item["link"] for item in g.todo.recommendations] + # Convert list of links to a single string + previous_links_str = ", ".join(links) + + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name, previous_links_str) + + # Save the recommendations to the database + try: + g.todo.recommendations_json = json.dumps(g.todo.recommendations) + db.session.add(g.todo) + db.session.commit() + except Exception as e: + print(f"Error adding and committing todo: {e}") + return + + return render_template('index.html') +``` + +This code adds a new route `/refresh//refresh` to the existing recommend function that will handle the refresh of the recommendations. It also updates the parameter to the `recommend` route to include a `refresh` parameter that will be used to determine if the user is requesting a refresh of the recommendations. If the user is requesting a refresh, the existing recommendations are loaded from the database and passed to the `get_recommendations` method along with the `previous_links_str` that is generated from the existing recommendations. The new recommendations are then saved to the database and displayed on the UI. + +
+ +### Update the User Interface to include a "refresh" button + +#### 1. Update the `index.html` file +Finally we will need to update the UI to include a "refresh" button. Open the `index.html` file in the `templates` folder of your application. Replace the `
`section with the following changes: + +```html +
+
+
+
AI Recommendations for "{{ g.todo.name }}"
+ {% for recommend in g.todo.recommendations %} + {{ recommend.title }} + {% endfor %} +
+
+ Don't like recommendations? + Refresh +
+
+``` + +This code adds a "Refresh Recommendations" button to the UI that will call the `/recommend//refresh` route when clicked. This will allow the user to refresh the recommendations for a task and see new recommendations generated by the AI recommendation engine. + +
+ +#### 2. Run the Web Application +Now let's see this functionality in action. Start the web app by running the following command in the terminal: + +```bash +python app.py +``` + +You should now see a "Refresh" button within the recommendations section of the task details page. It should look something like this: +![Refresh Recommendations](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/images/outcome-S05-F01-US03.png) + + +
+🎉 Congratulations! You have now have the ability to refresh your AI recommendations. + +
+ +> [!NOTE] +> 📄For the full source code for this exercise please see [here](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/src/app-s05-f01-us03/). + + +
+ +[🔼 Back **Workshop** Instructions ](/Track_2_ToDo_App/Workshop-Format.md) | [🔼 Back to **Hackathon** Sprint 5 ](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/README.md) | [**◀ Previous user story**](User%20Story%202%20-%20Cache%20recommendations%20in%20DB.md) | [**Next user story** (in next sprint) ▶](/Track_2_ToDo_App/Sprint-06%20-%20Advanced%20To-Do%20Details/Feature%201%20-%20Add%20Additional%20To-Do%20Details/User%20Story%201%20-%20Add%20additional%20details%20to%20to-do%20item.md) diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/README.md b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/README.md new file mode 100644 index 00000000..7954f3d6 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/README.md @@ -0,0 +1,13 @@ +# Sprint 5: Advanced AI Recommendations +⏲️ _Est. time to complete: 60 min._ ⏲️ + +In this sprint you will be taking the application that you built in Sprint 4 and adding in AI recommendations to help users complete their tasks. This will be done by leveraging the Azure OpenAI API to generate recommendations based on the task name. + +**📕Feature: Advanced AI Recommendations** +1. [**📖 Get Recommendations from Generative AI based on To-Do name**](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/Feature%201%20-%20Get%20Generative%20AI%20recommendation/User%20Story%201%20-%20Get%20Gen%20AI%20recommendation.md) +2. [**📖 Store the recommendations in the DB for a task**](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/Feature%201%20-%20Get%20Generative%20AI%20recommendation/User%20Story%202%20-%20Cache%20recommendations%20in%20DB.md) +3. [**📖 Allow the user to refresh the recommendations**](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/Feature%201%20-%20Get%20Generative%20AI%20recommendation/User%20Story%203%20-%20Refresh%20Recommendations.md) + +
+ +[🔼 Hackathon Home Page ](/Track_2_ToDo_App/README.md) | [◀ Previous Sprint](/Track_2_ToDo_App/Sprint-04%20-%20Voice%20To%20Text/README.md) | [Next sprint ▶](/Track_2_ToDo_App/Sprint-06%20-%20Advanced%20To-Do%20Details/README.md) \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/Recommendation_engine_changes-S05-F01-US03.png b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/Recommendation_engine_changes-S05-F01-US03.png new file mode 100644 index 00000000..aaf4c45d Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/Recommendation_engine_changes-S05-F01-US03.png differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US01.png b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US01.png new file mode 100644 index 00000000..53caa624 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US01.png differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US03.png b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US03.png new file mode 100644 index 00000000..d7ebc5c3 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/outcome-S05-F01-US03.png differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/recommendation_engine_test-S5-F1-US1-01.png b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/recommendation_engine_test-S5-F1-US1-01.png new file mode 100644 index 00000000..be7e019f Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/images/recommendation_engine_test-S5-F1-US1-01.png differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/README.md b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/README.md new file mode 100644 index 00000000..bd5bba9e --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/README.md @@ -0,0 +1,8 @@ +# Sprint 5 - Source Code Directory +This directory contains the completed source code after the end of each user story. The directory is structured as `app-s05-f01-us02` where +- `s05` - represents the sprint number, in this case sprint 5 +- `f01` - represents the feature number, in this case feature 1 +- `us02` - represents the user story number, in this case user story 2 + +> [!NOTE] +> The code in the directory is the completed solution after the completion of that user story. Like many other things in life, there are many ways to solve a problem. The source code in these directories is just one solution to the problem and does not necessarily represent best practices for a given solution. In many cases we chose simplicity or readability over efficiency. \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/.env-example b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/.env-example new file mode 100644 index 00000000..7d05b090 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/.env-example @@ -0,0 +1,9 @@ +DEBUG_APP="True" +USE_AZURE_OPENAI="True" +OPENAI_API_KEY="" +OPENAI_ORG_ID="" +OPEN_AI_DEPLOYMENT_NAME="gpt-3.5-turbo" +AZURE_OPENAI_DEPLOYMENT_NAME="gpt-35-turbo" +AZURE_OPENAI_ENDPOINT="" +AZURE_OPENAI_API_KEY="" +AZURE_OPENAI_VERSION="2023-05-15" diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/app.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/app.py new file mode 100644 index 00000000..7a591a1c --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/app.py @@ -0,0 +1,63 @@ +############################################################################### +## Sprint 5: Advanced AI Recommendations +## Feature 1: Get Gen AI Recommendation +## User Story 1: Get Gen AI Recommendation +############################################################################ +import os +from flask import Flask, render_template, request, redirect, url_for, g +from database import db, Todo +from recommendation_engine import RecommendationEngine + +app = Flask(__name__) +basedir = os.path.abspath(os.path.dirname(__file__)) # Get the directory of the this file +todo_file = os.path.join(basedir, 'todo_list.txt') # Create the path to the to-do list file using the directory +app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(basedir, 'todos.db') +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db.init_app(app) + +with app.app_context(): + db.create_all() + +@app.before_request +def load_data_to_g(): + todos = Todo.query.all() + g.todos = todos + g.todo = None + +@app.route("/") +def index(): + return render_template("index.html") + +@app.route("/add", methods=["POST"]) +def add_todo(): + # Get the data from the form + todo = Todo( + name=request.form["todo"], + ) + # Add the new ToDo to the list + db.session.add(todo) + db.session.commit() + + # Add the new ToDo to the list + return redirect(url_for('index')) + +# Delete a ToDo +@app.route('/remove/', methods=["POST"]) +def remove_todo(id): + db.session.delete(Todo.query.filter_by(id=id).first()) + db.session.commit() + return redirect(url_for('index')) + +# Show AI recommendations +@app.route('/recommend/', methods=['GET']) +async def recommend(id): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name) + + return render_template('index.html') + + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/database.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/database.py new file mode 100644 index 00000000..3a5f56d3 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/database.py @@ -0,0 +1,19 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import Integer, String, Boolean, func +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + pass + +db = SQLAlchemy(model_class=Base) + +class Todo(db.Model): + id = db.Column(Integer, primary_key=True) + name = db.Column(String(100), nullable=False) + recommendations = [] # non-persistent field + + def __str__(self): + return self.name + + + \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/recommendation_engine.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/recommendation_engine.py new file mode 100644 index 00000000..d2c002e5 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/recommendation_engine.py @@ -0,0 +1,76 @@ +import json +import asyncio +import semantic_kernel as sk +from services import Service +from openai import AzureOpenAI +from dotenv import dotenv_values + +config = dotenv_values(".env") + +#uses the USE_AZURE_OPENAI variable from the .env file to determine which AI service to use +#False means use OpenAI, True means use Azure OpenAI +selectedService = Service.AzureOpenAI if config.get("USE_AZURE_OPENAI") == "True" else Service.OpenAI + +deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() + +class RecommendationEngine: + + def __init__(self): + + self.client = AzureOpenAI(azure_endpoint = endpoint, + api_key=api_key, + api_version="2024-02-15-preview" + ) + + + async def get_recommendations(self, keyword_phrase): + prompt = f"""Please return 5 recommendations based on the input string: '{keyword_phrase}' using correct JSON syntax that contains a title and a hyperlink back to the supporting website. RETURN ONLY JSON AND NOTHING ELSE""" + system_prompt = """You are an administrative assistant bot who is good at giving + recommendations for tasks that need to be done by referencing website links that can provide + assistance to helping complete the task. + + If there are not any recommendations simply return an empty collection. + + EXPECTED OUTPUT: + Provide your response as a JSON object with the following schema: + [{"title": "...", "link": "..."}, + {"title": "...", "link": "..."}, + {"title": "...", "link": "..."}] + """ + + message_text = [{"role":"system","content":system_prompt}, + {"role":"user","content":prompt},] + + response = self.client.chat.completions.create( + model= deployment, + messages = message_text, + temperature=0.14, + max_tokens=800, + top_p=0.17, + frequency_penalty=0, + presence_penalty=0, + stop=None + ) + + result = response.choices[0].message.content + print(result) + + try: + recommendation = json.loads(result) + except Exception as e: + print(f"Error loading recommendations: {e}") + recommendation = [{"title": "Sorry, unable to recommendation at this time", "link": ""}] + + return recommendation + +async def test_recommendation_engine(): + engine = RecommendationEngine() + recommendations = await engine.get_recommendations("Buy a birthday gift for mom") + count = 1 + for recommendation in recommendations: + print(f"{count} - {recommendation['title']}: {recommendation['link']}") + count += 1 + +if __name__ == "__main__": + asyncio.run(test_recommendation_engine()) + \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/services.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/services.py new file mode 100644 index 00000000..657f1270 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/services.py @@ -0,0 +1,18 @@ +""" +This module defines an enumeration representing different services. +""" + +from enum import Enum + + +class Service(Enum): + """ + Attributes: + OpenAI (str): Represents the OpenAI service. + AzureOpenAI (str): Represents the Azure OpenAI service. + HuggingFace (str): Represents the HuggingFace service. + """ + + OpenAI = "openai" + AzureOpenAI = "azureopenai" + HuggingFace = "huggingface" \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/css/style.css b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/css/style.css new file mode 100644 index 00000000..5629fa75 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/css/style.css @@ -0,0 +1,21 @@ +body { + background-color: f0f0f0; /* light grey */ + color: darkslategray; /* default font color */ + font-family: Arial, sans-serif; + background-image: url("../images/Designer02.jpeg"); + background-repeat: no-repeat; + background-position: center; + background-size:cover; +} + +h1 { + color: darkgray; /* font for the h1 header*/ +} + +.list-group-item { + color: #333; /* dark grey */ +} + + + + diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer01.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer01.jpeg new file mode 100644 index 00000000..f59d0d61 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer01.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer02.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer02.jpeg new file mode 100644 index 00000000..0c36a33e Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/Designer02.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/favicon.ico b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/favicon.ico new file mode 100644 index 00000000..47f38580 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/images/favicon.ico differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/js/app.js b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/js/app.js new file mode 100644 index 00000000..eaad153d --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/static/js/app.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", function() { + const nameInput = document.getElementById("todo"); + + //add javascript to support speech recognition for the todo input field + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const recognition = new SpeechRecognition(); + recognition.continuous = false; + recognition.lang = "en-US"; + recognition.interimResults = false; + + window.captureVoice = function() { + recognition.start(); + nameInput.value = "Your microphone is activated, speak to record voice"; + }; + + recognition.onresult = function(event) { + const transcript = event.results[0][0].transcript; + const recognizedText = transcript.endsWith('.') ? transcript.slice(0, -1) : transcript; + nameInput.value = recognizedText; + }; + + recognition.onspeechend = function() { + recognition.stop(); + }; + + recognition.onnomatch = function(event) { + nameInput.value = "I didn't recognize that prompt."; + }; + + recognition.onerror = function(event) { + nameInput.value = "Error occurred in recognition: " + event.error; + }; +}); \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/templates/index.html b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/templates/index.html new file mode 100644 index 00000000..0d932f61 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us01/templates/index.html @@ -0,0 +1,72 @@ + + + + My To-Do List + + + + + + +
+
+
+
+

My To-Do List

+
+
+
+
+
+
    + {% for todo in g.todos %} +
  1. +
    +
    {{ todo.name }}
    +
    + + + + +
  2. + {% endfor %} +
+
+
+
+
+
+ + + + + + +
+ +
+
+ {% if g.todo != None %} +
+
+
+
AI Recommendations for "{{ g.todo.name }}"
+ {% for recommend in g.todo.recommendations %} + {{ recommend.title }} + {% endfor %} +
+
+
+
+ {% endif %} +
+
+
+ + \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/.env-example b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/.env-example new file mode 100644 index 00000000..7d05b090 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/.env-example @@ -0,0 +1,9 @@ +DEBUG_APP="True" +USE_AZURE_OPENAI="True" +OPENAI_API_KEY="" +OPENAI_ORG_ID="" +OPEN_AI_DEPLOYMENT_NAME="gpt-3.5-turbo" +AZURE_OPENAI_DEPLOYMENT_NAME="gpt-35-turbo" +AZURE_OPENAI_ENDPOINT="" +AZURE_OPENAI_API_KEY="" +AZURE_OPENAI_VERSION="2023-05-15" diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/app.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/app.py new file mode 100644 index 00000000..1919b12d --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/app.py @@ -0,0 +1,84 @@ +############################################################################### +## Sprint 5: Advanced AI Recommendations +## Feature 1: Get Gen AI Recommendation +## User Story 2: Cache Recommendations in the Database +############################################################################ +import os +import json +from flask import Flask, render_template, request, redirect, url_for, g +from database import db, Todo +from recommendation_engine import RecommendationEngine + +app = Flask(__name__) +basedir = os.path.abspath(os.path.dirname(__file__)) # Get the directory of the this file +todo_file = os.path.join(basedir, 'todo_list.txt') # Create the path to the to-do list file using the directory +app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(basedir, 'todos.db') +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db.init_app(app) + +with app.app_context(): + db.create_all() + +@app.before_request +def load_data_to_g(): + todos = Todo.query.all() + g.todos = todos + g.todo = None + +@app.route("/") +def index(): + return render_template("index.html") + +@app.route("/add", methods=["POST"]) +def add_todo(): + # Get the data from the form + todo = Todo( + name=request.form["todo"], + ) + + # Add the new ToDo to the list + db.session.add(todo) + db.session.commit() + + # Add the new ToDo to the list + return redirect(url_for('index')) + +# Delete a ToDo +@app.route('/remove/', methods=["POST"]) +def remove_todo(id): + db.session.delete(Todo.query.filter_by(id=id).first()) + db.session.commit() + return redirect(url_for('index')) + +# Show AI recommendations +@app.route('/recommend/', methods=['GET']) +async def recommend(id): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + + if g.todo: + try: + #attempt to load any saved recommendation from the DB + if g.todo.recommendations_json is not None: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + return render_template('index.html') + except ValueError as e: + print("Error:", e) + + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name) + + # Save the recommendations to the database + try: + g.todo.recommendations_json = json.dumps(g.todo.recommendations) + db.session.add(g.todo) + db.session.commit() + except Exception as e: + print(f"Error adding and committing todo: {e}") + return + + return render_template('index.html') + + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/database.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/database.py new file mode 100644 index 00000000..b6c47d4c --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/database.py @@ -0,0 +1,20 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import Integer, String, Boolean, JSON, func +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + pass + +db = SQLAlchemy(model_class=Base) + +class Todo(db.Model): + id = db.Column(Integer, primary_key=True) + name = db.Column(String(100), nullable=False) + recommendations = [] # non-persistent field + recommendations_json = db.Column(db.JSON) + + def __str__(self): + return self.name + + + diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/recommendation_engine.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/recommendation_engine.py new file mode 100644 index 00000000..c56f584b --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/recommendation_engine.py @@ -0,0 +1,74 @@ +import json +import asyncio +import semantic_kernel as sk +from services import Service +from openai import AzureOpenAI +from dotenv import dotenv_values + +config = dotenv_values(".env") + +#uses the USE_AZURE_OPENAI variable from the .env file to determine which AI service to use +#False means use OpenAI, True means use Azure OpenAI +selectedService = Service.AzureOpenAI if config.get("USE_AZURE_OPENAI") == "True" else Service.OpenAI + +deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() + +class RecommendationEngine: + def __init__(self): + + self.client = AzureOpenAI(azure_endpoint = endpoint, + api_key=api_key, + api_version="2024-02-15-preview" + ) + + + async def get_recommendations(self, keyword_phrase): + prompt = f"""Please return 5 recommendations based on the input string: '{keyword_phrase}' using correct JSON syntax that contains a title and a hyperlink back to the supporting website. RETURN ONLY JSON AND NOTHING ELSE""" + system_prompt = """You are an administrative assistant bot who is good at giving + recommendations for tasks that need to be done by referencing website links that can provide + assistance to helping complete the task. + + If there are not any recommendations simply return an empty collection. + + EXPECTED OUTPUT: + Provide your response as a JSON object with the following schema: + [{"title": "...", "link": "..."}, + {"title": "...", "link": "..."}, + {"title": "...", "link": "..."}] + """ + + message_text = [{"role":"system","content":system_prompt}, + {"role":"user","content":prompt},] + + response = self.client.chat.completions.create( + model=deployment, + messages = message_text, + temperature=0.14, + max_tokens=800, + top_p=0.17, + frequency_penalty=0, + presence_penalty=0, + stop=None + ) + + result = response.choices[0].message.content + print(result) + + try: + recommendation = json.loads(result) + except Exception as e: + print(f"Error loading recommendations: {e}") + recommendation = [{"title": "Sorry, unable to recommendation at this time", "link": ""}] + + return recommendation + +async def test_recommendation_engine(): + engine = RecommendationEngine() + recommendations = await engine.get_recommendations("Buy a birthday gift for mom") + count = 1 + for recommendation in recommendations: + print(f"{count} - {recommendation['title']}: {recommendation['link']}") + count += 1 + +if __name__ == "__main__": + asyncio.run(test_recommendation_engine()) diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/services.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/services.py new file mode 100644 index 00000000..657f1270 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/services.py @@ -0,0 +1,18 @@ +""" +This module defines an enumeration representing different services. +""" + +from enum import Enum + + +class Service(Enum): + """ + Attributes: + OpenAI (str): Represents the OpenAI service. + AzureOpenAI (str): Represents the Azure OpenAI service. + HuggingFace (str): Represents the HuggingFace service. + """ + + OpenAI = "openai" + AzureOpenAI = "azureopenai" + HuggingFace = "huggingface" \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/css/style.css b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/css/style.css new file mode 100644 index 00000000..5629fa75 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/css/style.css @@ -0,0 +1,21 @@ +body { + background-color: f0f0f0; /* light grey */ + color: darkslategray; /* default font color */ + font-family: Arial, sans-serif; + background-image: url("../images/Designer02.jpeg"); + background-repeat: no-repeat; + background-position: center; + background-size:cover; +} + +h1 { + color: darkgray; /* font for the h1 header*/ +} + +.list-group-item { + color: #333; /* dark grey */ +} + + + + diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer01.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer01.jpeg new file mode 100644 index 00000000..f59d0d61 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer01.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer02.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer02.jpeg new file mode 100644 index 00000000..0c36a33e Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/Designer02.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/favicon.ico b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/favicon.ico new file mode 100644 index 00000000..47f38580 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/images/favicon.ico differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/js/app.js b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/js/app.js new file mode 100644 index 00000000..eaad153d --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/static/js/app.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", function() { + const nameInput = document.getElementById("todo"); + + //add javascript to support speech recognition for the todo input field + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const recognition = new SpeechRecognition(); + recognition.continuous = false; + recognition.lang = "en-US"; + recognition.interimResults = false; + + window.captureVoice = function() { + recognition.start(); + nameInput.value = "Your microphone is activated, speak to record voice"; + }; + + recognition.onresult = function(event) { + const transcript = event.results[0][0].transcript; + const recognizedText = transcript.endsWith('.') ? transcript.slice(0, -1) : transcript; + nameInput.value = recognizedText; + }; + + recognition.onspeechend = function() { + recognition.stop(); + }; + + recognition.onnomatch = function(event) { + nameInput.value = "I didn't recognize that prompt."; + }; + + recognition.onerror = function(event) { + nameInput.value = "Error occurred in recognition: " + event.error; + }; +}); \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/templates/index.html b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/templates/index.html new file mode 100644 index 00000000..0d932f61 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us02/templates/index.html @@ -0,0 +1,72 @@ + + + + My To-Do List + + + + + + +
+
+
+
+

My To-Do List

+
+
+
+
+
+
    + {% for todo in g.todos %} +
  1. +
    +
    {{ todo.name }}
    +
    + + + + +
  2. + {% endfor %} +
+
+
+
+
+
+ + + + + + +
+ +
+
+ {% if g.todo != None %} +
+
+
+
AI Recommendations for "{{ g.todo.name }}"
+ {% for recommend in g.todo.recommendations %} + {{ recommend.title }} + {% endfor %} +
+
+
+
+ {% endif %} +
+
+
+ + \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/.env-example b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/.env-example new file mode 100644 index 00000000..7d05b090 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/.env-example @@ -0,0 +1,9 @@ +DEBUG_APP="True" +USE_AZURE_OPENAI="True" +OPENAI_API_KEY="" +OPENAI_ORG_ID="" +OPEN_AI_DEPLOYMENT_NAME="gpt-3.5-turbo" +AZURE_OPENAI_DEPLOYMENT_NAME="gpt-35-turbo" +AZURE_OPENAI_ENDPOINT="" +AZURE_OPENAI_API_KEY="" +AZURE_OPENAI_VERSION="2023-05-15" diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/app.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/app.py new file mode 100644 index 00000000..7c755d40 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/app.py @@ -0,0 +1,95 @@ +############################################################################### +## Sprint 5: Advanced AI Recommendations +## Feature 1: Get Gen AI Recommendation +## User Story 3: Refresh Recommendations +############################################################################ +import os +import json +from flask import Flask, render_template, request, redirect, url_for, g +from database import db, Todo +from recommendation_engine import RecommendationEngine + + +app = Flask(__name__) +basedir = os.path.abspath(os.path.dirname(__file__)) # Get the directory of the this file +todo_file = os.path.join(basedir, 'todo_list.txt') # Create the path to the to-do list file using the directory +app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(basedir, 'todos.db') +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +db.init_app(app) + +with app.app_context(): + db.create_all() + +@app.before_request +def load_data_to_g(): + todos = Todo.query.all() + g.todos = todos + g.todo = None + + +@app.route("/") +def index(): + return render_template("index.html") + +@app.route("/add", methods=["POST"]) +def add_todo(): + # Get the data from the form + todo = Todo( + name=request.form["todo"], + ) + + # Add the new ToDo to the list + db.session.add(todo) + db.session.commit() + + # Add the new ToDo to the list + return redirect(url_for('index')) + +# Delete a ToDo +@app.route('/remove/', methods=["POST"]) +def remove_todo(id): + db.session.delete(Todo.query.filter_by(id=id).first()) + db.session.commit() + return redirect(url_for('index')) + +# Show AI recommendations +@app.route('/recommend/', methods=['GET']) +@app.route('/recommend//', methods=['GET']) +async def recommend(id, refresh=False): + recommendation_engine = RecommendationEngine() + g.todo = db.session.query(Todo).filter_by(id=id).first() + + if g.todo and not refresh: + try: + #attempt to load any saved recommendation from the DB + if g.todo.recommendations_json is not None: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + return render_template('index.html') + except ValueError as e: + print("Error:", e) + + previous_links_str = None + if refresh: + g.todo.recommendations = json.loads(g.todo.recommendations_json) + # Extract links + links = [item["link"] for item in g.todo.recommendations] + # Convert list of links to a single string + previous_links_str = ", ".join(links) + + g.todo.recommendations = await recommendation_engine.get_recommendations(g.todo.name, previous_links_str) + + # Save the recommendations to the database + try: + g.todo.recommendations_json = json.dumps(g.todo.recommendations) + db.session.add(g.todo) + db.session.commit() + except Exception as e: + print(f"Error adding and committing todo: {e}") + return + + return render_template('index.html') + + +if __name__ == "__main__": + app.run(debug=True) \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/database.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/database.py new file mode 100644 index 00000000..01265d51 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/database.py @@ -0,0 +1,20 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import Integer, String, Boolean, JSON, func +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + pass + +db = SQLAlchemy(model_class=Base) + +class Todo(db.Model): + id = db.Column(Integer, primary_key=True) + name = db.Column(String(100), nullable=False) + recommendations = [] + recommendations_json = db.Column(db.JSON) + + def __str__(self): + return self.name + + + diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/recommendation_engine.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/recommendation_engine.py new file mode 100644 index 00000000..35bed9af --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/recommendation_engine.py @@ -0,0 +1,77 @@ +import json +import asyncio +import semantic_kernel as sk +from services import Service +from openai import AzureOpenAI +from dotenv import dotenv_values + +config = dotenv_values(".env") + +#uses the USE_AZURE_OPENAI variable from the .env file to determine which AI service to use +#False means use OpenAI, True means use Azure OpenAI +selectedService = Service.AzureOpenAI if config.get("USE_AZURE_OPENAI") == "True" else Service.OpenAI +deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() + +class RecommendationEngine: + def __init__(self): + deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env() + + self.client = AzureOpenAI(azure_endpoint = endpoint, + api_key=api_key, + api_version="2024-02-15-preview" + ) + + + async def get_recommendations(self, keyword_phrase, previous_links_str=None): + prompt = f"""Please return 5 recommendations based on the input string: '{keyword_phrase}' using correct JSON syntax that contains a title and a hyperlink back to the supporting website. RETURN ONLY JSON AND NOTHING ELSE""" + system_prompt = """You are an administrative assistant bot who is good at giving + recommendations for tasks that need to be done by referencing website links that can provide + assistance to helping complete the task. + + If there are not any recommendations simply return an empty collection. + + EXPECTED OUTPUT: + Provide your response as a JSON object with the following schema: + [{"title": "...", "link": "..."}, + {"title": "...", "link": "..."}, + {"title": "...", "link": "..."}] + """ + + if previous_links_str is not None: + prompt = prompt + f". EXCLUDE the following links from your recommendations: {previous_links_str}" + + message_text = [{"role":"system","content":system_prompt}, + {"role":"user","content":prompt},] + + response = self.client.chat.completions.create( + model=deployment, + messages = message_text, + temperature=0.14, + max_tokens=800, + top_p=0.17, + frequency_penalty=0, + presence_penalty=0, + stop=None + ) + + result = response.choices[0].message.content + print(result) + + try: + recommendation = json.loads(result) + except Exception as e: + print(f"Error loading recommendations: {e}") + recommendation = [{"title": "Sorry, unable to recommendation at this time", "link": ""}] + + return recommendation + +async def test_recommendation_engine(): + engine = RecommendationEngine() + recommendations = await engine.get_recommendations("Buy a birthday gift for mom") + count = 1 + for recommendation in recommendations: + print(f"{count} - {recommendation['title']}: {recommendation['link']}") + count += 1 + +if __name__ == "__main__": + asyncio.run(test_recommendation_engine()) diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/services.py b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/services.py new file mode 100644 index 00000000..657f1270 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/services.py @@ -0,0 +1,18 @@ +""" +This module defines an enumeration representing different services. +""" + +from enum import Enum + + +class Service(Enum): + """ + Attributes: + OpenAI (str): Represents the OpenAI service. + AzureOpenAI (str): Represents the Azure OpenAI service. + HuggingFace (str): Represents the HuggingFace service. + """ + + OpenAI = "openai" + AzureOpenAI = "azureopenai" + HuggingFace = "huggingface" \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/css/style.css b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/css/style.css new file mode 100644 index 00000000..9d832830 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/css/style.css @@ -0,0 +1,20 @@ +body { + background-color: f0f0f0; /* light grey */ + color: darkslategray; /* default font color */ + font-family: Arial, sans-serif; + background-image: url("../images/Designer02.jpeg"); + background-repeat: no-repeat; + background-position: center; + background-size:cover; +} + +h1 { + color: darkgray; /* font for the h1 header*/ +} + +.list-group-item { + color: #333; /* dark grey */ +} + + + diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer01.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer01.jpeg new file mode 100644 index 00000000..f59d0d61 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer01.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer02.jpeg b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer02.jpeg new file mode 100644 index 00000000..0c36a33e Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/Designer02.jpeg differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/favicon.ico b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/favicon.ico new file mode 100644 index 00000000..47f38580 Binary files /dev/null and b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/images/favicon.ico differ diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/js/app.js b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/js/app.js new file mode 100644 index 00000000..eaad153d --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/static/js/app.js @@ -0,0 +1,33 @@ +document.addEventListener("DOMContentLoaded", function() { + const nameInput = document.getElementById("todo"); + + //add javascript to support speech recognition for the todo input field + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const recognition = new SpeechRecognition(); + recognition.continuous = false; + recognition.lang = "en-US"; + recognition.interimResults = false; + + window.captureVoice = function() { + recognition.start(); + nameInput.value = "Your microphone is activated, speak to record voice"; + }; + + recognition.onresult = function(event) { + const transcript = event.results[0][0].transcript; + const recognizedText = transcript.endsWith('.') ? transcript.slice(0, -1) : transcript; + nameInput.value = recognizedText; + }; + + recognition.onspeechend = function() { + recognition.stop(); + }; + + recognition.onnomatch = function(event) { + nameInput.value = "I didn't recognize that prompt."; + }; + + recognition.onerror = function(event) { + nameInput.value = "Error occurred in recognition: " + event.error; + }; +}); \ No newline at end of file diff --git a/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/templates/index.html b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/templates/index.html new file mode 100644 index 00000000..f4f1a252 --- /dev/null +++ b/Track_2_ToDo_App/Sprint-05 - Advanced AI recommendations/src/app-s05-f01-us03/templates/index.html @@ -0,0 +1,74 @@ + + + + My To-Do List + + + + + + +
+
+
+
+

My To-Do List

+
+
+
+
+
+
    + {% for todo in g.todos %} +
  1. +
    +
    {{ todo.name }}
    +
    + + + + +
  2. + {% endfor %} +
+
+
+
+
+
+ + + + + + +
+ +
+
+ {% if g.todo != None %} +
+
+
+
AI Recommendations for "{{ g.todo.name }}"
+ {% for recommend in g.todo.recommendations %} + {{ recommend.title }} + {% endfor %} +
+
+ Don't like recommendations? + Refresh +
+
+ {% endif %} +
+
+
+ + \ No newline at end of file diff --git a/Track_2_ToDo_App/Workshop-Format.md b/Track_2_ToDo_App/Workshop-Format.md index 930eff53..0deaee19 100644 --- a/Track_2_ToDo_App/Workshop-Format.md +++ b/Track_2_ToDo_App/Workshop-Format.md @@ -81,7 +81,9 @@ This sprint is designed to help students add voice to text functionality to the ## Sprint 5 - Advanced AI Recommendations -This sprint is designed to help students add advanced AI recommendations to the To-Do application. The sprint will walk students through creating recommendations for tasks that the user hasd added to the To-Do application. +⏲️ _Est. time to complete: 60 min._ ⏲️ + +In this sprint you will be taking the application that you built in Sprint 4 and adding in AI recommendations to help users complete their tasks. This will be done by leveraging the Azure OpenAI API to generate recommendations based on the task name. **📕Feature: Advanced AI Recommendations** 1. [**📖 Get Recommendations from Generative AI based on To-Do name**](/Track_2_ToDo_App/Sprint-05%20-%20Advanced%20AI%20recommendations/Feature%201%20-%20Get%20Generative%20AI%20recommendation/User%20Story%201%20-%20Get%20Gen%20AI%20recommendation.md)