Tech
12 min read

How I Run Minimal AI Evals Across 5 Products

How to build minimal 1-file AI evals for voice, images, decks, and text across production apps like AISEOTracker, GenPPT, and LinkDR.

How I Run Minimal AI Evals Across 5 Products banner showing eval.ts diagram with latency, validity, and cost metrics
TL;DR — Summary

How I run minimal 1-file AI evals across 5 production apps:

  • Skip heavy SaaS platforms: A single eval.ts file per feature co-located with eval.md is all you need.
  • Never trust model self-scoring: Models reward their own quantity and confidence, not quality.
  • "Passed" (✅) = schema valid: Passing a Zod schema doesn't mean the content is accurate or good.
  • Install the skill: Run npx skills add Illyism/skills --skill eval to drop this workflow into Cursor.

Most AI developers treat evaluations like an enterprise chore.

They think you need a $500/month eval SaaS, a dedicated vector database, or complex auto-scoring dashboards before you can compare prompts or test a new model.

So they skip evals entirely. They tweak a prompt in Cursor, test it manually once, push to production, and pray it doesn't hallucinate or break edge cases.

I run AI features across five products: AISEOTracker, LinkDR, GenPPT, headshot-generator.ai, and MagicBuddy. We ship AI features for voice TTS, AI image generation, presentation decks, long-form markdown, and structured JSON.

Every single feature runs on a minimal, 1-file eval system. No databases. No external SaaS. Just direct production imports, real model runs, and a co-located eval.md file that diffs cleanly in git.

Here is how the system works, what we learned from running hundreds of benchmarks, and the exact code pattern you can drop into your codebase today.


What Are AI Evals?

An AI evaluation (eval) is an automated benchmark system that tests your AI application's prompts, models, and pipelines against a fixed set of test inputs, recording how well the system performs on latency, token costs, schema validity, and output quality.

Think of an eval as a unit test for non-deterministic AI outputs.

In traditional software development, a unit test checks if add(2, 2) returns 4. In AI development, your model might output 4 today, a 500-word paragraph explaining arithmetic tomorrow, or a malformed JSON payload on Friday.

An AI eval executes real inputs through your production code, captures the raw output, measures machine metrics (wall-clock latency, token usage, API cost), and gives you a judgment surface to evaluate accuracy before pushing changes to production.


Why Are AI Evals Important?

If you ship AI features without evals, every prompt update, model upgrade, or provider swap is a gamble.

Here is why AI evals are critical for any product using LLMs or generative models:

  1. Catch Silent Model Regressions: Model providers update model checkpoints, system defaults, or API behavior behind the scenes. An eval alerts you the second a new model version breaks your tool calling or degrades JSON formatting.
  2. Stop Prompt Drift: As prompts grow complex, tweaking a system message to fix one edge case often breaks three others. Running a repeatable eval suite ensures your prompt edits don't introduce regressions.
  3. Unlock Massive Profit Margins: Models vary wildly in price and speed. By benchmarking candidate models side-by-side on real production inputs, you can safely switch from an expensive flagship model to a lightweight alternative (like swapping gemini-3-pro for gemini-3.1-flash-lite), cutting API costs by 75%+ without sacrificing quality.
  4. Prevent Silent Schema Failures: High reasoning models can still fail structured JSON schemas or tool calling loops. An eval catches malformed outputs before your users see broken UI states.

The Self-Score Fallacy (Why Auto-Scoring Lies)

When we first built our multi-model benchmarks for AISEOTracker, we let the models evaluate their own outputs.

For a competitor suggestion feature testing Framer alternatives, we had gpt-5.6-luna, google/gemini-3.6-flash, and google/gemini-2.5-flash-lite generate direct competitors. The summary table auto-calculated a relevance score based on how many candidates each model marked above an 80/100 threshold.

gpt-5.6-luna popped out a score of 7/10, while gemini-3.6-flash got 4/10.

On paper, gpt-5.6-luna won.

Then we looked at the raw verbatim outputs side-by-side.

gemini-3.6-flash had returned four precise, undeniable direct competitors: Webflow, Wix, Squarespace, and Elementor.

