Thanks to visit codestin.com
Credit goes to github.com

Skip to content

voice: output retries for run(output_type=...)#6080

Merged
theomonnom merged 22 commits into
mainfrom
theo/output-retries
Jul 7, 2026
Merged

voice: output retries for run(output_type=...)#6080
theomonnom merged 22 commits into
mainfrom
theo/output-retries

Conversation

@theomonnom

@theomonnom theomonnom commented Jun 12, 2026

Copy link
Copy Markdown
Member

A run with an output_type ends with final_output=None whenever the model finishes its turn in prose instead of calling the task's completion tool β€” common with chatty models, and currently surfaced as a generic RuntimeError that callers can't distinguish or recover from.

Following pydantic-ai's output-tool semantics:

  • New output_options on run() (an options TypedDict in the style of keyterm_options/expressiveness): when the run ends without its output_type, the session re-prompts in the same context as a per-turn system message (max_retries, default 2) before raising; retry_instructions overrides the built-in retry prompt.

    result = await sess.run(
        user_input=...,
        output_type=SummarizeOutput,
        output_options={"max_retries": 2, "retry_instructions": "Call submit_analysis, nothing else."},
    )
  • A distinct UnexpectedModelBehavior (exported from livekit.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

@theomonnom theomonnom requested a review from a team as a code owner June 12, 2026 17:52
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@theomonnom theomonnom force-pushed the theo/output-retries branch from c63462a to 86acdca Compare June 12, 2026 23:00
Comment thread livekit-agents/livekit/agents/voice/agent_session.py Outdated
Comment thread livekit-agents/livekit/agents/voice/agent_session.py Outdated
Comment thread livekit-agents/livekit/agents/voice/run_result.py Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +1155 to +1157
if session is not None:
_SessionMockTools.setdefault(session, {})[agent] = dict(mocks)
return None

@devin-ai-integration devin-ai-integration Bot Jul 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ”΄ 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.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

# Conflicts:
#	livekit-agents/livekit/agents/voice/__init__.py

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines +293 to +296
except Exception:
# an unhandled exception here would leave the run future
# unresolved; fall through to UnexpectedModelBehavior instead
return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ” 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.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

@theomonnom theomonnom merged commit bb62c33 into main Jul 7, 2026
25 of 26 checks passed
@theomonnom theomonnom deleted the theo/output-retries branch July 7, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants