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">