gpt-5.6-luna had returned those same four, plus three noisy, broad additions: bare "WordPress" (redundant with Elementor), "Builder.io" (a headless visual CMS for existing codebases, not a Framer replacement), and "Bubble" (a full-stack app builder, wrong category entirely).

Because gpt-5.6-luna confidently assigned high internal scores to its own weak suggestions, the auto-eval rewarded it for producing more items.

Self-scoring rewards quantity and confidence, not correctness. A model that emits 7 loosely-relevant items looks like it beats a model that emits 4 precise ones, when the opposite is true.

Diagram showing how auto-scoring rewards bad models for quantity and confidence over precision and correctness.

The Rule

  1. Summary tables are for machine metrics only: Latency (ms), input/output tokens, total cost ($), and execution status (schema pass/fail).
  2. Quality requires raw outputs: Group candidate outputs verbatim under each test case so a human or a strong independent judge model (gpt-5.6-sol or claude-opus) can evaluate precision, directness, and ranking quality side-by-side.

Image Generation Evals (headshot-generator.ai)

Evaluating text is one thing, but how do you eval AI image generation?

On headshot-generator.ai, users upload photos to generate style clones (e.g., subway film portraits, studio headshots, or cinematic editorial shots). A bad model run either loses the scene grammar (missing tiles or lighting) or copies the reference subject's face instead of generating a new person.

Our clone-eval.ts runs style analysis, calls the image generation endpoint, and passes the resulting image path to an independent visual judge model.

We ran a benchmark comparing our default model (google/gemini-3-pro-image-preview) against a faster candidate (google/gemini-3.1-flash-lite-image) on our fuji-street subway portrait test case.

Here is what the ground-truth benchmark recorded:

ModelLatencyPass/FailCost per ImageUnit Margin ($0.17 credit)
gemini-3-pro-image-preview~30,000ms✅ PASS (7/10)$0.13420%
gemini-3.1-flash-lite-image~12,000ms✅ PASS (6/10)$0.03480%

Flash Lite Image captured the hard scene grammar (white subway tile, orange stripe, red/white curb), scrubbed the subject's identity cleanly, and ran 2.5x faster at 1/4th the cost.

That single benchmark unlocked an 80% profit margin on standard credit packs. Without an eval, you either burn margin on overkill models or ship an untested cheaper model that ruins style transfer.

Workflow diagram showing an AI image evaluation pipeline comparing generation models for metrics and style transfer quality.


Tool Calling & Multi-Step Deck Generation (genppt.com)

On GenPPT, users turn articles, documents, or prompts into 18-slide presentation decks. This isn't a single text prompt; the model must execute structured tool calls (addSlides, setTheme, insertImages) across multiple turns.

When evaluating new candidate models against our baseline (gpt-5.4-nano), we ran our 18-slide lecture guide benchmark across five models:

ModelPass ScoreSlides CreatedDurationTotal CostStatus
gpt-5.4-nano100%1894.3s$0.0206✅ Complete
gpt-5.6-luna92%1872.1s$0.1127✅ Complete
gpt-5.6-terra91%1852.3s$0.1867✅ Complete
gpt-5.6-sol90%18115.3s$0.5047✅ Complete
gemini-3.6-flash21%055.3s$0.2126❌ Failed Tool Calls

Notice what happened to gemini-3.6-flash: it repeatedly attempted addSlides with malformed tool arguments, looping 13 times and generating 0 slides while racking up $0.21 in token costs.

Detailed loop illustration showing how an AI model gets stuck in schema failure loops during multi-step tool calls.

General model leaderboards showed gemini-3.6-flash as a top-tier fast reasoning model. But on our specific tool-calling schema, it choked.

An eval prevents you from shipping a hyped model update that breaks your core product workflow.


Voice TTS & Speech Tags (magicbuddy.ai)

On MagicBuddy, our Telegram AI bot sends realistic voice notes using TTS providers like Grok Voice TTS and Gemini.

Voice evals (voice.eval.ts) test candidate voices (Sulafat, Eve), language settings (English, French, Spanish), and inline emotion tags:

