
Dspy Citations
- 2 installs
- 11 repo stars
- Updated June 28, 2026
- lebsral/dspy-programming-not-prompting-lms-skills
Adds structured source attribution to DSPy outputs so answers link claims to specific passages in source documents, for RAG and compliance use cases.
About
Guides using dspy.experimental.Citations and Document to produce machine-readable citations that identify which passage supports each claim. A developer uses it for grounded RAG answers and legal or compliance cases needing source traceability.
- Works natively with Anthropic's Citations API, falls back to prompt-based extraction
- Returns machine-readable citation objects with cited_text and document_index
Dspy Citations by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,958 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lebsral/dspy-programming-not-prompting-lms-skills --skill dspy-citationsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 11 |
| Last updated | June 28, 2026 |
| Repository | lebsral/dspy-programming-not-prompting-lms-skills ↗ |
What it does
Adds structured source attribution to DSPy outputs so answers link claims to specific passages in source documents, for RAG and compliance use cases.
Files
Add Structured Citations to DSPy Outputs
Guide the user through adding structured citations to DSPy outputs so AI answers can be traced back to specific source passages.
What are DSPy Citations
dspy.experimental.Citations provides structured source attribution for LM outputs. Instead of inline quotes, you get machine-readable citation objects that identify exactly which passage from which document supports each claim. Works natively with Anthropic's Citations API and falls back to prompt-based extraction for other providers.
When to use Citations
| Use Citations when... | Use something else when... |
|---|---|
| You need to verify which document supports a claim | Simple RAG where inline quotes are enough |
| Legal/compliance requires source traceability | Output does not reference source material |
| Users need clickable references back to source docs | You only have one source document |
| You want machine-readable citation metadata | Human-readable quotes in text are sufficient |
| Building fact-checking or grounding verification | The task is creative generation (no sources) |
Step 1: Set up Documents
Create Document objects from your source material:
import dspy
from dspy.experimental import Citations, Document
lm = dspy.LM("openai/gpt-4o-mini") # or "anthropic/claude-sonnet-4-5-20250929", etc.
dspy.configure(lm=lm)
# Create documents from your sources
documents = [
Document(
text="DSPy is a framework for programming language models. It replaces prompting with composable modules that can be optimized.",
title="DSPy Overview",
source_id="doc-001",
),
Document(
text="MIPROv2 is the most powerful DSPy optimizer. It jointly optimizes instructions and few-shot demonstrations.",
title="Optimizers Guide",
source_id="doc-002",
),
]Step 2: Build a signature with Citations output
class CitedQA(dspy.Signature):
"""Answer the question using the provided context. Cite your sources."""
context: list[str] = dspy.InputField(desc="source documents")
question: str = dspy.InputField()
answer: str = dspy.OutputField()
citations: Citations = dspy.OutputField(desc="structured citations for claims in the answer")Step 3: Use with a standard module
qa = dspy.ChainOfThought(CitedQA)
# Format documents as context strings
context = [f"[{doc.source_id}] {doc.title}: {doc.text}" for doc in documents]
result = qa(
context=context,
question="What is the most powerful DSPy optimizer?",
)
print(result.answer)
# "MIPROv2 is the most powerful DSPy optimizer..."
for citation in result.citations:
print(f" Cited: '{citation.cited_text}' from document {citation.document_index}")Step 4: Anthropic native Citations API
For Anthropic models, enable native citation support for higher accuracy:
lm = dspy.LM("anthropic/claude-sonnet-4-5-20250929")
dspy.configure(lm=lm)
# Enable native citations via adapter feature
dspy.configure(
lm=lm,
adapter=dspy.ChatAdapter(
adapt_to_native_lm_feature=["citations"],
),
)
# Now Citations output uses Anthropic's built-in citation extraction
# instead of prompt-based parsing -- higher accuracy, structured response
result = qa(
context=context,
question="How does DSPy replace prompting?",
)Native mode advantages:
- Citations are extracted by the model during generation (not post-hoc)
cited_textexactly matches source text (character-level accuracy)document_indexreliably maps to the input document list
Step 5: Parse and validate citations
# Each citation has these fields
for citation in result.citations:
print(f"Cited text: {citation.cited_text}")
print(f"Document index: {citation.document_index}")
print(f"Start char: {citation.start}")
print(f"End char: {citation.end}")
# Validate that cited text exists in the source document
for citation in result.citations:
source_doc = documents[citation.document_index]
if citation.cited_text in source_doc.text:
print(f"VALID: Citation found in {source_doc.title}")
else:
print(f"INVALID: Citation not found in source")Step 6: Citations with streaming
Stream answers while accumulating citations:
from dspy.streaming import streamify, StreamListener
qa = dspy.ChainOfThought(CitedQA)
answer_listener = StreamListener(signature_field_name="answer")
streaming_qa = streamify(qa, stream_listeners=[answer_listener])
async for chunk in streaming_qa(context=context, question="..."):
if hasattr(chunk, "answer"):
print(chunk.answer, end="", flush=True)
elif isinstance(chunk, dspy.Prediction):
# Citations are available in the final prediction
for citation in chunk.citations:
print(f"\n[{citation.document_index}] {citation.cited_text}")Step 7: Non-Anthropic fallback patterns
For providers without native citation support, use prompt engineering:
class CitedAnswer(dspy.Signature):
"""Answer using ONLY information from the provided documents.
For each claim, include [doc_N] inline where N is the document number."""
context: list[str] = dspy.InputField(desc="numbered source documents")
question: str = dspy.InputField()
answer: str = dspy.OutputField(desc="answer with [doc_N] inline citations")
# Number your documents explicitly
numbered_context = [
f"[doc_{i}] {doc.title}: {doc.text}"
for i, doc in enumerate(documents)
]
qa = dspy.ChainOfThought(CitedAnswer)
result = qa(context=numbered_context, question="...")
# Parse [doc_N] references from the answer textGotchas
1. Claude omits the `Citations` type import. You must import from dspy.experimental -- it is not in the main dspy namespace. Use from dspy.experimental import Citations, Document. 2. Native citations only work with Anthropic models. If you configure adapt_to_native_lm_feature=["citations"] with OpenAI, it silently falls back to prompt-based parsing which may be less accurate. 3. Claude hardcodes document indices. The document_index in citations maps to the position in the context list. If you reorder documents, indices change. Always use the index to look up the source, do not hardcode. 4. Citations are experimental. The API is in dspy.experimental and may change between versions. Pin your DSPy version in production. 5. Claude generates citations without source material. Citations only make sense when you provide context documents. Without context, the model fabricates citation metadata. Always pair Citations with a retrieval step.
Additional resources
- dspy.ai/api/experimental/Citations
- For API details, see reference.md
- For worked examples, see examples.md
Cross-references
Install any skill: npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill <name>- Stopping hallucinations with grounding -- see
/ai-stopping-hallucinations - Searching docs for RAG pipelines -- see
/ai-searching-docs - Retrieval modules for getting context -- see
/dspy-retrieval - Streaming citations progressively -- see
/dspy-streaming - Install `/ai-do` if you do not have it -- it routes any AI problem to the right skill and is the fastest way to work:
npx skills add lebsral/DSPy-Programming-not-prompting-LMs-skills --skill ai-do
last_audit:
date: 2026-05-04
score: 0/0
versions:
dspy: 3.2.0
[
{
"prompt": "I want my RAG pipeline to return structured citations showing which document each claim came from. Show me how to set this up with DSPy.",
"expected_output": "A signature with Citations output field, Document setup, and a module that retrieves context and generates cited answers.",
"assertions": [
"Imports Citations from dspy.experimental",
"Creates a signature with Citations as an output field type",
"Provides context documents as input to the module",
"Shows how to iterate over citations and access cited_text and document_index",
"Does not fabricate citations without source documents"
]
},
{
"prompt": "How do I use Anthropic's native Citations API with DSPy for higher accuracy citations?",
"expected_output": "Configure ChatAdapter with adapt_to_native_lm_feature=['citations'] and use an Anthropic model.",
"assertions": [
"Uses adapt_to_native_lm_feature=['citations'] in ChatAdapter",
"Specifies an Anthropic model (claude-sonnet or similar)",
"Explains that native mode gives character-level accuracy",
"Notes this only works with Anthropic models"
]
},
{
"prompt": "How can I validate that my AI's citations actually reference real text in the source documents?",
"expected_output": "A validation function that checks each citation's cited_text exists in the source document at the given document_index.",
"assertions": [
"Iterates over citations",
"Uses document_index to look up the source document",
"Checks if cited_text is present in the source text",
"Handles out-of-range document_index"
]
}
]
Citations Examples
Example 1: RAG with verifiable citations
A retrieval-augmented QA system that returns cited answers:
import dspy
from dspy.experimental import Citations, Document
lm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=lm)
class CitedRAG(dspy.Module):
def __init__(self, retriever):
self.retriever = retriever
self.qa = dspy.ChainOfThought(CitedQA)
def forward(self, question):
# Retrieve relevant documents
passages = self.retriever(question).passages
# Format as context with source IDs
context = [f"[{i}] {p}" for i, p in enumerate(passages)]
# Generate answer with citations
result = self.qa(context=context, question=question)
return result
class CitedQA(dspy.Signature):
"""Answer using only the provided context. Cite each claim."""
context: list[str] = dspy.InputField()
question: str = dspy.InputField()
answer: str = dspy.OutputField()
citations: Citations = dspy.OutputField()
# Usage
retriever = dspy.ColBERTv2(url="http://localhost:8893/api/search")
rag = CitedRAG(retriever=retriever)
result = rag(question="What optimizers does DSPy support?")
print(result.answer)
for c in result.citations:
print(f" Source [{c.document_index}]: {c.cited_text[:60]}...")Example 2: Legal document citation
Extracting claims with precise source attribution for compliance:
import dspy
from dspy.experimental import Citations, Document
lm = dspy.LM("anthropic/claude-sonnet-4-5-20250929")
dspy.configure(
lm=lm,
adapter=dspy.ChatAdapter(adapt_to_native_lm_feature=["citations"]),
)
class LegalCitedAnswer(dspy.Signature):
"""Answer the legal question citing specific clauses from the contract.
Every factual claim must have a citation."""
contract_sections: list[str] = dspy.InputField(desc="numbered contract sections")
question: str = dspy.InputField()
answer: str = dspy.OutputField()
citations: Citations = dspy.OutputField()
qa = dspy.ChainOfThought(LegalCitedAnswer)
contract_sections = [
"Section 1.1: The term of this agreement is 24 months from the effective date.",
"Section 2.3: Either party may terminate with 90 days written notice.",
"Section 4.1: The monthly fee is $5,000, payable within 30 days of invoice.",
]
result = qa(
contract_sections=contract_sections,
question="What is the termination notice period?",
)
print(f"Answer: {result.answer}")
print(f"\nCitations:")
for c in result.citations:
print(f" Section [{c.document_index}]: \"{c.cited_text}\"")
# Verify citation accuracy
assert c.cited_text in contract_sections[c.document_index]Example 3: Multi-source research with citation validation
Research across multiple documents with validation:
import dspy
from dspy.experimental import Citations, Document
lm = dspy.LM("openai/gpt-4o-mini")
dspy.configure(lm=lm)
class ResearchAnswer(dspy.Signature):
"""Synthesize information from multiple sources. Cite each claim."""
sources: list[str] = dspy.InputField(desc="labeled source documents")
question: str = dspy.InputField()
answer: str = dspy.OutputField(desc="synthesis with citations")
citations: Citations = dspy.OutputField()
def validate_citations(result, sources):
"""Check that all citations reference real text in the sources."""
valid = 0
invalid = 0
for c in result.citations:
if c.document_index < len(sources):
if c.cited_text in sources[c.document_index]:
valid += 1
else:
invalid += 1
print(f" INVALID: '{c.cited_text[:40]}...' not in source {c.document_index}")
else:
invalid += 1
print(f" INVALID: document_index {c.document_index} out of range")
print(f"Citations: {valid} valid, {invalid} invalid")
return invalid == 0
qa = dspy.ChainOfThought(ResearchAnswer)
sources = [
"[Wikipedia] Python was created by Guido van Rossum and first released in 1991.",
"[Official docs] Python 3.12 introduced per-interpreter GIL and improved error messages.",
"[Blog] Python is the most popular language for machine learning and data science.",
]
result = qa(sources=sources, question="When was Python created and what is it used for?")
print(f"Answer: {result.answer}\n")
validate_citations(result, sources)Citations API Reference
Condensed from dspy.ai/api/experimental/Citations. Verify against upstream for latest.
Citations type
from dspy.experimental import CitationsA list-like container of citation objects. Used as an output field type in DSPy signatures.
Usage in signatures:
class MySignature(dspy.Signature):
context: list[str] = dspy.InputField()
question: str = dspy.InputField()
answer: str = dspy.OutputField()
citations: Citations = dspy.OutputField()Iteration:
for citation in result.citations:
print(citation.cited_text, citation.document_index)Citation object fields
| Field | Type | Description |
|---|---|---|
cited_text | str | The exact text passage being cited |
document_index | int | Index into the context list identifying the source |
start | int | Start character offset in the source document (native mode) |
end | int | End character offset in the source document (native mode) |
Document type
from dspy.experimental import Document
doc = Document(
text="...", # str -- required, document content
title="...", # str -- optional, document title
source_id="...", # str -- optional, unique identifier
)| Field | Type | Default | Description |
|---|---|---|---|
text | str | required | Full text content of the document |
title | str | "" | Human-readable document title |
source_id | str | "" | Unique identifier for the document |
Native Anthropic Citations
Enable via adapter configuration:
dspy.configure(
lm=dspy.LM("anthropic/claude-sonnet-4-5-20250929"),
adapter=dspy.ChatAdapter(
adapt_to_native_lm_feature=["citations"],
),
)When enabled:
- Anthropic's Citations API extracts citations during generation
cited_textexactly matches source text (character-level)startandendoffsets are populated- Higher accuracy than prompt-based extraction
Only works with Anthropic models. Other providers fall back to prompt-based citation extraction.
Class methods
| Method | Description |
|---|---|
Citations.from_dict_list(dicts) | Create from list of citation dictionaries |
Citations.parse_lm_response(response) | Extract citations from raw LM response |
Citations.format(citations) | Format citations for display |
Provider compatibility
| Provider | Native citations | Prompt-based fallback |
|---|---|---|
| Anthropic (Claude) | Yes (via adapt_to_native_lm_feature) | Yes |
| OpenAI | No | Yes |
| Local models | No | Yes |