Triage a multilingual corpus
Short social and conversational texts are often ambiguous. A useful first pass should accept strong routes and send uncertain texts for review.
This example reads either CSV or JSONL, applies the default router and writes one JSON object per input record. The accepted_language field is empty when the score margin is below the threshold.
import argparse
import json
from low_resource_nlp import LexicalLanguageRouter
from low_resource_nlp.datasets import iter_text_records
parser = argparse.ArgumentParser()
parser.add_argument("path")
parser.add_argument("--min-score-margin", type=float, default=0.75)
args = parser.parse_args()
router = LexicalLanguageRouter.default()
for record_number, record in enumerate(iter_text_records(args.path), start=1):
route = router.route_selectively(
record.text,
min_score_margin=args.min_score_margin,
)
print(
json.dumps(
{
"record": record_number,
"expected_language": record.language,
"proposed_language": route.decision.language_code,
"accepted_language": route.accepted_language_code,
"score_margin": route.score_margin,
"abstention_reason": route.abstention_reason,
},
ensure_ascii=False,
)
)
Save the script as route_corpus.py, then run:
python route_corpus.py examples/sample_texts.jsonl > routes.jsonl
The original text is not written to the output in this example. That is useful when routing results need to be shared without reproducing the source material.
Inspecting an uncertain record
The corpus pass identifies records for review. The audit API gives a closer look at one of them:
from low_resource_nlp import audit_code_switching
audit = audit_code_switching("abeg make una check this model output")
for token in audit.tokens:
print(token.text, token.accepted_language_code, token.score_margin, token.signals)
Token offsets in the audit refer to the original text, so a review interface can mark the exact evidence without re-tokenising the record.
Measuring a policy
When corpus-level language labels are available, report coverage alongside accuracy. Accuracy among accepted records can rise simply because harder examples have been rejected.
import json
from low_resource_nlp import LexicalLanguageRouter, selective_classification_report
from low_resource_nlp.datasets import iter_text_records
router = LexicalLanguageRouter.default()
truth = []
predictions = []
accepted = []
for record in iter_text_records("labelled_samples.jsonl"):
route = router.route_selectively(record.text, min_score_margin=0.75)
truth.append(record.language or "")
predictions.append(route.decision.language_code)
accepted.append(route.accepted)
report = selective_classification_report(truth, predictions, accepted)
print(json.dumps(report, indent=2))
Choose thresholds on development data, then keep them fixed for the test set. The AfriSenti benchmark follows this split and records the selected policy, confidence intervals and per-language coverage.
Before using the routes
- Check results separately for every language represented in the corpus.
- Keep diacritics unless the downstream method requires their removal.
- Review rejected and code-mixed records as a separate group; do not treat them as noise.
- Do not interpret a text route as a person's identity or language community.