const TEST_CASES = [
  {
    id: 'whisper-french',
    voice: 'Sulafat',
    input: '[whispers] Mon chéri... come closer. I have a secret.',
    instructions: 'Soft Parisian French accent. Slow, intimate, whispered delivery.',
  },
]

The eval outputs .ogg and .mp3 audio artifacts to voice-samples/ alongside an eval.md logging audio duration, payload size, and latency.

Instead of manually prompting a Telegram bot on your phone to test voice changes, you run bun voice.eval.ts in your terminal and listen to the exported audio files directly.


Hard Lessons Learned Running Evals in Production

After benchmarking dozens of models across text, image, voice, and JSON outputs, here are the non-obvious traps to avoid:

A 2x2 grid infographic detailing key traps in running AI evaluations, including hidden reasoning tokens and verbosity distortions.

1. "Passed" (✅) Only Means Schema Valid

A ✅ Passed status in an eval script means one thing: the API call succeeded and the JSON matched your Zod schema. It does not mean the content is smart, accurate, or high quality. Always separate execution validity from quality.

2. Control Output Constraints (Verbosity Distorts Benchmarks)

If Model A generates 60 tokens and Model B generates 1,400 tokens, Model B will look 20x slower and pricier. But you aren't testing model speed; you're testing unconstrained verbosity. Force identical top_k, character limits, or array lengths across candidate models during benchmarks so comparisons are fair.

3. Measure Real Billed Costs (Watch for Reasoning Tokens)

Modern reasoning models (like Gemini Flash thinking or OpenAI o-series) consume hidden reasoning tokens before outputting text. Calculating cost solely from visible completion word length will undercount API spend. Always extract total billed cost from response step metadata.

4. Absolute Dollars Beat Scary Percentages

Saying a candidate model is "94x more expensive" sounds terrifying. But if running a full database backfill costs $0.22 with Model A vs $20.70 with Model B, the $20 difference might be trivial compared to the product quality or speed gain. Evaluate absolute dollar impact against your margins, not just scary relative multipliers.


Core Rules of Our 1-File Eval System

To keep evals zero-friction across all our repos, we enforce these core rules:

Visual schematic depicting the core design principles of a lightweight one-file AI evaluation architecture.

Rule 1: Import Production Code Directly

ALWAYS import the actual production function, system prompt constants, and Zod schemas from your feature file:

// GOOD: direct import prevents prompt drift
import { generateKeywords, KEYWORD_MODEL } from './generate-keywords'

NEVER copy-paste prompts into eval.ts. If you duplicate prompts in your eval, production logic evolves while your benchmark tests obsolete behavior.

Rule 2: Benchmark Real Runs (Never Estimate)

Execute every candidate model with real API calls to record true wall-clock latency, token usage, and cost. Models cannot predict their own streaming throughput or latency.

Rule 3: Single eval.ts with CLI Flags

Keep one script per feature folder that runs the production model by default and expands to candidates via flags:

  • bun eval.ts — runs production default model only (fast iteration)
  • bun eval.ts --matrix — runs all candidate comparison models
  • bun eval.ts --models=gemini-3.6-flash,gpt-5.6-luna — runs a specific subset

Rule 4: Co-located Markdown Output (eval.md)

Write all results to path.join(__dirname, 'eval.md').

eval.md files diff cleanly in git pull requests. You can inspect exact cost, speed, and output quality changes across commits without opening an external dashboard.


The Code Pattern (eval.ts)

Here is the exact production-ready TypeScript template we use across our packages:

import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import { calculateModelCost } from '../client'
import { FEATURE_MODEL, runProductionFeature } from './generate-feature'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const TEST_CASES = [
  { name: 'Linear', input: { domain: 'linear.app' } },
  { name: 'PostHog', input: { domain: 'posthog.com' } },
]

const CANDIDATE_MODELS = [
  FEATURE_MODEL,
  'google/gemini-3.6-flash',
  'openai/gpt-5.6-luna',
]

const isMatrix = process.argv.includes('--matrix')
const requested = process.argv
  .find(a => a.startsWith('--models='))
  ?.split('=')[1]
  ?.split(',')
  .map(m => m.trim().toLowerCase())

