Simple RAG over Indian Court Judgments
Summary
Chunk a versioned judgment manifest, embed it locally, and return the passage with its character offsets.
Companion code: 02-simple-rag in indian-legal-ai-lab.
Previous: Part 1: LLM Fundamentals in C#
Part 1 only works when you paste the passage. This part builds the index that chooses the passage.
What you will be able to do
- Load the checksummed manifest.
- Cut a judgment into chunks that remember their character offsets.
- Rank chunks with a local vector and print the case id and chunk id.
The manifest is the source of truth
`CorpusLoader.Load` walks up from the application directory until it finds `sample-data/manifest.json`. It hashes the file and compares it with `manifest.sha256`. A mismatch throws before any record is built. Each row becomes a `JudgmentRecord`. If `textFile` points at a file, the text is loaded and `ExtractionStatus` is `extracted`. Metadata-only rows stay without text.
The committed set has extracted text for Romesh Thappar (`1950INSC14`) and Kharak Singh (`1962 INSC 389`). Puttaswamy and the other large PDFs are metadata only. Fictional rows have titles that start with `FICTIONAL`.
Chunks keep a location
`TextChunker.Chunk` uses a 900-character window and a 120-character overlap. Each piece stores `Start`, `End`, and a `ChunkId` of `{caseId}:{index}`.
chunks.Add(new TextChunk
{
ChunkId = $"{record.CaseId}:{index}",
CaseId = record.CaseId,
Index = index,
Text = slice,
Start = start,
End = start + len
});
start += Math.Max(1, size - overlap);An empty judgment returns no chunks. That is how a metadata-only case stays out of retrieval.
The vector is a hash, not a model
`HashEmbeddingGenerator` tokenizes to words longer than two characters, folds case, and adds 1 to one of 64 buckets:
hash = unchecked((hash * 31) + ch);
return (hash & int.MaxValue) % Dimensions;`VectorMath.Normalize` divides by the Euclidean length. `LegalIndex.Add` embeds each chunk and stores an `IndexedChunk`. `Search` with method `vector` embeds the query and ranks by cosine, which is a dot product of those unit vectors.
Run it
dotnet run --project labs/02-simple-ragThe lab searches for `freedom of speech Romesh Thappar` and prints score, case id, and chunk id for three hits. `Hybrid_search_finds_thappar_for_speech_query` locks the top hybrid hit for a related query to `1950 INSC 14`.
Tradeoff
Hashing vectors need no model download. They also confuse short shared legal words, which Part 3 measures on a Kharak Singh query. The passage object still carries the citation and the character span, so a later answer can point at the source even when the rank is wrong.
Next
Part 3: Advanced Legal Retrieval
Verified in this revision
`EmbeddingService.EmbedAsync` calls `/v1/embeddings` only when a base URL and `HttpClient` are present, and marks that result `IsSemantic`. With neither, it returns the 64-dimension hash vector, provider `hash-offline`, and `IsSemantic` false. `Embedding_without_a_provider_is_labeled_offline` checks that fallback. The hash path is still the index used by the labs.
More in Applied AI
LLM Fundamentals in C#
A supplied judgment can be summarized only from passages you pass in. The same client, with no passage, names a citation that the catalog rejects.
Advanced Legal Retrieval
Keyword, hashing-vector, and hybrid search on the same question, plus the SQL a pgvector store would run.
Function Calling and Legal Research Tools
Deterministic C# tools for case metadata, passages, and a short constitutional catalog, with rejected blank input.