> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rumik.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# prompting silk mulberry 1.5

> how to steer silk mulberry 1.5 with a natural-language description.

export const VoiceSample = ({tone, label, note, description, text, src}) => {
  const TONE_PILL = {
    happy: "bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-300",
    excited: "bg-orange-100 text-orange-700 dark:bg-orange-500/10 dark:text-orange-300",
    sad: "bg-sky-100 text-sky-700 dark:bg-sky-500/10 dark:text-sky-300",
    whisper: "bg-indigo-100 text-indigo-700 dark:bg-indigo-500/10 dark:text-indigo-300",
    angry: "bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-300",
    neutral: "bg-gray-100 text-gray-600 dark:bg-white/10 dark:text-gray-300"
  };
  const PLAY_EVENT = "voicesample:play";
  const fmtTime = s => {
    if (!s || !isFinite(s)) return "0:00";
    const m = Math.floor(s / 60);
    const sec = Math.floor(s % 60).toString().padStart(2, "0");
    return m + ":" + sec;
  };
  const audioRef = React.useRef(null);
  const idRef = React.useRef(null);
  if (idRef.current === null) idRef.current = Math.random();
  const [playing, setPlaying] = React.useState(false);
  const [cur, setCur] = React.useState(0);
  const [dur, setDur] = React.useState(0);
  const [copied, setCopied] = React.useState(false);
  React.useEffect(() => {
    const onOther = e => {
      if (e.detail !== idRef.current && audioRef.current) {
        audioRef.current.pause();
        setPlaying(false);
      }
    };
    window.addEventListener(PLAY_EVENT, onOther);
    return () => window.removeEventListener(PLAY_EVENT, onOther);
  }, []);
  const toggle = () => {
    const a = audioRef.current;
    if (!a) return;
    if (playing) {
      a.pause();
      setPlaying(false);
    } else {
      window.dispatchEvent(new CustomEvent(PLAY_EVENT, {
        detail: idRef.current
      }));
      a.play();
      setPlaying(true);
    }
  };
  const copyValue = description ? "description: " + description + "\ntext: " + text : text;
  const copy = () => {
    if (navigator.clipboard) navigator.clipboard.writeText(copyValue);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  const seekTo = (clientX, el) => {
    const a = audioRef.current;
    if (!a || !dur) return;
    const rect = el.getBoundingClientRect();
    const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
    a.currentTime = ratio * dur;
    setCur(a.currentTime);
  };
  const pct = dur ? cur / dur * 100 : 0;
  const pill = label ? TONE_PILL.neutral : TONE_PILL[tone] || TONE_PILL.neutral;
  const pillLabel = label || "[" + (tone || "neutral") + "]";
  const lbl = "text-gray-400 dark:text-gray-500";
  return <div className="not-prose my-3 flex flex-col gap-3 rounded-2xl border border-gray-200 bg-white p-4 dark:border-white/10 dark:bg-white/[0.03]">
      <div className="flex items-start justify-between gap-3">
        <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
          <span className={"rounded-md px-2 py-0.5 font-mono text-xs font-semibold " + pill}>
            {pillLabel}
          </span>
          {note && <span className="text-xs text-gray-500 dark:text-gray-400">{note}</span>}
        </div>
        <button type="button" onClick={copy} aria-label={copied ? "Copied" : "Copy prompt"} className="flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-xs text-gray-500 transition hover:bg-gray-100 hover:text-gray-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 dark:text-gray-400 dark:hover:bg-white/10 dark:hover:text-gray-200">
          {copied ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <path d="M20 6 9 17l-5-5" />
            </svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <rect x="9" y="9" width="13" height="13" rx="2" />
              <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
            </svg>}
          {copied ? "copied" : "copy"}
        </button>
      </div>

      <div className="overflow-hidden rounded-lg bg-gray-50 px-3 py-2.5 font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-gray-700 dark:bg-white/5 dark:text-gray-300">
        {description ? <>
            <span className={lbl}>description:</span> {description}
            {"\n"}
            <span className={lbl}>text:</span> {text}
          </> : text}
      </div>

      <audio ref={audioRef} src={src} preload="metadata" onLoadedMetadata={e => setDur(e.currentTarget.duration)} onTimeUpdate={e => setCur(e.currentTarget.currentTime)} onEnded={() => {
    setPlaying(false);
    setCur(0);
    if (audioRef.current) audioRef.current.currentTime = 0;
  }} />

      <div className="flex items-center gap-3">
        <button type="button" onClick={toggle} aria-label={playing ? "Pause" : "Play " + (label || tone || "voice") + " sample"} className="flex size-9 shrink-0 items-center justify-center rounded-full bg-gray-900 text-white transition hover:bg-gray-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-900 focus-visible:ring-offset-2 dark:bg-white dark:text-gray-900 dark:focus-visible:ring-white dark:focus-visible:ring-offset-gray-900">
          {playing ? <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
              <rect x="6" y="5" width="4" height="14" rx="1" />
              <rect x="14" y="5" width="4" height="14" rx="1" />
            </svg> : <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
              <path d="M8 5v14l11-7z" />
            </svg>}
        </button>

        <div role="slider" aria-label="Seek" aria-valuemin={0} aria-valuemax={Math.round(dur) || 0} aria-valuenow={Math.round(cur)} tabIndex={0} onClick={e => seekTo(e.clientX, e.currentTarget)} onKeyDown={e => {
    const a = audioRef.current;
    if (!a || !dur) return;
    if (e.key === "ArrowRight") {
      a.currentTime = Math.min(dur, a.currentTime + 2);
      setCur(a.currentTime);
    } else if (e.key === "ArrowLeft") {
      a.currentTime = Math.max(0, a.currentTime - 2);
      setCur(a.currentTime);
    }
  }} className="relative h-1.5 flex-1 cursor-pointer rounded-full bg-gray-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 dark:bg-white/15">
          <div className="absolute inset-y-0 left-0 rounded-full bg-gray-900 dark:bg-white" style={{
    width: pct + "%"
  }} />
        </div>

        <span className="shrink-0 font-mono text-xs tabular-nums text-gray-500 dark:text-gray-400">
          {fmtTime(cur)} / {fmtTime(dur)}
        </span>
      </div>
    </div>;
};

mulberry is steered by a **description**: one natural sentence that says how the
voice should sound. write the voice you want, then your spoken text. hear one:

<VoiceSample label="podcast host" description="a female 30s hindi voice, normal pitch, smooth timbre, conversational pacing, energetic, casual register, like a podcast host." text="आज का episode थोड़ा अलग है। एक minute के लिए सीधा बैठ जाओ।" src="/audio/mulberry/podcast-host.mp3" />

## the description formula

weave the attributes into one sentence instead of listing them as fields. a useful
shape is:

> a `{gender}` `{age}` `{accent}` voice, `{pitch}` pitch, `{timbre}`,
> `{pacing}` pacing, `{emotion}`, `{register}` register, like a `{role}`.

the `{...}` parts are placeholders you fill in. mention only the ones you care
about; the model fills in the rest.

<CodeGroup>
  ```text use theme={null}
  a deep male 30s indian voice, slow pacing, formal register, like a corporate training narrator.
  ```

  ```text avoid theme={null}
  30s. indian. deep. slow. formal. corporate narrator.
  ```
</CodeGroup>

## hear different voices

each one follows the formula. press play, and copy any sample to start from it.

<VoiceSample label="podcast host" description="a female 30s hindi voice, normal pitch, smooth timbre, conversational pacing, energetic, casual register, like a podcast host." text="आज का episode थोड़ा अलग है। एक minute के लिए सीधा बैठ जाओ।" src="/audio/mulberry/podcast-host.mp3" />

<VoiceSample label="streamer" description="a male 20s american voice, high pitch, smooth timbre, very fast pacing, excited, casual register, like a streamer reacting live." text="oh my god, did you see that play? that was insane!" src="/audio/mulberry/streamer.mp3" />

<VoiceSample label="narrator" description="a male 40s british voice, low pitch, gravelly timbre, slow pacing, neutral, formal register, like a dramatic narrator." text="the door creaked open. nobody was there. and yet, something watched." src="/audio/mulberry/narrator.mp3" />

<VoiceSample label="support agent" description="a female 30s indian voice, normal pitch, warm timbre, conversational pacing, neutral, neutral register, like a customer support agent." text="मैं आपकी help के लिए यहाँ हूँ। एक minute, मैं check करती हूँ।" src="/audio/mulberry/support.mp3" />

<VoiceSample label="storyteller" description="a female 30s hindi voice, low pitch, warm timbre, slow pacing, neutral, casual register, like a storyteller." text="एक बार की बात है, एक छोटे से गाँव में एक लड़की रहती थी।" src="/audio/mulberry/storyteller.mp3" />

<Note>
  **saying numbers, IDs and dates.** silk normalizes your `text` before speaking it, so
  amounts, dates, times and percentages are read out for you. anything that must be read
  digit by digit, like an OTP or an order number, has to be wrapped in double quotes. your
  `description` is never touched. see [numbers, IDs & dates](/normalization).
</Note>

## send it

put the voice in `description` and your spoken text in `text`. add `speaker` to use
a preset voice instead of a description.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://silk-api.rumik.ai/v1/tts \
    -H "Authorization: Bearer rk_live_•••••••••" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "mulberry",
      "text": "आज का episode थोड़ा अलग है।",
      "description": "a female 30s hindi voice, smooth timbre, conversational pacing, casual register, like a podcast host"
    }' \
    --output mulberry.wav
  ```

  ```python python theme={null}
  import requests

  resp = requests.post(
      "https://silk-api.rumik.ai/v1/tts",
      headers={"Authorization": "Bearer rk_live_•••••••••"},
      json={
          "model": "mulberry",
          "text": "आज का episode थोड़ा अलग है।",
          "description": "a female 30s hindi voice, smooth timbre, conversational pacing, casual register, like a podcast host",
      },
  )
  with open("mulberry.wav", "wb") as f:
      f.write(resp.content)
  ```

  ```javascript javascript theme={null}
  const resp = await fetch("https://silk-api.rumik.ai/v1/tts", {
    method: "POST",
    headers: {
      Authorization: "Bearer rk_live_•••••••••",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "mulberry",
      text: "आज का episode थोड़ा अलग है।",
      description:
        "a female 30s hindi voice, smooth timbre, conversational pacing, casual register, like a podcast host",
    }),
  });
  const fs = require("fs");
  fs.writeFileSync("mulberry.wav", Buffer.from(await resp.arrayBuffer()));
  ```
</CodeGroup>

see the [API reference](/api-reference) for the full request schema.

## voice attributes

mention any of these in your description. write hindi words in devanagari and
english words in latin, e.g. `आज का episode थोड़ा अलग है`.

* **gender**: `male`, `female`
* **age**: `20s`, `30s`, `40s`
* **pitch**: `low`, `normal`, `high`
* **pacing**: `very slow`, `slow`, `conversational`, `brisk`, `fast`, `very_fast`
* **emotion**: `neutral`, `energetic`, `excited`, `sad`, `sarcastic`, `dry`, `crying`, `angry`
* **intensity**: `low`, `med`, `high`
* **register**: `formal`, `neutral`, `casual`

<AccordionGroup>
  <Accordion title="accents">
    **global**: `american`, `british`, `middle_eastern`, `asian_american`, `indian`

    **indian regional**: `hindi`, `punjabi`, `bihari`, `south_indian`, `bengali`,
    `rajasthani`, `marathi`, `gujarati`, `kashmiri`, `assamese`, `odia`, `telugu`,
    `kannada`, `malayali`, `haryanvi`, `chhattisgarhi`
  </Accordion>

  <Accordion title="timbre">
    **realistic**: `deep`, `warm`, `gravelly`, `smooth`, `raspy`, `nasally`,
    `throaty`, `harsh`, `whisper`

    **creative**: adds `robotic`, `ethereal` to the realistic set
  </Accordion>

  <Accordion title="speaking roles">
    pick a role from a domain to anchor the delivery style.

    * **social**: `youtube_vlogger`, `social_media_creator`, `influencer_voice`, `streamer_companion`
    * **podcast**: `podcast_host`, `interviewer`
    * **commercial**: `ad_narrator`, `brand_spokesperson`, `product_demo_voice`, `sales_pitch_voice`
    * **education**: `elearning_instructor`, `kids_story_voice`
    * **support**: `customer_support_agent`, `virtual_receptionist`, `healthcare_assistant`
    * **entertainment**: `storyteller`, `social_media_reaction`, `meme_voice`
    * **corporate**: `explainer_video_voice`, `event_host`, `corporate_training_narrator`
    * **viral**: `short_form_narrator`, `meme_voice`
  </Accordion>

  <Accordion title="creative-only attributes">
    available when you want a non-realistic timbre (characters, stylized voices):

    ```text theme={null}
    animated_cartoon  ai_machine_voice  alien_scifi  seductively  flirty  anime
    cyborg  pirate  dark_villain  demon  gangster  mafia  dramatic_narrator
    mythical_godlike_magical  spy  vampire  alpha
    ```
  </Accordion>
</AccordionGroup>

## inline tags

drop these in the `text` to trigger a sound. they render as part of the
performance, not as words.

```text theme={null}
<laugh>  <laugh_harder>  <sigh>  <chuckle>  <gasp>  <angry>  <excited>
<whisper>  <cry>  <scream>  <sing>  <snort>  <exhale>  <gulp>  <giggle>
<sarcastic>  <curious>
```

## preset voices

your `description` is always required, and on its own it's what the voice is built
from. add `speaker` to pin one of the twelve named voices instead:

| voice                                                   | gender |
| ------------------------------------------------------- | ------ |
| `emma` `mia` `sophia` `ava` `ira` `siya` `aisha` `zoya` | female |
| `lucas` `noah` `theo` `adam`                            | male   |

names are case-insensitive, and `description` still goes with the request. see
[the mulberry page](/mulberry#preset-voices) for a full request.

## using mulberry in a voice agent

mulberry reads a **complete sentence or utterance** at once, not a running token
stream. but it **streams the audio out**, so the first audio comes back before the
whole line finishes synthesizing. wait until your LLM has a full sentence, send that,
and stream the audio to the caller. that makes it a good fit for low-latency
conversational agents.

**keep one voice across the conversation.** the `description` defines the voice, so
for a given bot send the **same `description` string on every request**. reusing it
keeps the persona consistent turn to turn. a preset `speaker` is the other way to
pin a fixed voice.

an unknown or misspelled inline tag is **spoken literally, not rejected**, so
validate the text before production. for transport, see [streaming](/streaming) and
the [pipecat integration](/pipecat).

### system prompt for your agent's LLM

hand this to the LLM that writes mulberry's lines. tune the wording, not the attributes.

```text theme={null}
You write voice descriptions and spoken text for the silk mulberry 1.5
text-to-speech model.