const TEST_MODELS = requested
  ? CANDIDATE_MODELS.filter(m => requested.some(r => m.toLowerCase().includes(r)))
  : isMatrix
    ? CANDIDATE_MODELS
    : [FEATURE_MODEL]

interface ResultRow {
  model: string
  caseName: string
  timeMs: number
  cost: number
  inputTokens?: number
  outputTokens?: number
  output?: string
  error?: string
}

async function runEval() {
  console.log(`Running evaluation across ${TEST_MODELS.length} model(s)...\n`)
  const results: ResultRow[] = []

  for (const model of TEST_MODELS) {
    for (const testCase of TEST_CASES) {
      const start = Date.now()
      try {
        const result = await runProductionFeature(testCase.input, { model })
        const timeMs = Date.now() - start
        results.push({
          model,
          caseName: testCase.name,
          timeMs,
          cost: calculateModelCost(model, result.usage),
          inputTokens: result.usage?.inputTokens,
          outputTokens: result.usage?.outputTokens,
          output: JSON.stringify(result.data, null, 2),
        })
        console.log(`✅ ${model} — ${testCase.name} (${timeMs}ms)`)
      } catch (err) {
        results.push({
          model,
          caseName: testCase.name,
          timeMs: Date.now() - start,
          cost: 0,
          error: err instanceof Error ? err.message : String(err),
        })
        console.log(`❌ ${model} — ${testCase.name}: ${err}`)
      }
    }
  }

  const md: string[] = []
  md.push('# Evaluation Results\n')
  md.push('## Objective Summary\n')
  md.push('| Model | Test Case | Latency (ms) | Tokens (In/Out) | Cost ($) | Status |')
  md.push('| :--- | :--- | ---: | ---: | ---: | :--- |')

  results.forEach(r => {
    const tokens = r.inputTokens !== undefined ? `${r.inputTokens} / ${r.outputTokens}` : 'N/A'
    md.push(`| \`${r.model}\` | ${r.caseName} | ${r.timeMs} | ${tokens} | $${r.cost.toFixed(5)} | ${r.error ? '❌' : '✅'} |`)
  })

  md.push('\n## Verbatim Outputs\n')
  for (const testCase of TEST_CASES) {
    md.push(`### ${testCase.name}\n`)
    results
      .filter(r => r.caseName === testCase.name)
      .forEach(r => {
        md.push(`#### \`${r.model}\` — ${r.timeMs}ms, $${r.cost.toFixed(5)}`)
        md.push(r.error ? `\n**Error**: ${r.error}\n` : '\n```json\n' + r.output + '\n```\n')
      })
  }

  const outputPath = path.join(__dirname, 'eval.md')
  await fs.writeFile(outputPath, md.join('\n'), 'utf-8')
  console.log(`\n✅ Results written to ${outputPath}`)
}

runEval().catch(console.error)

Install the Skill

I packaged this exact eval system into an open-source Cursor skill so you can generate or run evals automatically inside your project.

Agent Prompt
codev1.0.0
eval

Build, run, or update minimal evaluation benchmarks for AI prompts, LLM calls, multi-model comparisons, and core functions. Use when creating new eval scripts, comparing models side-by-side, filtering specific candidate models, running benchmarks, or inspecting eval.md output artifacts.

Use this skill in Cursor, Claude Code, or any LLM
Full Prompt

Copy directly or install via CLI command.

Raw SKILL.md
# Eval System

A clean, minimal benchmark system that runs production code against test cases and writes results to a co-located markdown artifact (`eval.md`). The `eval.md` is a **judgment surface**: it exists so a strong external model or a human can read the real outputs and judge quality, not so the eval can auto-declare a winner.

---

## Core Principles & Rules

### 1. Import Production Logic Directly
- **ALWAYS** import and invoke the actual production function, prompt constants, and schemas from the feature file.
- **NEVER** duplicate prompts, system messages, or JSON schemas inside `eval.ts`.
- *Why:* Re-implementing prompts in the eval causes prompt drift. When production changes, a duplicated eval becomes a false benchmark testing obsolete behavior.

