DeepCitation + AG-UI
Integrate citation verification into AG-UI protocol agents. AG-UI’s event-driven SSE streaming pairs naturally with DeepCitation’s deferred citation pattern — verification results are sent as STATE_DELTA events after the LLM finishes streaming.
A complete, runnable example is available at agui-chat (live demo).
Install
npm install deepcitation @ag-ui/core
Architecture
Client ──SSE──> /api/agent ──> LLM (streaming tokens via TEXT_MESSAGE_CONTENT)
│
├── TEXT_MESSAGE_END (LLM done)
├── DeepCitation.verify() (runs after LLM completes)
└── STATE_DELTA (verification results pushed to client)
The key insight: AG-UI’s STATE_DELTA events let you push verification results to the client after the LLM finishes streaming, without a separate API call.
Key Integration Pattern
import { EventType } from "@ag-ui/core";
import { DeepCitation, extractVisibleText } from "deepcitation/client";
import { wrapCitationPrompt } from "deepcitation/prompts";
const dc = new DeepCitation({ apiKey: process.env.DEEPCITATION_API_KEY });
// In your AG-UI agent route handler:
export async function POST(req: Request) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
function send(event: Record<string, unknown>) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
}
// 1. Prepare attachment (cached across requests)
const { fileDataParts, deepTextPages } = await dc.prepareAttachments([
{ file: pdfBuffer, filename: "report.pdf" },
]);
// 2. Wrap prompts
const { enhancedSystemPrompt, enhancedUserPrompt } = wrapCitationPrompt({
systemPrompt, userPrompt, deepTextPages,
});
// 3. Stream LLM response via AG-UI events
send({ type: EventType.RUN_STARTED, threadId, runId });
send({ type: EventType.TEXT_MESSAGE_START, messageId, role: "assistant" });
let fullResponse = "";
// ... stream LLM tokens, sending TEXT_MESSAGE_CONTENT events ...
// ... collect fullResponse ...
send({ type: EventType.TEXT_MESSAGE_END, messageId });
// 4. Verify citations and push results as STATE_DELTA
const { verifications } = await dc.verify({ llmOutput: fullResponse });
const visibleText = extractVisibleText(fullResponse);
send({
type: EventType.STATE_DELTA,
delta: [
{ op: "replace", path: "/verifications", value: verifications },
{ op: "replace", path: "/visibleText", value: visibleText },
],
});
send({ type: EventType.RUN_FINISHED, threadId, runId });
controller.close();
},
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
});
}
Client-Side: Consuming Verification Results
Import the DeepCitation stylesheet in your client entry point: import "deepcitation/styles.css" (or deepcitation/tailwind.css for Tailwind projects).
On the client, listen for STATE_DELTA events to receive verification results and render CitationComponent:
import { CitationComponent } from "deepcitation/react";
import type { StateDeltaEvent } from "@ag-ui/core";
// When STATE_DELTA arrives with verifications:
function onStateDelta(delta: StateDeltaEvent["delta"]) {
for (const op of delta) {
if (op.path === "/verifications") {
setVerifications(op.value);
}
}
}
Next Steps
- Components — Display verified citations with React
- Vercel AI SDK Guide — Alternative streaming approach
- Error Handling — Handle verification failures gracefully