← All sessions, cheat sheets & labs
Week 1 · Hands-on lab · after Session 2

Lab 1: Build a Workflow Router

The cheapest, highest-leverage pattern in the course. Classify each incoming request, then send it down the right path: a rules handler, a small model, or a big model, with a fallback. No agent required.

~2–3 hourstypescript1 model provider key

What you'll build

One entry point, three exits, zero agents.

A single handle(request) function that decides how much machine a request deserves. Trivial and known cases never touch a model. Easy language goes to a small model. Only genuinely hard requests reach the expensive one. Everything is wrapped in code you can test.

💡 In plain English

Like a receptionist who answers simple questions herself, sends routine ones to a junior, and only books the senior partner for the hard cases. Nobody pays partner rates to ask where the toilets are.

The shape

Route first, then spend.

flowchart TB
  IN["request"] --> R{"classify
rule or tiny model"} R -- "known / trivial" --> RULES["Rules handler
no model call"] R -- "easy language" --> SMALL["Small model"] R -- "hard / ambiguous" --> BIG["Big model"] SMALL --> V["validate + return"] BIG --> V RULES --> V V -- "bad output" --> FB["Fallback: escalate or safe default"] classDef code fill:#D6F5E3,stroke:#1F2937,color:#0E1726; classDef big fill:#EEE6FF,stroke:#1F2937,color:#0E1726; class RULES,V,FB code; class BIG big;

Starter code

Fill in the three exits.

Language-agnostic; TypeScript shown. The router itself is plain code, the only model calls are inside smallModel and bigModel.

import OpenAI from "openai";
const client = new OpenAI();

// classify with a cheap signal first: rules, then a tiny model if needed
function classify(request) {
  if (matchesFaq(request)) return "trivial";   // exact / keyword rules, free
  if (isShortAndSimple(request)) return "easy"; // heuristic, or a tiny classifier model
  return "hard";
}

async function smallModel(request) {          // TODO: cheap, fast model
  const res = await client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: request }],
  });
  return res.choices[0].message.content;
}

async function bigModel(request) {            // TODO: capable, pricey model
  const res = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: request }],
  });
  return res.choices[0].message.content;
}

async function handle(request) {
  const route = classify(request);
  let answer;
  if (route === "trivial") {
    answer = faqLookup(request);              // TODO: no model call
  } else if (route === "easy") {
    answer = await smallModel(request);       // cheap, fast model
  } else {
    answer = await bigModel(request);         // capable, pricey model
  }

  if (!isValid(answer)) {                    // schema / sanity check in code
    return fallback(request, route);        // escalate or safe default
  }
  return { answer, route, cost: costOf(route) };
}

Do this, in order

The steps.

  1. 1
    Define your three routes for a real use casePick something concrete (support triage, a doc Q&A, a coding helper). Write one sentence per route: what counts as trivial, easy, hard.
  2. 2
    Build the rules handler firstHandle the trivial route with zero model calls: a lookup table, a regex, an FAQ match. This is free and instant.
  3. 3
    Wire the small and big model callsSame prompt, two model sizes. Return structured output you can validate (JSON or a known shape).
  4. 4
    Add the classifierRules first; only if unsure, a tiny model call that returns the route label. Never route with the expensive model.
  5. 5
    Add validation and a fallbackCheck the output shape in code. On failure, escalate (small → big) once, then return a safe default. Never crash the request.
  6. 6
    Measure the splitRun 20 realistic requests. Log the route and estimated cost of each. Report what fraction avoided the big model.

You're done when

Acceptance criteria.

Go further

Stretch goals.

  • Confidence escalation: if the small model is unsure (low logprob, or it says so), auto-escalate to the big model.
  • Prompt-cache the shared prefix so even the model routes get cheaper.
  • Add a fourth route that refuses out-of-scope requests in code, before any model sees them.
  • Turn the cost split into a tiny dashboard line you print after each run. It becomes real in Week 3.

Wrapping up

Ship the router + the split.

Hand in your code and one line of output: the route-and-cost split over your test requests. This is slice one of the system you will build all six weeks.

Deliverable
Working handle() + the cost-split number. A repo link or a gist.
bring it Thursday
Stuck?
Start with only the rules route and the big model. Add the small model and classifier once the skeleton runs.
rewatch S1
For the instructorHow to run this labclick to open ▾

Frame it as the highest-ROI thing they will build all course: "routing alone halves most teams' bill, and it is just an if-statement in front of two model calls." Insist the classifier uses rules first, the classic mistake is calling the big model just to decide which model to call.

Push on validation and fallback, that is the boundary lesson from S2 arriving early. If a student's router crashes on a weird input, that is the teachable moment: the model is a flaky dependency, wrap it. Ask everyone to post their cost split in the chat, the spread across the room makes the point better than any slide.