Idempotent normalization pipelines

When code must normalize or classify structured data (messages, request bodies, model/provider selection, metrics), implement it as deterministic, *idempotent*, and *non-mutating* transformations.

copy reviewer prompt

Prompt

Reviewer Prompt

When code must normalize or classify structured data (messages, request bodies, model/provider selection, metrics), implement it as deterministic, idempotent, and non-mutating transformations.

Apply these rules:

  • Normalize shapes with explicit merge/update rules: if an external API rejects a structure (e.g., consecutive same-role entries), implement a function that enforces the required invariant (e.g., merge adjacent same-role items).
  • Keep transformations idempotent: prepends/appends should be guarded by an idempotency key or prefix/text detection so re-running the pipeline doesn’t duplicate content.
  • Avoid unintended mutation: if you transform an array/object derived from caller input, copy the elements/arrays you modify.
  • Use precise matching for classification: prefer boundary-aware regex or generalized capability checks over naive includes() where false positives are possible.
  • Ensure aggregation update rules are correct: when computing totals, accumulate instead of overwriting per-group values.

Example (non-mutating consecutive-role merge + idempotent ops sketch):

type Content = { role: string; parts: Array<{ text: string }> };

function mergeConsecutiveSameRole(contents: Content[]): Content[] {
  const merged: Content[] = [];
  for (const entry of contents) {
    const last = merged[merged.length - 1];
    if (last && last.role === entry.role) {
      // mutate only the new output copy
      last.parts.push(...entry.parts);
    } else {
      // prevent caller mutation
      merged.push({ ...entry, parts: [...entry.parts] });
    }
  }
  return merged;
}

type Op = { kind: 'prepend_system_block'; text: string; idempotencyKey?: string };
function applyOp(blocks: Array<{ type: 'text'; text: string }>, op: Op) {
  const alreadyThere = blocks.some((b) => b.text === op.text || b.text.startsWith(op.text));
  return alreadyThere ? blocks : [{ type: 'text', text: op.text }, ...blocks];
}

Practical checklist:

  • Add regression tests for (a) repeated execution, (b) edge input shapes (empty/missing fields), and (c) realistic provider/model strings to prevent matching errors.
  • For metrics/analytics, add tests that verify accumulation (sum += value) rather than overwriting. If you follow these, you’ll prevent the common failure modes seen across the discussions: invalid request shapes, duplicated system blocks, false-positive classification, and incorrect aggregation totals.

Source discussions