#AI#LLM#Gemini#Evaluation#Architecture

Gemini 4 Hydrogen: Don't Swap Your Fast-Tier Model Without a Regression Harness

webhani·

Google announced Gemini 4 Hydrogen on November 12 and is rolling it out in the Gemini app, Search, and for developers. Press coverage positions it as the speed-focused model for everyday use, and some reports say it beats the larger Gemini 4 Argon on Google's overall metrics. Model names and dates vary between secondary sources, so check Google's own announcement for the exact lineup and pricing before you plan anything.

This post is not a feature tour. Whatever the benchmark charts say, the practical question for a team running LLM features in production is the same: can I move my high-volume, latency-sensitive calls to the new model without making anything worse? The only reliable way to answer that is a regression harness built from your own traffic.

Why vendor benchmarks don't answer your question

A fast-tier model is usually the one behind your highest-volume features: classification, extraction, summarization of short inputs, routing, autocomplete. These are exactly the workloads where small behavioural changes matter most, because downstream code often depends on the output shape.

Typical failure modes after a model swap:

  • JSON that was always valid now has an occasional trailing comment or an extra key.
  • A classifier's label distribution drifts by a few percentage points, which shifts a business metric.
  • Responses get shorter or longer, which changes token cost and UI layout.
  • Latency improves at the median but the tail gets worse under load.

None of these show up in a public benchmark. All of them show up in a harness that replays real inputs.

Step 1: Build a replay set from production

Sample real requests, strip personal data, and store the input plus the current model's output. You do not need thousands. A few hundred well-chosen cases per feature is enough to catch most regressions.

Pick the sample deliberately:

  • Random cases for the common path.
  • Cases where users edited or rejected the output (these are your hard cases).
  • Known edge cases: empty input, very long input, mixed-language input (important for Japanese/Korean/English products).
// eval/cases.ts
export type EvalCase = {
  id: string;
  feature: "ticket-triage" | "summary" | "extract-invoice";
  input: string;
  baseline: string; // output of the model currently in production
  tags: string[];
};

Step 2: Put the model behind a narrow interface

If model IDs are scattered through the codebase, you cannot compare models cheaply. Keep one small interface and let configuration choose the implementation.

// llm/client.ts
export interface Completion {
  text: string;
  inputTokens: number;
  outputTokens: number;
  latencyMs: number;
}
 
export interface LlmClient {
  complete(args: {
    system: string;
    user: string;
    maxOutputTokens: number;
  }): Promise<Completion>;
}
 
export function createClient(modelId: string): LlmClient {
  // Wire up your provider SDK or gateway here.
  // The model ID comes from config, never from feature code.
  throw new Error(`not implemented for ${modelId}`);
}

This is the Dependency Inversion principle applied to the thing that changes most often in an AI product. If you already route through a gateway, the same idea applies: the model name is data, not code.

Step 3: Score with checks, not vibes

Write assertions that are cheap and deterministic first, and only then add an LLM judge for the rest.

// eval/score.ts
import { z } from "zod";
 
const Triage = z.object({
  category: z.enum(["billing", "bug", "feature", "other"]),
  priority: z.number().int().min(1).max(4),
});
 
export function scoreTriage(output: string, baseline: string) {
  let parsed: z.infer<typeof Triage>;
  try {
    parsed = Triage.parse(JSON.parse(output));
  } catch {
    return { valid: false, sameCategory: false };
  }
  const base = Triage.parse(JSON.parse(baseline));
  return {
    valid: true,
    sameCategory: parsed.category === base.category,
  };
}

Metrics worth recording for every run:

MetricWhy it matters
Schema validity rateA drop here breaks downstream code immediately
Agreement with baselineMeasures drift, not correctness; review disagreements by hand
p50 / p95 latencyThe tail is what users feel
Output tokens per callDirect driver of cost
Refusal or empty-output rateSafety tuning differs between versions

Agreement with the baseline is not accuracy. When the new model disagrees, it may be wrong or it may be right where the old one was wrong. Read a sample of disagreements. That review is the most valuable ten minutes in the whole process.

Step 4: Roll out behind a flag with a shadow phase

Even a clean harness run misses live-traffic effects. A low-risk sequence:

  1. Shadow: send the same request to both models, return the old model's answer, log both.
  2. Canary: serve the new model to a small percentage of traffic, keyed by a stable hash of the user or tenant so behaviour is consistent per user.
  3. Ramp: increase gradually while watching validity rate, latency, and the business metric that the feature feeds.
  4. Keep the rollback: the old model ID stays in config until you have a full week of clean data.
function pickModel(userId: string, rolloutPercent: number) {
  const bucket = hash(userId) % 100;
  return bucket < rolloutPercent ? "candidate" : "stable";
}

Where we land

A faster model is genuinely useful. Lower latency improves perceived quality, and cheaper tokens can make features viable that were not before. But a model identifier is a dependency like any other, and upgrades to dependencies need tests.

For clients, our recommendation is:

  • Keep model IDs in configuration, behind one interface.
  • Maintain a replay set per feature and run it on every model change, including "minor" ones.
  • Treat structured-output validity as a hard gate, and agreement/latency/cost as things to review.
  • Do the shadow and canary phases even when the harness looks clean.

The harness takes about a day to build and pays for itself the first time a vendor ships a model that is better on average and worse on your one critical path.