A first run without an account
- Lesson 2
- 40 min
- Module 1: Start with one model request
Lesson 2 of 19 · Building Reliable Applications with LLM APIsA first run without an account
1. Start with one model request
2. Prompting as an engineering task
3. Structured outputs and validation
4. Tool calling safely
5. Conversations and streaming
6. Reliability and cost controls
What you will be able to do
Get the course project running on your computer, and prove that it works, before you need any account or key. Every later lesson starts from this project and this check.
Before you startWhat to know and to have ready
What you need to know
- How to open a terminal and move between folders, from Files and terminal in Python for Practical AI Engineering.
- How to make and activate a virtual environment, from Isolate dependencies: Worked example: make and use an environment.
- What travels in a request and a response, from What an LLM API is.
What you need on your computer
- Python 3.12 or newer, from Install and verify Python.
- The folder
~/projects. If it is missing, runmkdir ~/projectsfirst. - An editor such as VS Code, and an internet connection to download the project and its packages (about 30 MB).
- Git is optional: you can download a ZIP file instead.
You need no account, no API key and no payment. Nothing in this lesson calls a model.
Tested on macOS (Apple silicon), with Python 3.12.13 and 3.14.6. The package versions: openai 3.26.1, pydantic 2.14.0, httpx 0.28.1 and pytest 9.1.1. The Windows and Linux commands follow the Python course; they were not run for this lesson.
A stand-in at the rehearsal
Section titled “A stand-in at the rehearsal”In a theatre, the star is busy and expensive. So the other actors rehearse with a stand-in, who reads the star’s lines from the script, word for word. The actors can repeat a scene twenty times, at no cost, and the lines never change. If an actor says a line that is not in the script, the stand-in has no answer. That is useful too: it shows that someone changed the scene.
The problem: a real model is a bad partner for practice
Section titled “The problem: a real model is a bad partner for practice”TermAmiraLarkfield’s developer. She writes the code of the help-desk tools in the courses. wants to work on the assistant’s code all day: change a line, run it, check the output, and repeat. With a real TermHosted modelA model that runs on a provider’s computers. Your code sends it requests over the internet, and you pay for each call.Learn more Hosted and self-hosted choicesLLMs: How They Work and How to Choose One, every run needs three things that a learner may not have: an account, a key and a payment method. Every run also needs the network, and it takes about three seconds.
Worse, the answer changes. Amira sent the same request for ticket T-80008 to Termchat-smallThe name that Larkfield’s code gives to a small, low-cost hosted model: gpt-6-luna on Azure. The code uses the name, so the model can change without a code change.Learn more What an LLM API isBuilding Reliable Applications with LLM APIs three times. She got three different replies, with 241, 238 and 308 output tokens. All three were correct, but a test cannot check an exact output that changes on every run. And a failure, such as a provider error, does not happen when you want to study it.
The idea: a mock that replays real answers
Section titled “The idea: a mock that replays real answers”A mock is an imitation of a real part, made for practice and tests. The word means “not real, but made to look real”, as in a mock exam. The project’s mock provider is the stand-in: it does not call a model. It looks up a real answer that was recorded for exactly the same request, and returns it.
The mock needs a name for each request, so that it can find the right recording. It computes the name from the request itself: the model, the messages and the settings. Then it takes a hash of them, a short fingerprint that changes completely when one character changes. The course’s recordings are stored under these fingerprints.
A tiny worked example: two keys
Section titled “A tiny worked example: two keys”Here is the fingerprint, the request key, of two tiny requests. They differ by one character: “Hi” and “Hi!”.
from assistant.providers import request_key
a = {"model": "chat-small", "messages": [{"role": "user", "content": "Hi"}], "max_completion_tokens": 10}print(request_key(a))a["messages"][0]["content"] = "Hi!"print(request_key(a))The output:
f820d94a816df5e3ee89cd3383490bd7One character changed, and the whole key changed. So the mock answers only a request that it has seen, exactly. The project’s request for ticket T-80008, prompt v1 and chat-small has the key 3b337102809abc06. The file recordings/eval_text.jsonl has a real answer under that key: gpt-6-luna on Azure.
Text description of the diagram
The command python -m assistant analyse T-80008 starts the flow. load_ticket reads the ticket from data/tickets.csv. The function build_request makes the request: model chat-small, two messages, max_completion_tokens 1000. The function request_key computes the SHA-256 fingerprint of the request fields. It keeps the first 16 characters: 3b337102809abc06. If the recordings have an entry with this key, the mock returns it as a Completion. A Completion holds the recorded answer text, the finish reason, the tokens and the time of the real call. The command prints the answer as JSON and one summary line. If no recording has this key, the mock raises RecordingNotFound with the message “No recording for this request”.
Step 1: get the project
Section titled “Step 1: get the project”The project is in the course’s labs repository, as snapshots: copies of the project at the start and at the end of each module. You start from start.
-
Open a terminal and move to your projects folder:
Terminal window cd ~/projects -
If you have Git, clone the labs repository:
Terminal window git clone https://github.com/Nextia-Intelligence/nextia-learning-labs.gitIf you do not have Git, download the ZIP file of the repository. Unzip it in
~/projects. Its folder isnextia-learning-labs-main: use that name in the next step. -
Copy the
startsnapshot into a new folder,ticket-assistant:Terminal window cp -R nextia-learning-labs/C08/snapshots/start ticket-assistantTerminal window Copy-Item -Recurse nextia-learning-labs\C08\snapshots\start ticket-assistantTerminal window cp -R nextia-learning-labs/C08/snapshots/start ticket-assistant -
Move into the project, and open it in your editor:
Terminal window cd ticket-assistantcode .If
codeis not found, open the folder from the editor’s File menu.
You work in your copy, ticket-assistant. The labs folder stays as it is, so that you can copy a snapshot again at any time.
Step 2: the virtual environment
Section titled “Step 2: the virtual environment”The project needs four packages at exact versions. They go into a TermVirtual environmentA folder with its own Python and its own packages, for one project. Packages that you install there do not change other projects.Learn more Isolate dependenciesPython for Practical AI Engineering in the project folder, as in the Python course.
-
Make and activate the environment:
Terminal window python3 -m venv .venvsource .venv/bin/activateTerminal window python -m venv .venv.venv\Scripts\Activate.ps1Terminal window python3 -m venv .venvsource .venv/bin/activateThe prompt now starts with
(.venv). From here, typepythonon every system. -
Install the packages from
requirements.txt:Terminal window python -m pip install -r requirements.txtThe last line of the output:
Successfully installed annotated-types-0.8.0 anyio-4.15.1 certifi-2026.7.22 h11-0.16.0 httpcore-1.0.9 httpcore2-2.13.1 httpx-0.28.1 httpx2-2.13.1 idna-3.20 iniconfig-2.3.1 jiter-0.17.0 openai-3.26.1 packaging-26.3 pluggy-1.6.0 pydantic-2.14.0 pydantic-core-2.50.0 pygments-2.21.0 pytest-9.1.1 sniffio-1.3.1 truststore-0.10.4 typing-extensions-4.16.0 typing-inspection-0.4.4You asked for four packages, and pip installed the packages that they need too. A notice about a new pip version is safe to ignore.
requirements.txt pins the versions that the course tested: openai==3.26.1, pydantic==2.14.0, httpx==0.28.1 and pytest==9.1.1. The mock does not use openai yet. Lesson 3 of this module uses httpx, and lesson 4 uses openai.
Step 3: the known-good first run
Section titled “Step 3: the known-good first run”Two checks prove that the setup works: the tests, then one ticket.
-
Run the tests:
Terminal window python -m pytestThe output (your folder path differs):
============================= test session starts ==============================platform darwin -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0rootdir: /Users/amira/projects/ticket-assistantconfigfile: pytest.initestpaths: testsplugins: anyio-4.15.1collected 2 itemstests/test_first_run.py .. [100%]============================== 2 passed in 0.01s ===============================Each dot is one test that passed. The important part is
2 passed. -
Analyse ticket T-80008:
Terminal window python -m assistant analyse T-80008The output:
{"language": "en","team": "delivery","needs_human": false,"reason": "The customer is asking for the delivery date of an order that has shipped, so delivery is the appropriate team and no human-escalation condition is present.","confidence": "high","order": {"order_id": "LK-581106","status": null},"reply": "I’m sorry you don’t have a delivery date yet. Our delivery team can check the latest tracking information for order LK-581106 and share the estimated arrival date. Please allow 3–5 business days for standard delivery in Canada."}gpt-6-luna-2026-09-22 | finish_reason: stop | 1032 tokens in, 241 out | 2.678 s when it was recorded
Your output must be the same, character for character. The mock replays one recording, so there is nothing random. The last line is the summary. gpt-6-luna-2026-09-22 is the model that wrote the answer. finish_reason: stop means that it finished normally. The tokens are the usage, and 2.678 s is how long the real call took. Your run took a fraction of a second: nothing travelled.
The diagram shows what to do if a check fails.
Text description of the diagram
First run python -m pytest. If you do not see “2 passed”, check that the prompt starts with (.venv), install the requirements again, and run the tests again. When you see “2 passed”, run python -m assistant analyse T-80008. If the last line ends with “1032 tokens in, 241 out”, the setup works. If it does not, reset: copy the start snapshot again into a new folder, make a new .venv, and start the checks again.
What is in the project
Section titled “What is in the project”| Path | What it does |
|---|---|
assistant/__main__.py |
The command line: python -m assistant analyse TICKET. |
assistant/data.py |
Reads the 69 tickets from data/tickets.csv. |
assistant/context.py |
Builds the request: the instructions of prompts/v1.md, Grace’s policy and the ticket. |
assistant/prompts/ |
v1.md, Amira’s first prompt, and policy.md, Grace’s support policy. |
assistant/providers.py |
The adapter: the only code that talks to a model provider. For now it has the mock. |
recordings/eval_text.jsonl |
276 real answers: 69 tickets, two prompt versions, two hosted models. |
tests/ |
The two tests that you ran. |
The heart of the mock is short. It reads every recording into a dictionary, by key. To answer a request, it computes the key and looks it up:
def _entry(self, request: dict) -> dict: key = request_key(request) if key not in self.recordings: raise RecordingNotFound( f"No recording for this request (key {key}). The mock replays only the requests that were " "recorded for the course: check the ticket ID, the prompt version and the model.") ...The rest of the assistant does not know that the answer is a recording. It calls complete(request) and gets a Completion, with the text, the finish reason and the tokens. Lesson 4 of this module adds a second provider that calls a real model, with the same complete(request). Nothing else has to change.
When a mock helps, and when it does not
Section titled “When a mock helps, and when it does not”Use the mock for learning, for every test and for most development. It is free, fast and the same every time. It can also replay a failure, such as an error from a provider, as often as you need.
A mock cannot tell you how a model answers a new request: a new ticket, a new prompt or a new model. It knows only the past. So before a release, you check a small set of tickets against a live model too. Module 6 adds that check as an optional test. The cost of the mock is the work of recording answers, and of recording them again when a prompt changes.
Guided practice: three more tickets
Section titled “Guided practice: three more tickets”-
Analyse ticket T-80001:
Terminal window python -m assistant analyse T-80001Tom asks about his neighbour’s order, LK-615204. The answer has
"needs_human": true, and the reply says that the team cannot share someone else’s delivery details. The last line ends1054 tokens in, 211 out | 2.523 s when it was recorded. -
Analyse ticket T-80006. This ticket has no text, only an attachment,
IMG_2041.jpg.The answer has
"needs_human": trueand"confidence": "low". The reason names the no-readable-request rule. -
Analyse ticket T-80005, a French ticket about a hammock.
The answer has
"language": "fr", and the reply is in French. -
Analyse a ticket that does not exist:
python -m assistant analyse T-99999.The output is
No ticket T-99999 in tickets.csv. Nothing else is printed.
Show the full output of step 2
{ "language": "en", "team": "account", "needs_human": true, "reason": "The ticket contains no readable request, so the team cannot be determined and a person must review it under the no-readable-request rule.", "confidence": "low", "order": null, "reply": "We received your attachment, but we can’t view it here and your message didn’t include any details. Please describe what you need help with, and we’ll direct your request to the right team."}gpt-6-luna-2026-09-22 | finish_reason: stop | 1020 tokens in, 285 out | 2.473 s when it was recordedThe model had to choose a team, so it chose account, with low confidence. TermGraceThe lead of Larkfield’s help desk. She writes the support policy and decides what a good answer to a customer is.’s TermTest setA fixed set of examples with known correct answers. You run each version of a prompt or model on the same set, so the results compare fairly.Learn more Build a representative test setLLMs: How They Work and How to Choose One scores no team for this ticket: a person decides.
Your turn: change one word of the prompt
Section titled “Your turn: change one word of the prompt”Open assistant/prompts/v1.md. In the first line after [system], change “the customer’s ticket.” to “the customer’s ticket carefully.” Save the file. Before you run anything, predict: what does python -m assistant analyse T-80008 print now? And the tests?
Show the answer
The command stops with an error. The last line is:
assistant.providers.RecordingNotFound: No recording for this request (key 27a68d8d70e9b11a). The mock replays only the requests that were recorded for the course: check the ticket ID, the prompt version and the model.The system message changed, so the request changed, so the key changed: 27a68d8d70e9b11a instead of 3b337102809abc06. No recording has that key. python -m pytest reports 1 failed, 1 passed: the first-run test fails for the same reason.
This is the stand-in with a line that is not in the script. It is useful: the mock tells you at once that the request is not the one that was recorded. Undo the change, save, and run the command again. The known-good line is back.
Common mistake
No module named assistant
Symptom: you run python -m assistant analyse T-80008 and see:
/Users/amira/projects/ticket-assistant/.venv/bin/python: No module named assistantCause: the terminal is not in the project folder. python -m assistant looks for the assistant package in the current folder. Here it ran from ~/projects, one level too high. A similar message, No module named pytest, means that the environment is not active: the computer’s own Python has no pytest.
Fix:
- Move into the project:
cd ~/projects/ticket-assistant. - Check that the prompt starts with
(.venv). If it does not, activate the environment again (Step 2). - Run the command again.
If it still fails, reset the project.
Reset the project
Section titled “Reset the project”If the project stops working and you cannot find why, start again from the snapshot. Your old folder stays, with a new name, so you lose nothing.
-
Deactivate the environment, and move to your projects folder:
Terminal window deactivatecd ~/projects -
Rename your project:
Terminal window mv ticket-assistant ticket-assistant-oldTerminal window Rename-Item ticket-assistant ticket-assistant-oldTerminal window mv ticket-assistant ticket-assistant-old -
Copy the snapshot again, as in Step 1, step 3.
-
Make a new environment and install the packages, as in Step 2.
-
Run the two checks of Step 3.
Later modules use the same steps with their own snapshot, for example end-of-m01 at the start of Module 2.
Knowledge check
Section titled “Knowledge check”Check your understanding
What is a mock provider, and why does the course use one for every test?
Show the answer
A mock provider is an imitation of a real provider. It does not call a model: it returns a real answer that was recorded for exactly the same request. The course uses it for tests because it is free, needs no account or network, and gives the same answer every time. A real model gives a different reply to the same request, so a test could not check an exact output.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Predict: you change the model in the request from chat-small to chat-strong and run T-80008 with the mock. What happens, and why?
Show the answer
The mock returns a different recorded answer, from chat-strong. The model name is part of the request key, so the key changes. The course recorded the 69 tickets with both chat-small and chat-strong, so a recording with the new key exists. With a model name that was never recorded, the mock raises RecordingNotFound.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Your first run prints a JSON answer for T-80008, but the last line says 1641 tokens in. What does that tell you?
Show the answer
The request is not the one of the start snapshot. More input tokens means a longer prompt: for example, a later version of the prompt. The project is probably a later snapshot, or the code was changed. Check that you copied start, or reset the project.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
- The project
ticket-assistantcomes from the labs repository as snapshots. You copystartand work in your copy. - A mock imitates a real part. The mock provider returns real recorded answers, found by a request key: a hash of the model, the messages and the settings.
- The known-good first run:
python -m pytestgives2 passed, andpython -m assistant analyse T-80008ends with1032 tokens in, 241 out | 2.678 s when it was recorded. - One changed character in a request gives a new key and
RecordingNotFound. That shows at once that a request changed. - If a check fails, look at the folder and the
(.venv)prompt first. If it still fails, rename your project and copy the snapshot again.
Next, you open the request and the response, field by field.
Next: Request anatomy