Session 2. Call a model through the adapter — Tue 15 Sep

Call a model through the adapter

Tuesday, September 15, 2026 · 2h · Thread: harness engineering

Outcome

You call a model through the course adapter instead of a vendor SDK. The same prompt runs on two lanes — the deterministic FakeLLM and whichever provider your .env names — and you can say what differed and what was never allowed to. You write down your reliability bar for the rest of the course. Then you handle the three ways a provider fails you: a credential that is missing, a model the lane does not serve, and a call that runs past its deadline. The timeout leaves as a flagged refusal your caller can read, not a traceback.

Contract and threat boundary

Input one system instruction and one user question, both strings.
Output the model's text. On a provider failure: a refusal, flagged for human review, citing nothing.
Budget one method, complete(system, user) -> str. No SDK import in your code, no key in your code, no retry loop yet. The fake lane costs 0; the local Ollama lane costs 0.
Failures this session must handle A missing credential. The lane needs a key, the environment has none, get_client raises ConfigError before any request and never prints the key. An unsupported model. The provider name or the model name is not one this lane serves; you get a named error with the fix in it, not a stack trace from inside an SDK. A timeout. The provider does not answer inside the deadline, and your code returns a flagged refusal instead (ch02-e4).

The threat is not a hostile provider. It is an ordinary one: it is down, it is slow, it renamed a model, your key expired. Each of those arrives as an exception from someone else's library, at the one point in your program where you are not in control. The adapter is where you decide what that becomes.

Session flow

  1. Warm-up and diagnostic (10m). Preflight cell on every screen. Read the lane line out loud: lane = fake or lane = ollama (...). That line, not a guess, is what LIVE is for the next two hours.
  2. Contract and threat boundary (15m). The contract table above. Where the provider sits relative to your process, and which of its failures you have to name today.
  3. Concept and live implementation (30m). LLMClient as a one-method Protocol. FakeLLM, OllamaClient, AnthropicClient, OpenAICompatibleClient behind it. Settings and get_client: everything provider-specific reduced to four fields and one function.
  4. Guided lab (35m). notebook.ipynb: three calls through the fake, the same prompt on both lanes, your reliability bar. Checks ch02-e1 to ch02-e3.
  5. Failure injection (15m). Ask for the anthropic lane with no key. Ask for a provider that does not exist. Then write answer_with_timeout so a provider that never answers becomes a refusal: ch02-e4.
  6. Evaluation and artifact receipt (10m). review("ch02"). Two learners read their reliability bar; the class names the difference.
  7. Exit ticket (5m). One thing that works, one thing that is unclear, your next action. Homework: run the same prompt on a second lane and record one sentence on what changed.

Evidence

uv run bootcamp check ch02                    # runs the notebook, prints the scorecard it printed
uv run bootcamp submit ch02 --github <you>    # builds the submission bundle

bootcamp check executes the notebook and reads back the lines check() and review() printed. It judges nothing itself. Unfilled TODO(you) lines report ❌ and the command exits non-zero: that is the scorecard doing its job on unfinished work, not a crash. solutions/notebook.ipynb is the reference; open it after your check, not before.

Previous: Configure the assistant and the repository instructions · Next: Structured outputs