input– what the model was given, the file includedoutput– what it wrote backproperties– everything else on the event
Catch a code assistant inventing functions
The coding assistant you built suggests a call to formatCurrency(), a function that doesn't exist. The syntax of the call is valid, the tone of the conversation is confident, and the call looks like it succeeded.
This use case walks through one code-based evaluation end to end: the eval itself, the generations it can't score, and what its pass rate does when a prompt change goes wrong.
What an eval can answer
Whether the assistant called something that doesn't exist is a yes or no question. You can answer it by comparing the file it was given to the code it wrote back.
That's what a code-based eval is perfect for: code you write that runs against each generation and returns pass or fail. No model is involved, so it costs nothing to run. You can score every generation rather than sampling a slice of them, and a failure that only turns up a few times is still visible.
Write the eval
The eval runs inside PostHog rather than on your servers, so you write it in Hog, the language PostHog compiles and runs itself. Nothing of yours has to be awake for a generation to get scored.
You don't have to write it by hand either. Generate with AI in the code editor opens PostHog AI with your evaluation loaded and drafts the Hog for you. The prompt at the end of this guide takes the same route.
The eval hands three things to you:
Return true to pass and false to fail. Whatever you print() becomes the reason stored next to the result, so print something you'd want to read a week later.
Start with one function you keep seeing invented. The eval looks for a call to it in the output, then looks for the same name in the file the model was given. In one and not the other means the model made it up.
Add the next function as another if. Once you're checking a dozen of them, pull the names into a list and loop over it.
Settings the eval needs
If someone asked a question with no file attached, there's nothing to compare the call against. Marking it pass or fail either way puts a number in your pass rate that isn't true, which is why the eval returns null there.
The code is only part of an eval. These settings decide what it runs against:
- Allows N/A – turn it on, so a
nullis recorded as N/A instead of counted - Sampling rate – set it to 100%. There's no per-call cost to ration
- Property filters – keep your own dev and local traffic out of the number
Then, test it on a sample. This runs the eval against a subset of recent generations and shows the pass, fail, or N/A each one would get, along with anything you printed. Nothing is written, so you can keep editing until the reasons read the way you want.
Read the runs
Every result lands on the generation it scored:
- Reasoning is your
print()output – the middle row namesformatCurrency, so you know what to look for - Result is
True,False, orN/A - Target points back to the generation so you can open that trace and read the file the assistant was actually handed
An example of these results is outlined in Fig. 1 below.
| Timestamp | Target | Result | Reasoning |
|---|---|---|---|
| 14:02:11 | gen_8f21c4 | True | No calls to undefined helpers |
| 14:01:58 | gen_8f21b9 | False | Called formatCurrency, not defined in the provided context |
| 14:01:40 | gen_8f21a2 | N/A | No file context on this generation |
When it starts failing
Once enough generations have been scored you have a pass rate: how often your assistant only calls things that exist.
In Fig. 2, generations hold flat at around 9,000 a day and nothing errors, while the pass rate drops from 94% to 71% the day prompt v12 shipped. That prompt told the model to reuse existing helpers where it could, so it invented them to comply. Without the eval running, the first you'd hear is a developer complaining weeks later. You can also split the pass rate by $ai_model and $ai_prompt_version to see which one broke.
Generations
flat at ~9,000/day
Eval pass rate
94% → 71%
Build it with PostHog AI
This prompt builds the evaluation from this guide:
- The N/A guard for generations with no context to check against
- The comparison that fails an undefined call, with the function name as the reason
- The pass rate over 30 days, split by model and prompt version
Swap the function list for the ones your own assistant keeps inventing: