Citation Formatting
Grounded AI products need citations that users can verify. This guide adapts OpenAI citation-formatting patterns for AvalAI apps that use /v1/responses, /v1/chat/completions, web search, or manual RAG.
Warning
Hosted tool citations are route- and model-dependent. When AvalAI returns provider-managed citations, preserve the returned source IDs. For manual retrieval or injected context, create stable source IDs in your application and ask the model to cite only those IDs.
When to Use Citations
Use citation instructions when the answer depends on retrieved, uploaded, searched, or business-specific content:
- RAG assistants that answer from your documents.
- Web-search answers where claims should link back to sources.
- Compliance, legal, support, or finance workflows that need audit trails.
- Long reports where each paragraph may depend on different sources.
Do not ask the model to cite common style guidance, unsupported memory, or content that was never supplied to the model.
Choose Citable Units
Define what the model is allowed to cite before you write the prompt.
| Unit | Best for | AvalAI recommendation |
|---|---|---|
| Document | Broad provenance | Use for simple answers where page-level support is enough. |
| Block / chunk | Most RAG systems | Default choice: stable, readable, and precise enough. |
| Line range | Audits and legal review | Use only when your retriever stores line offsets reliably. |
Keep source IDs stable across retries. Store UI locators separately: the model should emit block_42, while your app resolves that ID to a URL, filename, paragraph, or highlighted line range.
Treat source IDs and locators as separate concepts. A source ID is the stable token the model emits, such as block_42 or turn0file1. A locator is the UI evidence your application renders, such as L8-L13, paragraph 21, a highlighted chunk, or a URL fragment. Do not ask the model to invent locators unless your retriever supplied them in the same request.
Prompt Template
Use a citation format that is explicit and easy to parse. OpenAI recommends marker-style citations with:
CITATION_START:\ue200CITATION_DELIMITER:\ue202CITATION_STOP:\ue201- citation family:
cite
For injected context, give each block an ID and require exact citation markers:
## Citations
The provided context contains citable blocks such as:
<BLOCK id="block_42"> ... </BLOCK>
Each block ID is a source reference. Cite only block IDs that appear in the provided context.
Write a citation as:
\ue200cite\ue202<block_id>\ue201
Rules:
- Place citations after punctuation.
- Do not place citations inside Markdown links, bold text, italics, or code fences.
- Do not write block IDs verbatim outside citation markers.
- Do not invent source IDs, URLs, titles, or line ranges.
- If the context does not support the answer, say what is missing instead of citing.
- If multiple blocks support a claim, cite each supporting block.For provider-managed tool output, keep the source IDs returned by the tool, such as turn0file1, turn0url2, or turn1block0, and ask the model to cite those exact IDs.
Use two citation patterns:
- Retrieved tool context: preserve IDs exactly as returned by the tool. If a tool runs multiple times, the
turn#prefix may change per tool invocation, so validate citations against the IDs returned in that response. - Injected context: create your own stable block IDs before calling AvalAI. You can use plain IDs like
block_42or aturn0block42style if that matches your renderer; the prompt and parser just need one consistent format.
If you support line-level citations, extend the marker with the locator:
\ue200cite\ue202turn0file1\ue202L8-L13\ue201Only request line ranges when the retrieved or injected context already contains reliable line numbers.
Hosted Tool Annotations
When an AvalAI route returns OpenAI-compatible hosted-tool annotations, treat the annotation object as the source of truth for rendering. For web search and deep research-style outputs, a url_citation annotation can include the cited URL, title, and character span for the related text. In streaming flows, collect events such as response.output_text.annotation.added and attach them to the final text after response.output_text.done or response.completed.
Use this display contract for user-facing citation UX:
- Make web-search citations clearly visible and clickable near the claim they support.
- Preserve
url,title,start_index, andend_indexwhen the provider returns them. - If a result has no usable URL, treat it as ordinary tool context and cite your own stable source ID instead of rendering an empty link.
- Validate file citations and generated-artifact references against the current user's permissions before showing links.
- Do not trust prose-only source names; render from annotations or from source IDs you created in the same request.
Grounding Quality Rules
Add these rules to high-risk RAG, web-search, compliance, legal, or finance prompts:
- Cite only sources that directly support the sentence or clause being cited.
- Prefer authoritative and current sources for time-sensitive or regulated claims.
- Use diverse sources when the answer compares viewpoints, vendors, policies, or regions.
- If sources disagree, cite the conflicting sources and describe the disagreement instead of smoothing it over.
- Never invent citations, URLs, titles, line ranges, or source IDs; if support is missing, say what is missing.
Manual RAG Example
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["AVALAI_API_KEY"],
base_url="https://api.avalai.ir/v1",
)
blocks = [
{
"id": "pricing_2026_block_3",
"title": "Pricing Policy",
"text": "Enterprise customers receive usage alerts at 80% and 100% of their monthly budget.",
},
{
"id": "support_sla_block_8",
"title": "Support SLA",
"text": "Priority support tickets receive an initial response within four business hours.",
},
]
context = "\n\n".join(
f'<BLOCK id="{block["id"]}" title="{block["title"]}">\n{block["text"]}\n</BLOCK>'
for block in blocks
)
instructions = """Answer only from the citable blocks.
Use citations in the format \\ue200cite\\ue202<block_id>\\ue201.
Place citations after punctuation. Never invent block IDs."""
response = client.responses.create(
model="gpt-5.5",
instructions=instructions,
input=[
{"role": "developer", "content": f"Citable context:\n{context}"},
{"role": "user", "content": "When do enterprise customers get budget alerts?"},
],
store=False,
)
print(response.output_text)import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.AVALAI_API_KEY,
baseURL: "https://api.avalai.ir/v1",
});
const blocks = [
{
id: "pricing_2026_block_3",
title: "Pricing Policy",
text: "Enterprise customers receive usage alerts at 80% and 100% of their monthly budget.",
},
{
id: "support_sla_block_8",
title: "Support SLA",
text: "Priority support tickets receive an initial response within four business hours.",
},
];
const context = blocks
.map((block) => `<BLOCK id="${block.id}" title="${block.title}">\n${block.text}\n</BLOCK>`)
.join("\n\n");
const response = await client.responses.create({
model: "gpt-5.5",
instructions:
"Answer only from the citable blocks. Use citations in the format \\ue200cite\\ue202<block_id>\\ue201. Place citations after punctuation. Never invent block IDs.",
input: [
{ role: "developer", content: `Citable context:\n${context}` },
{ role: "user", content: "When do enterprise customers get budget alerts?" },
],
store: false,
});
console.log(response.output_text);For legacy Chat Completions, put the same instructions in a developer or system message and the citable blocks in a separate message before the user question.
Parse and Render Citations
Post-process model output before rendering it. Resolve source IDs in your database, then replace raw markers with links, footnotes, or inline chips. The parser should support single-source citations, multiple supporting sources, and optional locators such as line ranges.
import re
CITATION_START = "\ue200"
CITATION_DELIMITER = "\ue202"
CITATION_STOP = "\ue201"
SOURCE_ID_RE = re.compile(r"^[A-Za-z0-9_-]+$")
LINE_LOCATOR_RE = re.compile(r"^L\d+(?:-L\d+)?$")
TOKEN_RE = re.compile(
re.escape(CITATION_START)
+ r"cite"
+ re.escape(CITATION_DELIMITER)
+ r"(.*?)"
+ re.escape(CITATION_STOP),
re.DOTALL,
)
def extract_citations(text):
citations = []
def replace(match):
parts = [
part.strip()
for part in match.group(1).split(CITATION_DELIMITER)
if part.strip()
]
if not parts:
return ""
locator = None
if LINE_LOCATOR_RE.fullmatch(parts[-1]):
locator = parts.pop()
if not parts or any(not SOURCE_ID_RE.fullmatch(part) for part in parts):
return ""
citations.append(
{
"source_ids": parts,
"locator": locator,
"start": match.start(),
"end": match.end(),
}
)
return ""
clean_text = TOKEN_RE.sub(replace, text).strip()
return clean_text, citations
answer, citations = extract_citations(
"Budget alerts are sent at 80% and 100%. \ue200cite\ue202pricing_2026_block_3\ue202support_sla_block_8\ue201"
)
print(answer)
print(citations)const CITATION_START = "\ue200";
const CITATION_DELIMITER = "\ue202";
const CITATION_STOP = "\ue201";
const sourceIdRe = /^[A-Za-z0-9_-]+$/;
const lineLocatorRe = /^L\d+(?:-L\d+)?$/;
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
const citationRe = new RegExp(
`${escapeRegExp(CITATION_START)}cite${escapeRegExp(CITATION_DELIMITER)}` +
`([\\s\\S]*?)` +
`${escapeRegExp(CITATION_STOP)}`,
"g"
);
function extractCitations(text) {
const citations = [];
const cleanText = text
.replace(citationRe, (raw, body, offset) => {
const parts = body
.split(CITATION_DELIMITER)
.map((part) => part.trim())
.filter(Boolean);
if (parts.length === 0) {
return "";
}
let locator = null;
if (lineLocatorRe.test(parts[parts.length - 1])) {
locator = parts.pop();
}
if (parts.length === 0 || parts.some((part) => !sourceIdRe.test(part))) {
return "";
}
citations.push({
sourceIds: parts,
locator,
start: offset,
end: offset + raw.length,
});
return "";
})
.trim();
return { cleanText, citations };
}
console.log(
extractCitations("Budget alerts are sent at 80% and 100%. \ue200cite\ue202pricing_2026_block_3\ue202support_sla_block_8\ue201")
);Production Checklist
- Store
source_id, title, URL/file ID, chunk text, and optional line range with every retrieved block. - Validate citation IDs, multi-source markers, and locators against the blocks provided in the same request.
- Reject or repair malformed citations before rendering; never show raw
\ue200...\ue201markers to end users. - Strip or render raw citation markers before returning content to end users.
- Log final answer text, selected source IDs, and rendered locators for audits.
- Treat citations as grounding evidence, not access control; tenant and permission checks belong in your retrieval layer.