Lightweight TypeScript evals for grounded AI workflows. No SaaS. No hosted dashboard. No mandatory tracing backend. Just small, local primitives for citation verification, deterministic scoring, report aggregation, and optional AI SDK-backed draft review.
@mundabra/ai-evals is currently in beta.
It is usable today for local evals and internal quality gates, but the first releases are still evolving in three areas:
- public API ergonomics
- custom-dimension and schema flexibility
- packaging patterns for mixed deterministic and model-backed evals
Most teams know they need evals, but many do not want a full hosted eval platform just to answer a few recurring product questions:
- did the model keep the required facts?
- did it invent a claim we explicitly forbid?
- are the citations actually grounded in available sources?
- did the draft need approval or human review?
- when a reviewer says “revise,” what exactly was weak?
Those questions come up constantly in real product workflows:
- a grounded answer includes a citation that was never in the prompt or tool outputs
- a customer-facing draft quietly drops one critical fact from the source set
- an internal summary states a launch is approved when approval is still pending
- a generated draft sounds plausible, but a reviewer can point to accuracy or actionability gaps
This package is meant to be the small application-side eval layer between your product and the model:
- verify citations against a known source registry
- score deterministic expectations like fact coverage and forbidden claims
- aggregate compact quality signals for CI or reporting
- run structured “review this draft” passes with the AI SDK when deterministic checks are not enough
- serialize reproducible run artifacts and export local JSON or CSV summaries
- run small eval suites programmatically without adopting a heavy benchmark platform
The goal is practical, inspectable eval building blocks for TypeScript apps, not a promise of truth or a substitute for human judgment.
- Source registry and citation verification for prompt docs, tool docs, and URLs
- Grounding metadata and summaries for compact downstream reporting
- Deterministic eval scoring for required facts, forbidden claims, output type checks, approval checks, and grounding checks
- Paraphrase-tolerant fact matching using exact-match and token-coverage rules
- Review score aggregation with default or custom dimensions
- Structured AI draft review behind the optional
./ai-sdkexport - Standard run artifacts for reproducible
results.json,report.json,run_config.json, anditems.jsonl - Local exporters for JSON bundles and flat CSV item views
- Programmatic suite runner with repetitions, concurrency, and aggregate metric stats
- Small OSS surface with dist-only publishing, ESM output, and no framework dependency in the root package
Core package:
npm install @mundabra/ai-evals
# or
pnpm add @mundabra/ai-evalsIf you want the AI SDK review helpers as well:
pnpm add @mundabra/ai-evals ai @ai-sdk/providerThen install whichever provider package you plan to use, for example @ai-sdk/openai or @ai-sdk/anthropic.
For coding agents and documentation crawlers:
LICENSE— repository licenseDISCLAIMER.md— usage and risk disclaimerAGENTS.md— shared repo instructions for coding agentsCLAUDE.md— minimal Claude entrypointCONTRIBUTING.md— contributor workflow and verification stepsSECURITY.md— disclosure path and security scopellms.txt— lightweight machine-readable indexllms-full.txt— single-file expanded contextdocs/agent-guide.md— architecture and invariantsdocs/eval-reference.md— concise API catalog
pnpm install
pnpm checkpnpm check runs tests, typecheck, build, and a pack smoke test that imports the built tarball.
This project is licensed under the MIT license. See LICENSE.
This package is provided as a practical evals layer, not as a complete assessment or governance system. Read DISCLAIMER.md and SECURITY.md before using it in production or high-risk workflows.
import { scoreDeterministicCase } from '@mundabra/ai-evals';
const result = scoreDeterministicCase({
evalCase: {
id: 'renewal-follow-up',
expected: {
outputType: 'follow_up_email',
shouldRequireApproval: true,
requireGrounding: true,
requiredFacts: [
{ label: 'security review is complete' },
{ label: 'pricing review happens next Tuesday' },
],
forbiddenClaims: [
{ label: 'contract is already signed' },
],
rubric: ['fact_faithfulness', 'tone_fit'],
},
},
outputType: 'follow_up_email',
requiredApproval: 'draft_review',
grounding: {
verificationScope: 'source',
status: 'verified',
candidateCount: 2,
attachedCount: 2,
removedCount: 0,
sourceCount: 2,
},
outputText:
'Security review is complete, and pricing review happens next Tuesday before we send the customer follow-up.',
});
console.log(result.deterministic.deterministicPass);
// trueimport {
SourceRegistry,
buildGroundingMetadata,
summarizeGrounding,
verifyCitations,
} from '@mundabra/ai-evals';
const registry = new SourceRegistry();
registry.addPromptDocuments([
{ id: 'doc_1', title: 'Account Brief', source: 'drive' },
]);
const candidateCitations = [
{ id: 'doc_1', label: 'drive: Account Brief', source: 'drive' },
];
const verification = verifyCitations(candidateCitations, registry);
const metadata = buildGroundingMetadata({
candidateCitations,
emittedCitations: verification.verifiedCitations,
registry,
removedCitations: verification.removedCitations,
});
const summary = summarizeGrounding(metadata);
console.log(summary?.status);
// "verified"import { reviewDraft } from '@mundabra/ai-evals/ai-sdk';
import { openai } from '@ai-sdk/openai';
const result = await reviewDraft(
{
userRequest: 'Draft a follow-up email after the pricing review call.',
retrievedFacts: 'Security review is complete. Pricing review happens next Tuesday.',
draftOutput: 'Hi team, following up after pricing review...',
reviewFocus: 'Customer-ready tone and grounded accuracy',
},
{
model: openai('gpt-4o-mini'),
},
);
if (result.status === 'completed') {
console.log(result.verdict);
console.log(result.scores.accuracy);
}import { runSuite, writeRunArtifacts } from '@mundabra/ai-evals';
const run = await runSuite({
suiteName: 'customer-draft-smoke',
cases: [
{
id: 'case-1',
expected: {
requiredFacts: [{ label: 'security review is complete' }],
},
},
],
evaluateCase: async ({ evalCase }) => {
const outputText = 'Security review is complete and the draft is ready for review.';
return {
deterministic: {
deterministicPass: true,
outputTypeCorrect: null,
approvalCorrect: null,
groundingPresent: null,
requiredFactsMatched: ['security review is complete'],
requiredFactsMissing: [],
requiredFactCoverage: 1,
forbiddenClaimsPresent: [],
},
metrics: {
tokens_used: 182,
},
metadata: {
promptCaseId: evalCase.id,
},
};
},
});
await writeRunArtifacts(run, {
outputDir: './eval-results/customer-draft-smoke',
csv: { metrics: ['metric.tokens_used'] },
});These are representative examples from the kind of product workflows this package was built for: grounded answers, internal summaries, approval-aware drafts, and reviewable outbound content.
Workflow: the assistant emits a citation that was never registered from prompt docs or tool outputs.
const registry = new SourceRegistry();
registry.addPromptDocuments([
{ id: 'doc_1', title: 'Account Brief', source: 'drive' },
]);
const verification = verifyCitations(
[{ id: 'doc_404', label: 'drive: Missing Brief', source: 'drive' }],
registry,
);Typical result:
verification.removedCitations[0].reason;
// "citation not found in source registry"Workflow: a draft includes one expected fact but silently drops another.
const result = scoreDeterministicCase({
evalCase: {
id: 'customer-follow-up',
expected: {
requiredFacts: [
{ label: 'security review is complete' },
{ label: 'pricing review happens next Tuesday' },
],
},
},
outputText: 'Security review is complete and we can proceed.',
});Typical result:
result.deterministic.requiredFactsMissing;
// ["pricing review happens next Tuesday"]Workflow: an internal summary states a launch is approved even though that claim is forbidden.
const result = scoreDeterministicCase({
evalCase: {
id: 'launch-summary',
expected: {
forbiddenClaims: [{ label: 'launch is fully approved' }],
},
},
outputText: 'The launch is fully approved and ready to send.',
});Typical result:
result.deterministic.forbiddenClaimsPresent;
// ["launch is fully approved"]Workflow: the draft looks plausible, but you want a quality-gate verdict and concrete issues.
Typical completed result:
{
status: 'completed',
verdict: 'revise_minor',
scores: {
accuracy: 4,
completeness: 4,
clarity: 5,
tone: 4,
actionability: 5,
},
}These are representative examples, not guarantees. Exact behavior depends on the expectations you encode and, for model-backed review, the model you choose.
const evalCase = {
id: 'draft-check',
expected: {
outputType: 'follow_up_email',
shouldRequireApproval: true,
requireGrounding: true,
requiredFacts: [
{ label: 'security review is complete' },
{
label: 'pricing review happens next Tuesday',
matchAny: ['pricing review happens next Tuesday', 'pricing review next Tuesday'],
},
],
forbiddenClaims: [
{ label: 'contract is already signed' },
],
rubric: ['fact_faithfulness', 'approval_correctness'],
},
} as const;Fields are all optional except id. If you omit a check, the scorer skips it instead of forcing a failure.
import { createDraftReviewer } from '@mundabra/ai-evals/ai-sdk';
import { anthropic } from '@ai-sdk/anthropic';
const reviewer = createDraftReviewer({
model: anthropic('claude-haiku-4.5'),
maxAttempts: 2,
dimensions: ['grounding', 'concision', 'tone'] as const,
});
const result = await reviewer.reviewDraft({
userRequest: 'Review this customer-facing draft',
draftOutput: 'Hi team...',
});Defaults:
maxAttempts:2maxOutputTokens:1400- default dimensions:
accuracy,completeness,clarity,tone,actionability - retry only on
NoOutputGeneratedError
Root package (deterministic core)
┌────────────────────────────────────┐
│ SourceRegistry │
│ verifyCitations │
│ buildGroundingMetadata │
│ summarizeGrounding │
│ scoreDeterministicCase │
│ buildGroundingDistribution │
│ buildReviewDistribution │
└────────────────────────────────────┘
Optional ./ai-sdk export
┌────────────────────────────────────┐
│ reviewDraft │
│ createDraftReviewer │
│ caller-supplied model │
│ structured output schema │
│ retry + failure shaping │
└────────────────────────────────────┘
The root export stays framework-agnostic. The AI SDK integration lives behind a subpath export so consumers who only need deterministic evals do not need to adopt a model provider.
- Register prompt docs, tool docs, or URLs in
SourceRegistry. - Verify emitted citations against the registry.
- Build
GroundingMetadata. - Reduce it to
GroundingSummarywhen compact reporting is enough.
scoreDeterministicCase uses two matching strategies:
- Exact normalized match for clear string containment checks
- Token coverage fallback for paraphrased required facts
For short candidates of three tokens or fewer, coverage must be exact. For longer candidates, the current threshold is >= 0.75.
reviewDraft uses AI SDK generateText with structured output:
- caller supplies the model
- the prompt includes the original request, facts, draft, and scoring dimensions
- oversized fact and draft sections are truncated before review
NoOutputGeneratedErrorretries by default- programming errors still throw
The root package now includes a small reproducibility layer:
buildRunArtifactturns item-level results into a standard run objectrunSuiteexecutes cases programmatically and returns a run artifactexportLocalJsonreturns structured run payloads for tools or CIexportLocalCsvflattens item-level rows into a spreadsheet-friendly formatwriteRunArtifactswrites:results.jsonreport.jsonrun_config.jsonwithrunId,generatedAt, package metadata, and suite configitems.jsonl- optional
items.csv
Numeric metrics from deterministic checks, review scores, durations, and custom item metrics are summarized into mean, min, max, standard deviation, and standard error.
Root export:
SourceRegistryverifyCitationsbuildGroundingMetadatasummarizeGroundingsourcePrecisionsourceCoveragescoreFixtureaggregateFixtureResultsscoreDeterministicCasebuildRunArtifactexportLocalJsonexportLocalCsvwriteRunArtifactsrunSuitebuildGroundingDistributionbuildReviewDistribution- shared types such as
Citation,SourceEntry,EvalCase,DeterministicScoreResult,EvalRun,EvalRunItem,GroundingMetricRecord, andReviewMetricRecord
Optional ./ai-sdk export:
reviewDraftcreateDraftReviewerdefaultReviewDimensions- AI review types such as
ReviewRequest,ReviewResult,ReviewIssue, andReviewScores
For the concise reference, see docs/eval-reference.md.
Use scoreDeterministicCase for product-specific fixture tests where the expected facts and forbidden claims are known ahead of time.
Use runSuite and writeRunArtifacts when you want a standard local run directory that CI, dashboards, or product scripts can read later without depending on a hosted eval system.
Use SourceRegistry, verifyCitations, and buildGroundingMetadata when your product emits citations or references retrieved content.
Use reviewDraft or createDraftReviewer when you want a structured “approve / revise” pass for a near-final draft before it goes to a human reviewer or customer workflow.
This package is useful for measuring conformance and review signals. It is not a guarantee of:
- factual correctness
- safety or compliance coverage
- authorization correctness
- legal review readiness
- product readiness on its own
- benchmark validity if the expectations themselves are weak
Read DISCLAIMER.md and SECURITY.md before using it in high-risk or externally regulated workflows.