Skip to content

Commit 9bc8bc1

Browse files
authored
Merge pull request #969 from huzaifa-dangote/huzaifa/Week1Exercise-solution
Add Huzaifa's week 1 exercise solution
2 parents 420f1ce + 2401d92 commit 9bc8bc1

File tree

1 file changed

+309
-0
lines changed

1 file changed

+309
-0
lines changed
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "fe12c203-e6a6-452c-a655-afb8a03a4ff5",
6+
"metadata": {},
7+
"source": [
8+
"# End of week 1 exercise\n",
9+
"\n",
10+
"To demonstrate your familiarity with OpenAI API, and also Ollama, build a tool that takes a technical question, \n",
11+
"and responds with an explanation. This is a tool that you will be able to use yourself during the course!"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": 12,
17+
"id": "c1070317-3ed9-4659-abe3-828943230e03",
18+
"metadata": {},
19+
"outputs": [],
20+
"source": [
21+
"# imports\n",
22+
"\n",
23+
"import os\n",
24+
"from dotenv import load_dotenv\n",
25+
"from IPython.display import Markdown, display, update_display\n",
26+
"from openai import OpenAI"
27+
]
28+
},
29+
{
30+
"cell_type": "code",
31+
"execution_count": null,
32+
"id": "4a456906-915a-4bfd-bb9d-57e505c5093f",
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"# constants\n",
37+
"\n",
38+
"MODEL_GPT = 'gpt-4o-mini'\n",
39+
"MODEL_LLAMA = 'llama3.2'\n",
40+
"\n",
41+
"load_dotenv(override=True)\n",
42+
"api_key = os.getenv('OPENAI_API_KEY')\n",
43+
"\n",
44+
"if api_key and api_key.startswith('sk-proj-') and len(api_key) > 0:\n",
45+
" print('API key looks good')\n",
46+
"else:\n",
47+
" print('API key is incorrect')\n",
48+
"\n",
49+
"openai = OpenAI()"
50+
]
51+
},
52+
{
53+
"cell_type": "code",
54+
"execution_count": 14,
55+
"id": "a8d7923c-5f28-4c30-8556-342d7c8497c1",
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"# set up environment\n",
60+
"\n",
61+
"system_prompt = \"\"\"\n",
62+
"You are a technical assistant that answers technical questions and\n",
63+
"explains them in a way they can easily be understood even by a dummy\n",
64+
"\"\"\""
65+
]
66+
},
67+
{
68+
"cell_type": "code",
69+
"execution_count": null,
70+
"id": "3f0d0137-52b0-47a8-81a8-11a90a010798",
71+
"metadata": {},
72+
"outputs": [],
73+
"source": [
74+
"# here is the question; type over this to ask something new\n",
75+
"\n",
76+
"question = \"\"\"\n",
77+
"Please explain what this code does and why:\n",
78+
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
79+
"\"\"\""
80+
]
81+
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": 15,
85+
"id": "60ce7000-a4a5-4cce-a261-e75ef45063b4",
86+
"metadata": {},
87+
"outputs": [
88+
{
89+
"data": {
90+
"text/markdown": [
91+
"Sure! Let's break down the code step by step to understand what it does.\n",
92+
"\n",
93+
"### Code Breakdown:\n",
94+
"```python\n",
95+
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
96+
"```\n",
97+
"\n",
98+
"1. **`books`**: This is presumably a list (or any iterable) of dictionaries, where each dictionary represents a book. Each book dictionary is expected to have an `\"author\"` key.\n",
99+
"\n",
100+
"2. **List Comprehension**: The part `{book.get(\"author\") for book in books if book.get(\"author\")}` is called a set comprehension. It goes through each `book` in the `books` iterable.\n",
101+
"\n",
102+
" - **`book.get(\"author\")`**: This tries to retrieve the value associated with the `\"author\"` key from each `book` dictionary. If the key does not exist, `get()` returns `None` instead of raising an error.\n",
103+
"\n",
104+
" - **`if book.get(\"author\")`**: This condition filters out any entries that do not have an `\"author\"`. If `book.get(\"author\")` is `None` (which would happen if the key is missing) or evaluates to `False` for any other reason, that book will not be included in the next steps.\n",
105+
"\n",
106+
"3. **Creating a Set**: The outer curly braces `{}` indicate that this is creating a set (not a list). A set in Python is a collection that automatically removes duplicate entries. So even if multiple books have the same author, that author's name will only appear once in the set.\n",
107+
"\n",
108+
"4. **`yield from`**: This is used in the context of a generator function. By using `yield from`, you are effectively yielding each item from the set you created in the previous step. \n",
109+
"\n",
110+
" - When this generator function is called, it will provide one author at a time to the caller until all authors in the set are exhausted.\n",
111+
"\n",
112+
"### What It Does:\n",
113+
"- The code collects the unique authors from a list of books dictionaries, ignoring any that do not have an author.\n",
114+
"- It then \"yields\" these unique authors one by one as a generator.\n",
115+
"\n",
116+
"### Why Use This Code:\n",
117+
"- **Efficiency**: It gathers unique authors, which can be helpful in scenarios where you want to process or list authors without duplicates.\n",
118+
"- **Simplicity**: Using `yield from` allows handling potentially large collections of authors without loading all of them into memory at once, making it scalable for large datasets.\n",
119+
"\n",
120+
"### Summary:\n",
121+
"This line of code is creating a generator that yields unique authors from a list of book dictionaries, while ignoring any entries that do not have an author. It’s a concise way to filter and collect specific data from a collection."
122+
],
123+
"text/plain": [
124+
"<IPython.core.display.Markdown object>"
125+
]
126+
},
127+
"metadata": {},
128+
"output_type": "display_data"
129+
}
130+
],
131+
"source": [
132+
"# Get gpt-4o-mini to answer, with streaming\n",
133+
"\n",
134+
"messages = [\n",
135+
" {'role': 'system', 'content': system_prompt},\n",
136+
" {'role': 'user', 'content': question}\n",
137+
"]\n",
138+
"\n",
139+
"stream = openai.chat.completions.create(\n",
140+
" model=MODEL_GPT,\n",
141+
" messages=messages,\n",
142+
" stream=True\n",
143+
")\n",
144+
"\n",
145+
"response = ''\n",
146+
"display_handle = display(Markdown(''), display_id=True)\n",
147+
"for chunk in stream:\n",
148+
" response += chunk.choices[0].delta.content or ''\n",
149+
" update_display(Markdown(response), display_id=display_handle.display_id)\n"
150+
]
151+
},
152+
{
153+
"cell_type": "code",
154+
"execution_count": 17,
155+
"id": "8f7c8ea8-4082-4ad0-8751-3301adcf6538",
156+
"metadata": {},
157+
"outputs": [
158+
{
159+
"data": {
160+
"text/markdown": [
161+
"Let's break down this line of code:\n",
162+
"\n",
163+
"**What is it doing?**\n",
164+
"\n",
165+
"This piece of code is using a Python syntax called \"generator expression\" or \"list comprehension with yield\". It's doing the following:\n",
166+
"\n",
167+
"- Retrieving author names from a list of books (`books`).\n",
168+
"- Only considering books with existing `author` information.\n",
169+
"- Yielding (one by one) the author names that satisfy the conditions.\n",
170+
"\n",
171+
"**The actual code:**\n",
172+
"\n",
173+
"```python\n",
174+
"yield from {book.get(\"author\") for book in books if book.get(\"author\")}\n",
175+
"```\n",
176+
"\n",
177+
"Let's understand each part of it:\n",
178+
"\n",
179+
"1. `{...}`: This is called a dict comprehension, which creates a dictionary from key-value pairs.\n",
180+
"\n",
181+
"2. `for book in books`: This part iterates over the `books` list to process each book's information.\n",
182+
"\n",
183+
"3. `if book.get(\"author\")`: Before yielding each author name, it checks if that particular book has an \"author\" key with some value (not necessarily a string).\n",
184+
"\n",
185+
"4. `yield from {...}`: It uses Python's `yield from` keyword to delegate to yields (produces) values provided by the inner dictionary comprehension `{book.get(\"author\") for book in books if book.get(\"author\")}`.\n",
186+
"\n",
187+
"5. `.get()` and `.if`: The `book.get('author')` would directly get the author's value if it is present, otherwise `dict.get()` would find a default value to `None`, then check condition using `if`\n",
188+
"\n",
189+
"Here's why:\n",
190+
"\n",
191+
"- Generator expressions in functions (like this code snippet or many other places) are super helpful because they reduce unnecessary computations:\n",
192+
" \n",
193+
" Instead of having to manually generate and return author names when the `author` data is needed, you can create a generator expression that does all these computations ahead of time, returning one value at a time.\n",
194+
"\n",
195+
"- `yield from expression`:\n",
196+
"\n",
197+
" The syntax has its source in some older Python features like `yield from Generator`. This version allows you to define a generator (an object which defines the iteration with next() and yield).\n",
198+
"\n",
199+
"Let's write this code snippet with more Pythonic ways and clear the doubt for it\n",
200+
"\n",
201+
"```python\n",
202+
"def authors():\n",
203+
" def get_author(book):\n",
204+
" if book.get('author'):\n",
205+
" return book['author']\n",
206+
" else:\n",
207+
" return None\n",
208+
" if __name__ == '__main__':\n",
209+
" from data_classes import books\n",
210+
" def iterate_over_books():\n",
211+
" for book in books:\n",
212+
" author = get_author(book)\n",
213+
" if not author is None: # instead of if 'get()' which finds the value or returns None\n",
214+
" yield author\n",
215+
"\n",
216+
" final_books = iterate_over_books()\n",
217+
" # do something with your \"yield\" iter object. We could make a list out of it for instance \n",
218+
"# # print(your_iter_result)\n",
219+
" return final_books \n",
220+
"\n",
221+
"def final_book_objects():\n",
222+
" from data_classes import books # Define the class 'books'\n",
223+
" \n",
224+
"# If we have some book objects and wish to extract only that author value - this is our code\n",
225+
"# book_obj = Book(title='A',author= 'J')\n",
226+
"\n",
227+
" def generate_list_of_author_names(): \n",
228+
" final_books = []\n",
229+
" for book in books: \n",
230+
" final_book = { \"book\":book}\n",
231+
" for key, value in final_book.items():\n",
232+
" if value == \"author\":\n",
233+
" author = value\n",
234+
" # yield from author\n",
235+
" final_books.append(author) # append to the list of final books with 'author' appended to it.\n",
236+
" return final_books\n",
237+
" \n",
238+
" result = generate_list_of_author_names()\n",
239+
" # do something with your iter object. We could make a list out of it for instance \n",
240+
"# # print(result) \n",
241+
" return result \n",
242+
"\n",
243+
"iterating_code = final_book_objects()\n",
244+
"\n",
245+
"```"
246+
],
247+
"text/plain": [
248+
"<IPython.core.display.Markdown object>"
249+
]
250+
},
251+
"metadata": {},
252+
"output_type": "display_data"
253+
}
254+
],
255+
"source": [
256+
"# Get Llama 3.2 to answer\n",
257+
"\n",
258+
"messages = [\n",
259+
" {'role': 'system', 'content': system_prompt},\n",
260+
" {'role': 'user', 'content': question}\n",
261+
"]\n",
262+
"\n",
263+
"OLLAMA_BASE_URL = 'http://localhost:11434/v1'\n",
264+
"ollama = OpenAI(base_url=OLLAMA_BASE_URL, api_key='ollama')\n",
265+
"\n",
266+
"stream = ollama.chat.completions.create(\n",
267+
" model=MODEL_LLAMA,\n",
268+
" messages=messages,\n",
269+
" stream=True\n",
270+
")\n",
271+
"\n",
272+
"response = ''\n",
273+
"display_handle = display(Markdown(''), display_id=True)\n",
274+
"for chunk in stream:\n",
275+
" response += chunk.choices[0].delta.content or ''\n",
276+
" update_display(Markdown(response), display_id=display_handle.display_id)"
277+
]
278+
},
279+
{
280+
"cell_type": "code",
281+
"execution_count": null,
282+
"id": "9ecab4ce",
283+
"metadata": {},
284+
"outputs": [],
285+
"source": []
286+
}
287+
],
288+
"metadata": {
289+
"kernelspec": {
290+
"display_name": ".venv",
291+
"language": "python",
292+
"name": "python3"
293+
},
294+
"language_info": {
295+
"codemirror_mode": {
296+
"name": "ipython",
297+
"version": 3
298+
},
299+
"file_extension": ".py",
300+
"mimetype": "text/x-python",
301+
"name": "python",
302+
"nbconvert_exporter": "python",
303+
"pygments_lexer": "ipython3",
304+
"version": "3.12.8"
305+
}
306+
},
307+
"nbformat": 4,
308+
"nbformat_minor": 5
309+
}

0 commit comments

Comments
 (0)