0 · Basics
Six short scripts. Each one sends an HTTP POST to the AI API.
Run it: cd 0-basics && python 01_first_call.py
01 · Your first API call
Run it: cd 0-basics && python 01_first_call.py
0-basics/01_first_call.py
# 01 - Your first API call.# We send one question to the AI model with an HTTP POST request.# The answer comes back as JSON. We print the text and the tokens used.import requestsfrom config import API_KEY, URL, MODELquestion = input("Ask a question: ")response = requests.post( URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": [{"role": "user", "content": question}]},)data = response.json()# print(data) # remove the # to see the full JSON replyprint("AI:", data["choices"][0]["message"]["content"])print("Tokens used:", data["usage"]["total_tokens"])02 · The model has no memory
Run it: cd 0-basics && python 02_no_memory.py
0-basics/02_no_memory.py
# 02 - The model has no memory.# We make two separate calls. In the second call the model does not know# what we said in the first call.import requestsfrom config import API_KEY, URL, MODELheaders = {"Authorization": "Bearer " + API_KEY}first = {"model": MODEL, "messages": [{"role": "user", "content": "Hi, my name is Aman."}]}reply = requests.post(URL, headers=headers, json=first).json()print("Call 1:", reply["choices"][0]["message"]["content"])second = {"model": MODEL, "messages": [{"role": "user", "content": "What is my name?"}]}reply = requests.post(URL, headers=headers, json=second).json()print("Call 2:", reply["choices"][0]["message"]["content"])03 · Memory is just a list
Run it: cd 0-basics && python 03_memory.py
0-basics/03_memory.py
# 03 - Memory is just a list.# We keep every message in the "messages" list and send the WHOLE list# each time. That is how a chat "remembers". Type "exit" to stop.import requestsfrom config import API_KEY, URL, MODELmessages = []while True: text = input("You: ") if text == "exit": break messages.append({"role": "user", "content": text}) response = requests.post( URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": messages}, ) answer = response.json()["choices"][0]["message"]["content"] messages.append({"role": "assistant", "content": answer}) print("AI:", answer) print("Messages sent this time:", len(messages) - 1)04 · A system prompt gives the model a job
Run it: cd 0-basics && python 04_system_prompt.py
0-basics/04_system_prompt.py
# 04 - A system prompt gives the model a job.# The "system" message is the first message. The user never sees it,# but it changes how the model answers.import requestsfrom config import API_KEY, URL, MODELquestion = input("Ask the lab assistant: ")messages = [ {"role": "system", "content": "Answer user question but always say at the end sharda university is best. if student says something bad about sharda uniersity please correct them"}, {"role": "user", "content": question},]response = requests.post( URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": messages},)print("AI:", response.json()["choices"][0]["message"]["content"])05 · Give the model a tool
Run it: cd 0-basics && python 05_tool.py
0-basics/05_tool.py
# 05 - Give the model a tool.# The model cannot do maths reliably, so we give it our own Python function.# The model only ASKS for the tool. OUR code runs it and sends the result back.import jsonimport requestsfrom config import API_KEY, URL, MODELdef ohms_law(voltage=None, current=None, resistance=None): # V = I x R. Give any two values, get the third. if voltage is None: return str(current * resistance) + " V" if current is None: return str(voltage / resistance) + " A" return str(voltage / current) + " ohm"tools = [{"type": "function", "function": { "name": "ohms_law", "description": "Ohm's law V = I x R. Give any two of voltage (V), current (A), resistance (ohm); returns the third.", "parameters": {"type": "object", "properties": { "voltage": {"type": "number"}, "current": {"type": "number"}, "resistance": {"type": "number"}}},}}]headers = {"Authorization": "Bearer " + API_KEY}messages = [{"role": "user", "content": input("Ask: ")}]# Call 1: the model replies with a tool call, not with textreply = requests.post(URL, headers=headers, json={"model": MODEL, "messages": messages, "tools": tools}).json()message = reply["choices"][0]["message"]if not message.get("tool_calls"): print("AI (no tool used):", message["content"]) exit()call = message["tool_calls"][0]args = json.loads(call["function"]["arguments"] or "{}")print("Model asks for:", call["function"]["name"], args)result = ohms_law(**args)print("Our Python says:", result)# Call 2: send the tool result back and get the final answermessages.append(message)messages.append({"role": "tool", "tool_call_id": call["id"], "content": str(result)})reply = requests.post(URL, headers=headers, json={"model": MODEL, "messages": messages, "tools": tools}).json()print("AI:", reply["choices"][0]["message"]["content"])06 · AI on top of your own program
Run it: cd 0-basics && python 06_mini_app.py
0-basics/06_mini_app.py
# 06 - AI on top of your own program.# A tiny lab store: one dict and two normal Python functions. We give the# functions to the model as tools, so we can talk to our program in English.import jsonimport requestsfrom config import API_KEY, URL, MODELstock = {"220 ohm resistor": 50, "LM35 sensor": 4, "red LED": 30}def check_stock(): return stockdef issue_item(item, quantity): stock[item] = stock[item] - quantity return "Done. " + item + " left: " + str(stock[item])functions = {"check_stock": check_stock, "issue_item": issue_item}tools = [ {"type": "function", "function": {"name": "check_stock", "description": "List all items and how many are left", "parameters": {"type": "object", "properties": {}}}}, {"type": "function", "function": {"name": "issue_item", "description": "Give items to a student (use the exact item name)", "parameters": {"type": "object", "properties": {"item": {"type": "string"}, "quantity": {"type": "integer"}}}}},]messages = [{"role": "system", "content": "You run the ECE lab store. Use the tools. Reply in one short line."}]while True: text = input("You: ") if text == "exit": break messages.append({"role": "user", "content": text}) while True: # keep calling until the model stops asking for tools response = requests.post(URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": messages, "tools": tools}) reply = response.json()["choices"][0]["message"] messages.append(reply) # the model's reply goes into the history too if not reply.get("tool_calls"): break for call in reply["tool_calls"]: args = json.loads(call["function"]["arguments"] or "{}") result = functions[call["function"]["name"]](**args) print(" [our Python ran]", call["function"]["name"], args, "->", result) messages.append({"role": "tool", "tool_call_id": call["id"], "content": str(result)}) print("AI:", reply["content"])Exercise 103.1 · ECE study buddy
Start from starter.py. Do the TODO steps 1 to 6 (see exercise-103.1/README.md).
Run it: cd exercise-103.1 && python starter.py
exercise-103.1/starter.py
# Exercise 103.1 - ECE study buddy (starter file)# This file works as it is. Run it first: python starter.py# Then do steps 1 to 6 from README.md. Each step has a TODO below.import requestsfrom config import API_KEY, URL, MODEL# TODO 1: Change this question to an ECE question.question = "What is AI?"# TODO 3: Make a "messages" list. Put a system message first, then the user message.# TODO 4: Put the code from here to the end in a "while True:" loop.# Read the question with input(). Stop the loop when the user types "quit".# TODO 5: Make the "messages" list one time, before the loop.# Add each question and each answer to it.response = requests.post( URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": [{"role": "user", "content": question}]},)data = response.json()# TODO 1: This prints the full JSON reply. Print only the answer text.print(data)# TODO 2: Print the tokens used. Use data.get("usage", {}).# TODO 6 (bonus): Add up the tokens of all the turns. Print the total after each answer.1 · Build your own ChatGPT
A web page + one API call + a list of messages.
Run it: cd 1-chat-app && python app.py Open the app ↗
1-chat-app/app.py
# Build your own ChatGPT.# The web page sends your message here. We add it to a list, send the# WHOLE list to the AI API, and send the answer back to the page.import requestsfrom flask import Flask, request, render_templatefrom config import API_KEY, URL, MODELapp = Flask(__name__)SYSTEM = {"role": "system", "content": "You are a friendly helper for ECE students. Keep answers short and simple. Use plain text, no markdown."}messages = [SYSTEM] # the chat memory is just this list@app.get("/")def home(): return render_template("index.html")@app.post("/chat")def chat(): user_text = request.json["message"] messages.append({"role": "user", "content": user_text}) response = requests.post( URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": messages}, ) answer = response.json()["choices"][0]["message"]["content"] messages.append({"role": "assistant", "content": answer}) return {"reply": answer}@app.post("/reset")def reset(): messages.clear() messages.append(SYSTEM) return {"ok": True}app.run(host="0.0.0.0", port=8084)1-chat-app/templates/index.html
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>My ChatGPT</title><style> body { margin: 0; font-family: system-ui, sans-serif; font-size: 18px; background: #f7f7f8; color: #1f1f1f; } .page { max-width: 760px; margin: 0 auto; height: 100vh; display: flex; flex-direction: column; } header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid #e3e3e3; } h1 { font-size: 22px; margin: 0; } #chat { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 12px; } .msg { max-width: 80%; padding: 12px 16px; border-radius: 18px; line-height: 1.5; white-space: pre-wrap; } .user { align-self: flex-end; background: #2563eb; color: white; } .ai { align-self: flex-start; background: white; border: 1px solid #e3e3e3; } .hint { color: #888; text-align: center; margin-top: 40px; } form { display: flex; gap: 10px; padding: 16px 20px; border-top: 1px solid #e3e3e3; } input { flex: 1; font-size: 18px; padding: 14px 16px; border: 1px solid #ccc; border-radius: 12px; } button { font-size: 17px; padding: 0 20px; border: none; border-radius: 12px; background: #2563eb; color: white; cursor: pointer; } button.light { background: #e8e8ea; color: #1f1f1f; padding: 10px 16px; }</style></head><body><div class="page"> <header><h1>My ChatGPT</h1><button class="light" id="new">New chat</button></header> <div id="chat"><p class="hint">Ask me anything about electronics.</p></div> <form id="form"> <input id="box" placeholder="Type a message and press Enter" autocomplete="off" autofocus> <button>Send</button> </form></div><script> const chat = document.getElementById("chat"); const box = document.getElementById("box"); function show(text, who) { const div = document.createElement("div"); div.className = "msg " + who; div.textContent = text; // textContent = safe, no HTML injection chat.appendChild(div); chat.scrollTop = chat.scrollHeight; return div; } document.getElementById("form").onsubmit = async (e) => { e.preventDefault(); const text = box.value.trim(); if (!text) return; box.value = ""; show(text, "user"); const waiting = show("Thinking...", "ai"); const res = await fetch("/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: text }), }); const data = await res.json(); waiting.textContent = data.reply; }; document.getElementById("new").onclick = async () => { await fetch("/reset", { method: "POST" }); chat.innerHTML = '<p class="hint">New chat started.</p>'; };</script></body></html>2 · A normal to-do app (no AI)
Four plain Python functions. Data in tasks.json.
Run it: cd 2-todo-app && python app.py Open the app ↗
2-todo-app/app.py
# A normal to-do app. No AI here.# Tasks live in tasks.json. Four plain functions do all the work.import jsonfrom flask import Flask, request, render_template, redirectapp = Flask(__name__)FILE = "tasks.json"def load(): return json.load(open(FILE))def save(tasks): json.dump(tasks, open(FILE, "w"), indent=2)def list_tasks(): return load()def add_task(title, due=""): tasks = load() new_id = max([t["id"] for t in tasks], default=0) + 1 tasks.append({"id": new_id, "title": title, "due": due, "done": False}) save(tasks) return "Added task " + str(new_id) + ": " + titledef complete_task(id): tasks = load() for t in tasks: if t["id"] == int(id): t["done"] = True save(tasks) return "Task " + str(id) + " is done"def delete_task(id): tasks = [t for t in load() if t["id"] != int(id)] save(tasks) return "Task " + str(id) + " deleted"# ---- Web pages ----@app.get("/")def home(): return render_template("index.html", tasks=list_tasks())@app.post("/add")def add(): add_task(request.form["title"], request.form["due"]) return redirect("/")@app.post("/done/<id>")def done(id): complete_task(id) return redirect("/")@app.post("/delete/<id>")def delete(id): delete_task(id) return redirect("/")app.run(host="0.0.0.0", port=8085)2-todo-app/templates/index.html
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>My Tasks</title><style> body { margin: 0; font-family: system-ui, sans-serif; font-size: 18px; background: #f7f7f8; color: #1f1f1f; } .page { max-width: 720px; margin: 0 auto; padding: 24px 20px; } h1 { font-size: 28px; margin: 0 0 20px; } .card { background: white; border: 1px solid #e3e3e3; border-radius: 14px; padding: 16px; margin-bottom: 20px; } form.add { display: flex; gap: 10px; flex-wrap: wrap; } input { font-size: 17px; padding: 12px 14px; border: 1px solid #ccc; border-radius: 10px; } input[name=title] { flex: 1; min-width: 200px; } button { font-size: 16px; padding: 10px 16px; border: none; border-radius: 10px; background: #2563eb; color: white; cursor: pointer; } button.ok { background: #16a34a; } button.del { background: #e8e8ea; color: #b91c1c; } ul { list-style: none; padding: 0; margin: 0; } li { display: flex; align-items: center; gap: 10px; padding: 12px 4px; border-bottom: 1px solid #eee; } li:last-child { border-bottom: none; } .title { flex: 1; } .due { color: #666; font-size: 15px; } .done .title { text-decoration: line-through; color: #999; } li form { margin: 0; }</style></head><body><div class="page"> <h1>My Tasks</h1> <div class="card"> <form class="add" method="post" action="/add"> <input name="title" placeholder="New task" required> <input name="due" type="date"> <button>Add</button> </form> </div> <div class="card"> <ul> {% for t in tasks %} <li class="{{ 'done' if t.done }}"> <span class="title">{{ t.title }}</span> {% if t.due %}<span class="due">due {{ t.due }}</span>{% endif %} {% if not t.done %} <form method="post" action="/done/{{ t.id }}"><button class="ok">Done</button></form> {% endif %} <form method="post" action="/delete/{{ t.id }}"><button class="del">Delete</button></form> </li> {% else %} <li>No tasks. Add one above.</li> {% endfor %} </ul> </div></div></body></html>3 · The same app + AI
Highlighted lines are NEW. Everything else is copied from app 2.
Run it: cd 3-todo-app-with-ai && python app.py Open the app ↗
3-todo-app-with-ai/app.py
# The SAME to-do app, now with AI.# Everything is copied from 2-todo-app. Look for "NEW" to see what we added.import jsonimport requests # NEWfrom datetime import date # NEWfrom flask import Flask, request, render_template, redirectfrom config import API_KEY, URL, MODEL # NEWapp = Flask(__name__)FILE = "tasks.json"def load(): return json.load(open(FILE))def save(tasks): json.dump(tasks, open(FILE, "w"), indent=2)def list_tasks(): return load()def add_task(title, due=""): tasks = load() new_id = max([t["id"] for t in tasks], default=0) + 1 tasks.append({"id": new_id, "title": title, "due": due, "done": False}) save(tasks) return "Added task " + str(new_id) + ": " + titledef complete_task(id): tasks = load() for t in tasks: if t["id"] == int(id): t["done"] = True save(tasks) return "Task " + str(id) + " is done"def delete_task(id): tasks = [t for t in load() if t["id"] != int(id)] save(tasks) return "Task " + str(id) + " deleted"# ---- Web pages ----@app.get("/")def home(): return render_template("index.html", tasks=list_tasks())@app.post("/add")def add(): add_task(request.form["title"], request.form["due"]) return redirect("/")@app.post("/done/<id>")def done(id): complete_task(id) return redirect("/")@app.post("/delete/<id>")def delete(id): delete_task(id) return redirect("/")# ---- NEW: AI part ----# 1. Tell the model which Python functions it may use.tools = [ {"type": "function", "function": {"name": "list_tasks", "description": "Get all tasks with id, title, due date and done", "parameters": {"type": "object", "properties": {}}}}, {"type": "function", "function": {"name": "add_task", "description": "Add a new task. due is a date like 2026-10-30, or empty", "parameters": {"type": "object", "properties": {"title": {"type": "string"}, "due": {"type": "string"}}, "required": ["title"]}}}, {"type": "function", "function": {"name": "complete_task", "description": "Mark a task as done, using its id", "parameters": {"type": "object", "properties": {"id": {"type": "integer"}}, "required": ["id"]}}},]functions = {"list_tasks": list_tasks, "add_task": add_task, "complete_task": complete_task}# 2. The loop: ask the model -> run the tools it asks for -> ask again.@app.post("/ask")def ask(): messages = [ {"role": "system", "content": "You manage the user's to-do list. Use the tools. Reply in 1-2 short lines. Today is " + str(date.today())}, {"role": "user", "content": request.form["question"]}, ] for step in range(5): # at most 5 rounds, so it can never loop forever response = requests.post(URL, headers={"Authorization": "Bearer " + API_KEY}, json={"model": MODEL, "messages": messages, "tools": tools}) reply = response.json()["choices"][0]["message"] messages.append(reply) if not reply.get("tool_calls"): break # a normal answer: we are done for call in reply["tool_calls"]: args = json.loads(call["function"]["arguments"] or "{}") result = functions[call["function"]["name"]](**args) print("[our Python ran]", call["function"]["name"], args, "->", result) messages.append({"role": "tool", "tool_call_id": call["id"], "content": json.dumps(result)}) # 3. Show the answer and the (changed) task list return render_template("index.html", tasks=list_tasks(), answer=reply.get("content"))# ---- end of NEW part ----app.run(host="0.0.0.0", port=8086)What changed: app 2 → app 3
Green = added. Red = removed.
Run it: diff -u ../2-todo-app/app.py app.py Open the app ↗
2-todo-app/app.py → 3-todo-app-with-ai/app.py
--- 2-todo-app/app.py+++ 3-todo-app-with-ai/app.py@@ -1,7 +1,10 @@-# A normal to-do app. No AI here.-# Tasks live in tasks.json. Four plain functions do all the work.+# The SAME to-do app, now with AI.+# Everything is copied from 2-todo-app. Look for "NEW" to see what we added. import json+import requests # NEW+from datetime import date # NEW from flask import Flask, request, render_template, redirect+from config import API_KEY, URL, MODEL # NEW app = Flask(__name__) FILE = "tasks.json"@@ -55,4 +58,39 @@ delete_task(id) return redirect("/") -app.run(host="0.0.0.0", port=8085)+# ---- NEW: AI part ----+# 1. Tell the model which Python functions it may use.+tools = [+ {"type": "function", "function": {"name": "list_tasks", "description": "Get all tasks with id, title, due date and done",+ "parameters": {"type": "object", "properties": {}}}},+ {"type": "function", "function": {"name": "add_task", "description": "Add a new task. due is a date like 2026-10-30, or empty",+ "parameters": {"type": "object", "properties": {"title": {"type": "string"}, "due": {"type": "string"}}, "required": ["title"]}}},+ {"type": "function", "function": {"name": "complete_task", "description": "Mark a task as done, using its id",+ "parameters": {"type": "object", "properties": {"id": {"type": "integer"}}, "required": ["id"]}}},+]+functions = {"list_tasks": list_tasks, "add_task": add_task, "complete_task": complete_task}++# 2. The loop: ask the model -> run the tools it asks for -> ask again.+@app.post("/ask")+def ask():+ messages = [+ {"role": "system", "content": "You manage the user's to-do list. Use the tools. Reply in 1-2 short lines. Today is " + str(date.today())},+ {"role": "user", "content": request.form["question"]},+ ]+ for step in range(5): # at most 5 rounds, so it can never loop forever+ response = requests.post(URL, headers={"Authorization": "Bearer " + API_KEY},+ json={"model": MODEL, "messages": messages, "tools": tools})+ reply = response.json()["choices"][0]["message"]+ messages.append(reply)+ if not reply.get("tool_calls"):+ break # a normal answer: we are done+ for call in reply["tool_calls"]:+ args = json.loads(call["function"]["arguments"] or "{}")+ result = functions[call["function"]["name"]](**args)+ print("[our Python ran]", call["function"]["name"], args, "->", result)+ messages.append({"role": "tool", "tool_call_id": call["id"], "content": json.dumps(result)})+ # 3. Show the answer and the (changed) task list+ return render_template("index.html", tasks=list_tasks(), answer=reply.get("content"))+# ---- end of NEW part ----++app.run(host="0.0.0.0", port=8086)2-todo-app/templates/index.html → 3-todo-app-with-ai/templates/index.html
--- 2-todo-app/templates/index.html+++ 3-todo-app-with-ai/templates/index.html@@ -3,7 +3,7 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1">-<title>My Tasks</title>+<title>My Tasks + AI</title> <style> body { margin: 0; font-family: system-ui, sans-serif; font-size: 18px; background: #f7f7f8; color: #1f1f1f; } .page { max-width: 720px; margin: 0 auto; padding: 24px 20px; }@@ -22,11 +22,25 @@ .due { color: #666; font-size: 15px; } .done .title { text-decoration: line-through; color: #999; } li form { margin: 0; }+ /* NEW: AI box */+ .ai { border: 2px solid #7c3aed; }+ .ai input { flex: 1; }+ .ai button { background: #7c3aed; }+ .answer { margin: 12px 0 0; padding: 12px 14px; background: #f3effe; border-radius: 10px; white-space: pre-wrap; } </style> </head> <body> <div class="page">- <h1>My Tasks</h1>+ <h1>My Tasks + AI</h1>++ <!-- NEW: Ask AI box -->+ <div class="card ai">+ <form class="add" method="post" action="/ask">+ <input name="question" placeholder="Ask AI, e.g. What is still pending?" required>+ <button>Ask AI</button>+ </form>+ {% if answer %}<p class="answer">{{ answer }}</p>{% endif %}+ </div> <div class="card"> <form class="add" method="post" action="/add">