> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Catch a code assistant inventing functions – AI Observability pocket guide [](/pocket-guides.md)Aa [](/pocket-guides/ai-observability/quality-output.md)[](/pocket-guides/ai-observability/ai-cost.md) # Catch a code assistant inventing functions – AI Observability pocket guide 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](/docs/ai-evals.md) 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](/docs/ai-evals.md) 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](/docs/hog.md), 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: - `input` – what the model was given, the file included - `output` – what it wrote back - `properties` – everything else on the event 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. Hog ```rust let context := ifNull(input, '') let suggestion := ifNull(output, '') // Asked with no file attached, so there is nothing to check the call against if (length(context) == 0) { print('No file context on this generation') return null } // Called it, but the file it was given never defines it if (suggestion ilike '%formatCurrency(%' and context !ilike '%formatCurrency%') { print('Called formatCurrency, not defined in the provided context') return false } print('No calls to undefined helpers') return true ``` 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 `null` is 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 names `formatCurrency`, so you know what to look for - **Result** is `True`, `False`, or `N/A` - **Target** points back to the generation so you can open that [trace](/docs/ai-observability/traces.md) and read the file the assistant was actually handed An example of these results is outlined in Fig. 1 below. | Timestamp | Target 1 | Result 2 | Reasoning 3 | | --- | --- | --- | --- | | 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 | Fig. 1 – The eval's runs tab: one row per generation it scored, with whatever the eval printed.Hover over the figure to learn about each element. ## 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. prompt v12 Generations flat at ~9,000/day Eval pass rate 94% → 71% Fig. 2 – Prompt v12 shipped on the 4th. Volume held, nothing errored, and the assistant started making things up. ## 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: Prompt for PostHog AI ```text Create a code-based (Hog) evaluation on my $ai_generation events for a code assistant. It should return null when the generation has no input context to check a call against, and fail when the output calls a function that appears nowhere in that context, printing the function name as the reason. Enable Allows N/A, sample at 100%, exclude generations where the environment property is dev or local, and show me the pass rate over the last 30 days broken down by $ai_model and by my prompt-version property if my generations have one. If my project has no real $ai_generation traffic yet, create the eval disabled anyway, skip the breakdowns, and tell me how to instrument AI observability using the wizard or manual install. ``` [Build this with PostHog AI](https://app.posthog.com/#panel=max:!Create%20a%20code-based%20(Hog)%20evaluation%20on%20my%20%24ai_generation%20events%20for%20a%20code%20assistant.%20It%20should%20return%20null%20when%20the%20generation%20has%20no%20input%20context%20to%20check%20a%20call%20against%2C%20and%20fail%20when%20the%20output%20calls%20a%20function%20that%20appears%20nowhere%20in%20that%20context%2C%20printing%20the%20function%20name%20as%20the%20reason.%20Enable%20Allows%20N%2FA%2C%20sample%20at%20100%25%2C%20exclude%20generations%20where%20the%20environment%20property%20is%20dev%20or%20local%2C%20and%20show%20me%20the%20pass%20rate%20over%20the%20last%2030%20days%20broken%20down%20by%20%24ai_model%20and%20by%20my%20prompt-version%20property%20if%20my%20generations%20have%20one.%20If%20my%20project%20has%20no%20real%20%24ai_generation%20traffic%20yet%2C%20create%20the%20eval%20disabled%20anyway%2C%20skip%20the%20breakdowns%2C%20and%20tell%20me%20how%20to%20instrument%20AI%20observability%20using%20the%20wizard%20or%20manual%20install.)It creates the eval disabled, so you can read it before it starts scoring anything. Needs AI Observability instrumented. ``` npx @posthog/wizard ai-observability ``` See also: [Evaluations](/docs/ai-evals.md) · [Hog](/docs/hog.md) · [Score your AI's answers with evals](/pocket-guides/ai-observability/quality-output.md) [‹ Score your AI's answers with evals](/pocket-guides/ai-observability/quality-output.md)[All guides](/pocket-guides.md)p. 3 of 6[Attribute AI cost to users and features ›](/pocket-guides/ai-observability/ai-cost.md) Catch a code assistant inventing functions[Build this with PostHog AI](https://app.posthog.com/#panel=max:!Create%20a%20code-based%20(Hog)%20evaluation%20on%20my%20%24ai_generation%20events%20for%20a%20code%20assistant.%20It%20should%20return%20null%20when%20the%20generation%20has%20no%20input%20context%20to%20check%20a%20call%20against%2C%20and%20fail%20when%20the%20output%20calls%20a%20function%20that%20appears%20nowhere%20in%20that%20context%2C%20printing%20the%20function%20name%20as%20the%20reason.%20Enable%20Allows%20N%2FA%2C%20sample%20at%20100%25%2C%20exclude%20generations%20where%20the%20environment%20property%20is%20dev%20or%20local%2C%20and%20show%20me%20the%20pass%20rate%20over%20the%20last%2030%20days%20broken%20down%20by%20%24ai_model%20and%20by%20my%20prompt-version%20property%20if%20my%20generations%20have%20one.%20If%20my%20project%20has%20no%20real%20%24ai_generation%20traffic%20yet%2C%20create%20the%20eval%20disabled%20anyway%2C%20skip%20the%20breakdowns%2C%20and%20tell%20me%20how%20to%20instrument%20AI%20observability%20using%20the%20wizard%20or%20manual%20install.)