Exercises — Week 7 — CloudWave Ticket Bot¶
Do these after reading Week 7. Starter: there is no model to install. The firewall is the point.
Predict before you run¶
Does python -m eval.router exit 0 before you add the sixth golden line? After you add “Will user_041906 leave us next month?” without touching allowed_tools, what fails?
Runnable command¶
Each task has three hints, closed by default. Open only as far as you need.
1. Golden file¶
Run python -m eval.router. It must exit 0. allowed_tools is a keyword firewall, not get_churn_score. Add a sixth line to eval/golden_tickets.jsonl whose question does not already contain churn or cancel (example: “Will user_041906 leave us next month?”) and extend the allowlist so that question maps to get_churn_score. Optionally, when the firewall allows it, call Week 17’s get_churn_score if you have artifacts/prod.
Hint 1 — a nudge
Write the test first. Add the golden line, run the suite, and read the failure — it tells you exactly which phrase the firewall doesn't know yet.
Hint 2 — the approach
allowed_tools checks the block list first, then a tuple of churn phrases. Add the narrowest phrase that matches your question ("leave us") to the churn tuple — not to the block list, and not a single word like "leave" that would match "leave a review".
Hint 3 — most of the code
2. Allowlist, not a prompt¶
Implement issue_refund as a Python function. Do not add it to allowed_tools. Show that ticket t2 still calls nothing.
Hint 1 — a nudge
A function existing in your codebase and a function being reachable by the bot are different things. Which one does the firewall control?
Hint 2 — the approach
Write issue_refund(user_id, amount) — it can just return a dict. Then load t2 from the golden file and assert allowed_tools(t2["question"]) == [].
Hint 3 — most of the code
import json
from pathlib import Path
import eval.router as router
def issue_refund(user_id: str, amount: float) -> dict:
return {"user_id": user_id, "refunded": amount}
cases = [json.loads(line) for line in Path(router.GOLDEN).read_text().splitlines()]
t2 = next(c for c in cases if c["id"] == "t2")
print(t2["question"], "→", router.allowed_tools(t2["question"]))
3. I don’t know¶
Write answer(question, hits) from the lesson. Feed it an empty hits and a question that is not a churn question. Assert refuse is True.
Hint 1 — a nudge
The bot has two ways to know something: a tool the firewall allows, or a document retrieval found. If it has neither, what's the only honest answer?
Hint 2 — the approach
Copy the lesson's answer: ask allowed_tools first; if there are no tools and no hits, return refuse=True, reason="no_doc". Also refuse a top hit below the score floor.
Hint 3 — most of the code
def answer(question: str, hits: list[tuple[float, str, str]]) -> dict:
tools = router.allowed_tools(question)
if not tools and not hits:
return {"answer": "I don't know.", "refuse": True, "reason": "no_doc", "tools_called": []}
if hits and hits[0][0] < 0.25:
return {"answer": "I don't know.", "refuse": True, "reason": "no_doc", "doc_ids": []}
return {"answer": "...", "refuse": False, "reason": "ok", "tools_called": tools}
print(answer("What colour is the CloudWave logo?", hits=[]))
4. Cost line¶
Assume 800 tokens in, 200 out, $0.75 / 1M in, $4.50 / 1M out, 2,000 asks/day. Print dollars/day. If you add a second serial call that doubles tokens, print the new number.
Hint 1 — a nudge
Input and output tokens are priced differently. Price one ask first, then multiply.
Hint 2 — the approach
per_ask = tokens_in × price_in / 1e6 + tokens_out × price_out / 1e6. Wrap it in a function of the token counts so the "second call" version is one more line.
Hint 3 — most of the code
The two-call number is one more line.Expected observation¶
Open after you run
failures 0 once the allowlist maps the new churn phrasing to get_churn_score. Ticket t2 still calls nothing even if issue_refund exists as a Python function.
Self-check¶
allowed_tools is a keyword firewall, not a prompt. Empty hits + non-churn question → refuse is True. Print dollars/day for the cost line.