### 2. Benchmark Real Runs — Never Estimate
- **ALWAYS** execute every candidate model to capture ground-truth latency, tokens, and cost.
- **NEVER** estimate or hallucinate latency, token counts, or pricing in a report without running the models.
- *Why:* Models cannot predict real streaming latency, throughput, or cost. Only direct execution records the truth.

### 3. "Passed" (✅) Means Schema Valid — NOT High Quality
- **ALWAYS** treat the status column as execution correctness (did the call succeed and conform to the Zod schema?).
- **NEVER** assume a "Passed" status implies the output is accurate, relevant, or good.
- *Why:* A model can return 100% valid JSON conforming to a schema while outputting completely wrong or noisy content.

### 4. Never Self-Score Quality — Surface Raw Outputs for Independent Judgment
- **NEVER** put the generating model's own relevance/quality/confidence score — or a count derived from it (e.g. "candidates scored 80+") — in the summary as if it were a quality metric.
- **ALWAYS** render each model's full raw output verbatim so quality can be judged from the output itself, not from the model's self-assessment.
- *Why:* A model scoring its own output rewards quantity and confidence, not correctness. A model that emits 7 loosely-relevant items looks like it "beats" one that emits 4 precise ones, when the opposite is true. Self-scores are biased and not comparable across models. Real quality only emerges when a strong external judge (or human) reads the actual outputs side-by-side.

### 5. Summary Table = Objective Machine Metrics Only
- **ALWAYS** limit the summary table to metrics the machine measures directly: latency, input/output tokens, cost, and pass/fail (schema/execution status).
- **ALWAYS** caption any raw output count (e.g. "Items") as a count, never as "Relevant", "Quality", or "Score".
- *Why:* Objective metrics are trustworthy and comparable; quality labels sneak the model's biased self-judgment back into the leaderboard.

### 6. Control Output Constraints for Fair Comparison
- **ALWAYS** enforce identical output constraints (e.g. `top_k`, length limits, schema bounds) across candidate models when benchmarking.
- *Why:* Unconstrained models that generate 1,500 tokens of verbose fluff will appear unfairly slow and expensive compared to concise models emitting 100 tokens, distorting latency and cost comparisons.

### 7. Track Real Billed Costs (Including Reasoning Tokens)
- **ALWAYS** calculate cost using total billed tokens from API response metadata, including hidden reasoning/thinking completion tokens.
- *Why:* Modern reasoning models (e.g., Gemini Flash thinking or OpenAI o-series) consume reasoning tokens that don't show up in visible text length but directly impact API billing.

### 8. Support Model Selection & Filtering
- **ALWAYS** support running the production model only by default, all candidates via `--matrix`, or a subset via `--models=a,b`.
- *Why:* Running the full suite on every iteration wastes time and API credits when a developer only needs 2–3 target models.

### 9. Output to Co-located Markdown (`eval.md`)
- **ALWAYS** compile output into a string buffer and write to `path.join(__dirname, 'eval.md')`.
- **NEVER** output results exclusively to stdout or an external database.
- *Why:* Co-located `eval.md` files diff cleanly in git, render in the editor, and persist benchmark history with zero infrastructure.

### 10. Direct Terminal Progress
- **ALWAYS** log progress lines during the run, prefix success with `✅` and failure with `❌`, and log the final `eval.md` path.
- *Why:* CLI feedback tells the developer whether the run worked without tailing files mid-execution.

---

## Decision Flowchart: Eval Workflow

```
Are you creating or running an evaluation?
├── Running an existing eval?
│   ├── Production model only: `bun path/to/eval.ts`
│   ├── All candidates:        `bun path/to/eval.ts --matrix`
│   └── Specific models only:  `bun path/to/eval.ts --models=model-a,model-b`
└── Creating a new eval?
    ├── Does an eval file exist in the target module folder?
    │   ├── Yes → Edit `TEST_CASES` / `CANDIDATE_MODELS` in existing `eval.ts`
    │   └── No  → Identify evaluation target type:
    │       ├── AI / LLM feature (single or multi-model) → Pattern A
    │       ├── Automated quality scoring needed         → Pattern B (independent judge)
    │       └── Sync / async utility function            → Pattern C
```

