Skip to main content
AI & Voice

Building a Multilingual Voice AI Pipeline with Bhashini for Public Welfare Surveys

By Shiva Prasad

A real-time speech pipeline on Bhashini covering 14 Indian languages, running speech recognition, translation, and synthesis end to end in under two seconds.

Platform

Web & Telephony

Duration

2 Months

<2s

Pipeline latency

14

Languages supported

0

Human translators needed

Project overview

We built a real-time speech pipeline on Bhashini, the Government of India's national language technology platform, that takes a citizen's spoken answer in their own language, transcribes it, translates it, and replies in synthesized speech, in under two seconds end to end.

Fourteen Indian languages went live. No human translator sits anywhere in the loop. Static survey content is synthesized ahead of time and served from storage, so the audio a citizen hears has no synthesis latency at all, while personalized fragments are generated on demand and stitched in.

The wider point is that production-grade multilingual voice for India does not require expensive commercial APIs or a compromise on which languages you support. Bhashini's models are open, publicly funded, and good enough to ship on, provided you route requests to the right model for each language and design for the failure modes of a three-stage pipeline that depends on an external service.

Platform

Web & Telephony

Duration

2 Months

Type

AI & Voice

Stack

8 technologies

The challenge

A public welfare program needed to run large-scale citizen surveys across several Indian states. The system it had only worked in English, which meant the majority of the people it was meant to reach could not take part without someone sitting beside them to interpret.

Human interpreters were the existing workaround, and they did not scale. A pool of translators could handle a few hundred calls a day against a target measured in the hundreds of thousands, and every additional language meant recruiting for it separately. Survey quality suffered too: an interpreter paraphrasing a question in real time introduces variation that makes responses harder to compare across states.

The commercial alternatives did not solve it either. Speech recognition and synthesis from the large cloud providers is strong in English and thin across Indian regional languages, particularly outside the handful with the largest speaker counts. Where coverage existed, per-minute pricing made a survey run at national scale financially unviable before it started.

English-only surveys excluded the majority of the target population

Human translators created bottlenecks and couldn't scale beyond a few hundred calls per day

Commercial speech APIs lacked reliable support for most Indian regional languages

Per-minute pricing from commercial providers made large-scale deployment financially unviable

No unified pipeline existed that could handle speech recognition, translation, and synthesis in a single flow

What we set out to do

  • 01

    Build a unified STT → Translation → TTS pipeline supporting 14 Indian languages

  • 02

    Achieve end-to-end voice pipeline latency under 2 seconds

  • 03

    Integrate with Bhashini for sovereign, cost-effective speech services

  • 04

    Enable citizens to complete surveys entirely in their native language without human intervention

  • 05

    Pre-generate survey audio assets at scale using async queue processing

How we solved it

01

Onboarding and the two-call Bhashini flow

Bhashini is run by the Digital India Bhashini Division under MeitY as part of the National Language Translation Mission, and access starts with registration on ULCA, the Universal Language Contribution API platform that fronts it. An integrator registers, verifies by email, then generates credentials from the My Profile section: a userID and a ulcaApiKey. Bhashini currently caps an integrator at five keys, and registered app names must be lower-case and may contain underscores.

Inference is not a single call. Every request is a pair. The first is the pipeline config call, a POST to https://meity-auth.ulcacontrib.org/ulca/apis/v0/model/getModelsPipeline carrying userID and ulcaApiKey as headers, along with the task sequence you want and a pipeline ID. It answers with the serviceId and modelId that will serve each task for the language pair you asked for.

That same response carries the endpoint and credential for the second call, under pipelineInferenceAPIEndPoint: a callbackUrl, which for the Dhruva pipeline is https://dhruva-api.bhashini.gov.in/services/inference/pipeline, and an inferenceApiKey whose name is Authorization and whose value is the token to send. The compute call goes there.

The config response is a capability negotiation rather than just an auth handshake. It tells you which models can actually serve the language pair you asked for, and it is stable for a given pipeline and pair, which makes it a good candidate for a cache rather than something to run on the hot path of every request.

Key decision

Bhashini over Google Cloud Speech / AWS Transcribe

Result

Coverage for 14 Indian languages. Significantly more cost-effective at scale.

02

Chaining ASR, translation, and TTS in one compute call

The compute call accepts a pipelineTasks array and Bhashini runs the tasks in sequence, feeding each stage's output into the next. A full speech-to-speech round trip is three entries: asr, translation, tts.

Each task carries its own config block. The asr task takes language.sourceLanguage, a serviceId, an audioFormat, and a samplingRate. The translation task takes language.sourceLanguage and language.targetLanguage plus its own serviceId. The tts task takes language.sourceLanguage, a serviceId, and a gender for the voice. Input audio goes in base64 under inputData.audio[].audioContent, and text-only pipelines use inputData.input[].source instead.

The language codes have to line up across the chain or the request fails: the ASR source and the translation source must match, and the translation target must match the TTS source. It reads as an obvious constraint and it is still a common source of confusing errors, because a mismatch surfaces at the stage that received bad input rather than at the stage that was misconfigured.

We tracked latency per stage rather than only end to end. With three models behind one HTTP call, an end-to-end number tells you that you missed two seconds but not which stage to fix, and the three stages do not degrade at the same rate under load.

Key decision

Three-stage pipeline with per-stage latency tracking

Result

End-to-end pipeline latency under 2 seconds.

03

Automated translation instead of human interpreters

