build strategy · sarvam ai

Indian-language native, one key, one build.

Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a Lovable account ship a Sarvam AI native demo in one shot — Sarvam-105B for the reasoning, one Sarvam primitive (Bulbul / Saaras / Mayura) for the user surface.

Why Sarvam AI and not a generic English LLM?

Sarvam-105B reasons IN 22 Indian languages, with wiki_grounding for Indian context. Bulbul v3 speaks Hindi, Tamil, Bengali, Telugu, Kannada, Malayalam, Marathi, Gujarati, Punjabi and Odia with 30+ natural speakers. Saaras v3 transcribes code-mixed Hindi/English and telephony audio. Mayura translates across all 22 Indian languages with script control — all behind one HTTP API and one subscription key.

The recipe

recipe
# 1. In your Lovable project, add the single required secret (Settings -> Secrets):
SARVAM_API_KEY=sk_...        # https://dashboard.sarvam.ai
# Sarvam's auth header is "api-subscription-key" (NOT "Authorization: Bearer").

# 2. Copy a mega-prompt from this repo into Lovable. One paste:
#    - scaffolds the React + TanStack Start app
#    - writes a server function that proxies api.sarvam.ai
#      (Bulbul TTS / Saaras STT / Sarvam-105B chat / Mayura translate)
#    - wires the client surface (audio playback, mic recorder, chat, language switcher)
#    - includes the hackathon credit in the footer and in JSDoc on the server fn

# 3. Hit play. Your demo is reasoning + speaking in an Indian language, natively.

1. Bulbul TTS server function

Keep the api-subscription-key on the server with a TanStack createServerFn, POST to /text-to-speech, and return the base64 WAV that Sarvam already gives you.

src/lib/tts.functions.ts
// src/lib/tts.functions.ts — TanStack server function for Sarvam Bulbul v2
// Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";

export const speak = createServerFn({ method: "POST" })
  .inputValidator((d) => z.object({
    text: z.string().min(1).max(2000),
    languageCode: z.string().default("hi-IN"),
    speaker: z.string().default("anushka"),
  }).parse(d))
  .handler(async ({ data }) => {
    const r = await fetch("https://api.sarvam.ai/text-to-speech", {
      method: "POST",
      headers: {
        "api-subscription-key": process.env.SARVAM_API_KEY!,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        text: data.text,
        target_language_code: data.languageCode,
        speaker: data.speaker,
        model: "bulbul:v2",
        enable_preprocessing: true,
      }),
    });
    if (!r.ok) throw new Error(`Bulbul failed: ${r.status}`);
    const { audios } = await r.json() as { audios: string[] };
    return { audio: audios[0] }; // base64 WAV
  });

2. Sarvam-105B as the brain

The OpenAI-compatible endpoint at api.sarvam.ai/v1 works with @ai-sdk/openai-compatible. Override the header to api-subscription-key; everything else (tools, streaming, structured output) behaves like a normal AI SDK provider.

src/lib/sarvam.server.ts
// src/lib/sarvam.server.ts — Sarvam-105B via the OpenAI-compatible endpoint
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { createServerFn } from "@tanstack/react-start";
import { generateText } from "ai";
import { z } from "zod";

export function sarvam() {
  return createOpenAICompatible({
    name: "sarvam",
    baseURL: "https://api.sarvam.ai/v1",
    headers: { "api-subscription-key": process.env.SARVAM_API_KEY! },
  });
}

export const ask = createServerFn({ method: "POST" })
  .inputValidator((d) => z.object({
    question: z.string().min(1),
    languageCode: z.string().default("hi-IN"),
  }).parse(d))
  .handler(async ({ data }) => {
    const { text } = await generateText({
      model: sarvam()("sarvam-105b"),
      system: `Answer IN ${data.languageCode}. Be specific, ground facts in Wikipedia.`,
      prompt: data.question,
      providerOptions: { sarvam: { wiki_grounding: true } },
    });
    return { text };
  });

3. Mayura translation across 22 languages

src/lib/translate.functions.ts
// src/lib/translate.functions.ts — Mayura across 22 Indian languages
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";

export const translate = createServerFn({ method: "POST" })
  .inputValidator((d) => z.object({
    input: z.string().min(1),
    source: z.string().default("en-IN"),
    target: z.string(),    // e.g. "ta-IN", "bn-IN", "hi-IN"
  }).parse(d))
  .handler(async ({ data }) => {
    const r = await fetch("https://api.sarvam.ai/translate", {
      method: "POST",
      headers: {
        "api-subscription-key": process.env.SARVAM_API_KEY!,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        input: data.input,
        source_language_code: data.source,
        target_language_code: data.target,
        mode: "formal",
        model: "mayura:v1",
      }),
    });
    const { translated_text } = await r.json() as { translated_text: string };
    return { translated_text };
  });

Hackathon rules of thumb

  • · One mega-prompt = one build message. Don't iterate the architecture, iterate the UI copy.
  • · Keep the API key on the server. Browsers never touch api-subscription-key.
  • · Default the language picker to hi-IN so the demo opens in Hindi without a click.
  • · Use a font stack that covers Devanagari, Tamil, Bengali, Telugu, etc. — system fonts on macOS/iOS/Android handle this; on Windows fall back to Noto Sans Indic.
  • · Always show a microphone permission UX before starting Saaras recording, or browsers will silently block it.
  • · Footer: "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14".