Data as of Sep 17, 2026 · Based on 295 AI responses from ChatGPT Search and Google AI Mode · See how Parse measures this
Use LlamaIndex as your core for legal document parsing and hierarchical indexing; pair it with a metadata-capable vector database like
Weaviate or
Qdrant to filter citations by jurisdiction/date, and add LangGraph if you need multi-agent citation validation.
Brands AI recommends here
Named in 51% of answers
Named in 49% of answers
Best when you need modular, legal-focused parsing and hierarchical indexing (small chunks for retrieval, larger parents for context). It supports structured ingestion for contracts but should be paired with a vector DB for metadata filters.
Named in 42% of answers
Named in 33% of answers
Yes. For legal work, I would **not** build a generic “embed → vector search → LLM” RAG system. I’d use a **citation-first, authority-aware RAG architecture** where retrieval and citation verification are first-class components. Recent Legal RAG Bench results are particularly relevant: retrieval quality appears to be a…
Yes. For legal work, I would not build a generic “embed → vector search → LLM” RAG system. I’d use a citation-first, authority-aware RAG architecture where retrieval and citation verification are first-class components.
Recent Legal RAG Bench results are particularly relevant: retrieval quality appears to be a major bottleneck, and improving the legal retriever can substantially improve groundedness and correctness.
┌──────────────────────┐
│ User legal query │
└──────────┬───────────┘
│
Query understanding
│
┌──────────────────┴──────────────────┐
│ │
Issue extraction Citation extraction
jurisdiction existing authorities
date / posture statutes / regulations
│ │
└──────────────────┬──────────────────┘
│
Hybrid Legal Retrieval
│
┌──────────────────────┼──────────────────────┐
│ │ │
BM25 / lexical Legal embeddings Citation graph
│ │ │
└──────────────────────┼──────────────────────┘
│
Authority reranker
│
┌─────────────────────┼─────────────────────┐
│ │ │
jurisdiction court hierarchy temporal validity
/ venue / precedential force / treatment
│ │ │
└─────────────────────┼─────────────────────┘
│
Pinpoint passage retrieval
│
Evidence / citation store
│
Citation-constrained LLM
│
┌──────────┴───────────┐
│ │
Draft answer Citation verifier
│
┌───────────┴───────────┐
│ │
Citation exists? Proposition supported?
│ │
Pinpoint valid? Authority appropriate?
│ │
└───────────┬───────────┘
│
Final response
This is probably the most important design choice.
Don't store a case merely as:
document_id
text
embedding
Store something closer to:
{
"document_id": "case_123",
"case_name": "Example v. Example",
"citation": "123 F.3d 456",
"court": "3d Cir.",
"jurisdiction": "federal",
"date": "2024-05-14",
"precedential": true,
"status": "good",
"cites": [
"456 U.S. 789",
"321 F.3d 654"
],
"paragraphs": [
{
"id": "case_123_p47",
"text": "...",
"page": 12,
"paragraph": 47
}
]
}
That lets retrieval reason about authority, not merely semantic similarity.
Legal citations identify specific sources, and pinpoint citations identify the particular portion supporting a proposition.
I'd use three retrieval channels:
Use BM25/OpenSearch/Elasticsearch for:
This is particularly important because "410 U.S. 113" should not depend on an embedding model understanding what the number means.
Use a legal-domain embedding model rather than relying exclusively on a generic embedding model.
Legal RAG Bench's 2026 results specifically found that retrieval/embedding quality can have a large effect on downstream legal RAG performance.
Maintain:
Case A ──cites──> Case B
Case A ──distinguishes──> Case C
Case A ──overrules──> Case D
Case A ──follows──> Case E
Then use citation relationships as an additional retrieval signal.
This is important because the most semantically similar case isn't necessarily the most legally authoritative case.
Your final ranking shouldn't simply be:
score = semantic_similarity
I'd use something like:
final_score =
0.35 * semantic_score
+ 0.20 * lexical_score
+ 0.20 * authority_score
+ 0.10 * citation_graph_score
+ 0.10 * temporal_score
+ 0.05 * factual_similarity
The exact weights should be learned/evaluated rather than treated as universal.
authority_score could incorporate:
binding jurisdiction +++
same court ++
higher court +++
persuasive jurisdiction +
overruled ---
distinguished -
superseded statute ---
very old / stale -
This makes the system precedent-aware, rather than simply similarity-aware. That distinction has also been emphasized in recent precedent-aware RAG work.
Don't choose between document-level and chunk-level retrieval.
Use both:
Query
│
├── Case-level retrieval
│ └── identify relevant authorities
│
└── Passage-level retrieval
└── identify exact supporting propositions
For example:
Does a contractual limitation clause bar this claim? First retrieve the relevant cases.
Then retrieve the specific paragraphs discussing:
The generator should ultimately cite the passage, not merely say “Case X is relevant.”
There is emerging benchmark work specifically targeting paragraph-level pinpoint citation retrieval, rather than just case-to-case retrieval.
Don't initially ask:
“Answer this legal question and provide citations.” Instead have it produce an intermediate representation:
{
"propositions": [
{
"text": "A limitation clause is enforceable when ...",
"support": [
{
"document_id": "case_123",
"paragraph_id": "case_123_p47",
"citation": "123 F.3d 456, 462"
}
]
}
]
}
Then generate prose from that structure.
This gives you:
Legal proposition
↓
Evidence
↓
Citation
↓
Generated sentence
rather than:
LLM-generated sentence
↓
LLM invents citation
This should be mandatory.
For every generated citation:
Resolve:
123 F.3d 456
to an actual case.
If the model says:
123 F.3d 456, 462
verify that page/paragraph 462 actually exists.
Run an entailment/support check:
PROPOSITION:
The court held that X.
CITED PASSAGE:
"..."
SUPPORT:
SUPPORTED / PARTIALLY_SUPPORTED / UNSUPPORTED
Check:
A California appellate decision shouldn't outrank a controlling U.S. Supreme Court decision merely because its text is more semantically similar.
For U.S. law, CourtListener's citation infrastructure is particularly useful. Its citation-lookup API can parse citations from blocks of text and resolve them against its database; it uses Eyecite for citation parsing.
You can therefore put this directly into your verification pipeline:
LLM output
↓
extract citations
↓
citation resolver
↓
canonical case ID
↓
retrieve authoritative text
↓
verify pinpoint
↓
verify proposition
That is much safer than asking an LLM whether its own citation is correct.
For a U.S. system I'd separate the corpus into:
CASE LAW
├── Supreme Court
├── Circuit courts
├── District courts
└── State courts
STATUTES
├── U.S. Code
└── State codes
REGULATIONS
├── CFR
└── State regulations
PROCEDURAL MATERIAL
├── Rules
├── Local rules
└── Court orders
SECONDARY AUTHORITY
├── Treatises
├── Law reviews
└── Practice guides
And assign each source an authority class.
For example:
PRIMARY_BINDING
PRIMARY_PERSUASIVE
SECONDARY
BACKGROUND
Do not mix these into a single undifferentiated vector index.
CourtListener is a particularly useful open starting point for U.S. case law, although coverage, metadata and licensing considerations still need to be handled carefully.
Generic:
chunk every 500 tokens
is a poor fit.
I'd make the legal document hierarchy:
Case
└── Opinion
├── Header
├── Procedural history
├── Facts
├── Issue
├── Analysis
│ ├── Rule
│ ├── Application
│ └── Counterargument
├── Holding
└── Disposition
And retain:
case_id
section
page
paragraph
sentence offsets
citation
court
date
jurisdiction
judge
precedential status
The embedding chunk can be ~200–600 tokens, but the canonical evidence unit should remain the legal paragraph/page, not the arbitrary embedding chunk.
I'd give the model rules like:
RULE 1:
Every material legal proposition requires supporting authority.
RULE 2:
Never fabricate a citation.
RULE 3:
Only cite authorities present in the evidence set.
RULE 4:
A citation must support the proposition immediately preceding it.
RULE 5:
Prefer controlling authority over persuasive authority.
RULE 6:
Use pinpoint citations whenever available.
RULE 7:
If sufficient authority was not retrieved, say so.
RULE 8:
Do not infer a holding from a case merely because the case discusses
the same topic.
That last rule is especially important.
I'd evaluate the system as several separate problems:
| Layer | Metric |
|---|---|
| Retrieval | Recall@K |
| Passage retrieval | Recall@K |
| Authority ranking | NDCG/MRR |
| Citation resolution | Accuracy |
| Pinpoint accuracy | Accuracy |
| Citation entailment | Precision/recall |
| Citation completeness | % propositions supported |
| Citation correctness | % citations actually supporting claim |
| Answer correctness | Expert evaluation |
| Hallucination | Unsupported-claim rate |
Don't just measure:
"Did the LLM give a good answer?"
Measure:
Did we retrieve the correct authority?
Did we retrieve the correct passage?
Did we cite it correctly?
Does that passage actually support the proposition?
Is the authority legally appropriate?
Legal RAG Bench is useful here because it explicitly separates retrieval failures from reasoning failures rather than treating every bad answer as an LLM hallucination.
For a production-quality U.S. legal citation RAG, I'd start with:
API
│
FastAPI / TypeScript
│
Query Orchestrator
│
┌─────────────┴─────────────┐
│ │
Elasticsearch/OpenSearch PostgreSQL
lexical + vectors metadata
│ │
└─────────────┬─────────────┘
│
Legal Reranker
│
Citation Graph
│
Evidence Store
│
LLM
│
Citation Verification
│
Final Response
For the data model, I'd use Postgres + OpenSearch/Elasticsearch + a citation graph, rather than putting everything into a single vector database.
Don't build “RAG with citations.” Build a “legal evidence retrieval system” whose final presentation happens to be RAG.
The pipeline should fundamentally be:
Question
↓
Legal issues
↓
Authorities
↓
Controlling authorities
↓
Supporting passages
↓
Verified propositions
↓
Draft
↓
Verified citations
↓
Answer
That architecture gives you a much better path toward correct case citations, pinpoint citations, jurisdiction-aware precedent ranking, and auditable answers than a conventional semantic RAG pipeline.
If you're building this yourself, I can also lay out a concrete Python implementation using LangChain/LlamaIndex + PostgreSQL/pgvector + Elasticsearch + CourtListener/Eyecite, including the schemas, retrieval algorithm, reranker, citation-verification pipeline, and API structure.
Resolve:
123 F.3d 456
to an actual case.
If the model says:
123 F.3d 456, 462
verify that page/paragraph 462 actually exists.
Run an entailment/support check:
PROPOSITION:
The court held that X.
CITED PASSAGE:
"..."
SUPPORT:
SUPPORTED / PARTIALLY_SUPPORTED / UNSUPPORTED
Check:
A California appellate decision shouldn't outrank a controlling U.S. Supreme Court decision merely because its text is more semantically similar.
For U.S. law, CourtListener's citation infrastructure is particularly useful. Its citation-lookup API can parse citations from blocks of text and resolve them against its database; it uses Eyecite for citation parsing.
Building a production-grade Retrieval-Augmented Generation (RAG) framework for legal citations requires moving beyond standard text-splitting and vector search. Legal documents demand strict adherence to structural boundaries, exact citation matching (e.g., Bluebook formatting, parallel citations), and verifiable…
Building a production-grade Retrieval-Augmented Generation (RAG) framework for legal citations requires moving beyond standard text-splitting and vector search. Legal documents demand strict adherence to structural boundaries, exact citation matching (e.g., Bluebook formatting, parallel citations), and verifiable attribution layers.
Here is an optimized architectural blueprint for a legal citation-focused RAG framework:
Cohere Re-rank or a fine-tuned legal model) to re-rank the top k candidates based on contextual relevance to the legal query.[Chunk_ID] object containing the parallel citation and pinpoint page.For evaluating your pipeline's performance on domain-specific corpora, you can benchmark against resources like the LegalBench-RAG GitHub Repository.
To help tailor this implementation, tell me:
Building a RAG framework optimized for legal citations requires strict precision, as hallucinated case law or misattributed holdings can lead to court sanctions. Standard chunking and vector search often fail here because legal arguments rely on hierarchical structures (sections, headings, sub-parts) and precise pin…
Building a RAG framework optimized for legal citations requires strict precision, as hallucinated case law or misattributed holdings can lead to court sanctions. Standard chunking and vector search often fail here because legal arguments rely on hierarchical structures (sections, headings, sub-parts) and precise pin cites (e.g., specific page numbers or paragraph markers).
Here is a blueprint for a legal-optimized RAG architecture, broken down by component:
Title -> Chapter -> Section -> Subsection metadata.text-embedding-3-large or open-source legal models like Legal-BERT/SFR-Embedding-Mistral ) with sparse keyword search (BM25) via a hybrid search approach (Reciprocal Rank Fusion). Legal search requires matching exact statutory terms, docket numbers, and case names alongside conceptual meaning.Cohere Re-rank or BGE-Reranker-Large ) post-retrieval. This step re-scores the top 50 retrieved legal segments down to the top 5–10 most relevant ones based on precise citation intersection.If you'd like, let me know:
I can provide a code snippet or detailed architecture diagram tailored to your stack.
No sources cited
Yes. For legal work, I would **not** build a conventional “embed chunks → retrieve top-k → ask an LLM” RAG system. Legal citation accuracy needs a retrieval layer that understands **authorities, citation relationships, jurisdiction, court hierarchy, dates, procedural posture, and pinpoint passages**. Recent legal-RAG…
Yes. For legal work, I would not build a conventional “embed chunks → retrieve top-k → ask an LLM” RAG system. Legal citation accuracy needs a retrieval layer that understands authorities, citation relationships, jurisdiction, court hierarchy, dates, procedural posture, and pinpoint passages.
Recent legal-RAG research strongly supports this architecture: retrieval quality appears to be a major ceiling on end-to-end legal RAG performance, and benchmarks specifically emphasize retrieving precise passages rather than merely whole documents.
┌─────────────────────┐
│ User Question │
└──────────┬──────────┘
│
Query analysis / rewrite
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
Citation search Semantic search Metadata search
"410 U.S. 113" "standing..." court/date/jurisdiction
│ │ │
└─────────────────────┼─────────────────────┘
▼
Hybrid candidate retrieval
BM25 + dense + citation graph
│
▼
Cross-encoder reranker
│
▼
Authority-aware evidence selection
│
┌────────────────┴────────────────┐
▼ ▼
Supporting passages Citation metadata
+ pinpoint locations + authority hierarchy
│ │
└────────────────┬────────────────┘
▼
Evidence-constrained LLM
│
▼
Claim → Evidence mapping
│
▼
Citation verification layer
│
┌──────────────┴──────────────┐
▼ ▼
Valid citation? Supported claim?
│ │
└──────────────┬──────────────┘
▼
Final answer
Your document model should look more like a legal database than a vector database.
For each authority, maintain something like:
{
"document_id": "us_supreme_410_113",
"citation": "410 U.S. 113",
"case_name": "Roe v. Wade",
"court": "U.S. Supreme Court",
"jurisdiction": "US",
"decision_date": "1973-01-22",
"precedential_status": "precedential",
"reporter": "U.S.",
"volume": 410,
"first_page": 113,
"citations_to": [],
"cited_by": [],
"passages": [
{
"text": "...",
"page": 153,
"paragraph": 7,
"token_start": 1842,
"token_end": 1914
}
]
}
The important difference is that the citation itself is an entity, rather than merely text appearing somewhere in a chunk.
I would use three retrieval channels:
Then combine them with reciprocal-rank fusion or a learned ranker.
This is particularly important because legal search has unusually high value in exact textual matching. CourtListener's current search infrastructure, for example, explicitly supports both keyword and semantic search.
After retrieving perhaps 50–200 candidates, rerank them using features such as:
score =
semantic_relevance
+ lexical_relevance
+ citation_match
+ jurisdiction_match
+ court_authority
+ temporal_relevance
+ procedural_posture_match
+ cited_by_relevance
+ passage_specificity
Critically, don't let semantic similarity alone determine the winner.
For example, if the question asks:
What is the Eleventh Circuit standard for X? a highly semantically similar Ninth Circuit case should not outrank a slightly less semantically similar Eleventh Circuit precedent.
Legal documents have natural boundaries:
Case
├── Opinion
│ ├── Procedural history
│ ├── Facts
│ ├── Issue
│ ├── Analysis
│ │ ├── Rule
│ │ ├── Application
│ │ └── Holding
│ └── Disposition
└── Metadata
Use these structures when chunking.
For cases, I would preserve:
A recent legal-RAG benchmark specifically argues for minimal, highly relevant text segments because retrieving giant document chunks can hurt both context efficiency and citation generation.
This is one of the biggest upgrades over generic RAG.
Represent:
Case A ──cites──> Case B
Case A ──overrules──> Case C
Case A ──distinguishes──> Case D
Case A ──follows──> Case E
Case A ──quotes──> Case F
Then retrieval can perform graph expansion:
initial results
↓
important authorities
↓
cited authorities
↓
later treatment
↓
controlling/current authority
CourtListener exposes APIs for citation relationships between opinions, making this kind of architecture practical for U.S. case law.
This is essential.
Don't simply prompt:
“Cite your sources.” Instead, have the model produce an internal structured representation:
{
"claims": [
{
"claim": "The court requires X.",
"evidence": [
{
"document_id": "abc123",
"page": 17,
"paragraph": 42
}
]
}
]
}
Then a verifier checks:
Claim
↓
Does cited authority exist?
↓
Does pinpoint exist?
↓
Does passage actually support claim?
↓
Is citation jurisdictionally appropriate?
↓
Is authority still valid/current?
↓
Final citation
This is much safer than allowing the LLM to manufacture Bluebook citations from memory.
CourtListener's citation-lookup API is explicitly designed to parse and verify citations and is described as a guardrail against hallucinated citations.
I'd make these separate components:
CitationResolver
↓
AuthorityResolver
↓
PassageRetriever
↓
EvidenceVerifier
↓
CitationFormatter
For example, the model shouldn't decide that:
Brown v. Board of Education, 347 U.S. 483 (1954)
is the canonical authority merely because it generated that string.
Instead:
citation = citation_resolver.resolve(
volume=347,
reporter="U.S.",
page=483
)
assert citation.document_id is not None
Then the formatter generates the appropriate citation style from your canonical metadata.
Legal systems have an especially nasty failure mode:
The model finds several cases supporting proposition X and assumes X is the law. Your retriever should actively search for:
So the pipeline becomes:
Support retrieval
+
Contrary retrieval
+
Authority-status retrieval
↓
Balanced evidence set
This is particularly important for legal research rather than simple legal QA.
I would give the generation model a contract like:
RULES
1. Every material legal proposition must have supporting evidence.
2. Do not create a citation that does not appear in the evidence set.
3. Do not cite a document merely because it discusses the same topic.
4. Pinpoint citations must correspond to the retrieved passage.
5. Distinguish holding, dicta, procedural history, and factual discussion.
6. Identify conflicting authority when retrieved.
7. If the evidence is insufficient, say so.
8. Never fill missing legal authority from model memory.
Then require structured output internally:
{
"answer": "...",
"claims": [
{
"text": "...",
"authority": "....",
"pinpoint": "...",
"support_score": 0.94
}
],
"uncertainties": []
}
Only after verification should this become prose.
For a serious U.S. legal research system, I'd consider:
| Layer | Recommendation |
|---|---|
| Document store | PostgreSQL + object storage |
| Full-text search | Elasticsearch/OpenSearch |
| Vector search | pgvector, Qdrant, or OpenSearch |
| Reranker | Legal/domain-specific cross-encoder |
| Citation parser | Eyecite |
| Citation graph | PostgreSQL/Neo4j |
| Metadata | PostgreSQL |
| Retrieval | BM25 + dense + graph |
| Generation | Strong reasoning LLM |
| Verification | Separate LLM + deterministic rules |
| Evaluation | LegalBench-RAG + CLERC + proprietary cases |
CourtListener is particularly useful as an open U.S. legal-data component because its API exposes case-law search, citation lookup and citation-network functionality.
For evaluation, I'd start with LegalBench-RAG, CLERC, and the newer Legal RAG Bench. CLERC evaluates both retrieving relevant precedent and using retrieved authorities to produce analysis, while Legal RAG Bench specifically decomposes retrieval, reasoning, and hallucination failures.
I would package it as a modular framework roughly like:
legalrag/
│
├── ingest/
│ ├── opinions.py
│ ├── statutes.py
│ ├── regulations.py
│ └── metadata.py
│
├── parsing/
│ ├── citations.py
│ ├── sections.py
│ ├── holdings.py
│ └── pinpoint.py
│
├── indexing/
│ ├── lexical.py
│ ├── embeddings.py
│ └── citation_graph.py
│
├── retrieval/
│ ├── lexical.py
│ ├── semantic.py
│ ├── citation.py
│ ├── authority.py
│ └── hybrid.py
│
├── ranking/
│ ├── reranker.py
│ └── authority_ranker.py
│
├── evidence/
│ ├── selector.py
│ ├── support.py
│ └── contradiction.py
│
├── generation/
│ ├── prompts.py
│ └── generator.py
│
├── verification/
│ ├── citation.py
│ ├── pinpoint.py
│ ├── authority.py
│ └── claim_support.py
│
├── formatting/
│ ├── bluebook.py
│ └── citations.py
│
└── evaluation/
├── retrieval.py
├── grounding.py
├── citations.py
└── benchmarks.py
Don't optimize for “answer accuracy.” Optimize separately for:
Retrieval accuracy
↓
Authority selection
↓
Evidence sufficiency
↓
Claim grounding
↓
Citation correctness
↓
Citation completeness
↓
Legal reasoning
That separation matters. Recent research indicates that apparently “LLM hallucination” errors in legal RAG are often downstream of retrieval failures, so improving the generator alone is unlikely to solve the problem.
If you're building this for U.S. case law, I can next give you a concrete Python implementation architecture (FastAPI + PostgreSQL/pgvector + Elasticsearch + CourtListener/Eyecite + an LLM), including the database schema, retrieval algorithm, citation-verification pipeline, and starter code.
This is essential.
Don't simply prompt:
“Cite your sources.” Instead, have the model produce an internal structured representation:
{
"claims": [
{
"claim": "The court requires X.",
"evidence": [
{
"document_id": "abc123",
"page": 17,
"paragraph": 42
}
]
}
]
}
Then a verifier checks:
Claim
↓
Does cited authority exist?
↓
Does pinpoint exist?
↓
Does passage actually support claim?
↓
Is citation jurisdictionally appropriate?
↓
Is authority still valid/current?
↓
Final citation
This is much safer than allowing the LLM to manufacture Bluebook citations from memory.
CourtListener's citation-lookup API is explicitly designed to parse and verify citations and is described as a guardrail against hallucinated citations.
I'd make these separate components:
CitationResolver
↓
AuthorityResolver
↓
PassageRetriever
↓
EvidenceVerifier
↓
CitationFormatter
For example, the model shouldn't decide that:
Brown v. Board of Education, 347 U.S. 483 (1954)
is the canonical authority merely because it generated that string.
Instead:
citation = citation_resolver.resolve(
volume=347,
reporter="U.S.",
page=483
)
assert citation.document_id is not None
Then the formatter generates the appropriate citation style from your canonical metadata.
Legal systems have an especially nasty failure mode:
The model finds several cases supporting proposition X and assumes X is the law. Your retriever should actively search for:
So the pipeline becomes:
Support retrieval
+
Contrary retrieval
+
Authority-status retrieval
↓
Balanced evidence set
This is particularly important for legal research rather than simple legal QA.
I would give the generation model a contract like:
A **production-grade RAG framework optimized for legal citations** requires structural chunking, hybrid search, hierarchical reranking, and a strict citation-verification post-processor to eliminate hallucinated case law.[](https://scholar.gist.ac.kr/bitstream/local/9035/2/Legal_Query_RAG.pdf)…
A production-grade RAG framework optimized for legal citations requires structural chunking, hybrid search, hierarchical reranking, and a strict citation-verification post-processor to eliminate hallucinated case law.
Standard vector search breaks legal texts arbitrarily, splitting holding from context. A legal-optimized framework treats statutory codes, case law, and contracts as structured hierarchical objects rather than plain text streams.
Core Architecture Blueprint
| Component | Recommended Tool / Method | Legal Optimization Function |
|---|---|---|
| Parsing & Chunking | UndatasIO or Marker / LlamaParse | Chunks by structural legal boundaries (e.g., Section → Subsection → Paragraph) rather than token length. |
| Embedding & Storage | Zilliz Cloud or Pinecone + BM25 Hybrid | Combines dense vector embeddings with sparse keyword search (BM25) to catch specific legal terms, citations, and docket numbers. |
| Reranking | Cohere Rerank or BGE-Reranker-Large | Re-scores retrieved sections using cross-encoders specifically fine-tuned on legal or academic corpora. |
| Generation & Guardrails | Llama 3 / Claude 3.5 Sonnet via Instructor / Guidance | Forces structured JSON output containing exact quote strings mapped explicitly to metadata citation keys. |
jurisdiction, court_level, date_filed, statute_code, section_number , and precedential_status.text-embedding-3-large or fine-tuned legal BERT) in parallel with sparse lexical search (BM25).jurisdiction: "DE" or court: "Delaware Court of Chancery").Instructor or Pydantic to enforce an output schema where the response is split into claims: List[ClaimWithCitation] . Run a deterministic regex/string-matching check to verify that the cited text snippet actually exists verbatim or near-verbatim inside the retrieved source chunk before rendering it to the user.If you'd like to proceed, let me know:
I can provide a concrete Python boilerplate architecture using LangChain/LlamaIndex or a custom pipeline tailored to your stack.
Yes. For legal work, I would **not** build a generic “vector DB + top-k chunks + LLM” RAG. Legal citation retrieval has unusually strict requirements around **exact authorities, jurisdiction, court hierarchy, precedential status, pinpoint citations, and temporal validity**. Recent legal-RAG research strongly supports…
Yes. For legal work, I would not build a generic “vector DB + top-k chunks + LLM” RAG. Legal citation retrieval has unusually strict requirements around exact authorities, jurisdiction, court hierarchy, precedential status, pinpoint citations, and temporal validity.
Recent legal-RAG research strongly supports putting most of the engineering effort into retrieval: Legal RAG Bench found retrieval quality to be a major determinant of downstream accuracy, while the 2026 AusLaw citation study found BM25 and hybrid/re-ranker approaches particularly strong for citation prediction.
┌─────────────────────┐
│ Legal Corpus │
│ │
│ Cases │
│ Statutes │
│ Regulations │
│ Rules │
│ Briefs / Filings │
└──────────┬──────────┘
│
Parse + Normalize
│
┌─────────────────┴─────────────────┐
│ │
Citation Extraction Legal Metadata
│ │
┌──────▼───────┐ ┌────────▼────────┐
│ Citation │ │ jurisdiction │
│ graph │ │ court │
│ │ │ date │
│ cites/cited │ │ precedential? │
│ overruled │ │ treatment │
└──────┬───────┘ └────────┬────────┘
│ │
└─────────────────┬─────────────────┘
▼
┌────────────────────┐
│ Hybrid Retrieval │
│ │
│ BM25 │
│ Dense embeddings │
│ Citation graph │
│ Metadata filters │
└─────────┬──────────┘
▼
┌────────────────────┐
│ Cross-Encoder / │
│ Legal Re-ranker │
└─────────┬──────────┘
▼
┌────────────────────┐
│ Citation Resolver │
│ │
│ case → reporter │
│ statute → section │
│ pinpoint → page │
└─────────┬──────────┘
▼
┌────────────────────┐
│ LLM Generation │
│ │
│ claim → evidence │
│ claim → citation │
└─────────┬──────────┘
▼
┌────────────────────┐
│ Citation Validator │
│ │
│ Exists? │
│ Supports claim? │
│ Correct pinpoint? │
│ Correct jurisdiction│
│ Still good law? │
└────────────────────┘
Instead of treating a case as merely a text document, create structured records such as:
{
"authority_id": "us.scotus.410.113",
"citation": "Roe v. Wade, 410 U.S. 113 (1973)",
"court": "U.S. Supreme Court",
"jurisdiction": "US",
"decision_date": "1973-01-22",
"precedential": true,
"status": "overruled",
"overruled_by": [
"Dobbs v. Jackson Women's Health Organization, 597 U.S. 215 (2022)"
],
"parallel_citations": [],
"sections": [
{
"id": "us.scotus.410.113.p153",
"page": 153,
"text": "..."
}
]
}
That lets the system distinguish:
“Find cases discussing personal jurisdiction” from:
“Find binding Supreme Court precedent discussing personal jurisdiction that was valid as of 2019.” Those are completely different retrieval problems.
I'd use three retrieval channels:
BM25 is extremely important for legal citations because exact terminology matters.
For example:
"Chevron deference"
"Rule 12(b)(6)"
"qualified immunity"
"28 U.S.C. § 1332"
Dense retrieval can understand the concept, but lexical retrieval is often much better at finding the exact legal phrase, citation, statute number, or case name.
Recent citation-prediction research specifically found BM25 outperforming dense embeddings in one large jurisdiction-specific evaluation, with hybrid reranking producing the strongest results.
Use a legal-domain embedding model for conceptual similarity:
query:
"When can a court disregard the corporate form?"
retrieves:
cases discussing alter ego,
veil piercing,
corporate separateness,
domination/control,
etc.
Legal RAG Bench's 2026 results are particularly relevant here: its experiments found domain-adapted legal embeddings could substantially improve retrieval compared with general-purpose embeddings.
Build a graph:
Case A
├── cites → Case B
├── cites → Case C
├── follows → Case D
├── distinguishes → Case E
└── overrules → Case F
Then retrieval can exploit legal relationships rather than just textual similarity.
A particularly useful scoring function is:
score =
0.35 * BM25
+ 0.30 * dense_similarity
+ 0.15 * citation_graph_score
+ 0.10 * authority_score
+ 0.10 * metadata_match
I'd tune these weights empirically rather than hard-code them in production.
Research on incorporating legal structure similarly points toward combining semantic retrieval with citation networks and legal knowledge graphs.
This is one of the most important differences from ordinary RAG.
Don't retrieve:
Case X — 47 pages
Retrieve:
Case X
├── Facts
├── Issue
├── Holding
├── Reasoning
├── Rule
├── Application
└── Disposition
And preferably individual passages:
authority_id
document_id
paragraph_id
page
section
text
proposition
For example:
{
"authority": "Anderson v. Liberty Lobby",
"citation": "477 U.S. 242 (1986)",
"pinpoint": "248",
"proposition":
"The court must determine whether the evidence presents a
sufficient disagreement to require submission to a jury.",
"text": "...",
"source_url": "...",
"jurisdiction": "US",
"court_level": "Supreme Court"
}
LegalBench-RAG is particularly instructive here: its benchmark emphasizes retrieving minimal, highly relevant text segments, rather than entire documents or large imprecise chunks.
I'd actually use a two-stage system:
User question
│
▼
Authority retrieval
│
├── Case A
├── Case B
├── Statute C
└── Regulation D
│
▼
Passage retrieval within authorities
│
├── A § holding
├── B § rule
├── C § subsection
└── D § exception
│
▼
Re-ranking
│
▼
Generation
This solves a common RAG problem where a highly relevant case is missed because the exact relevant passage has low embedding similarity.
Never let the LLM invent citations.
Instead, generation should operate on citation IDs:
[AUTH-18372]
[AUTH-92182]
[AUTH-77261]
The model produces:
The court applied the substantial-factor test.[AUTH-18372]
Then a deterministic citation renderer converts it to:
The court applied the substantial-factor test.
Smith v. Jones, 123 F.4th 456, 461 (9th Cir. 2025).
This dramatically reduces:
After generation, run every proposition through a validator:
Claim
│
▼
Which citation supports this?
│
▼
Retrieve cited passage
│
▼
Does passage entail/support claim?
│
├── YES → accept
│
└── NO → regenerate / remove claim
I'd classify every citation as:
SUPPORTED
PARTIALLY_SUPPORTED
UNSUPPORTED
WRONG_AUTHORITY
WRONG_PINPOINT
This is more useful than simply asking another LLM, "Are these citations correct?"
Legal RAG needs a concept ordinary RAG usually doesn't:
What was the law at a particular point in time? Every authority should therefore have temporal metadata:
effective_from
effective_to
decided_at
overruled_at
superseded_at
amended_at
Then queries can include:
{
"jurisdiction": "California",
"court": "state appellate",
"as_of": "2024-06-01",
"precedential": true
}
This prevents a particularly dangerous failure mode:
citing a case that is highly semantically relevant but was overruled before the date relevant to the user's matter.
For complicated legal questions, don't make one retrieval query.
Transform:
Can a Delaware corporation's officer be personally liable
for an allegedly fraudulent transaction when the corporation
is insolvent?
into something like:
Q1: Delaware officer personal liability
Q2: officer fiduciary duties
Q3: fraudulent transaction
Q4: corporate insolvency
Q5: personal liability exceptions
Q6: Delaware cases combining these doctrines
Then retrieve independently and merge/rerank the authorities.
Research on legal RAG has also explored query rewriting specifically because ordinary user questions don't necessarily resemble the language used in legal authorities.
For a production implementation, I'd start with:
| Layer | Recommendation |
|---|---|
| Document parsing | Custom legal parser + OCR fallback |
| Storage | PostgreSQL |
| Full-text search | Elasticsearch/OpenSearch |
| Vector search | pgvector or Qdrant |
| Graph | Neo4j or PostgreSQL graph tables |
| Embeddings | Legal-domain embedding model |
| Reranking | Legal cross-encoder |
| LLM | Strong reasoning model |
| Citation resolver | Deterministic service |
| Evaluation | LegalBench-RAG + CLERC + custom corpus |
| Observability | OpenTelemetry + retrieval traces |
You don't necessarily need all of these as separate services. For an MVP, Postgres + pgvector + Elasticsearch/OpenSearch + one LLM can be enough.
I'd measure:
Recall@5
Recall@20
Recall@100
MRR
nDCG
Authority Recall
Pinpoint Recall
Citation precision
Citation recall
Citation entailment
Pinpoint accuracy
Citation validity
Authority appropriateness
Answer correctness
Groundedness
Unsupported-claim rate
Citation completeness
Jurisdiction accuracy
Court-hierarchy accuracy
Temporal validity
Precedential-status accuracy
Treatment accuracy
CLERC is especially useful because it explicitly evaluates both finding the relevant citations and using those citations to construct analysis; its authors found substantial difficulty even for contemporary systems.
LegalBench-RAG, Stanford/Princeton's reasoning-focused benchmarks, and Legal RAG Bench give you complementary ways to test retrieval and end-to-end performance.
If your goal is a serious legal-research product, my preferred pipeline is:
┌─────────────────┐
│ Legal documents │
└────────┬────────┘
↓
┌───────────────────────┐
│ Parse / OCR / segment │
└───────────┬───────────┘
↓
┌────────────────────────────┐
│ Citation + legal metadata │
└─────────────┬──────────────┘
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
BM25 Dense Citation
search retrieval graph
└──────────────┼──────────────┘
↓
Hybrid retrieval
↓
Legal reranker
↓
Authority selection
↓
Passage-level retrieval
↓
Evidence assembly
↓
LLM reasoning
↓
Claim → citation IDs
↓
Citation verification
↓
Deterministic renderer
↓
┌──────────────────────────┐
│ Answer + verified cites │
└──────────────────────────┘
The key design principle: treat this as a legal information-retrieval system with an LLM attached, rather than an LLM application with a vector database attached. Current benchmarks increasingly point in that direction: retrieval failures account for a substantial portion of apparent "hallucinations," and strong retrieval can materially raise the ceiling for the entire system.
If you tell me your target jurisdiction (e.g. U.S. federal, a particular state, UK, EU, or multi-jurisdictional) and whether this is for case-law research, contract research, litigation drafting, or general legal Q&A, I can turn this into a concrete Python/FastAPI architecture with schemas, retrieval algorithms, database design, and an implementation skeleton.
For a production implementation, I'd start with:
Building an optimized Retrieval-Augmented Generation (RAG) framework for legal citations requires precise chunking, hybrid search (lexical + semantic), and strict verification to prevent hallucinations of case law or statutes. Here is a structured, production-ready framework blueprint designed specifically for legal…
Building an optimized Retrieval-Augmented Generation (RAG) framework for legal citations requires precise chunking, hybrid search (lexical + semantic), and strict verification to prevent hallucinations of case law or statutes.
Here is a structured, production-ready framework blueprint designed specifically for legal text.
Core Architecture Components
Unstructured or Marker ) that preserve hierarchical legal structures (e.g., Title → Chapter → Section → Clause). Retain citation strings (e.g., 42 U.S.C.§§§ 1983, 345 U.S.** d 22) explicitly in the metadata.text-embedding-3-large or domain-specific legal embeddings) with sparse keyword search (BM25) using reciprocal rank fusion (RRF). Legal research heavily relies on exact statutory terms and case names.Cohere Rerank or BGE-Reranker ) to score retrieved chunks based on strict contextual relevance to the legal query.Recommended Open-Source & Enterprise Stack
Implementation Blueprint
title, chapter, section , and text fields.To tailor this framework further, let me know:
No sources cited
Yes. For legal work, I’d **avoid a generic “vector DB + LLM” RAG architecture**. Legal citation RAG needs retrieval, authority ranking, pinpoint evidence, citation validation, and provenance as first-class components. Recent evaluations support this: retrieval quality appears to be a major bottleneck in legal RAG, and…
Yes. For legal work, I’d avoid a generic “vector DB + LLM” RAG architecture. Legal citation RAG needs retrieval, authority ranking, pinpoint evidence, citation validation, and provenance as first-class components.
Recent evaluations support this: retrieval quality appears to be a major bottleneck in legal RAG, and legal-specific retrieval can materially outperform generic embeddings.
┌──────────────────────┐
│ User Question │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Legal Query Analyzer │
│ • jurisdiction │
│ • court │
│ • date / currency │
│ • legal issues │
│ • citation patterns │
└──────────┬───────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
Lexical Search Dense Retrieval Citation Search
BM25 Legal Embedder Shepard/KeyCite-
style graph
└─────────────────┬─────────────────┘
▼
┌──────────────────────┐
│ Candidate Fusion │
│ + Reranker │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Authority Ranker │
│ • binding? │
│ • jurisdiction │
│ • precedential value │
│ • subsequent history │
│ • date │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Evidence Extractor │
│ exact passages + │
│ page/¶/section │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ LLM │
│ evidence-constrained │
│ generation │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Citation Validator │
│ • exists? │
│ • supports claim? │
│ • pinpoint correct? │
│ • quotation exact? │
│ • authority current? │
└──────────┬───────────┘
│
┌──────────▼───────────┐
│ Answer + Citations │
│ + Evidence Provenance│
└──────────────────────┘
Don't chunk cases simply every 500–1,000 tokens.
Each passage should retain something like:
{
"document_id": "case_12345",
"citation": "Smith v. Jones, 123 F.3d 456",
"court": "2d Cir.",
"jurisdiction": "federal",
"decision_date": "2024-05-14",
"precedential": true,
"page": 461,
"paragraph": 27,
"section": "Discussion",
"parent_document": "case_12345",
"text": "...",
"source_url": "...",
"preceding_text": "...",
"following_text": "..."
}
Crucially, preserve parent-document relationships. A retrieved paragraph often needs surrounding paragraphs to correctly understand a holding or qualification. Research on legal RAG specifically identifies document-level retrieval mismatch as a serious problem.
I'd use hierarchical retrieval:
Case
├── Headnote / metadata
├── Procedural history
├── Facts
├── Issue
├── Analysis
│ ├── proposition
│ ├── exception
│ └── application
└── Holding / disposition
Retrieve the precise passage first, then expand to its parent/context window.
For legal citations, dense retrieval alone is not enough.
Use:
A useful scoring model is:
FinalScore =
0.30 * semantic_relevance
+ 0.20 * lexical_relevance
+ 0.15 * citation_graph_score
+ 0.15 * authority_score
+ 0.10 * jurisdiction_match
+ 0.05 * temporal_relevance
+ 0.05 * passage_specificity
Don't treat those weights as universal—they should be learned/tuned against your evaluation set.
This emphasis on retrieval is especially important: Legal RAG Bench's 2026 evaluation found retrieval to be a primary determinant of downstream performance and reported substantial gains from legal-domain embeddings.
Don't ask the LLM:
"Answer this question and provide citations." Instead give it an evidence contract:
Every legal proposition must be supported by one or more
EVIDENCE objects.
You may cite only authorities present in the supplied evidence.
For each proposition:
- identify the supporting authority
- identify the exact passage
- provide the pinpoint
- distinguish holding from dicta where possible
If the evidence does not establish the proposition:
say "The retrieved authorities do not establish this."
Then have the model produce structured intermediate output:
{
"claims": [
{
"text": "A district court generally lacks authority to ...",
"evidence": [
{
"document_id": "case_123",
"page": 461,
"paragraph": 27,
"support": "direct"
}
],
"confidence": 0.94
}
]
}
Only after validation render that into Bluebook or another citation style.
This is probably the most important architectural difference from ordinary RAG.
For every generated citation, check:
Citation existence
Does "123 F.3d 456" actually exist?
Bibliographic correctness
case name
volume
reporter
page
court
year
Pinpoint correctness
Does page 461 actually contain the relevant proposition?
Entailment
Does the cited passage actually support the generated claim?
Negative treatment
Has the cited authority subsequently been:
- overruled?
- reversed?
- vacated?
- superseded?
- distinguished?
Quote verification
If the answer contains a quotation, perform an exact text comparison against the primary source.
This gives you:
Generated claim
↓
Citation
↓
Primary source
↓
Exact passage
↓
Entailment / support test
↓
PASS / FAIL
That separation is important because legal RAG research shows that systems can produce superficially plausible answers while failing at retrieval or grounding.
I'd maintain something like:
┌─────────────┐
│ Supreme Ct. │
└──────┬──────┘
│ controls
▼
┌─────────────┐
│ Circuit A │
└──────┬──────┘
│ follows
▼
┌─────────────┐
│ District X │
└─────────────┘
Case A ──cites──► Case B
Case A ──distinguishes──► Case C
Case D ──overrules──► Case E
Case F ──quotes──► Case A
Then retrieval can answer a much more useful question than:
"What documents are semantically similar?" It can answer:
"What currently authoritative authorities support this particular proposition in this jurisdiction?" That's a major distinction.
For an open-source implementation, I'd build roughly:
| Component | Recommendation |
|---|---|
| Document parsing | PyMuPDF + specialized legal parser |
| Storage | PostgreSQL |
| Vector index | pgvector initially |
| Lexical retrieval | PostgreSQL FTS / OpenSearch |
| Embeddings | Legal-domain embedding model |
| Reranker | Legal/domain cross-encoder |
| Citation graph | PostgreSQL tables or Neo4j |
| Retrieval orchestration | Custom Python service |
| LLM | Model-agnostic |
| Citation normalization | Custom parser + legal citation library |
| Evaluation | LegalBench-RAG + CLERC + Legal RAG Bench |
| Observability | Langfuse / OpenTelemetry |
| API | FastAPI |
I'd avoid committing to a vector database too early. For a legal corpus, PostgreSQL + pgvector + full-text search + a citation graph can take you surprisingly far.
Don't use only answer-level accuracy.
Track at least:
Retrieval
─────────
Recall@5
Recall@20
Recall@100
MRR
nDCG
Citation
────────
Citation precision
Citation recall
Citation completeness
Citation correctness
Pinpoint accuracy
Quote accuracy
Grounding
─────────
Claim → evidence entailment
Unsupported claim rate
Hallucinated authority rate
Authority
─────────
Correct jurisdiction
Correct court
Correct precedential status
Treatment-history accuracy
Generation
──────────
Legal answer accuracy
Issue coverage
Contradiction rate
LegalBench-RAG is particularly useful for evaluating the retrieval layer; it contains thousands of expert-annotated query/evidence relationships and explicitly emphasizes precise passage retrieval rather than merely finding the correct document.
CLERC is useful for case-law citation retrieval and retrieval-augmented legal analysis.
And Legal RAG Bench is useful for end-to-end testing because it explicitly separates retrieval, groundedness, and generation failures.
There's also an open evaluation framework, legal-rag-eval, that focuses specifically on legal RAG and citation verification.
If you're actually building this for production, I'd make the core principle:
Retrieve → rank authority → extract evidence → generate claims → verify citations → render answer rather than:
Retrieve → stuff chunks into context → ask LLM. The resulting system is closer to a legal research engine with an LLM interface than a conventional RAG chatbot—and that's what I'd recommend for citation-sensitive legal applications.
If you tell me your jurisdiction (e.g. U.S. federal, New York, UK) and whether your corpus is case law, statutes/regulations, briefs, or all three, I can lay out the actual database schema, retrieval algorithm, citation-validation pipeline, and Python implementation.
Building a RAG framework optimized for legal citations requires handling strict constraints: exact statute/case referencing, handling complex hierarchical documents (codes, sections, subsections), and avoiding hallucinations on legal reasoning. Here is a blueprint for a high-accuracy Legal RAG framework, optimized for…
Building a RAG framework optimized for legal citations requires handling strict constraints: exact statute/case referencing, handling complex hierarchical documents (codes, sections, subsections), and avoiding hallucinations on legal reasoning.
Here is a blueprint for a high-accuracy Legal RAG framework, optimized for precise attribution and citation integrity.
Standard fixed-size chunking destroys legal context. Legal texts must be chunked structurally.
$jurisdiction$, $court$, $date_filed$, $title_number$, $section_number$ , and $parent_heading$.Legal search requires a mix of exact keyword matching (for specific code numbers like 42 U.S.C. § 1983 ) and semantic search (for conceptual arguments).
text-embedding-3-large or Legal-BERT-Base ) to capture conceptual similarity.Passing raw retrieved chunks often introduces noise that misleads the LLM.
Cohere Re-rank or BGE-Reranker-Large ) trained on legal datasets to score passage relevance relative to the query.The LLM must be constrained to only cite from the provided context and format citations following strict standards (e.g., Bluebook format).
response_text and a separate list of citations mapped to specific chunk IDs.To tailor this framework further, let me know:
No sources cited
If your primary requirement is **accurate, verifiable legal citations**, I would not use a vanilla “vector DB + LLM” RAG. I’d build a **citation-aware, jurisdiction-aware hybrid RAG** where citations are first-class objects. Recent legal-RAG work strongly supports this direction: retrieval quality is a major…
If your primary requirement is accurate, verifiable legal citations, I would not use a vanilla “vector DB + LLM” RAG. I’d build a citation-aware, jurisdiction-aware hybrid RAG where citations are first-class objects.
Recent legal-RAG work strongly supports this direction: retrieval quality is a major bottleneck, while newer systems explicitly model legal hierarchy, temporal validity, and citation graphs rather than relying on semantic similarity alone.
┌─────────────────────┐
│ User Query │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Query Understanding │
│ • jurisdiction │
│ • court level │
│ • date/as-of │
│ • legal issue │
│ • citation intent │
└──────────┬──────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌─────▼─────┐
│ Lexical IR │ │ Vector IR │ │ Citation │
│ BM25 / FTS │ │ embeddings │ │ Graph │
└──────┬──────┘ └──────┬──────┘ └─────┬─────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌──────────▼──────────┐
│ Legal Reranker │
│ authority + │
│ jurisdiction + │
│ recency + relevance │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Evidence Builder │
│ exact passages + │
│ citation metadata │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ LLM Generator │
│ evidence-constrained│
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Citation Validator │
│ existence + support │
│ + quotation check │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ Answer + Citations │
└─────────────────────┘
Don't store a case as simply:
document_id
chunk_text
embedding
Instead, I'd use something closer to:
{
"document_id": "case_12345",
"case_name": "Smith v. Jones",
"citation": "123 F.3d 456",
"court": "11th Cir.",
"jurisdiction": "US",
"decision_date": "2024-03-15",
"precedential": true,
"status": "good_law",
"docket_number": "...",
"parent_document": "...",
"citations": [
{
"target": "456 U.S. 789",
"type": "followed"
}
],
"passages": [
{
"passage_id": "p17",
"text": "...",
"page": 12,
"paragraph": 43,
"holding": true,
"legal_proposition": "..."
}
]
}
That lets you retrieve the proposition and the authority supporting it, rather than merely finding semantically similar text.
I would use three retrieval channels:
Then combine them with reciprocal-rank fusion or a learned reranker.
This is particularly important because legal retrieval isn't equivalent to ordinary semantic search. Research on precedent-aware RAG explicitly argues that jurisdiction, authority and temporal constraints need to influence retrieval.
For U.S. case law, Free Law Project's wiki.free.law are particularly useful.
You can model:
Case A
│
├── cites ──────────> Case B
│
├── follows ────────> Case C
│
├── distinguishes ──> Case D
│
└── overrules ──────> Case E
The distinction matters enormously.
A naive RAG might retrieve an old case because it is highly similar. Your legal RAG should instead be able to conclude:
Highly relevant, but subsequently overruled → exclude from authoritative evidence. CourtListener exposes APIs for citation networks as well as citation lookup/verification; its citation-lookup API is specifically described as a guardrail against hallucinated citations.
I'd give every retrieved authority a score approximately like:
AuthorityScore =
semantic_relevance
+ lexical_relevance
+ jurisdiction_match
+ court_authority
+ precedential_status
+ citation_centrality
+ temporal_validity
+ proposition_match
- negative_treatment
For example:
Weight
semantic relevance 0.20
proposition match 0.25
jurisdiction 0.15
court authority 0.15
precedential status 0.10
temporal validity 0.10
citation graph 0.05
The exact weights should be learned/tuned against your evaluation set rather than treated as universal.
This is one of the biggest differences between a legal RAG and generic RAG.
For legal citation generation, I would strongly favor:
Case
↓
Relevant proposition
↓
Supporting passage
↓
Pinpoint location
rather than:
Case
↓
20-page chunk
↓
LLM
LegalBench-RAG specifically emphasizes retrieving minimal, highly relevant passages rather than large document chunks, partly because precise evidence makes citation generation easier and more reliable.
A useful evidence object would be:
{
"authority": "Ashcroft v. Iqbal, 556 U.S. 662 (2009)",
"passage": "...",
"page": 678,
"proposition": "Plausibility requires more than a mere possibility...",
"retrieval_score": 0.93,
"authority_score": 0.97
}
Don't let the LLM freely generate:
Smith v. Jones, 123 F.3d 456 (11th Cir. 2023) Instead:
LLM drafts proposition
↓
Extract citations
↓
Resolve citations against corpus
↓
Check cited case exists
↓
Check citation metadata
↓
Check retrieved passage supports proposition
↓
Check subsequent treatment
↓
Accept / reject / regenerate
CourtListener's citation lookup API can parse citations from blocks of text and resolve them, and its underlying Eyecite system is designed specifically for identifying legal citations.
This gives you an important invariant:
No citation reaches the user unless it resolves to an actual authority in the corpus. You can make an even stronger invariant:
No citation reaches the user unless the retrieved evidence supports the proposition attributed to it.
Have the model internally produce something like:
{
"claims": [
{
"claim": "A plaintiff must plead sufficient facts to make liability plausible.",
"support": [
{
"authority_id": "case_123",
"passage_id": "case_123_p45"
}
]
}
]
}
Then your renderer turns that into:
A plaintiff must plead sufficient facts to make liability plausible. Smith v. Jones, 123 F.3d 456, 461 (11th Cir. 2024). This is much safer than asking the model to invent Bluebook citations from scratch.
For a production U.S. legal-research system, I'd consider:
| Layer | Recommendation |
|---|---|
| Document store | PostgreSQL + object storage |
| Full text | Elasticsearch/OpenSearch |
| Vector search | pgvector, Qdrant, or Elasticsearch |
| Citation graph | PostgreSQL initially; Neo4j if graph queries become substantial |
| Citation parsing | Eyecite |
| Legal extraction | LexNLP can extract courts, regulations and citations from unstructured legal text. GitHub |
| Case-law source | CourtListener + licensed sources where required |
| Embeddings | Legal-domain embedding model + strong general embedding baseline |
| Reranker | Cross-encoder / legal reranker |
| LLM | Strong instruction-following model with structured output |
| Evaluation | LegalBench-RAG + your proprietary gold set |
| Observability | Retrieval traces + evidence/citation audit log |
I'd also look at LegalGraphRAG, published at ACL 2026. Its architecture is quite close to what I'd recommend: a hierarchical legal graph plus separate Researcher, Auditor and Adjudicator stages.
Don't evaluate this system solely with "answer accuracy."
Track at least:
Retrieval
├── Recall@k
├── Precision@k
├── MRR
└── proposition recall
Citation
├── citation existence
├── citation correctness
├── citation completeness
├── citation entailment
└── pinpoint accuracy
Authority
├── jurisdiction correctness
├── precedential-status correctness
├── temporal validity
└── negative-treatment detection
Generation
├── factual accuracy
├── groundedness
├── unsupported-claim rate
└── refusal/abstention quality
Legal RAG research increasingly treats retrieval and generation errors separately; Legal RAG Bench, for example, is designed specifically for end-to-end evaluation and reports that retrieval can be the dominant determinant of downstream performance.
If I were building this today, I'd use:
Hybrid RAG + citation graph + temporal authority filter + proposition-level retrieval + evidence-constrained generation + automatic citation verification.
In shorthand:
Query
↓
Legal query parser
↓
BM25 ──────┐
Dense ─────┼──→ Hybrid candidate retrieval
Citation ──┘
↓
Jurisdiction / temporal / authority filtering
↓
Legal reranker
↓
Proposition + pinpoint evidence extraction
↓
LLM structured answer
↓
Citation resolver
↓
Citation/evidence entailment checker
↓
Regenerate if unsupported
↓
Final answer with verified citations
That architecture is much closer to a legal research engine than a conventional RAG chatbot. The recent literature is moving in exactly this direction: hierarchical legal graphs, citation-aware retrieval, temporal constraints, and explicit evidence verification.
If you're building this for U.S. case law specifically, I can also lay out the actual Python/FastAPI + PostgreSQL/pgvector + Elasticsearch + CourtListener architecture, including the database schema, retrieval pipeline, reranking formula, citation-verification code, and an example end-to-end implementation.