Skip to main content
A voice agent usually waits for a transcript, asks an LLM what to do, and only then acts. That adds seconds of dead air after every turn. With Tazo you can decide from the audio itself, while the caller is still speaking: stop the agent the moment they interrupt, and call the right function as soon as their answer is clear. This cookbook builds the backend for a bank agent that offers a credit card upgrade. There is no UI. The backend takes a stream of caller audio and calls any of three functions: It uses the Tazo Python SDK and two streaming sessions.

Why two sessions

A streaming session accepts exactly one question, and the two questions need different settings:
  • Is the caller talking? looks at new audio only (audio_scope="chunk"), so it answers “right now” rather than “at some point in this turn”.
  • Which function should be called? looks at the whole turn so far (audio_scope="turn"), because a “yes” only means something with the words around it.
The backend sends every audio frame to both sessions.

Setup

If you have a recorded .wav file instead of a live microphone, the SDK’s wav_chunks reads it and yields 100 ms frames of audio, which is how the example below replays a call. For a live call, yield frames from your telephony or microphone stream the same way. The streaming protocol has its own wire format, which the SDK handles for you; see streaming if you need the details.

Define the questions

The criteria are the real decision logic: each option says in plain language when it should be chosen. NO_TOOL is the safe default for silence, noise, mid-sentence pauses and unclear replies.

Define the functions

The controller calls these. Replace the bodies with your telephony and CRM code.

Turn answers into function calls

Two rules keep this from firing early:
  • Threshold. An option only counts at 70% or more.
  • Streak. The same function option must win on 2 updates in a row, so a single noisy update never triggers a call.
In our design, once the caller starts speaking we want the agent to stop talking, because we do not want it to talk over them. The controller runs stop_talking() to enable this. Additionally, if the Action stream returns a clear yes or no at the same time as stop_talking() fires, the controller waits 2 seconds after stop_talking() before calling the function. The pause makes sure the caller has finished talking, so the agent is more likely to respond when the caller is not speaking, and the call does not end abruptly. A better approach however is to confirm the caller has stopped using the Speaking stream instead of a fixed delay. Another assumption we make in this example, as covered in Limitations: there is no re-ask step. If the caller says something unclear, NO_TOOL wins and the agent keeps waiting. A simple fix that reuses the existing streams: once the caller has started and then finished speaking, start a timeout. If the Action stream still has not produced a clear yes or no when it expires, have the agent repeat the offer, or ask whether the caller understood it and whether they are interested.

Run a call

run_call opens both sessions, sends each frame to both, and handles their events in parallel. The context managers authenticate and wait for session.ready. Once a function has run, the loop stops sending audio and closes both sessions. If the audio ends without a decision, the backend commits both sessions so Tazo returns a final answer for the turn.
Try it with a recording of a caller:
paced_wav sleeps for the duration of each frame to imitate a microphone. With a live call, frames already arrive in real time, so you would pass them straight in.

Tune it

Limitations

  • end_call always uses emotion="neutral". Add a second session with a sentiment question if you want to pass the caller’s emotion through.
  • There is no re-ask step: if the caller says something unclear, NO_TOOL wins and the agent keeps waiting. In addition to the fix mentioned in Turn answers into function calls, you can also add more streams (questions) for better signals to steer the agent, which would result in a slightly more complex workflow yet better experience for the end user.
  • Sessions are not reconnected automatically. If one fails, the TaskGroup raises a StreamingError and you decide whether to retry the call.