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.
Setup
.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.
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.
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_callalways usesemotion="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_TOOLwins 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
TaskGroupraises aStreamingErrorand you decide whether to retry the call.