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

Show source citations in responses·续 - 图1Docs

{"@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


Skip to content

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.