**One file per feature.** Keep a single `eval.ts` that runs the production model by default and expands to a matrix via flags — do not split into `eval.ts` + `eval.matrix.ts`. One script and one `eval.md` keeps test cases in a single source of truth and stops agents guessing which file to run.

---

## Implementation Patterns

### Pattern A: AI Feature Evaluation (`eval.ts`)

Default runs the production model; `--matrix` runs all candidates; `--models=` runs a subset. The summary table stays objective; raw outputs carry the quality signal.

```typescript
import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import { calculateModelCost } from '../client'
import { FEATURE_MODEL, runProductionFeature } from './generate-feature'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const TEST_CASES = [
  { name: 'TestCase1', input: { /* production function params */ } },
]

// Production default first, then comparison candidates
const CANDIDATE_MODELS = [
  FEATURE_MODEL,
  'google/gemini-3.6-flash',
  'openai/gpt-5.6-luna',
]

// Flags: --matrix (all candidates) | --models=a,b (subset) | none (production only)
const isMatrix = process.argv.includes('--matrix')
const requested = process.argv.find(a => a.startsWith('--models='))
  ?.split('=')[1]?.split(',').map(m => m.trim().toLowerCase())

const TEST_MODELS = requested
  ? CANDIDATE_MODELS.filter(m => requested.some(r => m.toLowerCase().includes(r)))
  : isMatrix
    ? CANDIDATE_MODELS
    : [FEATURE_MODEL]

interface ResultRow {
  model: string
  caseName: string
  timeMs: number
  cost: number
  inputTokens?: number
  outputTokens?: number
  itemCount?: number
  error?: string
  output?: string
}

async function runEval() {
  console.log(`Running evaluation across ${TEST_MODELS.length} model(s)...\n`)
  const results: ResultRow[] = []

  for (const model of TEST_MODELS) {
    for (const testCase of TEST_CASES) {
      const start = Date.now()
      try {
        const result = await runProductionFeature(testCase.input, { model })
        const timeMs = Date.now() - start
        results.push({
          model,
          caseName: testCase.name,
          timeMs,
          cost: calculateModelCost(model, result.usage),
          inputTokens: result.usage?.inputTokens,
          outputTokens: result.usage?.outputTokens,
          itemCount: Array.isArray(result.data) ? result.data.length : undefined,
          output: JSON.stringify(result.data, null, 2),
        })
        console.log(`✅ ${model} — ${testCase.name} (${timeMs}ms)`)
      } catch (err) {
        results.push({
          model,
          caseName: testCase.name,
          timeMs: Date.now() - start,
          cost: 0,
          error: err instanceof Error ? err.message : String(err),
        })
        console.log(`❌ ${model} — ${testCase.name}: ${err}`)
      }
    }
  }

  const md: string[] = []
  md.push('# Multi-Model Comparison Evaluation\n')

  // Summary: objective machine metrics ONLY. "Items" is a raw count, not a quality score.
  md.push('## Summary (objective metrics — judge quality from the raw outputs below)\n')
  md.push('| Model | Test Case | Latency (ms) | Tokens (In/Out) | Cost ($) | Items | Status |')
  md.push('| :--- | :--- | ---: | ---: | ---: | ---: | :--- |')
  results.forEach(r => {
    const tokens = r.inputTokens !== undefined ? `${r.inputTokens} / ${r.outputTokens}` : 'N/A'
    const items = r.itemCount !== undefined ? String(r.itemCount) : '—'
    md.push(`| \`${r.model}\` | ${r.caseName} | ${r.timeMs} | ${tokens} | $${r.cost.toFixed(5)} | ${items} | ${r.error ? '❌' : '✅'} |`)
  })

  // Raw outputs grouped by test case so models sit side-by-side for independent judgment.
  md.push('\n## Raw Outputs\n')
  for (const testCase of TEST_CASES) {
    md.push(`## ${testCase.name}\n`)
    results.filter(r => r.caseName === testCase.name).forEach(r => {
      md.push(`### \`${r.model}\` — ${r.timeMs}ms, $${r.cost.toFixed(5)}`)
      md.push(r.error ? `\n**Error**: ${r.error}\n` : '\n```json\n' + r.output + '\n```\n')
    })
    md.push('---\n')
  }

  const outputPath = path.join(__dirname, 'eval.md')
  await fs.writeFile(outputPath, md.join('\n'), 'utf-8')
  console.log(`\n✅ Results written to ${outputPath}`)
}

