RAG citations should come from the retrieval pipeline, not from the model inventing a source label after it has written the answer.

Keep chunk identifiers, document identifiers, page numbers, section headings, versions, and source URLs attached to the retrieved context. When the model uses a claim, the application should be able to map that claim back to the chunk that was actually retrieved.

This makes failures easier to debug. If the answer cites the wrong source, you can inspect whether retrieval returned the wrong chunk, the model used the wrong evidence, or the citation renderer lost the mapping.

One simple shape is to inject the chunk ID beside the retrieved text, then require the model to cite that exact ID:

using System.Text;

public record RetrievedChunk(
    string ChunkId,
    string DocumentUrl,
    string Text);

var contextBuilder = new StringBuilder();

foreach (RetrievedChunk chunk in chunks)
{
    contextBuilder.AppendLine($"[Source: {chunk.ChunkId}]");
    contextBuilder.AppendLine(chunk.Text);
    contextBuilder.AppendLine();
}

var systemPrompt = $"""
    Answer using only the provided context.
    When you use a piece of context, cite the exact [Source: ID].
    Do not invent source IDs, document titles, or URLs.

    Context:
    {contextBuilder}
    """;

The visible citation can still become a clean URL or document title later. The important part is that the application maps the emitted chunk ID back to the retrieved database record.

Avoid citation theater. A link to a whole document is weak when the answer depended on one paragraph. A citation to a source that was not retrieved is worse, because it makes the answer look grounded when it is not.

For production RAG, citations are part of the data contract. Keep the evidence path inspectable from query to retrieved chunk to final answer.