Integration guide
Vercel AI SDK + VERITAS
Add candidate-record retrieval to a Next.js + AI SDK chat or completion. Two patterns: model-initiated lookup and post-stream candidate retrieval, each followed by evidence review.
Install
pnpm add ai @ai-sdk/openai zodPattern 1 — tool() function-calling
Define two tools and let the model invoke them. The AI SDK handles the call/result loop transparently.
// app/api/chat/route.ts
import { streamText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const VERITAS = "https://sourcescore.org/api/v1";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o-mini"),
system:
"Use search_claims or verify_claim to retrieve relevant catalog records. " +
"A bestMatch is similarity, not proof: compare primary sources before asserting a fact and cite [claim_id].",
messages,
tools: {
search_claims: tool({
description: "Search the SourceScore VERITAS catalog of reviewed AI/ML claim records.",
parameters: z.object({
query: z.string(),
limit: z.number().int().min(1).max(20).default(5),
}),
execute: async ({ query, limit }) => {
const r = await fetch(`${VERITAS}/search?q=${encodeURIComponent(query)}&limit=${limit}`);
return await r.json();
},
}),
find_claim_candidate: tool({
description: "Retrieve a similar catalog record. Returns confidence + a citation to review, not a truth verdict.",
parameters: z.object({
statement: z.string(),
min_confidence: z.number().min(0).max(1).default(0.85),
}),
execute: async ({ statement, min_confidence }) => {
const r = await fetch(`${VERITAS}/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ claim: statement, minConfidence: min_confidence }),
});
return await r.json();
},
}),
},
maxSteps: 4, // allow up to 4 tool-call iterations
});
return result.toDataStreamResponse();
}
Front-end uses useChat() as normal. Tool calls + results stream alongside the text — the AI SDK's data protocol handles surfacing them to UI for citation badges.
Pattern 2 — Post-stream candidate retrieval
When you want free-form generation, retrieve candidate records after the stream completes. A match should link to evidence for review, not be rendered as verification.
// lib/verify.ts
export async function verifyLines(text: string) {
const lines = text.split("\n").map(s => s.trim()).filter(Boolean);
const VERITAS = "https://sourcescore.org/api/v1";
return Promise.all(lines.map(async line => {
const r = await fetch(`${VERITAS}/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ claim: line, minConfidence: 0.85 }),
});
const { bestMatch } = await r.json();
return {
statement: line,
candidateFound: !!bestMatch,
confidence: bestMatch?.confidence ?? 0,
claimId: bestMatch?.id ?? null,
url: bestMatch ? `https://sourcescore.org/claims/${bestMatch.id}/` : null,
};
}));
}
// app/page.tsx (client component)
"use client";
import { useState } from "react";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
export default function Page() {
const [out, setOut] = useState<Array<Awaited<ReturnType<typeof verifyLines>>[number]>>([]);
async function ask(question: string) {
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: `Answer with one fact per line:\n${question}`,
temperature: 0,
});
setOut(await verifyLines(text));
}
return (
<div>
<button onClick={() => ask("When was the Transformer introduced?")}>Ask</button>
<ul>
{out.map((r, i) => (
<li key={i}>
{r.statement}{" "}
{r.candidateFound
? <a href={r.url!}>🔎 candidate [{r.claimId}] ({r.confidence.toFixed(2)}) — review sources</a>
: <span className="text-amber-600">⚠️ no catalog candidate</span>}
</li>
))}
</ul>
</div>
);
}
Edge runtime considerations
The fetch-based VERITAS client works in both Node and Edge runtimes — no native dependencies. For Vercel Edge Functions:
- Set
export const runtime = "edge"in your route. - Measure VERITAS latency from your own deployment region and set an explicit timeout.
- No SDK import needed — VERITAS is plain HTTP.
UI patterns
Render candidate records with a clickable link to their evidence. Render absent matches with an amber chip. Neither state establishes factual correctness; compare independent primary evidence first.
<span className="candidate-badge">
🔎 [{claimId}] {confidence.toFixed(2)} — review sources
</span>
<span className="no-candidate-chip" title="No similar catalog record returned">
⚠ no catalog candidate
</span>The candidate badge should be a link to https://sourcescore.org/claims/<id>/ for full provenance.