Realtime target

The realtime target tests an agent that speaks the OpenAI Realtime protocol over a
websocket: the Realtime API itself, with your instructions and tools as the agent under test, or a
server that fronts your own agent with the same events (many voice stacks expose it). Text
mode sends each user turn as a conversation item and reads the response’s text deltas: fast and
cheap. Audio mode speaks the turn as 24 kHz PCM16 through the configured TTS model, reads the
response’s audio and its transcript, and leaves a stereo WAV of every run.
targets:
rt:
type: realtime
url: wss://api.openai.com/v1/realtime # default; ws://, wss://, http(s):// upgraded
model: gpt-realtime # added as ?model= when the url has none
headers: { Authorization: "Bearer {{secrets.OPENAI_API_KEY}}" }
session_update: # merged into the session.update event
instructions: "You are the support agent of Mochi Shoes. ..."
tools:
- { type: function, name: lookup_policy, description: "...", parameters: { type: object, properties: { topic: { type: string } } } }
- { type: function, name: end_session, description: "Call when the customer is done.", parameters: { type: object, properties: {} } }
audio: { output: { voice: marin } }
mode: text # text | audio
protocol: ga # ga | beta (the older event names)
done_tool: end_session # a call to this function ends the session
tool_results: # answers to the agent's function calls
lookup_policy: { topic: "{{args.topic}}", window_days: 30 }
audio_format: pcm16 # audio mode: pcm16 | g711_ulaw | g711_alaw
turn_timeout: 60s # how long to wait for response.done
How a turn works
- tamesu connects with the rendered headers, sends
session.update(the mode’s defaults with yoursession_updatemerged on top) and waits briefly forsession.updated; anerrorevent here fails the test as an infrastructure error. A server that never confirms is given up on after 3 s, not failed. - Text mode: each user turn is a
conversation.item.create(input_text) followed byresponse.create. Audio mode: the turn is synthesised withspeech.tts, resampled to 24 kHz and sent as 100 msinput_audio_buffer.appendchunks, theninput_audio_buffer.commitandresponse.create. Server-side turn detection is switched off, so the agent answers when asked and nothing else. - The reply is read until
response.done:response.output_text.deltaandresponse.output_audio_transcript.deltabuild the text (the beta namesresponse.text.deltaandresponse.audio_transcript.deltatoo),response.output_audio.deltais decoded into the agent’s track, and function calls (response.function_call_arguments.done, or thefunction_callitem) are recorded as tool calls, once per call id. A call todone_toolends the session, which is whatscenario.stop_whenandmax_turnsotherwise do. - A function listed under
tool_resultsis answered: its value goes back as the call’sfunction_call_output(a string as is, anything else as JSON;{{args.<key>}}in a string is the call’s argument) and a new response is requested, so the agent’s turn includes what it says with the result. Up to five such rounds make one turn; calls to other functions and todone_toolare recorded only. Thetool_resultevent marks each answer. - In audio mode a persona with
behaviors.interrupt_probabilitytalks over the agent: after 0.6 to 2 s of agent audio the response is cancelled and the spoken item truncated to what was heard, the turn is marked interrupted, andinterruptionsandinterruption_stop_ms(the cancel to the server’s confirmation) are measured. - A
response.donewith statusfailed, anerrorevent, a closed connection or noresponse.donewithinturn_timeoutis a transport error for the turn.
Keys
| Key | Meaning |
|---|---|
url | The websocket endpoint (default wss://api.openai.com/v1/realtime). Templates apply. |
model | Added as ?model= when the URL has none, such as gpt-realtime. |
headers | Handshake headers. The OpenAI API needs Authorization: Bearer {{secrets.OPENAI_API_KEY}}; validate warns when it is missing. With protocol: beta, OpenAI-Beta: realtime=v1 is added for you. |
session_update | Merged (recursively for maps) over the defaults into session.update: text mode sends {type: realtime, output_modalities: [text]}, audio mode {type: realtime, output_modalities: [audio], audio: {input: {format: audio/pcm 24000, turn_detection: null}, output: {format: audio/pcm 24000}}} (beta: modalities, input_audio_format: pcm16). |
mode | text (default) or audio; a test’s mode or --mode overrides it. Audio mode needs speech.tts in tamesu.yaml. |
protocol | ga (default) or beta, the event dialect of the server. |
done_tool | The function whose call ends the session (default end_session). Give the agent such a tool, or rely on stop_when and max_turns. |
tool_results | Answers to the agent’s function calls, by name: any YAML value, string leaves may use {{args.<key>}}. Functions not listed are recorded without an answer. |
fuzz_tools (on a test) | Replaces the named tools’ answers for that test: error, empty, malformed, hostile, delay, … See when the tools fail. |
audio_format | Audio mode: the agent’s output audio, pcm16 (default, 24 kHz) or g711_ulaw / g711_alaw (8 kHz, decoded and upsampled into the WAV). The caller’s audio is always PCM16. |
turn_timeout | The wait for response.done, and the handshake (default 60s). |
Metrics
| Metric | Definition |
|---|---|
turn_latency_ms (_p50, _p95, _max) | the turn was sent → response.done |
time_to_first_token_ms | the turn was sent → the first text, transcript or audio delta |
time_to_first_audio_ms (_p50, _p95, _max) | audio mode: input_audio_buffer.commit → the first audio delta |
interruptions, interruption_stop_ms (_p50, _p95, _max) | audio mode: how often the caller talked over the agent, and response.cancel → the cancelled response.done |
agent_speech_ms, user_speech_ms | audio mode: how much each side spoke |
agent_input_tokens, agent_output_tokens | the agent’s own token usage from response.done, summed over the session |
turns | the number of agent turns |
ws_messages_received | the server events of the session |
Timestamps are taken around the socket, never from a model. Audio mode has no voice activity detection or line simulation, since the protocol itself marks where a turn starts and ends; for those, test through the LiveKit or SIP target.
Assertions that apply
Every assertion of Concepts except status, json_path and the
phone ones; tool_called sees the agent’s function calls:
assert:
- judge: "The agent states the 30-day return policy and does not promise an exception"
- tool_called: { name: lookup_policy, args: { topic: returns } }
- tool_not_called: issue_refund
- metric: { name: time_to_first_audio_ms_p95, lt: 1500 }
- turns: { lte: 6 }
Artifacts
Text mode leaves the transcript and result.json like any conversational test, with every
server event of each turn in the turn’s raw payload. Audio mode adds audio.wav: a 24 kHz stereo
file with the simulated user on the left and the agent on the right, which the HTML report plays.