Repository navigation
Conversation
GPT-Live sometimes hears a caller's answer to its backend and never delegates it, most often a short "okay", "mm-hmm" or "yep", or an answer said over the backend's question. The call then goes quiet, or the model promises an action that never starts. GPTLiveModel(delegation_recovery=True) turns on a check in AgentActivity: once both sides have stopped speaking for a grace period, a DelegationDetector judges each caller turn the model left undelegated, and RealtimeSession.delegate() starts the delegation when the turn needs the backend. The detector reads the agent's chat context, and marks a turn said over an agent message from the playback times the activity records; such a delegation carries a note so the backend can confirm before acting. RealtimeSession gains delegate() and a delegation_created event, which the duplex adapter passes through from the duplex session.
The check judged turns against str(instructions), which leaves out the
audio and dynamic sections the realtime model is configured with; it
now gets the rendered realtime instructions. The detector also lists
hosted provider tools, which GPT-Live's backend can run, and
delegation_recovery={} now means the defaults instead of off.
|
Two observations from a live Azure GPT-Live session ( 1. Azure does not echo the {"type": "session.delegation.created", "offset_ms": 3600,
"delegation": {"id": "item_…", "type": "delegation", "target": "responses", "response_id": "resp_…"},
"event_id": "event_…"}There is no 2. A use case for a per-delegation The tool has to be forced. Since A minimal extension of the API in this PR: def delegate(self, instructions: str | None = None, *, tool_choice: NotGivenOr[llm.ToolChoice] = NOT_GIVEN) -> NoneIt sets We run this as a fork patch and can open a PR on top of this one once it lands. |
Azure does not echo the response.create event id on session.delegation.created, so the waiting request never cleared and the backend counted as busy for the rest of the session. Any delegation or unattributed error now ends the wait; on Azure such a delegation reports source="model". delegate() is refused while an earlier request still waits, and the default detector no longer attaches a note to a turn said over the agent.
There was a problem hiding this comment.
Note
Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.
Devin Review found 3 new potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
A delegation that carries another request's event id no longer ends
the wait; one with no id still does, since Azure never sends it.
delegation_recovery={} is refused under decisions delegation, where
delegate() can never run.
The detector again delegates a turn said over the agent with a note:
it cannot see where in the agent's line the caller cut in, and the
backend's history keeps no overlap, so the note is how the backend
learns the caller may not have heard all of it.
A delegation or error without client_event_id ends a waiting delegate() request only on Azure, which never echoes the id. OpenAI and LiveKit Inference echo it, so there an id-less delegation is the model's own and the request keeps waiting for its echo. The talk-over note is fixed text: quoting the caller put their words in a developer message, above their own user turn, and the backend already has the turn in its history.
|
Thanks, both points are useful.
agents/livekit-agents/livekit/agents/llm/_realtime/gpt_live.py Lines 962 to 965 in e0bebcf There is one limitation on Azure: a delegation that delegate() starts reports source="model", because nothing on the wire tells it apart from the model's own.
|
|
Thanks for working on this! I played with this code, and after some testing, I’m comfortable trying this with the default 0.5-second grace period. My initial concern was that a late native delegation could arrive after recovery sends
Overall, I support merging this and starting with the default delay. Thank you again for implementing it! |
GPT-Live decides on its own which caller turns to delegate to its backend model. When the backend asks the caller a question and the answer is short (
okay,mm-hmm,yep) or said over the question, GPT-Live often transcribes it and never delegates it, so the call goes quiet or the model promises an action that never starts. In 20 calls built on that pattern, GPT-Live dropped 15 answers; a complete answer given in turn is dropped about 1 time in 200.A prompt does not fix it: a strict and a relaxed delegation policy drop the same share, and GPT-Live transcribes every answer it drops.
GPTLiveModel(delegation_recovery=True)adds a check inAgentActivity. Once both sides stop speaking for a grace period, aDelegationDetectorjudges each caller turn the model left undelegated, andRealtimeSession.delegate()starts the delegation when the turn needs the backend. With it, the same 20 calls dropped 1 answer.Why the check runs in
AgentActivityand reads the agent's chat context:yepas an ordinary reply.RealtimeSessiongainsdelegate()and adelegation_createdevent. GPT-Live implements both and the duplex adapter passes them through, so another realtime model can join by implementing the same two.The option also takes
grace_period(0.5 s by default). A detector is any async callable fromDelegationChecktobool | str; a string delegates the turn and goes to the backend as a note.DelegationDetectoraccepts anLLM, aDecisionModelor a LiveKit Inference model name.TODO: realtime models other than GPT-Live. A first request that GPT-Live answers by asking for details itself is left alone by design, since the model then delegates the caller's next answer.
Closes #7551.
Context for reviewing and coding agents