Citations and mid-stream errors
Now that all three routes work, you need to polish two things most demos skip: showing the user which chunks grounded the answer, and handling a provider that dies mid-stream without torching the UI.
// After similaritySearch, include chunk metadata in the response
const citations = relatedDocs.map((doc, i) => ({
id: `c-${'$'}{i}`,
snippet: doc.pageContent.slice(0, 240),
source: doc.metadata?.source ?? remoteUrl,
}));
// One approach: stream the answer, then append a final data frame with citations
const result = streamText({
model: google('gemini-2.5-flash'),
prompt: template,
system: systemPrompt,
});
return result.toUIMessageStreamResponse({
messageMetadata: () => ({ citations }),
});Conceptually: attach the chunks you used as metadata on the streamed message. The UI reads the metadata when the stream completes and renders a citation list under the answer.
try {
const htmlText = await cheerioLoader.load();
const chunkDocuments = await splitter.splitDocuments(htmlText);
await vectorStore.addDocuments(chunkDocuments);
const relatedDocs = await vectorStore.similaritySearch(userInput);
// ... build template, call streamText ...
} catch (err) {
return new Response(
JSON.stringify({ error: 'Could not reach the URL or the model provider.' }),
{ status: 502, headers: { 'content-type': 'application/json' } },
);
}A pragmatic first pass. Catch the whole pipeline and return a clean status the client can show instead of a half-streamed broken response. Refine to per-step handling once you have real failure telemetry.
This is the hard case. Once bytes are on the wire, you cannot change the status code. The Vercel AI SDK surfaces onError and onFinish callbacks on streamText, which let you log and optionally append a final error frame. On the client, useChat exposes an error state so you can render a friendly "something went wrong" notice and a retry button without crashing the chat log.
Quiz: Quiz
Loading practice…