Skip to content

A first run without an account

  • Lesson 2
  • 40 min
  • Module 1: Start with one model request

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 readyAbout 40 minutes

What you need to know

What you need on your computer

  • Python 3.12 or newer, from Install and verify Python.
  • The folder ~/projects. If it is missing, run mkdir ~/projects first.
  • 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.

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.

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:

f820d94a816df5e3
ee89cd3383490bd7

One 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.

A flow from top to bottom. python -m assistant analyse T-80008. load_ticket: the ticket from data/tickets.csv. build_request: model chat-small, 2 messages, max_completion_tokens 1000. request_key: SHA-256 of the request fields, 3b337102809abc06. A decision: a recording with this key? Yes: Completion, the recorded answer text, finish_reason, tokens, latency, then printed: the JSON answer and one summary line. No: RecordingNotFound, No recording for this request.
How the mock provider finds the answer for one ticket. The key is a real fingerprint of the project's request for T-80008.
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”.

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.

  1. Open a terminal and move to your projects folder:

    Terminal window
    cd ~/projects
  2. If you have Git, clone the labs repository:

    Terminal window
    git clone https://github.com/Nextia-Intelligence/nextia-learning-labs.git

    If you do not have Git, download the ZIP file of the repository. Unzip it in ~/projects. Its folder is nextia-learning-labs-main: use that name in the next step.

  3. Copy the start snapshot into a new folder, ticket-assistant:

    Terminal window
    cp -R nextia-learning-labs/C08/snapshots/start ticket-assistant
  4. Move into the project, and open it in your editor:

    Terminal window
    cd ticket-assistant
    code .

    If code is 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.

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.

  1. Make and activate the environment:

    Terminal window
    python3 -m venv .venv
    source .venv/bin/activate

    The prompt now starts with (.venv). From here, type python on every system.

  2. Install the packages from requirements.txt:

    Terminal window
    python -m pip install -r requirements.txt

    The 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.4

    You 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.

Two checks prove that the setup works: the tests, then one ticket.

  1. Run the tests:

    Terminal window
    python -m pytest

    The output (your folder path differs):

    ============================= test session starts ==============================
    platform darwin -- Python 3.14.6, pytest-9.1.1, pluggy-1.6.0
    rootdir: /Users/amira/projects/ticket-assistant
    configfile: pytest.ini
    testpaths: tests
    plugins: anyio-4.15.1
    collected 2 items
    tests/test_first_run.py .. [100%]
    ============================== 2 passed in 0.01s ===============================

    Each dot is one test that passed. The important part is 2 passed.

  2. Analyse ticket T-80008:

    Terminal window
    python -m assistant analyse T-80008

    The 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.

A decision flow. Run python -m pytest. If not 2 passed: check that the prompt shows .venv and install the requirements again, then run the tests again. If 2 passed: run python -m assistant analyse T-80008. If the last line ends 1032 tokens in, 241 out: ready, the setup works. If not: reset, copy the start snapshot again with a new .venv, and run the tests again.
The setup check. Two passed tests and the known-good line for T-80008 mean that you are ready.
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.

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:

assistant/providers.py (part)
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.

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.

  1. Analyse ticket T-80001:

    Terminal window
    python -m assistant analyse T-80001

    Tom 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 ends 1054 tokens in, 211 out | 2.523 s when it was recorded.

  2. Analyse ticket T-80006. This ticket has no text, only an attachment, IMG_2041.jpg.

    The answer has "needs_human": true and "confidence": "low". The reason names the no-readable-request rule.

  3. Analyse ticket T-80005, a French ticket about a hammock.

    The answer has "language": "fr", and the reply is in French.

  4. 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 recorded

The 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.

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 assistant

Cause: 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:

  1. Move into the project: cd ~/projects/ticket-assistant.
  2. Check that the prompt starts with (.venv). If it does not, activate the environment again (Step 2).
  3. Run the command again.

If it still fails, 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.

  1. Deactivate the environment, and move to your projects folder:

    Terminal window
    deactivate
    cd ~/projects
  2. Rename your project:

    Terminal window
    mv ticket-assistant ticket-assistant-old
  3. Copy the snapshot again, as in Step 1, step 3.

  4. Make a new environment and install the packages, as in Step 2.

  5. 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.

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.

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.

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.

Recap

  • The project ticket-assistant comes from the labs repository as snapshots. You copy start and 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 pytest gives 2 passed, and python -m assistant analyse T-80008 ends with 1032 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