voice: output retries for run(output_type=...)#6080
Conversation
β¦Error when exhausted
c63462a to
86acdca
Compare
| if session is not None: | ||
| _SessionMockTools.setdefault(session, {})[agent] = dict(mocks) | ||
| return None |
There was a problem hiding this comment.
π΄ Session-scoped mock tools are silently ignored during tool execution
Mock tools registered via the session-scoped path are stored (_SessionMockTools.setdefault(session, {})[agent] = dict(mocks) at livekit-agents/livekit/agents/voice/run_result.py:1154) but never consulted when tools are actually executed, so they have no effect.
Impact: Users calling mock_tools(MyAgent, {...}, session=session) will see no error but their mocks will never intercept tool calls.
Tool execution only reads from the context-variable store, not the session store
The tool execution code in livekit-agents/livekit/agents/voice/generation.py:856 only reads from _MockToolsContextVar:
mock_tools: dict[str, Callable] = _MockToolsContextVar.get({}).get(
type(session.current_agent), {}
)It never reads from _SessionMockTools. The _SessionMockTools dictionary is only written to (at run_result.py:1154) and declared (at run_result.py:1114), but has zero read sites anywhere in the codebase. The docstring at run_result.py:1150-1151 even describes the intended precedence ("the context-manager mocks take precedence over the session ones"), but this fallback lookup was never implemented.
To fix this, generation.py:856 needs to also check _SessionMockTools.get(session, {}).get(type(session.current_agent), {}) as a fallback when _MockToolsContextVar has no mocks for the agent type.
Was this helpful? React with π or π to provide feedback.
# Conflicts: # livekit-agents/livekit/agents/voice/__init__.py
| except Exception: | ||
| # an unhandled exception here would leave the run future | ||
| # unresolved; fall through to UnexpectedModelBehavior instead | ||
| return False |
There was a problem hiding this comment.
π Exception swallowing in _maybe_retry_output could hide root causes
The broad except Exception at run_result.py:293 catches all errors from generate_reply and returns False, causing the caller to fall through to UnexpectedModelBehavior. While the comment explains the rationale (preventing an unresolved future), this means errors like RuntimeError('AgentSession isn't running') or RuntimeError('AgentSession is closing') from agent_session.py:1297-1310 are silently swallowed. The user sees UnexpectedModelBehavior with no indication that a retry was attempted and failed. Consider logging the caught exception at warning/debug level before returning False.
Was this helpful? React with π or π to provide feedback.
A run with an
output_typeends withfinal_output=Nonewhenever the model finishes its turn in prose instead of calling the task's completion tool β common with chatty models, and currently surfaced as a genericRuntimeErrorthat callers can't distinguish or recover from.Following pydantic-ai's output-tool semantics:
New
output_optionsonrun()(an options TypedDict in the style ofkeyterm_options/expressiveness): when the run ends without itsoutput_type, the session re-prompts in the same context as a per-turn system message (max_retries, default 2) before raising;retry_instructionsoverrides the built-in retry prompt.A distinct
UnexpectedModelBehavior(exported fromlivekit.agents, same name as pydantic-ai's) replaces the generic RuntimeError once the budget is exhausted, so callers can catch the failure specifically.Defaults convert the dominant failure (model summarizes in prose) into a recovered run. Unit tests cover recovery, the prompt override, and exhaustion via FakeLLM.
π€ Generated with Claude Code