[{"data":1,"prerenderedAt":459},["ShallowReactive",2],{"mdc-167t48-key":3},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,17,24,30,35,42,47,58,68,78,88,94,99,163,168,283,296,302,307,317,327,337,342,348,358,363,368,392,404,417,423,428,433,437],{"type":9,"tag":10,"props":11,"children":13},"element","h3",{"id":12},"making-llm-output-trustworthy",[14],{"type":15,"value":16},"text","Making LLM output trustworthy",{"type":9,"tag":18,"props":19,"children":21},"h1",{"id":20},"test-one-prompt-a-hundred-times-not-a-hundred-prompts-once",[22],{"type":15,"value":23},"Test one prompt a hundred times, not a hundred prompts once",{"type":9,"tag":25,"props":26,"children":27},"p",{},[28],{"type":15,"value":29},"The standard way to test a prompt — write it, run it once, look at the response, tweak, repeat — measures the wrong thing. It samples a distribution once and treats the sample as the distribution. Run the same input a hundred times instead, and you get the thing you actually needed: the variance, the tail latency, the token spread, and the frequency of the failure you have been unable to reproduce.",{"type":9,"tag":25,"props":31,"children":32},{},[33],{"type":15,"value":34},"Every prompt-authoring interface encourages the wrong habit, because they all have a Preview button. You write, you preview, you see a decent response, you ship it. Production then behaves nothing like the preview, and the gap is not bad luck — it is the difference between one draw and many.",{"type":9,"tag":36,"props":37,"children":39},"h2",{"id":38},"what-does-single-run-testing-miss",[40],{"type":15,"value":41},"What does single-run testing miss?",{"type":9,"tag":25,"props":43,"children":44},{},[45],{"type":15,"value":46},"Four things, and every one of them is a production incident waiting.",{"type":9,"tag":25,"props":48,"children":49},{},[50,56],{"type":9,"tag":51,"props":52,"children":53},"strong",{},[54],{"type":15,"value":55},"Variance in quality.",{"type":15,"value":57}," The response you previewed might be at the 80th percentile of what that prompt produces. You have no way to know from one sample. The version a user gets on a bad draw is the one that sets their opinion.",{"type":9,"tag":25,"props":59,"children":60},{},[61,66],{"type":9,"tag":51,"props":62,"children":63},{},[64],{"type":15,"value":65},"Variance in latency.",{"type":15,"value":67}," Time-to-first-token varies substantially call to call, and it degrades under concurrency in ways a single sequential test cannot show. A feature that felt responsive in development can be abandoned in production simply because the p95 is somewhere the p50 never suggested.",{"type":9,"tag":25,"props":69,"children":70},{},[71,76],{"type":9,"tag":51,"props":72,"children":73},{},[74],{"type":15,"value":75},"Token spread.",{"type":15,"value":77}," Output length is not fixed. A prompt whose typical response is short can have a long tail that blows through a context budget or a cost model. You see that in a histogram, never in one call.",{"type":9,"tag":25,"props":79,"children":80},{},[81,86],{"type":9,"tag":51,"props":82,"children":83},{},[84],{"type":15,"value":85},"Intermittent misbehaviour.",{"type":15,"value":87}," The classification that is wrong one time in twelve. The field that is occasionally omitted. These are invisible at n=1 and obvious at n=100, and they are exactly the bugs that get filed as \"sometimes it just doesn't work.\"",{"type":9,"tag":36,"props":89,"children":91},{"id":90},"how-do-you-actually-run-it",[92],{"type":15,"value":93},"How do you actually run it?",{"type":9,"tag":25,"props":95,"children":96},{},[97],{"type":15,"value":98},"Build the smallest possible harness. This is a day of work, not a project.",{"type":9,"tag":100,"props":101,"children":102},"ol",{},[103,114,124,134,153],{"type":9,"tag":104,"props":105,"children":106},"li",{},[107,112],{"type":9,"tag":51,"props":108,"children":109},{},[110],{"type":15,"value":111},"Pick a representative input.",{"type":15,"value":113}," One real query. Not a synthetic one you invented — real inputs have shapes you would not have thought of.",{"type":9,"tag":104,"props":115,"children":116},{},[117,122],{"type":9,"tag":51,"props":118,"children":119},{},[120],{"type":15,"value":121},"Run it many times.",{"type":15,"value":123}," Fifty to a hundred is enough to see a distribution. Vary it slightly across runs if you want to cover a neighbourhood rather than a point.",{"type":9,"tag":104,"props":125,"children":126},{},[127,132],{"type":9,"tag":51,"props":128,"children":129},{},[130],{"type":15,"value":131},"Record four things per call.",{"type":15,"value":133}," Latency, input tokens, output tokens, and the response itself.",{"type":9,"tag":104,"props":135,"children":136},{},[137,142,144,151],{"type":9,"tag":51,"props":138,"children":139},{},[140],{"type":15,"value":141},"Score the responses",{"type":15,"value":143}," against your ",{"type":9,"tag":145,"props":146,"children":148},"a",{"href":147},"/blog/how-to-evaluate-an-llm-feature",[149],{"type":15,"value":150},"reference answers",{"type":15,"value":152},", blind.",{"type":9,"tag":104,"props":154,"children":155},{},[156,161],{"type":9,"tag":51,"props":157,"children":158},{},[159],{"type":15,"value":160},"Repeat across candidate models,",{"type":15,"value":162}," identically.",{"type":9,"tag":25,"props":164,"children":165},{},[166],{"type":15,"value":167},"The output is a table, and the table is the deliverable:",{"type":9,"tag":169,"props":170,"children":171},"table",{},[172,194],{"type":9,"tag":173,"props":174,"children":175},"thead",{},[176],{"type":9,"tag":177,"props":178,"children":179},"tr",{},[180,184,189],{"type":9,"tag":181,"props":182,"children":183},"th",{},[],{"type":9,"tag":181,"props":185,"children":186},{},[187],{"type":15,"value":188},"Model A",{"type":9,"tag":181,"props":190,"children":191},{},[192],{"type":15,"value":193},"Model B",{"type":9,"tag":195,"props":196,"children":197},"tbody",{},[198,213,227,241,255,269],{"type":9,"tag":177,"props":199,"children":200},{},[201,207,210],{"type":9,"tag":202,"props":203,"children":204},"td",{},[205],{"type":15,"value":206},"Median latency",{"type":9,"tag":202,"props":208,"children":209},{},[],{"type":9,"tag":202,"props":211,"children":212},{},[],{"type":9,"tag":177,"props":214,"children":215},{},[216,221,224],{"type":9,"tag":202,"props":217,"children":218},{},[219],{"type":15,"value":220},"p95 latency",{"type":9,"tag":202,"props":222,"children":223},{},[],{"type":9,"tag":202,"props":225,"children":226},{},[],{"type":9,"tag":177,"props":228,"children":229},{},[230,235,238],{"type":9,"tag":202,"props":231,"children":232},{},[233],{"type":15,"value":234},"Median output tokens",{"type":9,"tag":202,"props":236,"children":237},{},[],{"type":9,"tag":202,"props":239,"children":240},{},[],{"type":9,"tag":177,"props":242,"children":243},{},[244,249,252],{"type":9,"tag":202,"props":245,"children":246},{},[247],{"type":15,"value":248},"p95 output tokens",{"type":9,"tag":202,"props":250,"children":251},{},[],{"type":9,"tag":202,"props":253,"children":254},{},[],{"type":9,"tag":177,"props":256,"children":257},{},[258,263,266],{"type":9,"tag":202,"props":259,"children":260},{},[261],{"type":15,"value":262},"Correct, no fabrication",{"type":9,"tag":202,"props":264,"children":265},{},[],{"type":9,"tag":202,"props":267,"children":268},{},[],{"type":9,"tag":177,"props":270,"children":271},{},[272,277,280],{"type":9,"tag":202,"props":273,"children":274},{},[275],{"type":15,"value":276},"Cost per call",{"type":9,"tag":202,"props":278,"children":279},{},[],{"type":9,"tag":202,"props":281,"children":282},{},[],{"type":9,"tag":25,"props":284,"children":285},{},[286,288,294],{"type":15,"value":287},"I have deliberately left it blank. Any numbers I published would be stale within a quarter and would be ",{"type":9,"tag":289,"props":290,"children":291},"em",{},[292],{"type":15,"value":293},"my",{"type":15,"value":295}," workload rather than yours, which is the whole point — this is a table you fill in, and the filling-in takes an afternoon.",{"type":9,"tag":36,"props":297,"children":299},{"id":298},"what-tends-to-come-out-of-it",[300],{"type":15,"value":301},"What tends to come out of it",{"type":9,"tag":25,"props":303,"children":304},{},[305],{"type":15,"value":306},"Three findings recur often enough to be worth expecting.",{"type":9,"tag":25,"props":308,"children":309},{},[310,315],{"type":9,"tag":51,"props":311,"children":312},{},[313],{"type":15,"value":314},"Latency differences between models are much larger than the marketing suggests, and they do not track capability.",{"type":15,"value":316}," A model that is slightly worse on your task can be several times faster on it, and for an interactive feature that trade is frequently correct — a good answer nobody waits for is worth less than a decent answer that arrives.",{"type":9,"tag":25,"props":318,"children":319},{},[320,325],{"type":9,"tag":51,"props":321,"children":322},{},[323],{"type":15,"value":324},"Cutting tokens helps less than you expect.",{"type":15,"value":326}," Trimming a prompt is the obvious first optimisation and it does reduce cost. It often barely moves latency, because model compute time dominates rather than transfer. Worth doing, but if latency is the problem, the answer is usually a different model or a different decomposition rather than a shorter prompt.",{"type":9,"tag":25,"props":328,"children":329},{},[330,335],{"type":9,"tag":51,"props":331,"children":332},{},[333],{"type":15,"value":334},"A slower model's outputs make excellent few-shot examples for a faster one.",{"type":15,"value":336}," This is the most useful trick in the set. Take the responses from the model that produces the best quality, put a handful into the prompt as examples, and run the fast model. You often get most of the quality at most of the speed — because you have converted a capability gap into an imitation problem, and imitation is cheap.",{"type":9,"tag":25,"props":338,"children":339},{},[340],{"type":15,"value":341},"That last one generalises: when a fast model underperforms, the first thing to try is not a better prompt in the abstract, it is showing it what good looks like.",{"type":9,"tag":36,"props":343,"children":345},{"id":344},"what-about-testing-the-whole-agent-not-one-prompt",[346],{"type":15,"value":347},"What about testing the whole agent, not one prompt?",{"type":9,"tag":25,"props":349,"children":350},{},[351,353],{"type":15,"value":352},"Same principle, more care about what you assert, because agent test harnesses have a specific and dangerous failure mode: ",{"type":9,"tag":51,"props":354,"children":355},{},[356],{"type":15,"value":357},"a test can pass while the answer is fiction.",{"type":9,"tag":25,"props":359,"children":360},{},[361],{"type":15,"value":362},"If the expected result is specified loosely — \"should describe the product's cold-weather rating\" — then a response inventing a rating satisfies it perfectly. The grader is checking shape, and the shape is correct. The suite goes green. The output is wrong.",{"type":9,"tag":25,"props":364,"children":365},{},[366],{"type":15,"value":367},"Two rules avoid this:",{"type":9,"tag":369,"props":370,"children":371},"ul",{},[372,382],{"type":9,"tag":104,"props":373,"children":374},{},[375,380],{"type":9,"tag":51,"props":376,"children":377},{},[378],{"type":15,"value":379},"Assert exact values where a wrong fact would matter.",{"type":15,"value":381}," Specifications, figures, policies, anything a user acts on. Say what the right answer is, not what it looks like.",{"type":9,"tag":104,"props":383,"children":384},{},[385,390],{"type":9,"tag":51,"props":386,"children":387},{},[388],{"type":15,"value":389},"Assert shape only where variation is genuinely fine.",{"type":15,"value":391}," A confirmation, a greeting, a summary — \"should contain a date, a time and a confirmation\" is the right assertion there.",{"type":9,"tag":25,"props":393,"children":394},{},[395,397,402],{"type":15,"value":396},"A second thing to expect: ",{"type":9,"tag":51,"props":398,"children":399},{},[400],{"type":15,"value":401},"intermediate labels are noisier than final answers.",{"type":15,"value":403}," Whatever internal classification your agent does — which topic, which tool, which branch — will vary between runs on inputs that sit between categories, while the final response often stays correct anyway because other signals carry it. Do not treat the internal label as your success metric. Optimise for what the user receives; use the label variance as a signal that a category boundary needs sharpening.",{"type":9,"tag":25,"props":405,"children":406},{},[407,409,415],{"type":15,"value":408},"And when the same input produces different internal routing on repeated runs, that is information, not a flake. It means the input does not clearly belong to any of your defined buckets, which is a scoping problem you can actually fix — usually by ",{"type":9,"tag":145,"props":410,"children":412},{"href":411},"/blog/narrow-agents-beat-smart-ones",[413],{"type":15,"value":414},"naming the category you were missing",{"type":15,"value":416},".",{"type":9,"tag":36,"props":418,"children":420},{"id":419},"what-to-do-on-monday",[421],{"type":15,"value":422},"What to do on Monday",{"type":9,"tag":25,"props":424,"children":425},{},[426],{"type":15,"value":427},"Take the one prompt in your product that matters most. Run it a hundred times. Plot latency and token count, score the responses blind, and do the same for one cheaper model.",{"type":9,"tag":25,"props":429,"children":430},{},[431],{"type":15,"value":432},"You will learn more in that afternoon than in a month of tweaking against a Preview button — and you will end it with a table you can put in front of whoever is asking why the feature is slow.",{"type":9,"tag":434,"props":435,"children":436},"hr",{},[],{"type":9,"tag":25,"props":438,"children":439},{},[440],{"type":9,"tag":289,"props":441,"children":442},{},[443,445,450,452,458],{"type":15,"value":444},"Part 3 of ",{"type":9,"tag":145,"props":446,"children":448},{"href":447},"/blog?series=trustworthy-llm-output",[449],{"type":15,"value":16},{"type":15,"value":451},". Next: ",{"type":9,"tag":145,"props":453,"children":455},{"href":454},"/blog/observability-for-llm-features",[456],{"type":15,"value":457},"you can't debug what you can't see",{"type":15,"value":416},1787908866326]