Model
Daisy Global
A voice agent model that listens, thinks and speaks in 16 languages, in one voice. Set it up in the console with no code, or connect to it directly over the OpenAI Realtime protocol and configure everything from your own code.
At a glance
| Model id | daisy-global |
| Languages | English, Hindi, Tamil, Spanish, French, German, Portuguese, Italian, Dutch, Arabic, Japanese, Chinese, Korean, Russian, Indonesian, Turkish |
| Default voice | jessica |
Pick it when: Your callers speak English or one of the world languages below.
- One voice for the whole call, in every language it speaks.
- It follows the caller from one language to another. A language it cannot speak is answered in English, and the agent says once which languages it can help in.
- A short “mhm” or “okay” while the agent talks does not interrupt it. Only a real interruption stops the reply.
Need other languages? Daisy India: English and ten Indian languages, in one voice.
Two ways to use it
| In the console | Direct connection | |
|---|---|---|
| Prompt, voice, language, tools | Set once in the agent builder | Sent by your code, per session |
| Where tools run | On our server (Actions, Knowledge) | On your side: we send the call, you send the result |
| Credential | A deployment key, per endpoint | An API key, per workspace |
| Protocol | The widget, or the deployment protocol | OpenAI Realtime |
| Good for | A voice agent on a site or app with little code | Replacing OpenAI Realtime or Gemini Live in a product you already have |
Set it up in the console
- Open Agents and create an agent. On the Voice tab, pick the Daisy Global card. That sets the speech engines and the reply model for you.
- Fill in the settings below, then talk to the agent from its page.
- Click Publish. On the Share tab, click Add an endpoint and keep Authentication on Deployment key.
- Copy the key under Issued deployment key. It is shown once. Store it on your server as
VOICE_ORCH_DEPLOYMENT_KEY.
| Tab | Setting | What it does |
|---|---|---|
| Instructions | System prompt, Subject, Anything else, Style | What the agent knows and how it talks. |
| Instructions | Welcome message | What the agent says first. |
| Voice | Voice | A voice from the list below. |
| Voice | Language | Auto follows the caller. Pick one to pin the call to it. |
| Voice, Advanced | Pause before the agent replies | 0.3 s (recommended), 0.6 s or 0.9 s of silence before a reply. |
| Voice, Advanced | Let callers interrupt | Whether the caller can talk over the agent. |
| Knowledge | Documents and pages | What the agent can look up while it talks. |
| Actions | Tools | Functions our server calls for the agent. |
Your server mints a short-lived token for each call, and the page or app connects with it. The deployment key never leaves your server.
POST https://voice-orch-backend-100366934313.northamerica-northeast1.run.app/api/public/deployments/{publicId}/token
Authorization: Bearer <VOICE_ORCH_DEPLOYMENT_KEY>
Content-Type: application/json
{
"endUserId": "customer-123",
"sessionConfig": {
"voice": { "languageCode": "en", "vad": { "silenceDurationMs": 600 } }
}
}
# answer
{ "connectToken": "...", "websocketUrl": "wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/agents/{publicId}/ws",
"expiresAt": 1791234567, "deploymentId": "...", "publicId": "..." }Then drop in the widget (Quickstart, Widget reference), or open websocketUrl?token=<connectToken> yourself and speak the deployment protocol. The token is single use.
sessionConfig may change, for one call: voice.languageCode, voice.voiceId, voice.ttsOutputFormat and the voice.vad values (threshold, prefixPaddingMs, silenceDurationMs, interruptResponse, bargeInThreshold, bargeInFrames). Nothing else: the token reaches a browser, and a browser must not be able to rewrite your agent’s prompt or model.
Get a key
For a direct connection, open API keys and create a key with Realtime voice ticked under Services included. It expires in 30 days, 90 days (the default), a year, or never. The key (voa_live_…) is shown once and stored only as a hash: keep it on your server. Revoking it stops new connections at once; calls already connected run on until they end.
Connect from your server
Daisy speaks OpenAI’s Realtime protocol. A client written for OpenAI Realtime moves here by changing three things: the URL, the key and the model.
import WebSocket from 'ws';
const ws = new WebSocket('wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime?model=daisy-global', {
headers: { Authorization: `Bearer ${process.env.DAISY_API_KEY}` },
});
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
console.log(event.type, event);
});from openai import AsyncOpenAI
client = AsyncOpenAI(api_key="voa_live_...", base_url="https://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1")
async with client.realtime.connect(model="daisy-global") as connection:
await connection.session.update(session={
"type": "realtime",
"instructions": "You are the booking assistant for Sunrise Clinic. Keep answers short.",
"audio": {
"input": {"transcription": {"language": "en"}},
"output": {"voice": "sarah"},
},
})
async for event in connection:
print(event.type)The OpenAI Agents SDK takes the same URL in model_config: {"url": "wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime?model=daisy-global", "api_key": "voa_live_..."}.
Connect from a browser
Never put the API key in a web page. Your server asks for a short-lived client secret and hands that to the browser, exactly as with OpenAI. client.realtime.client_secrets.create() in the OpenAI SDK sends this request.
POST https://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime/client_secrets
Authorization: Bearer voa_live_...
Content-Type: application/json
{
"expires_after": { "anchor": "created_at", "seconds": 600 },
"session": {
"type": "realtime",
"model": "daisy-global",
"instructions": "You are the booking assistant for Sunrise Clinic. Keep answers short.",
"audio": { "output": { "voice": "sarah" } }
}
}
# answer
{ "value": "ek_daisy_...", "expires_at": 1791234567, "session": { ... } }// With the OpenAI Agents SDK
import { RealtimeAgent, RealtimeSession } from '@openai/agents-realtime';
const session = new RealtimeSession(new RealtimeAgent({ name: 'Assistant' }), {
transport: 'websocket',
model: 'daisy-global',
});
await session.connect({ apiKey: clientSecret, url: 'wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime?model=daisy-global' });
// Or a plain WebSocket: the secret goes in a subprotocol
const ws = new WebSocket('wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime?model=daisy-global', ['realtime', `openai-insecure-api-key.${clientSecret}`]);- A secret works once, for the model it names, and must be used before it expires: 10 seconds to 2 hours, 10 minutes by default. The call then lasts as long as any other.
- The
sessionin it applies before anything the browser sends; the browser can still change it withsession.update. - Errors:
400for another model or a badexpires_after,401for a missing, invalid or expired key,403for a key without Realtime voice,413for a session too large. - A long-lived key sent from a browser still connects, but an
errorof typewarning(codeinsecure_api_key) followssession.created.
Configure the session
session.created arrives as soon as the socket opens. Send one session.update; session.updated answers with the whole session as it is now in effect.
{
"type": "session.update",
"session": {
"type": "realtime",
"instructions": "You are the booking assistant for Sunrise Clinic. Keep answers short.",
"audio": {
"input": {
"format": { "type": "audio/pcm", "rate": 24000 },
"transcription": { "language": "en" },
"turn_detection": { "type": "server_vad", "silence_duration_ms": 300 }
},
"output": {
"format": { "type": "audio/pcm", "rate": 24000 },
"voice": "sarah"
}
},
"tools": [
{
"type": "function",
"name": "find_slot",
"description": "Find free appointment slots for a doctor on a date.",
"parameters": {
"type": "object",
"properties": {
"doctor": { "type": "string" },
"date": { "type": "string", "description": "YYYY-MM-DD" }
},
"required": ["doctor", "date"]
}
}
],
"tool_choice": "auto"
}
}| Field | Values and default | Mid-call |
|---|---|---|
instructions | Text. Empty by default. | Yes, but the next two replies are slower (see Steering) |
audio. | See Voices. Default jessica. | Yes, free |
audio. | A code from Languages, "auto", or leave out. Default auto. | Yes, from the caller’s next sentence |
audio., audio. | See Audio. Default PCM 24 kHz. | Yes |
audio. | server_vad (default) or semantic_vad; both run our endpointer. | |
…turn_detection. | 0 to 1; higher needs louder speech. Default in session.created. | Fixed once the caller first speaks |
…turn_detection. | Silence before a reply. Default in session.created. | Fixed once the caller first speaks |
…turn_detection. | Audio kept before speech. Default in session.created. | Fixed once the caller first speaks |
…turn_detection. | true (default) or false. | Yes, free |
tools | Function tools, flat format. None by default. | Yes, costs like instructions |
tool_choice | auto (default), none, required, or a named function. | Yes |
output_modalities | ["audio"] (default) or ["text"]. | Yes, free |
max_output_tokens | A number, or "inf" (default). | Yes, free |
temperature | A number. Server default. | Yes, free |
- Fixed once the caller first speaks: sending the same value again is fine; a different one gets an
error. - Accepted, not applied:
transcription.model,transcription.prompt,noise_reduction,eagerness,create_response,idle_timeout_ms,speed,tracing,include,truncation,prompt. - Refused with an error:
turn_detection: null(push to talk), a voice or audio format this model does not have, and tools that are not functions (MCP). The rest of the same update still applies. - Tools are flat:
name,descriptionandparametersat the top level. Flatten Chat Completions tools ({"type": "function", "function": {...}}). - The older beta shape (flat
voice,input_audio_format,turn_detection,modalities) is accepted too.
Keep instructions and tools byte-identical across sessions. The model’s cache is keyed on them, so the first reply of a new session is fast (0.5 to 0.7 s to first text). Each request must fit in 8,192 tokens: keep instructions plus tools under about 3,500, and put anything per caller in a system item.
Languages
audio.input.transcription.language takes one of these codes to hear and answer in that language. Leave it out, or send "auto", to follow the caller. Regional spellings (hi-IN, en-US) mean the same. Pin it when your callers speak one language: very short replies are easy to mishear as another.
| Code | Language |
|---|---|
en | English |
hi | Hindi |
ta | Tamil |
es | Spanish |
fr | French |
de | German |
pt | Portuguese |
it | Italian |
nl | Dutch |
ar | Arabic |
ja | Japanese |
zh | Chinese |
ko | Korean |
ru | Russian |
id | Indonesian |
tr | Turkish |
Voices
audio.output.voice. Voices by name, or by voice id. Ask us if you need a voice that is not listed. The default is jessica. The others: sarah, laura, alice, matilda, bella, lily, river, roger, charlie, george, callum, harry, liam, will, eric, chris, brian, daniel, adam, bill.
OpenAI voice names (alloy, marin, cedar and the rest) are accepted and use the default voice. Any other name gets an error, and the voice stays as it was.
Audio
| Format | In | Out |
|---|---|---|
{"type": "audio/pcm", "rate": 24000} | Yes | Yes |
{"type": "audio/pcmu"}, 8 kHz G.711 | Yes | Yes |
{"type": "audio/pcma"}, 8 kHz G.711 | Yes | Yes |
{"type": "audio/pcm", "rate": 16000} | Yes | No |
- Mono, 16-bit little-endian, base64 in JSON text frames. We resample on our side.
- Send 20 to 100 ms per message, as it is captured. At most 256 KB per message.
- No faster than 4 times real time over 10 s: pace by the clock. A file pushed faster ends the call.
- Keep sending audio, silence included, for the whole call.
Turns and interruptions
Turns are decided on our server: it finds the caller's speech, then judges whether they have finished, so a pause mid-thought does not always end the turn. speech_started arrives within about 15 ms of the audio_start_ms it reports, speech_stopped within about 60 ms.
While the agent speaks, a short acknowledgement sends no speech_started and does not interrupt. A real interruption looks like this:
input_audio_buffer.speech_started stop playback now
... a few more audio deltas of that reply may follow: drop them
response.output_audio_transcript.done
response.output_audio.done
response.content_part.done
response.output_item.done status "incomplete"
conversation.item.done
response.done status "cancelled", reason "turn_detected"
input_audio_buffer.speech_stopped
input_audio_buffer.committed
conversation.item.added the caller's turn
conversation.item.input_audio_transcription.delta / .completed
conversation.item.done
response.created the next reply- Drop audio after
speech_startedfor the reply in progress, until itsresponse.done. - A reply can resume. If the interruption was nothing (a cough), the unheard part is spoken again as a new response.
- We trim the interrupted reply in our history to what we estimate the caller heard.
conversation.item.truncateis acknowledged, but itsaudio_end_msis not applied yet. response.cancelstops the reply at once (response.done, reasonclient_cancelled).interrupt_response: falselets every reply finish.- Push to talk (
turn_detection: null) is not supported yet.input_audio_buffer.commitends the caller’s speech early, but the server still decides when the turn is over.
Transcripts
The caller’s come complete: one conversation.item.input_audio_transcription.delta with the whole text, then .completed, about 0.8 to 1.5 s after they stop. The recognisers revise their partials, so partials would garble. Custom vocabulary is not supported yet.
The agent’s stream: one response.output_audio_transcript.delta per phrase, sent before its audio (by 0.2 to 0.8 s). The deltas join up to the .done transcript.
Tools
Your tools run on your side. When the model calls one, the response ends with a function_call item. Run it, send the result, and the answer comes as a new response.
response.created resp_A
response.output_item.added function_call, call_id "chatcmpl-tool-94ec..."
response.function_call_arguments.delta the whole arguments, once
response.function_call_arguments.done name "find_slot", arguments "{...}"
response.output_item.done
response.done status "completed", output [function_call]
-> you send the function_call_output (and response.create)
conversation.item.added / .done your function_call_output
response.created resp_B, the answer
response.output_audio_transcript.delta, response.output_audio.delta ...
response.done status "completed", output [message]if (event.type === 'response.function_call_arguments.done') {
const args = JSON.parse(event.arguments || '{}');
const result = await runMyTool(event.name, args); // your code
ws.send(JSON.stringify({
type: 'conversation.item.create',
item: {
type: 'function_call_output',
call_id: event.call_id, // echo it exactly
output: JSON.stringify(result), // a string
},
}));
ws.send(JSON.stringify({ type: 'response.create' })); // optional
}call_idis opaque (ours look likechatcmpl-tool-…). Echo it back exactly.response.createafter the result is optional: the model continues as soon as it has the result, and we absorb the extra one OpenAI clients send.- Several calls in one turn (look up, then book): each ends its own response. Handle every call as it arrives.
- No time limit. If the caller speaks while you work, the turn moves on and a result sent after that gets an
error. - Do not count on a spoken “let me check” before a slow tool: the model often calls it straight away, and we add no filler of our own.
- Tool results are not kept in the history after the reply. If a later turn needs an id from a result, restate it in a system item.
Text, and speaking first
output_modalities: ["text"]replies inresponse.output_text.*instead of audio.- Send the caller’s words as text:
conversation.item.createwith ausermessage, thenresponse.create. - Agent speaks first: send
response.createright aftersession.update, optionally with"response": {"instructions": "Greet the caller."}. response.createcan setinstructions,output_modalitiesandmax_output_tokensfor one reply. Tools, voice and temperature there are not applied, and awarningsays so.
Steering a live call
Push guidance into a live call with a system item. It starts, cancels and delays nothing, and costs nothing.
{"type": "conversation.item.create", "item": {"type": "message", "role": "system",
"content": [{"type": "input_text", "text": "The caller is an existing patient of Dr. Rao."}]}}Changing instructions instead makes the model reread its whole context. Measured with a 2,700-token prompt and 19 tools, time to the reply’s first text:
| Before the reply | Median |
|---|---|
| Nothing changed | 0.95 s |
| A system item was added | 0.89 s |
instructions changed: that reply | 2.07 s |
instructions changed: the reply after | 2.0 s |
- Each system item stays in the history (the last 48 items, inside 8,192 tokens). Keep them to a sentence or two. Items cannot be deleted.
- A note sent while the caller is still speaking shapes the reply to that turn; one sent after they stop shapes the next.
- After a reconnect, send the earlier conversation as
user,assistantandsystemitems before the first turn. None of them triggers a reply.
Events
Every event carries an event_id; events about a reply also carry response_id, item_id, output_index and content_index. Server to client:
| Event | When | Key fields |
|---|---|---|
session. | The socket opened. | session (the values in effect, expires_at) |
session. | After each session.update. | session (the whole session now) |
error | Something you sent was refused, a warning, or a server failure. | error.type, error.code, error.message, error.param, error.event_id |
input_audio_buffer. | The caller started speaking, or really interrupted. Stop playback. | audio_start_ms, item_id |
input_audio_buffer. | The caller stopped speaking. | audio_end_ms, item_id |
input_audio_buffer. | The server ended the caller’s turn. | item_id, previous_item_id |
input_audio_buffer. | Answer to input_audio_buffer.clear. | |
conversation. | An item joined the conversation: a caller turn, a reply, a tool call, or your own item. | item, previous_item_id |
conversation. | That item is complete. | item |
conversation. | Answer to conversation.item.truncate. | item_id, content_index, audio_end_ms |
conversation. | The caller’s whole transcript, once per turn. | item_id, delta |
conversation. | The caller’s final transcript. | item_id, transcript, usage.seconds |
response. | A reply started. | response.id |
response. | The reply’s message or tool call started. | item |
response. | The reply’s audio or text part started. | part |
response. | A chunk of the agent’s audio. | delta (base64, in your output format) |
response. | A phrase of what the agent says, sent before its audio. | delta |
response. | A chunk of a text reply. | delta |
response. | A tool call’s whole arguments, once. | call_id, delta |
response. | A tool call for you to run. | call_id, name, arguments |
response. | What the agent said in this reply. | transcript |
response. | The last audio of this reply was sent. Your playback ends later. | |
response. | The full text of a text reply. | text |
response. | The part is complete. | part |
response. | The item is complete. | item.status: completed or incomplete |
response. | The reply ended. Always the last event of a response. | response.status, status_details, output, usage |
Client to server. Anything else is answered with an error:
| Event | What it does |
|---|---|
session. | Set or change settings (see Configure the session). |
input_audio_buffer. | Caller audio: {"audio": "<base64>"}. |
input_audio_buffer. | End the caller’s current speech now. The server still decides when the turn is over. |
input_audio_buffer. | Acknowledged. Audio already sent is not recalled. |
conversation. | A message (role user, system or assistant, text content) or a function_call_output. |
conversation. | Acknowledged. audio_end_ms is not applied yet. |
response. | Ask for a reply. Optional instructions, output_modalities, max_output_tokens. |
response. | Stop the reply in progress. |
- Not sent:
conversation.item.deleted,.retrieved,input_audio_transcription.failed,input_audio_buffer.timeout_triggered,output_audio_buffer.*,rate_limits.updated, MCP events, andresponse.cancelled(a cancelled reply ends withresponse.done). - Our diagnostics: add
&daisy_events=1to the URL for ourplatform.*events (usage per reply and per turn, barge-in, warnings). Off by default, because the OpenAI SDK misreads types it does not know. - Beta clients (header
OpenAI-Beta: realtime=v1) get the beta names:response.audio.delta,response.audio_transcript.delta,response.text.delta,conversation.item.created.
Errors and close codes
{"type": "error", "event_id": "event_...", "error": {"type": "invalid_request_error",
"code": "unknown_event", "message": "Unknown event type 'made.up'.", "param": "type",
"event_id": "<your event_id, if you sent one>"}}error.type is invalid_request_error, server_error or warning (log it and carry on). Codes: invalid_json, invalid_event, unknown_event, unsupported_event, unsupported_value, invalid_value, conversation_already_has_active_response, model_not_available, server_error, insecure_api_key.
| Close | Meaning | Retry |
|---|---|---|
1008 | Bad, expired or unscoped key; used or expired secret; unknown model. The reason says which. | No, fix it |
1009 | A message over 256 KB. | No, send smaller chunks |
1011 | The model is not available right now (an error comes first), or a server failure. | Yes, with backoff |
1012 | The server is restarting. | Yes, with backoff: new session |
1013 | The server is at capacity and did not start this session. | Yes, with backoff |
4408 | 30 minutes reached, or 60 s with no messages from you. | New session |
4409 | A per-call cost limit (an error comes first). | New session |
4429 | Too many calls for this key, or too many connection attempts. | Yes, with backoff |
Limits
| Call length | 30 minutes, in session.expires_at. No warning first. |
| No messages from you | Closed after 60 s. Silent audio counts; the clock stops while a tool call waits. |
| Calls at once, per key | 10 |
| New connections | 10 a minute from one address, per model |
| Agent speech per call | 32,000 characters |
| Model output per call | 64,000 tokens |
| Turns per call | 240 |
| History | The last 48 items, inside 8,192 tokens |
Measured end of speech to first audio, on our test setup and without your network: English 2.12 s median (2.42 s p90).
Moving from OpenAI Realtime
- Change the URL to
wss://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1/realtime(or the SDKbase_urltohttps://voice-orch-backend-100366934313.northamerica-northeast1.run.app/v1), the key to your API key, and the model todaisy-global. - Move a WebRTC browser client to the WebSocket transport, with client secrets.
- Set the language if your callers speak one, and pick a voice from the list above.
- Flatten Chat Completions tools, and keep server turn detection.
Everything else (instructions, tools, event names, ids, interruption, text in and out) works as on OpenAI. From Gemini Live, move the client to this protocol: setup becomes session.update, 16 kHz input is accepted as {"type": "audio/pcm", "rate": 16000}, toolCall is response.function_call_arguments.done, and interrupted is input_audio_buffer.speech_started.