Show source citations in responses·续
buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? "";
for (const line of lines) { // The chunks event arrives before the streamed answer. if (line.startsWith("event: ")) { currentEvent = line.slice(7).trim(); continue; }
// Transform the chunks data line into a citations event for your UI.
if (currentEvent === "chunks" && line.startsWith("data: ")) {
const chunks = JSON.parse(line.slice(6));
const citations = chunks.map((chunk) => ({
source: chunk.item.key,
score: chunk.score,
}));
await writer.write(
encoder.encode(
event: citations\ndata: ${JSON.stringify(citations)}\n\n,
),
);
currentEvent = "";
continue;
}
// Forward answer tokens and other SSE data unchanged. currentEvent = ""; await writer.write(encoder.encode(line + "\n")); } } } finally { reader.releaseLock(); await writer.close(); } };
pump().catch(() => writer.close());
return new Response(readable, { headers: { "content-type": "text/event-stream", "cache-control": "no-cache", }, }); }, };
*src/index.tsts*
```ts
export interface Env {
AI_SEARCH: AiSearchNamespace;
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
const query = url.searchParams.get("query") ?? "What is Cloudflare?";
// Stream answer tokens, but extract source chunks first.
const stream = await env.AI_SEARCH.get("my-instance").chatCompletions({
messages: [{ role: "user", content: query }],
stream: true,
});
// Transform the stream: extract the chunks event and forward the rest
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const reader = stream.getReader();
// Track the current SSE event type to identify source chunks.
let currentEvent = "";
const pump = async () => {
try {
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
// The chunks event arrives before the streamed answer.
if (line.startsWith("event: ")) {
currentEvent = line.slice(7).trim();
continue;
}
// Transform the chunks data line into a citations event for your UI.
if (currentEvent === "chunks" && line.startsWith("data: ")) {
const chunks = JSON.parse(line.slice(6));
const citations = chunks.map(
(chunk: { item: { key: string }; score: number }) => ({
source: chunk.item.key,
score: chunk.score,
}),
);
await writer.write(
encoder.encode(
`event: citations\ndata: ${JSON.stringify(citations)}\n\n`,
),
);
currentEvent = "";
continue;
}
// Forward answer tokens and other SSE data unchanged.
currentEvent = "";
await writer.write(encoder.encode(line + "\n"));
}
}
} finally {
reader.releaseLock();
await writer.close();
}
};
pump().catch(() => writer.close());
return new Response(readable, {
headers: {
"content-type": "text/event-stream",
"cache-control": "no-cache",
},
});
},
} satisfies ExportedHandler<Env>;
6. Use scoring details to rank citations
Each chunk includes a scoring_details object with a breakdown of how it was scored. Use these details to filter out low-quality citations or display confidence indicators.
To filter citations by relevance, update src/index.ts to use score fields:
src/index.jsjs
export default {
async fetch(request, env) {
const url = new URL(request.url);
const query = url.searchParams.get("query") ?? "What is Cloudflare?";
// AI Search returns scoring details with each source chunk.
const response = await env.AI_SEARCH.get("my-instance").chatCompletions({
messages: [{ role: "user", content: query }],
});
// Show this model response to the user.
const answer = response.choices[0]?.message?.content ?? "";
const citations = response.chunks
// Filter out lower-scoring chunks for stronger citations.
.filter((chunk) => chunk.score > 0.5)
// Expose scoring details if your UI shows confidence indicators.
.map((chunk, index) => ({
index: index + 1,
source: chunk.item.key,
score: chunk.score,
vectorScore: chunk.scoring_details?.vector_score,
keywordScore: chunk.scoring_details?.keyword_score,
rerankingScore: chunk.scoring_details?.reranking_score,
confidence: chunk.score > 0.8 ? "high" : "medium",
snippet: chunk.text.slice(0, 200),
}));
return Response.json({ answer, citations });
},
};
src/index.tsts
export interface Env {
AI_SEARCH: AiSearchNamespace;
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
const query = url.searchParams.get("query") ?? "What is Cloudflare?";
// AI Search returns scoring details with each source chunk.
const response = await env.AI_SEARCH.get("my-instance").chatCompletions({
messages: [{ role: "user", content: query }],
});
// Show this model response to the user.
const answer = response.choices[0]?.message?.content ?? "";
const citations = response.chunks
// Filter out lower-scoring chunks for stronger citations.
.filter((chunk) => chunk.score > 0.5)
// Expose scoring details if your UI shows confidence indicators.
.map((chunk, index) => ({
index: index + 1,
source: chunk.item.key,
score: chunk.score,
vectorScore: chunk.scoring_details?.vector_score,
keywordScore: chunk.scoring_details?.keyword_score,
rerankingScore: chunk.scoring_details?.reranking_score,
confidence: chunk.score > 0.8 ? "high" : "medium",
snippet: chunk.text.slice(0, 200),
}));
return Response.json({ answer, citations });
},
} satisfies ExportedHandler<Env>;
Use citation fields
Each chunk in the chunks array can include the following fields:
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier for the chunk. |
type |
string | Content type, typically text. |
score |
number | Overall relevance score between 0 and 1. |
text |
string | The text content of the chunk. |
item.key |
string | The file path or URL of the source document. |
item.timestamp |
number | Unix timestamp of when the item was last indexed. |
item.metadata |
object | Custom metadata associated with the source item. |
scoring_details.vector_score |
number | Semantic similarity score (0 to 1). |
scoring_details.keyword_score |
number | BM25 keyword match score. Present when using hybrid or keyword retrieval. |
scoring_details.keyword_rank |
number | Keyword rank position. |
scoring_details.vector_rank |
number | Vector rank position. |
scoring_details.reranking_score |
number | Reranking score (0 to 1). Present when reranking is enabled. |
scoring_details.fusion_method |
string | Fusion method used (rrf or max). Present when using hybrid retrieval. |
For multi-instance searches, each chunk also includes an instance_id field identifying which instance it came from. To search or chat across multiple instances, refer to namespace methods.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/ai-search/how-to/chunk-citations/#page","headline":"Show source citations in responses","description":"Display source citations alongside AI-generated answers.","url":"https://developers.cloudflare.com/ai-search/how-to/chunk-citations/","inLanguage":"en","image":"https://developers.cloudflare.com/ai-search/how-to/chunk-citations/og.png?v=9c6da9684828a4cb","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Expose your indexed content as a search tool for any MCP client or agent using the built-in MCP endpoint. title: Connect your AI Search to an MCP client image: https://developers.cloudflare.com/ai-search/how-to/connect-mcp-client/og.png?v=c86a99e5f0eea6f8
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/ai-search/llms.txt
Use this file to discover all available pages before exploring further.
Docs