← Applied AI
Applied AI· 2 min read

Simple RAG over Indian Court Judgments

.NETAI EngineeringIndian Case Law

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-rag

The 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