Survey questions are authored once, in one language, and the pipeline delivers them in whichever language the respondent speaks. Bhashini's neural machine translation service handles the conversion in both directions: question out, answer back.

Model choice matters here, because Bhashini exposes several translation services with different coverage. bhashini/iiith/nmt-all from IIIT Hyderabad handles 34 languages bidirectionally, while AI4Bharat's IndicTrans v2, exposed as ai4bharat/indictrans-v2-all-gpu--t4, covers 18 including English, Hindi, Telugu, Tamil, and Sanskrit. Narrower models from IIT Bombay, CDAC, and AUKBC serve specific pairs.

Removing the interpreter removed the bottleneck, and it also removed a source of variance. A machine translation of a survey question is identical on every call, which is what makes responses comparable across states and languages. An interpreter paraphrasing under time pressure produces a slightly different question every time.

Key decision

Automated NMT over manual/human translation workflows

Result

Zero human translators needed. Full round-trip translation handled automatically.

04

Pre-generating static audio, synthesizing only what changes

Most of what a survey says is fixed. The greeting, the question text, the instructions, and the closing do not change between respondents. Only the personalized fragments do, and there are far fewer of those than there is fixed content.

So we split the work. Fixed content is synthesized ahead of time through BullMQ queues backed by Redis, processed with FFmpeg, and stored in Azure Blob Storage, keyed by language. By the time a call connects, that audio already exists and is served straight from storage with no synthesis step at all. Dynamic content uses variable placeholders, is synthesized in real time, and is stitched into the fixed audio.

The effect is that the expensive part of TTS is paid once per language during pre-generation rather than once per call. For a survey that runs hundreds of thousands of times, that is the difference between a synthesis bill and a storage bill.

It also takes the least predictable component off the critical path. TTS latency varies with text length and with load. A blob fetch does not.

Key decision

Queue-based pre-generation + real-time synthesis for dynamic variables

Result

Zero TTS latency for static content. Personalized audio without sacrificing speed.

05

Routing each language to the model that serves it

Bhashini does not expose one model per task. It exposes a catalog, and the services are grouped by language family as much as by task. On ASR, bhashini/ai4bharat/conformer-multilingual-asr covers all 22 scheduled languages, while other services are scoped to Dravidian languages, to Indo-Aryan languages, or to specific sets from IIT Madras. TTS is organized the same way: Bhashini/IITM/TTS spans 23 languages, AI4Bharat's Coqui-based services are split by family, and IISc's covers 11.

That means the serviceId is not a constant you set once. Sending every language to a single broad multilingual model is the simple option and it is not always the accurate one, because a family-specific model can outperform a general one on the languages it was built for. Routing per language means maintaining a table and revisiting it when Bhashini publishes new services.

This is the part of a Bhashini integration that is easiest to underestimate. The API surface is small and the payloads are simple. The work is in knowing which of the available models to send each language to, and confirming that with your own audio rather than trusting a catalog description.

Key decision

Per-language service routing rather than one multilingual model everywhere

Result

Model selection tuned per language instead of left to a single default.

06

Isolating failures across three dependent stages

A three-stage pipeline on an external service has three places to fail and one HTTP response to explain it. When TTS fails after ASR and translation have already succeeded, the useful output is not a generic error. It is the transcription and the translation you already have, plus a clear signal about which stage dropped.

So each stage is independently observable, with its own latency and its own failure mode, rather than the pipeline being treated as one opaque call. That is also what makes per-stage latency tracking worth the instrumentation: the same data that tells you where the two seconds went tells you what broke.

The dependency is worth stating plainly, because it is the main risk of building on Bhashini. It is an external, publicly funded service, and your pipeline is only as available as it is. Designing for partial failure is not defensive over-engineering here. It is the difference between a survey call that degrades and one that ends.

Key decision

Per-stage error isolation over treating the pipeline as one opaque call

Result

Failures attributable to a stage rather than surfacing as one generic pipeline error.

Measurable impact

<2s

End-to-end pipeline latency

14

Indian languages supported

0

Human translators needed

0ms

TTS latency for pre-generated survey audio

Tech stack

NNestJsBBhashini Dhruva APIBBullMQRRedisWWebSocketsFFFmpegAAzure Blob StorageTTypeScript

What we learned

This project demonstrated that building production-grade multilingual voice systems for India does not require expensive commercial APIs or a compromise on language coverage. Fourteen languages, under two seconds end to end, and no human translator anywhere in the loop, on a platform that is publicly funded and open to any registered integrator.

What it costs you instead is care in the places the API does not cover: choosing the right model for each language, keeping language codes aligned across three chained stages, and designing for the failure of an external dependency you do not control.

  • 01

    Bhashini provides viable, production-ready speech AI for Indian languages, but the serviceId you route each language to matters as much as the pipeline you build around it.

  • 02

    Pre-generating static audio through a queue moves TTS cost and latency off the critical path entirely. Fixed content is synthesized once per language, not once per call.

  • 03

    Dynamic variable support is what makes pre-generation usable. Without it you either give up personalization or give up the latency win.

  • 04

    Error isolation across pipeline stages is critical when three models sit behind one HTTP call and only one of them failed.

  • 05

    The pipeline config call is a capability negotiation, not just authentication. It reports which models can serve a language pair, and it belongs in a cache rather than on every request.

  • 06

    Bhashini's published terms scope the free API to proof-of-concept use. Anything running in production, or charging end users, needs a conversation with the Bhashini team about a paid plan.

Ready to build something that matters?

We solve problems that don't have Stack Overflow answers. Let's talk.

Book a Discovery Call