Set up a session
What your server mints for a Nuance Live session, the AI persona's own tools, and the room your page joins.
Mint a session token
Your server mints the token with POST /session-tokens and your API key, as for any conversation. A Nuance Live token also names the interface, and settles the session in interface_setup:
// On your server: a session token for a Nuance Live conversation whose
// backend can run one of your tools.
const NUANCE_API = process.env.NUANCE_API_URL ?? "https://api.nuancelabs.ai";
const GET_WEATHER = {
type: "function",
name: "get_weather",
description: "Looks up the current weather in a city.",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
};
/** Mints a token for `endUserId`, your id for the signed-in person. */
export async function mintNuanceLiveToken(endUserId: string): Promise<string> {
const response = await fetch(`${NUANCE_API}/session-tokens`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NUANCE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
interface: "nuance_live",
// The persona's instructions: say when it hands work to its backend.
personality: {
system_prompt:
"You are Sam, a friendly weather assistant. When the user asks about " +
"the weather, hand the question to your backend, then tell them what it found.",
},
interface_setup: {
delegation: {
type: "responses",
responses: {
// Required: any non-empty name.
model: "default",
instructions:
"Use get_weather for any weather question, then answer in one short sentence.",
tools: [GET_WEATHER],
},
},
// The persona may hang up after its goodbye.
runtime_tools: { end_call: true },
},
end_user_id: endUserId,
}),
});
if (!response.ok) {
throw new Error(
`Couldn't mint a token: ${response.status} ${await response.text()}`,
);
}
// `warnings` names each tool the session will run without, and why.
const { token } = (await response.json()) as { token: string };
return token;
}
| Field | What it sets |
|---|---|
personality.system_prompt | The AI persona's instructions: who it is, and when it hands work to the backend. The persona decides on its own when to hand off, so say plainly which requests need a hand-off. Give it a name in the prompt. |
interface_setup.delegation | Who does the work the persona hands off: your page (client, the default), a backend that calls your tools (responses), or nobody (none). See Delegation and tools. |
interface_setup.video.input.camera | false keeps the persona from using the user's camera: the persona doesn't look at it, and it isn't recorded. Left out, the deployment's own setting applies. |
interface_setup.runtime_tools | The persona's own tools, on or off (below). |
expressiveness | How much the persona's face and head move, from 0 to 2: lower is calmer, higher more expressive. Left out, 1: the persona's own tuned level. |
end_user_id | Your id for the person, as for any token. |
- The token fixes the session. The page can't change any of this later, except the delegation's settings that
session.updateallows. - Tools go in the delegation. A Nuance Live session never runs the token's top-level
tools; the token'swarningssay so if you send them. expressivenessis a number from0to2. Anything else, such as2.5or"1", is refused with422, anddetailnamesexpressiveness.interface_setuptakes only these three keys. Any other key is refused with422,codeinterface_setup, and adetailthat names it. So is a delegation the session couldn't run, such as one missing itsmodel.- The backend's instructions and tools are reviewed against the Acceptable Use Policy at mint, as your prompt is. One the review refuses answers
400, and thedetailnamesinterface_setup.delegation.responses.
The token starts one conversation, within 60 seconds.
Write the prompts
- The persona's prompt names the requests to hand off. The persona decides on its own, and hands off when its prompt tells it to. Without that, it may talk about doing something and never hand it off.
- Have the persona say what it's doing, not that it's done: "Booking it now…", not "It's booked!". The persona's words play before your tool runs, and the result comes after.
- The delegation's
instructionstell the backend how to use your tools and how to answer.
The persona's own tools
interface_setup.runtime_tools turns the persona's own tools on or off, each with true or false. The persona runs them on the server: no event asks your page to run them.
| Tool | Default | What the persona can do |
|---|---|---|
stay_quiet | on | Choose not to speak: let a turn it was given pass in silence, and keep a check-in it promised ("I'll check back in 30 seconds"). Turn it off and the persona answers every turn. |
end_call | off | End the call after its goodbye. The session then closes with reason: "end_. |
call_time | off | Check how long the call has run and how long is left before it answers ("Let me check."). |
- A tool runs only where the service offers it.
nullis the same as leaving the tool out. Any other key, or a value that isn't a boolean, is refused with422interface_setup.session.startedechoesruntime_toolsas the session runs them: alwaysstay_quiet, andend_callandcall_timewhen you named them. A tool you named that the service doesn't offer readsfalse.
Start the conversation
The rest is the same as for any conversation, with the session token as the bearer:
POST /conversationwith the person'stimezonecreates it: the persona reads the date and time in that zone, else in UTC.- Follow
GET /conversation/{id}/eventsuntiladmitted, and keep one request open until the conversation ends. - Join the
admittedroom and publish the microphone right away.
Connect has each step.
session.started comes once the persona can hear the user's microphone, so the user can speak the moment it arrives. Publish the microphone when you join: without one, session.started may not come at all. Send audio for the whole session, as a microphone does: when the user says nothing, that is silence.
Send nothing on the nuance_live topic before session.started. An event sent earlier can be lost before the persona has joined, and no error comes back.
The nuance_live topic
Nuance Live events travel on the room's data channel, on the topic nuance_live, as JSON: one reliable data packet per event, both ways, in the order sent.
-
What you send may be up to 64,000 bytes a packet. Send a larger event, such as a tool result with images, as a LiveKit text stream on the same topic. A
response.createyou send after a stream waits until the runtime has read it. -
What you get comes one event a packet, up to 15 KiB. A larger event arrives as consecutive
message.partpackets, before anything else:{ "type": "message.part", "id": "evt_41", "index": 0, "count": 2, "text": "{\"type\":\"response.event\",…" }Join the
texts inindexorder and parse the result at the part whoseindexiscount - 1. If any other packet arrives between parts, or a part is out of order, drop the event you were joining. -
Clock sync. The room's
clock-synctopic carries{"type": "clock_sync_ping", "seq": …, "t0": …}. Answer each at once, reliably, with{"type": "clock_sync_pong", "seq": …, "t0": …, "t1": …, "t2": …}:seqandt0copied,t1when the ping arrived andt2as you reply, in Unix seconds. The session waits up to half a second for the replies before it starts. -
Ignore other topics, and event types and fields you don't know: new ones are added.
Whoever holds the session token can send every client event, including response.create and the appends, which steer what the persona says. Mint tokens only for people you let use your app, and check what your page sends on their behalf as you would any input from them.
End it
Send {"type": "session.close"}, then wait a few seconds for session.closed (or the events route's ended, if it comes first), and leave the room. When the session ends by itself, session.closed says why: see Events you get.