- "description" is ONE natural sentence describing the voice. Weave 3 to 5
  attributes together; never list them as separate fields.
- Draw attributes from these only: gender (male/female); age (20s/30s/40s);
  accent (e.g. american, british, indian, hindi, punjabi, bengali, south_indian);
  pitch (low/normal/high); timbre (deep, warm, gravelly, smooth, raspy, ...);
  pacing (slow, conversational, fast, ...); emotion (neutral, energetic, excited,
  sad, ...); register (formal, neutral, casual); and an optional role
  (e.g. podcast_host, customer_support_agent, storyteller).
- "text" is the spoken line: Hindi words in Devanagari, English words in Latin.
- You may add inline tags such as <laugh>, <sigh>, <chuckle> inside "text".

Return JSON: { "description": "...", "text": "..." }
```

## common mistakes

most "the voice ignored me" cases trace back to one of these:

| what you hear                 | likely cause                                       | fix                                                      |
| ----------------------------- | -------------------------------------------------- | -------------------------------------------------------- |
| an attribute is ignored       | the description lists fields, or packs in too many | write one sentence with 3 to 5 attributes                |
| the accent is wrong           | the accent is not in the supported vocabulary      | pick one from the accent list above (global or regional) |
| the voice sounds generic      | the description is too vague                       | add an age, an accent, a timbre, and a role              |
| a tag is read aloud as a word | the tag is misspelled or not in the supported set  | use an exact tag from the inline tags list               |
| pacing or pitch is off        | a value outside the listed vocabulary              | use the listed `pacing` and `pitch` values               |
| the audio is cut off early    | the line needs more tokens than the default cap    | raise `max_new_tokens` (default `2048`, up to `8192`)    |

## faq

<AccordionGroup>
  <Accordion title="how detailed should the description be?">
    one sentence with 3 to 5 attributes beats a paragraph of vague adjectives.
    mention only what you care about; the model fills in the rest.
  </Accordion>

  <Accordion title="description or preset speaker?">
    it isn't either/or. `description` is always required; send it every time. on
    its own, the voice is generated from it. add `speaker` when you want one of the
    twelve named voices instead of a generated one.
  </Accordion>

  <Accordion title="how do I keep the same voice across a conversation?">
    send the identical `description` on every request for that bot. pinning a
    `speaker` as well makes it steadier still.
  </Accordion>

  <Accordion title="can I add laughs and sighs?">
    yes. drop inline tags like `<laugh>`, `<sigh>`, `<chuckle>` directly in the
    `text` where you want the sound.
  </Accordion>
</AccordionGroup>
