A citizen asks in Hindi, in English, or — most often — in Romanized Hindi–English. The answer sits in a document written in the other language, in the other script. IndicRAG-QA finds it, cites it, or says it does not know.
One path,
three scripts.
Read the script
Devanagari, Latin, or both — and for Latin, whether Hindi function words like kya and ke liye make it Hinglish.
English · Indic · Code-MixedRetrieve twice
BM25 matches surface words; a multilingual encoder (e5-base) matches meaning across languages. 50 candidates each.
694 passagesFuse by eligibility
A passage in the other script is scored on the retriever that could see it — not docked for a vote BM25 could never cast.
script-aware RRFAnswer from evidence
The generator answers only from the retrieved passages and names the one it used.
cited passage idOr refuse
Below the BM25 floor, or when the generator finds no answer in the passages: "Insufficient information available in the provided documents."
4 answerability signalsBuilt once,
asked often.
Retrieve reads both offline indices; fusion reads each passage's script.
data/ ├── corpus_manifest.jsonl sha256 + licence per source ├── text/<doc_id>.txt extracted, before normalization ├── passages.jsonl 694 passages, frozen at v1 ├── lex/lexical.pkl TF-IDF + BM25 ├── emb/<model>.npy float32, L2-normalized ├── emb/<model>.meta.json passage fingerprint └── gen/<model>.jsonl generations, keyed by prompt hash
Nothing expensive is computed twice, and every cache carries the hash of its input: an index built from different passages refuses to load.
regenerate-reports.py --checkFusion was
the problem.
Cross-lingual Recall@5 on the sealed test split. Standard Reciprocal Rank Fusion docks a Devanagari passage for the lexical retriever's blindness to its script, and scores below the dense retriever it contains. Script-aware fusion counts only the retrievers that could have returned a passage — +0.411, p = 0.0001, no monolingual cost. Over dense retrieval alone it gains little: +0.030, significant only on code-mixed queries.
Retrieval makes answers groundable.
Share of answers the cited passage supports, closed-book against dense RAG, on 72 test questions (+0.684, p = 0.0001).
Which retriever does not reach the answer.
Token-F1, script-aware against dense RAG (p = 0.55). The generator still loses more than retrieval: 0.359 against 0.223.
Only a reader sees a missing fact.
Near-miss questions caught by the generator's own abstention. A BM25 score threshold catches about half; the fused score, almost none.
Watch the
gold sink.
BM25
Dense (e5-base)
Fused
Plain RRF scores each passage Σ 1/(60 + rank) over the retrievers that returned it. Script-aware RRF multiplies by 2 / |eligible|, where BM25 is ineligible for a passage in a script other than the query's. Both are recomputed here in your browser from the real candidate lists (50 per retriever) for ; the rankings match index/hybrid.py.
Knowing
when not to.
Questions are real items from the gold set. Recall is the share of each class correctly refused: the BM25 floor on the 273-item test split (report-answerability-bm25.txt); the generator's own abstention on 85 held-out items of an enriched sample (report-answerability-signals.txt). Each class rests on 7–23 items.
Ask it
something.
Evidence
Every system,
one test split.
| Recall@5 | all | monolingual | cross-lingual | code-mixed |
|---|
216 answerable test questions, scored once after tuning on the dev split; 95% bootstrap intervals and paired tests in evals/report-retrieval-test.txt. The gold set was verified in two language-model passes, with the test split then reviewed by the author; the blind second pass was a separate model instance.