runEval().catch(console.error)
```

### Pattern B: Independent Judge (optional, for automated quality scoring)

Only when you need a repeatable quality number. Run a SEPARATE strong judge model that reads all candidate outputs against a rubric. The judge must never be the model that generated the output.

```typescript
import { generateObject } from 'ai'
import { z } from 'zod'
import { getModel } from '../client'

const JUDGE_MODEL = 'openai/gpt-5.6-sol' // strong model, distinct from candidates

// `outputs` = the raw candidate outputs collected in Pattern A
async function judge(testCasePrompt: string, outputs: { model: string; output: string }[]) {
  const { object } = await generateObject({
    model: getModel(JUDGE_MODEL),
    schema: z.object({
      rankings: z.array(z.object({
        model: z.string(),
        score: z.number().min(0).max(10),
        precision: z.string(),  // are the items directly relevant, not just plausible?
        rationale: z.string(),
      })),
    }),
    // Include the rubric: reward precision, directness, ranking quality; penalize
    // broad/redundant/wrong items. Do NOT reward emitting more candidates.
    prompt: `Task prompt:\n${testCasePrompt}\n\nCandidate outputs:\n${JSON.stringify(outputs, null, 2)}`,
  })
  return object.rankings
}
```

### Pattern C: Pure Function / Utility Evaluation (`eval.ts`)

For algorithms, parsers, formatters, throughput, or accuracy assertions.

```typescript
import fs from 'fs'
import path from 'path'
import { productionUtility } from './index'

const TEST_INPUT = 'example-input'
const ITERATIONS = 100_000

function runEval() {
  console.log('Running utility evaluation...\n')
  let md = `# Utility Evaluation\n\n`

  const actual = productionUtility(TEST_INPUT)
  const expected = 'expected-output'
  if (actual !== expected) {
    console.error(`❌ Assertion failed: expected '${expected}', got '${actual}'`)
    process.exit(1)
  }
  console.log('✅ Correctness assertions passed')

  const start = performance.now()
  for (let i = 0; i < ITERATIONS; i++) productionUtility(TEST_INPUT)
  const totalMs = performance.now() - start
  const perCallMs = totalMs / ITERATIONS

  md += `Total time: ${totalMs.toFixed(2)}ms\n`
  md += `Time per call: ${perCallMs.toFixed(6)}ms\n`
  md += `Calls per second: ${(1000 / perCallMs).toFixed(0)}\n`
  md += `Sample Output: '${actual}'\n`

  const outputPath = path.join(__dirname, 'eval.md')
  fs.writeFileSync(outputPath, md, 'utf-8')
  console.log(`\n✅ Results written to ${outputPath}`)
}

runEval()
```

---

## Execution Checklist

1. Production model only: `bun <path-to-module>/eval.ts`.
2. Full matrix: `bun <path-to-module>/eval.ts --matrix`.
3. Specific candidates: `bun <path-to-module>/eval.ts --models=gemini-3.6-flash,gpt-5.6-luna`.
4. Read the **raw outputs** in `eval.md` (or run Pattern B's judge) to assess quality — never rank models by their own self-reported scores.
5. Inspect `git diff <path-to-module>/eval.md` to review latency, cost, and output changes before committing.

Once installed, you can prompt Cursor: "Create an eval for this feature" or "Run a multi-model benchmark on generate-title.ts" and it will build and execute this pattern automatically.

You don't need expensive infrastructure to ship reliable AI products. You just need direct production imports, real execution metrics, and a clean markdown file you can read with your own eyes.

Ilias Ism
Written byIlias Ism

Exited founder (Officient). Now building MagicSpace SEO, LinkDR, AI SEO Tracker, and GenPPT.

Join 10,000+ founders

Get weekly insights on SEO, AI tools, and growth strategies.

No spam. Unsubscribe anytime.

Related Posts