You will be able to
- Describe the four stages of an LLM app lifecycle and how they loop
- Write an eval set with clear pass rules for your own app
- Run evals as regression tests before changing a prompt or a model
- Log each model call with prompt version, latency, tokens and outcome
- Estimate and reduce the running cost of an AI feature
Why a demo is not a product
Classic machine learning teams spend most of their effort training models. With LLM apps you usually rent a model as a service, so the real work moves to prompts, data, integration and checking the output. That shift has a name, LLMOps, and it borrows the discipline of software testing for something far less predictable than normal code.
Three things make LLM features fragile. The same input can produce different text on different runs. The provider can update or retire a model while your code stays the same. A tiny wording change in the system prompt can fix one case and quietly break five others.
The answer is not to stop changing things. It is to make every change measurable, so you know whether Study Buddy got better or worse instead of guessing from a few chats.
The lifecycle: ideate, build, evaluate, operate
Think of the lifecycle as loops, not a straight line. Each stage feeds the next, and what you learn in production flows back into the next round of ideas.
Around all four stages sits governance: who may see the data, which laws apply, how keys are stored and who signs off a release. Lessons 4 and 14 covered most of it, and it never goes away.
Do not skip the ideate stage because coding feels like progress. Ten minutes of trying the task by hand often shows that the idea needs retrieval, a tool or a narrower scope, and that is far cheaper to learn before you write the app around it.
- Ideate: write down the job in one sentence, try it by hand in a playground with two or three models, and collect the first real examples of input you expect.
- Build: turn the best prompt into code, add retrieval, tools or structured output where needed, and keep prompts in files under version control.
- Evaluate: run a fixed set of test cases with pass rules, compare versions side by side and decide with numbers.
- Operate: deploy, log every call, watch errors, latency and spending, collect user feedback and add new failures to the eval set.
Eval sets and regression tests for prompts
An eval case is an input plus a rule that says what a good answer must contain or avoid. Ten to thirty cases are enough to start, as long as they cover typical requests, edge cases and things the app must refuse.
Good cases come from real use. Start with questions you asked Study Buddy yourself, add the ones friends type when they try it, and write a few deliberately awkward inputs: a misspelled topic, a question in another language, a request for the answer key instead of a quiz. Store them in a JSON file next to the code so they are versioned together.
Pass rules come in four strengths. Code checks are the cheapest: does the answer contain a keyword, parse as JSON or stay under a length limit. A second model can grade answers against a written rubric, which handles open-ended text but needs spot checks by a person. Human review is the slowest and the most trustworthy, so keep it for a sample. Comparison tests show two versions of an answer and ask which one is better.
Treat the eval set as a regression suite. Every time you edit a prompt, swap the model or change retrieval, run the whole set and compare the pass count with the previous version. A failure that a user reports becomes a new case, and that case stays in the set for good.
Model output varies even at temperature 0, so a single run can mislead you. Run important cases several times and look at the pass rate, not one lucky answer. Change one thing at a time, otherwise you cannot tell which edit caused the difference.
What to measure
A single pass count hides too much. Track a few separate numbers so you can see which dimension moved after a change.
- Quality: does the answer do the job the user asked for, judged by your pass rules.
- Groundedness: when the app uses notes or documents, is every claim supported by them.
- Harm: does the app refuse what it should and avoid unsafe, biased or private content.
- Latency: seconds until the first token and until the full answer.
- Cost: input and output tokens per request, multiplied by your provider's rates.
- Failures: timeouts, invalid JSON, empty answers and provider errors per hundred calls.
Logging and cost in operation
Write one structured log line per model call. Include a timestamp, the prompt version, the model name, token counts, latency, whether the answer passed any automatic checks, and an id that links it to user feedback. JSON Lines files are fine for a small app; a database or a tracing tool helps later.
Logs can hold personal data, so decide what you store. Mask emails and names where you can, keep logs only as long as you need them, and tell users in plain words that conversations may be reviewed to improve the service.
Cost grows with tokens, and output tokens usually cost more than input tokens. Most savings come from boring moves: shorter system prompts, sending only the relevant notes, a sensible max_tokens limit, caching answers to repeated questions and routing easy tasks to a smaller model. Set a spending limit or alert in your provider's dashboard before anything goes public.
Close the loop with feedback. A thumbs up and thumbs down under each answer costs little to build, and every thumbs down is a candidate eval case. Review a handful of logged answers every week, because dashboards show that something changed, while reading answers shows what changed.
Example
A tiny eval runner with a JSON Lines log
import json, os, timefrom openai import OpenAI client = OpenAI(base_url=os.environ["LLM_BASE_URL"], api_key=os.environ["LLM_API_KEY"])PROMPT_VERSION = "explain-v2"SYSTEM = "You are Study Buddy. Explain the topic for a beginner in at most three sentences."CASES = [ {"input": "Explain photosynthesis", "must": ["light", "oxygen"]}, {"input": "Explain what a prime number is", "must": ["itself"]}, {"input": "Explain the French Revolution", "must": ["1789"]},] passed = 0with open("eval_log.jsonl", "a", encoding="utf-8") as log: for case in CASES: start = time.perf_counter() r = client.chat.completions.create(model=os.environ["LLM_MODEL"], temperature=0, messages=[ {"role": "system", "content": SYSTEM}, {"role": "user", "content": case["input"]}]) answer = r.choices[0].message.content or "" ok = all(word.lower() in answer.lower() for word in case["must"]) passed += ok log.write(json.dumps({"prompt": PROMPT_VERSION, "input": case["input"], "ok": ok, "seconds": round(time.perf_counter() - start, 2), "tokens": r.usage.total_tokens if r.usage else None, "answer": answer}) + "\n") print("PASS" if ok else "FAIL", case["input"])print(f"{passed}/{len(CASES)} passed with {PROMPT_VERSION}")Each run appends one log line per case with the prompt version, time, token count and answer. Change SYSTEM, bump PROMPT_VERSION and run it again to compare versions. Some local servers do not report usage, which is why the token field may be empty.
Practice
Give Study Buddy a regression suite
- Save the example as
evals.pyin your Study Buddy folder and replace the three cases with ten of your own: six normal topics, two tricky ones and two requests Study Buddy should decline. - For each case write a pass rule a script can check, such as required words, a maximum length or a refusal phrase.
- Run the script twice with the current prompt and note the pass count both times.
- Edit the system prompt in one way only, change
PROMPT_VERSION, and run the suite again. - Open
eval_log.jsonland compare the answers of one case that changed between the two versions.
Done when: The script prints a pass count for two prompt versions, the log has one line per case per run, and you can point to one case whose result changed and explain why.
Common mistakes
- Judging a prompt change by chatting with the app a few times instead of running the same fixed cases.
- Changing the prompt and the model in one step, so a better or worse score cannot be traced to either edit.
- Building an eval set from easy, happy-path questions only, which hides the cases where users get hurt.
- Logging full conversations with personal details forever because nobody decided what the logs are for.
Check yourself
Pick an answer to see whether it is right and why.
From the founder
Write your ten eval cases before you touch the prompt again. From today, I want you to run evals.py before every change to Study Buddy, however small it looks.