Merge pull request #762 from Abdoulrasheed/wellness-companion
Wellness companion
This commit is contained in:
380
community-contributions/abdoul/week_one_ excercise.ipynb
Normal file
380
community-contributions/abdoul/week_one_ excercise.ipynb
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "fe12c203-e6a6-452c-a655-afb8a03a4ff5",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# End of week 1 exercise\n",
|
||||||
|
"\n",
|
||||||
|
"To demonstrate your familiarity with OpenAI API, and also Ollama, build a tool that takes a technical question, \n",
|
||||||
|
"and responds with an explanation. This is a tool that you will be able to use yourself during the course!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "c1070317-3ed9-4659-abe3-828943230e03",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import os\n",
|
||||||
|
"import requests\n",
|
||||||
|
"from openai import OpenAI\n",
|
||||||
|
"from dotenv import load_dotenv\n",
|
||||||
|
"from typing import Optional, Literal\n",
|
||||||
|
"from IPython.display import display, Markdown"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 32,
|
||||||
|
"id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# constants\n",
|
||||||
|
"\n",
|
||||||
|
"MODEL_GPT = 'gpt-4o-mini'\n",
|
||||||
|
"MODEL_LLAMA = 'llama3.2'"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 33,
|
||||||
|
"id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# set up environment\n",
|
||||||
|
"load_dotenv()\n",
|
||||||
|
"\n",
|
||||||
|
"class Model:\n",
|
||||||
|
" def __init__(self):\n",
|
||||||
|
" self.client_oai = OpenAI(api_key=os.getenv(\"OPENAI_KEY\"))\n",
|
||||||
|
" self.ollama_base_url = f\"{os.getenv('OLLAMA_URL')}/api/chat\"\n",
|
||||||
|
"\n",
|
||||||
|
" def _prompt_llama(self, text: str):\n",
|
||||||
|
" response = requests.post(\n",
|
||||||
|
" self.ollama_base_url,\n",
|
||||||
|
" json={\n",
|
||||||
|
" \"model\": MODEL_LLAMA,\n",
|
||||||
|
" \"messages\": [{\"role\": \"user\", \"content\": text}],\n",
|
||||||
|
" \"stream\": True\n",
|
||||||
|
" },\n",
|
||||||
|
" stream=True\n",
|
||||||
|
" )\n",
|
||||||
|
" for line in response.iter_lines():\n",
|
||||||
|
" if not line:\n",
|
||||||
|
" continue\n",
|
||||||
|
" \n",
|
||||||
|
" data = line.decode(\"utf-8\")\n",
|
||||||
|
" if data.strip() == \"\":\n",
|
||||||
|
" continue\n",
|
||||||
|
" \n",
|
||||||
|
" if data.startswith(\"data:\"):\n",
|
||||||
|
" data = data[5:].strip()\n",
|
||||||
|
" \n",
|
||||||
|
" yield data\n",
|
||||||
|
"\n",
|
||||||
|
" def _prompt_oai(self, question: str):\n",
|
||||||
|
" stream = self.client_oai.chat.completions.create(\n",
|
||||||
|
" model=MODEL_GPT,\n",
|
||||||
|
" messages=[\n",
|
||||||
|
" {\n",
|
||||||
|
" \"role\": \"system\",\n",
|
||||||
|
" \"content\": (\n",
|
||||||
|
" \"You are an advanced reasoning and explanation engine. \"\n",
|
||||||
|
" \"You write with precision, clarity, and conciseness. \"\n",
|
||||||
|
" \"You can explain Python, algorithms, code design, and systems-level behavior \"\n",
|
||||||
|
" \"with technical rigor, while being straight to the point.\"\n",
|
||||||
|
" ),\n",
|
||||||
|
" },\n",
|
||||||
|
" {\"role\": \"user\", \"content\": question},\n",
|
||||||
|
" ],\n",
|
||||||
|
" stream=True,\n",
|
||||||
|
" )\n",
|
||||||
|
" return stream\n",
|
||||||
|
"\n",
|
||||||
|
" def prompt(self, question: str, model: Optional[Literal[MODEL_GPT, MODEL_LLAMA]] = MODEL_GPT):\n",
|
||||||
|
" if \"gpt\" in model:\n",
|
||||||
|
" stream = self._prompt_oai(question)\n",
|
||||||
|
" buffer = []\n",
|
||||||
|
" for event in stream:\n",
|
||||||
|
" if event.choices and event.choices[0].delta.content:\n",
|
||||||
|
" chunk = event.choices[0].delta.content\n",
|
||||||
|
" buffer.append(chunk)\n",
|
||||||
|
" yield chunk\n",
|
||||||
|
" \n",
|
||||||
|
" output = \"\".join(buffer)\n",
|
||||||
|
"\n",
|
||||||
|
" else:\n",
|
||||||
|
" stream = self._prompt_llama(question)\n",
|
||||||
|
" buffer = []\n",
|
||||||
|
" for chunk in stream:\n",
|
||||||
|
" try:\n",
|
||||||
|
" import json\n",
|
||||||
|
" data = json.loads(chunk)\n",
|
||||||
|
" content = data.get(\"message\", {}).get(\"content\", \"\")\n",
|
||||||
|
" \n",
|
||||||
|
" if content:\n",
|
||||||
|
" buffer.append(content)\n",
|
||||||
|
" yield content\n",
|
||||||
|
" \n",
|
||||||
|
" except Exception as e:\n",
|
||||||
|
" print(\"An error occured\", e)\n",
|
||||||
|
" continue\n",
|
||||||
|
" \n",
|
||||||
|
" output = \"\".join(buffer)\n",
|
||||||
|
"\n",
|
||||||
|
" display(Markdown(output))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 25,
|
||||||
|
"id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# here is the question; type over this to ask something new\n",
|
||||||
|
"question = \"\"\"\n",
|
||||||
|
"Please explain what this code does and why:\n",
|
||||||
|
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
|
||||||
|
"\"\"\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 27,
|
||||||
|
"id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"The provided code snippet is a Python expression that uses `yield from` with a set comprehension. Here’s a breakdown of what it does:\n",
|
||||||
|
"\n",
|
||||||
|
"1. **Set Comprehension**: The expression `{book.get(\"author\") for book in books if book.get(\"author\")}` creates a set of unique authors extracted from a list (or any iterable) called `books`. \n",
|
||||||
|
" - `book.get(\"author\")` retrieves the value associated with the key `\"author\"` from each `book` dictionary.\n",
|
||||||
|
" - The `if book.get(\"author\")` condition filters out any books that do not have an author (i.e., where the author value is `None` or an empty string). \n",
|
||||||
|
"\n",
|
||||||
|
"2. **Yield from**: The `yield from` statement is used in a generator function to yield all values from the iterable that follows it. In this case, it yields the unique authors produced by the set comprehension.\n",
|
||||||
|
"\n",
|
||||||
|
"### Why Use This Code?\n",
|
||||||
|
"\n",
|
||||||
|
"- **Uniqueness**: Using a set comprehension ensures that only unique authors are collected, automatically eliminating duplicates.\n",
|
||||||
|
"- **Efficiency**: The code succinctly processes the list of books and yields only the relevant data (authors) directly from the generator, making it memory-efficient and lazy.\n",
|
||||||
|
"- **Readability**: The use of `yield from` keeps the code clean and avoids the need to create an intermediate list before yielding authors.\n",
|
||||||
|
"\n",
|
||||||
|
"### Example:\n",
|
||||||
|
"\n",
|
||||||
|
"Given a list of books represented as dictionaries:\n",
|
||||||
|
"\n",
|
||||||
|
"```python\n",
|
||||||
|
"books = [\n",
|
||||||
|
" {\"title\": \"Book 1\", \"author\": \"Author A\"},\n",
|
||||||
|
" {\"title\": \"Book 2\", \"author\": \"Author B\"},\n",
|
||||||
|
" {\"title\": \"Book 3\", \"author\": \"Author A\"},\n",
|
||||||
|
" {\"title\": \"Book 4\", \"author\": None},\n",
|
||||||
|
"]\n",
|
||||||
|
"```\n",
|
||||||
|
"\n",
|
||||||
|
"The code would yield `Author A` and `Author B`, iterating over each author only once.\n",
|
||||||
|
"\n",
|
||||||
|
"### Conclusion:\n",
|
||||||
|
"\n",
|
||||||
|
"The code succinctly generates a lazily yielded sequence of unique authors from a collection of book dictionaries, efficiently handling potential duplicates and missing values."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/markdown": [
|
||||||
|
"The provided code snippet is a Python expression that uses `yield from` with a set comprehension. Here’s a breakdown of what it does:\n",
|
||||||
|
"\n",
|
||||||
|
"1. **Set Comprehension**: The expression `{book.get(\"author\") for book in books if book.get(\"author\")}` creates a set of unique authors extracted from a list (or any iterable) called `books`. \n",
|
||||||
|
" - `book.get(\"author\")` retrieves the value associated with the key `\"author\"` from each `book` dictionary.\n",
|
||||||
|
" - The `if book.get(\"author\")` condition filters out any books that do not have an author (i.e., where the author value is `None` or an empty string). \n",
|
||||||
|
"\n",
|
||||||
|
"2. **Yield from**: The `yield from` statement is used in a generator function to yield all values from the iterable that follows it. In this case, it yields the unique authors produced by the set comprehension.\n",
|
||||||
|
"\n",
|
||||||
|
"### Why Use This Code?\n",
|
||||||
|
"\n",
|
||||||
|
"- **Uniqueness**: Using a set comprehension ensures that only unique authors are collected, automatically eliminating duplicates.\n",
|
||||||
|
"- **Efficiency**: The code succinctly processes the list of books and yields only the relevant data (authors) directly from the generator, making it memory-efficient and lazy.\n",
|
||||||
|
"- **Readability**: The use of `yield from` keeps the code clean and avoids the need to create an intermediate list before yielding authors.\n",
|
||||||
|
"\n",
|
||||||
|
"### Example:\n",
|
||||||
|
"\n",
|
||||||
|
"Given a list of books represented as dictionaries:\n",
|
||||||
|
"\n",
|
||||||
|
"```python\n",
|
||||||
|
"books = [\n",
|
||||||
|
" {\"title\": \"Book 1\", \"author\": \"Author A\"},\n",
|
||||||
|
" {\"title\": \"Book 2\", \"author\": \"Author B\"},\n",
|
||||||
|
" {\"title\": \"Book 3\", \"author\": \"Author A\"},\n",
|
||||||
|
" {\"title\": \"Book 4\", \"author\": None},\n",
|
||||||
|
"]\n",
|
||||||
|
"```\n",
|
||||||
|
"\n",
|
||||||
|
"The code would yield `Author A` and `Author B`, iterating over each author only once.\n",
|
||||||
|
"\n",
|
||||||
|
"### Conclusion:\n",
|
||||||
|
"\n",
|
||||||
|
"The code succinctly generates a lazily yielded sequence of unique authors from a collection of book dictionaries, efficiently handling potential duplicates and missing values."
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.Markdown object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Get gpt-4o-mini to answer, with streaming\n",
|
||||||
|
"model = Model()\n",
|
||||||
|
"\n",
|
||||||
|
"for token in model.prompt(question, model=MODEL_GPT):\n",
|
||||||
|
" print(token, end=\"\", flush=True)\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 34,
|
||||||
|
"id": "8f7c8ea8-4082-4ad0-8751-3301adcf6538",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"This line of code is a part of Python's iteration protocol, specifically using the `yield from` keyword.\n",
|
||||||
|
"\n",
|
||||||
|
"Here's what it does:\n",
|
||||||
|
"\n",
|
||||||
|
"- It generates an iterator that yields values from another iterable.\n",
|
||||||
|
"- The expression `{book.get(\"author\") for book in books if book.get(\"author\")}` generates an iterator over the authors of all books where an author is available (`book.get(\"author\") != None or book.get(\"author\") == \"\"`).\n",
|
||||||
|
"\n",
|
||||||
|
"The `yield from` keyword takes this generator expression and yields from it. It's essentially saying \"use this generator to generate values, I'll take them in order\".\n",
|
||||||
|
"\n",
|
||||||
|
"Here's a step-by-step explanation:\n",
|
||||||
|
"\n",
|
||||||
|
"1. `{book.get(\"author\") for book in books if book.get(\"author\")}` generates an iterator over the authors of all books where an author is available.\n",
|
||||||
|
"\n",
|
||||||
|
" - This works by iterating over each `book` in the collection (`books`), checking if it has a valid author, and then yielding the author's name.\n",
|
||||||
|
" - If `book.get(\"author\")` returns `None`, or if its value is an empty string, it skips that book.\n",
|
||||||
|
"\n",
|
||||||
|
"2. `yield from { ... }` takes this generator expression and yields from it.\n",
|
||||||
|
"\n",
|
||||||
|
" - It's like saying \"take all values yielded by the inner generator and use them one by one\". \n",
|
||||||
|
"\n",
|
||||||
|
" This makes the code cleaner and easier to read because you don't have to manually call `next(book_generator)` for each book in the collection. You can simply iterate over this new iterator to get all authors.\n",
|
||||||
|
"\n",
|
||||||
|
"However, without more context about how these books and their data are structured, it's hard to tell exactly why or when someone would use this line of code specifically. But generally, it's useful for generating values from other iterables while still following a clear sequence.\n",
|
||||||
|
"\n",
|
||||||
|
"Here's an example:\n",
|
||||||
|
"\n",
|
||||||
|
"```python\n",
|
||||||
|
"books = [\n",
|
||||||
|
" {\"title\": \"Book 1\", \"author\": None},\n",
|
||||||
|
" {\"title\": \"Book 2\", \"author\": \"Author 1\"},\n",
|
||||||
|
" {\"title\": \"Book 3\", \"author\": \"\"}\n",
|
||||||
|
"]\n",
|
||||||
|
"\n",
|
||||||
|
"for author in yield from {book.get(\"author\") for book in books if book.get(\"author\")}:\n",
|
||||||
|
" print(author)\n",
|
||||||
|
"```\n",
|
||||||
|
"\n",
|
||||||
|
"This would print:\n",
|
||||||
|
"\n",
|
||||||
|
"```\n",
|
||||||
|
"Author 1\n",
|
||||||
|
"```"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/markdown": [
|
||||||
|
"This line of code is a part of Python's iteration protocol, specifically using the `yield from` keyword.\n",
|
||||||
|
"\n",
|
||||||
|
"Here's what it does:\n",
|
||||||
|
"\n",
|
||||||
|
"- It generates an iterator that yields values from another iterable.\n",
|
||||||
|
"- The expression `{book.get(\"author\") for book in books if book.get(\"author\")}` generates an iterator over the authors of all books where an author is available (`book.get(\"author\") != None or book.get(\"author\") == \"\"`).\n",
|
||||||
|
"\n",
|
||||||
|
"The `yield from` keyword takes this generator expression and yields from it. It's essentially saying \"use this generator to generate values, I'll take them in order\".\n",
|
||||||
|
"\n",
|
||||||
|
"Here's a step-by-step explanation:\n",
|
||||||
|
"\n",
|
||||||
|
"1. `{book.get(\"author\") for book in books if book.get(\"author\")}` generates an iterator over the authors of all books where an author is available.\n",
|
||||||
|
"\n",
|
||||||
|
" - This works by iterating over each `book` in the collection (`books`), checking if it has a valid author, and then yielding the author's name.\n",
|
||||||
|
" - If `book.get(\"author\")` returns `None`, or if its value is an empty string, it skips that book.\n",
|
||||||
|
"\n",
|
||||||
|
"2. `yield from { ... }` takes this generator expression and yields from it.\n",
|
||||||
|
"\n",
|
||||||
|
" - It's like saying \"take all values yielded by the inner generator and use them one by one\". \n",
|
||||||
|
"\n",
|
||||||
|
" This makes the code cleaner and easier to read because you don't have to manually call `next(book_generator)` for each book in the collection. You can simply iterate over this new iterator to get all authors.\n",
|
||||||
|
"\n",
|
||||||
|
"However, without more context about how these books and their data are structured, it's hard to tell exactly why or when someone would use this line of code specifically. But generally, it's useful for generating values from other iterables while still following a clear sequence.\n",
|
||||||
|
"\n",
|
||||||
|
"Here's an example:\n",
|
||||||
|
"\n",
|
||||||
|
"```python\n",
|
||||||
|
"books = [\n",
|
||||||
|
" {\"title\": \"Book 1\", \"author\": None},\n",
|
||||||
|
" {\"title\": \"Book 2\", \"author\": \"Author 1\"},\n",
|
||||||
|
" {\"title\": \"Book 3\", \"author\": \"\"}\n",
|
||||||
|
"]\n",
|
||||||
|
"\n",
|
||||||
|
"for author in yield from {book.get(\"author\") for book in books if book.get(\"author\")}:\n",
|
||||||
|
" print(author)\n",
|
||||||
|
"```\n",
|
||||||
|
"\n",
|
||||||
|
"This would print:\n",
|
||||||
|
"\n",
|
||||||
|
"```\n",
|
||||||
|
"Author 1\n",
|
||||||
|
"```"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.Markdown object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Get Llama 3.2 to answer\n",
|
||||||
|
"\n",
|
||||||
|
"for token in model.prompt(question, model=MODEL_LLAMA):\n",
|
||||||
|
" print(token, end=\"\", flush=True)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
345
community-contributions/abdoul/week_two_exercise.ipynb
Normal file
345
community-contributions/abdoul/week_two_exercise.ipynb
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "b9633a89",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Adaptive Wellness Companion\n",
|
||||||
|
"\n",
|
||||||
|
"This experience pairs conversation, tailored wellness plans, DALLE imagery, and TTS audio into a single Gradio interface."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "e615aae1",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Core imports for the companion\n",
|
||||||
|
"import os\n",
|
||||||
|
"import json\n",
|
||||||
|
"import base64\n",
|
||||||
|
"from io import BytesIO\n",
|
||||||
|
"from pathlib import Path\n",
|
||||||
|
"from tempfile import NamedTemporaryFile\n",
|
||||||
|
"\n",
|
||||||
|
"from openai import OpenAI\n",
|
||||||
|
"import gradio as gr\n",
|
||||||
|
"from PIL import Image\n",
|
||||||
|
"from dotenv import load_dotenv"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"id": "107e0313",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Create the OpenAI client and validate configuration\n",
|
||||||
|
"load_dotenv()\n",
|
||||||
|
"if not os.getenv(\"OPENAI_API_KEY\"):\n",
|
||||||
|
" raise RuntimeError(\"Set OPENAI_API_KEY before running the wellness companion.\")\n",
|
||||||
|
"\n",
|
||||||
|
"client = OpenAI()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"id": "6818218b",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Model constants and system persona\n",
|
||||||
|
"MODEL = \"gpt-4o-mini\"\n",
|
||||||
|
"IMAGE_MODEL = \"dall-e-3\"\n",
|
||||||
|
"VOICE_MODEL = \"gpt-4o-mini-tts\"\n",
|
||||||
|
"\n",
|
||||||
|
"system_message = (\n",
|
||||||
|
" \"You are an upbeat adaptive wellness coach. \"\n",
|
||||||
|
" \"Blend evidence-backed guidance with empathy, tailor plans \"\n",
|
||||||
|
" \"to the user's mood, energy, and stress, and explain reasoning concisely.\"\n",
|
||||||
|
")\n",
|
||||||
|
"\n",
|
||||||
|
"tools = [\n",
|
||||||
|
" {\n",
|
||||||
|
" \"type\": \"function\",\n",
|
||||||
|
" \"function\": {\n",
|
||||||
|
" \"name\": \"get_wellness_plan\",\n",
|
||||||
|
" \"description\": \"Build a wellness micro-plan keyed to the user's current state.\",\n",
|
||||||
|
" \"parameters\": {\n",
|
||||||
|
" \"type\": \"object\",\n",
|
||||||
|
" \"properties\": {\n",
|
||||||
|
" \"mood\": {\"type\": \"string\", \"description\": \"How the user currently feels.\"},\n",
|
||||||
|
" \"energy\": {\"type\": \"string\", \"description\": \"Low, medium, or high energy.\"},\n",
|
||||||
|
" \"stress\": {\"type\": \"string\", \"description\": \"Stress intensity words like calm or overwhelmed.\"},\n",
|
||||||
|
" \"focus_goal\": {\"type\": \"string\", \"description\": \"What the user needs help focusing on right now.\"}\n",
|
||||||
|
" },\n",
|
||||||
|
" \"required\": [\"mood\", \"energy\", \"stress\", \"focus_goal\"]\n",
|
||||||
|
" }\n",
|
||||||
|
" }\n",
|
||||||
|
" }\n",
|
||||||
|
"]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"id": "a98e466e",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Tool backends that the model can call during chat\n",
|
||||||
|
"def get_wellness_plan(mood: str, energy: str, stress: str, focus_goal: str) -> str:\n",
|
||||||
|
" energy = energy.lower()\n",
|
||||||
|
" stress = stress.lower()\n",
|
||||||
|
" palette = \"calming watercolor\"\n",
|
||||||
|
" movement = \"gentle mobility flow\"\n",
|
||||||
|
" breathing = \"box breathing (4-4-4-4)\"\n",
|
||||||
|
" journaling = \"List three small wins and one supportive next step.\"\n",
|
||||||
|
"\n",
|
||||||
|
" if \"high\" in energy:\n",
|
||||||
|
" movement = \"energizing interval walk with posture resets\"\n",
|
||||||
|
" breathing = \"alternate nostril breathing to balance focus\"\n",
|
||||||
|
" elif \"low\" in energy:\n",
|
||||||
|
" movement = \"floor-based decompression stretches\"\n",
|
||||||
|
"\n",
|
||||||
|
" if \"over\" in stress or \"anx\" in stress:\n",
|
||||||
|
" palette = \"soothing pastel sanctuary\"\n",
|
||||||
|
" breathing = \"4-7-8 breathing to downshift the nervous system\"\n",
|
||||||
|
" elif \"calm\" in stress:\n",
|
||||||
|
" palette = \"sunlit studio with optimistic accents\"\n",
|
||||||
|
"\n",
|
||||||
|
" focus_goal = focus_goal.strip() or \"refocus\"\n",
|
||||||
|
"\n",
|
||||||
|
" plan = {\n",
|
||||||
|
" \"headline\": \"Adaptive wellness reset\",\n",
|
||||||
|
" \"visual_theme\": f\"{palette} inspired by {mood}\",\n",
|
||||||
|
" \"movement\": movement,\n",
|
||||||
|
" \"breathing\": breathing,\n",
|
||||||
|
" \"reflection\": f\"Prompt: {journaling}\",\n",
|
||||||
|
" \"focus_affirmation\": f\"Affirmation: You have the capacity to handle {focus_goal} with grace.\"\n",
|
||||||
|
" }\n",
|
||||||
|
" return json.dumps(plan)\n",
|
||||||
|
"\n",
|
||||||
|
"tool_registry = {\"get_wellness_plan\": get_wellness_plan}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"id": "7f0be3e0",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Multimodal helpers: text-to-speech and imagery\n",
|
||||||
|
"def talker(message: str) -> str | None:\n",
|
||||||
|
" if not message:\n",
|
||||||
|
" return None\n",
|
||||||
|
" try:\n",
|
||||||
|
" with client.audio.speech.with_streaming_response.create(\n",
|
||||||
|
" model=VOICE_MODEL,\n",
|
||||||
|
" voice=\"alloy\",\n",
|
||||||
|
" input=message\n",
|
||||||
|
" ) as response:\n",
|
||||||
|
" temp_file = NamedTemporaryFile(suffix=\".mp3\", delete=False)\n",
|
||||||
|
" temp_path = temp_file.name\n",
|
||||||
|
" temp_file.close()\n",
|
||||||
|
" response.stream_to_file(temp_path)\n",
|
||||||
|
" return temp_path\n",
|
||||||
|
" except Exception as exc:\n",
|
||||||
|
" print(f\"[warn] audio synthesis unavailable: {exc}\")\n",
|
||||||
|
" return None\n",
|
||||||
|
"\n",
|
||||||
|
"def artist(theme: str) -> Image.Image | None:\n",
|
||||||
|
" if not theme:\n",
|
||||||
|
" return None\n",
|
||||||
|
" try:\n",
|
||||||
|
" prompt = (\n",
|
||||||
|
" f\"Immersive poster celebrating a wellness ritual, {theme}, \"\n",
|
||||||
|
" \"with hopeful lighting and inclusive representation.\"\n",
|
||||||
|
" )\n",
|
||||||
|
" response = client.images.generate(\n",
|
||||||
|
" model=IMAGE_MODEL,\n",
|
||||||
|
" prompt=prompt,\n",
|
||||||
|
" size=\"1024x1024\",\n",
|
||||||
|
" response_format=\"b64_json\"\n",
|
||||||
|
" )\n",
|
||||||
|
" image_base64 = response.data[0].b64_json\n",
|
||||||
|
" image_data = base64.b64decode(image_base64)\n",
|
||||||
|
" return Image.open(BytesIO(image_data))\n",
|
||||||
|
" except Exception as exc:\n",
|
||||||
|
" print(f\"[warn] image generation unavailable: {exc}\")\n",
|
||||||
|
" return None"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"id": "3d6d9d2d",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Conversation orchestration with tool calls, imagery, and audio\n",
|
||||||
|
"def handle_tool_calls_and_theme(message) -> tuple[list[dict], str | None]:\n",
|
||||||
|
" responses = []\n",
|
||||||
|
" theme = None\n",
|
||||||
|
" for tool_call in message.tool_calls or []:\n",
|
||||||
|
" if tool_call.function.name not in tool_registry:\n",
|
||||||
|
" continue\n",
|
||||||
|
" arguments = json.loads(tool_call.function.arguments)\n",
|
||||||
|
" result = tool_registry[tool_call.function.name](**arguments)\n",
|
||||||
|
" responses.append(\n",
|
||||||
|
" {\"role\": \"tool\", \"tool_call_id\": tool_call.id, \"content\": result}\n",
|
||||||
|
" )\n",
|
||||||
|
" payload = json.loads(result)\n",
|
||||||
|
" theme = theme or payload.get(\"visual_theme\")\n",
|
||||||
|
" return responses, theme\n",
|
||||||
|
"\n",
|
||||||
|
"def chat(history: list[dict]) -> tuple[list[dict], str | None, Image.Image | None]:\n",
|
||||||
|
" conversation = [{\"role\": item[\"role\"], \"content\": item[\"content\"]} for item in history]\n",
|
||||||
|
" messages = [{\"role\": \"system\", \"content\": system_message}] + conversation\n",
|
||||||
|
" response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
|
||||||
|
" theme = None\n",
|
||||||
|
"\n",
|
||||||
|
" while response.choices[0].finish_reason == \"tool_calls\":\n",
|
||||||
|
" tool_message = response.choices[0].message\n",
|
||||||
|
" tool_responses, candidate_theme = handle_tool_calls_and_theme(tool_message)\n",
|
||||||
|
" if candidate_theme:\n",
|
||||||
|
" theme = candidate_theme\n",
|
||||||
|
" messages.append(tool_message)\n",
|
||||||
|
" messages.extend(tool_responses)\n",
|
||||||
|
" response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)\n",
|
||||||
|
"\n",
|
||||||
|
" reply = response.choices[0].message.content\n",
|
||||||
|
" updated_history = history + [{\"role\": \"assistant\", \"content\": reply}]\n",
|
||||||
|
" audio_path = talker(reply)\n",
|
||||||
|
" image = artist(theme)\n",
|
||||||
|
" print(image)\n",
|
||||||
|
" return updated_history, audio_path, image\n",
|
||||||
|
"\n",
|
||||||
|
"def put_message_in_chatbot(message: str, history: list[dict]) -> tuple[str, list[dict]]:\n",
|
||||||
|
" return \"\", history + [{\"role\": \"user\", \"content\": message}]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"id": "fb17fc4f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"Gradio Blocks instance: 2 backend functions\n",
|
||||||
|
"-------------------------------------------\n",
|
||||||
|
"fn_index=0\n",
|
||||||
|
" inputs:\n",
|
||||||
|
" |-<gradio.components.textbox.Textbox object at 0x11b3d2850>\n",
|
||||||
|
" |-<gradio.components.chatbot.Chatbot object at 0x11b3d20d0>\n",
|
||||||
|
" outputs:\n",
|
||||||
|
" |-<gradio.components.textbox.Textbox object at 0x11b3d2850>\n",
|
||||||
|
" |-<gradio.components.chatbot.Chatbot object at 0x11b3d20d0>\n",
|
||||||
|
"fn_index=1\n",
|
||||||
|
" inputs:\n",
|
||||||
|
" |-<gradio.components.chatbot.Chatbot object at 0x11b3d20d0>\n",
|
||||||
|
" outputs:\n",
|
||||||
|
" |-<gradio.components.chatbot.Chatbot object at 0x11b3d20d0>\n",
|
||||||
|
" |-<gradio.components.audio.Audio object at 0x11b3d25d0>\n",
|
||||||
|
" |-<gradio.components.image.Image object at 0x11b3d2210>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 12,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Assemble the Gradio Blocks UI\n",
|
||||||
|
"with gr.Blocks(title=\"Adaptive Wellness Companion\") as wellness_ui:\n",
|
||||||
|
" gr.Markdown(\"### Tell me how you are doing and I'll craft a micro-plan.\")\n",
|
||||||
|
" with gr.Row():\n",
|
||||||
|
" chatbot = gr.Chatbot(height=420, type=\"messages\", label=\"Conversation\")\n",
|
||||||
|
" image_output = gr.Image(height=420, label=\"Visual Inspiration\")\n",
|
||||||
|
" audio_output = gr.Audio(label=\"Coach Audio\", autoplay=True)\n",
|
||||||
|
" mood_input = gr.Textbox(label=\"Share your update\", placeholder=\"e.g. Feeling drained after meetings\")\n",
|
||||||
|
"\n",
|
||||||
|
" mood_input.submit(\n",
|
||||||
|
" fn=put_message_in_chatbot,\n",
|
||||||
|
" inputs=[mood_input, chatbot],\n",
|
||||||
|
" outputs=[mood_input, chatbot]\n",
|
||||||
|
" ).then(\n",
|
||||||
|
" fn=chat,\n",
|
||||||
|
" inputs=chatbot,\n",
|
||||||
|
" outputs=[chatbot, audio_output, image_output]\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
"wellness_ui.queue()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"id": "66bbe348",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"* Running on local URL: http://127.0.0.1:7860\n",
|
||||||
|
"* To create a public link, set `share=True` in `launch()`.\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/html": [
|
||||||
|
"<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
|
||||||
|
],
|
||||||
|
"text/plain": [
|
||||||
|
"<IPython.core.display.HTML object>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": []
|
||||||
|
},
|
||||||
|
"execution_count": 10,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Launch the interface inline when running in a notebook\n",
|
||||||
|
"wellness_ui.launch(inline=True, share=False, prevent_thread_lock=True)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user