Skip to content

Commit 2ecbd22

Browse files
committed
bidi exception chain
1 parent 93f1fa1 commit 2ecbd22

File tree

15 files changed

+65
-42
lines changed

15 files changed

+65
-42
lines changed

src/strands/experimental/bidi/_async/__init__.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import Awaitable, Callable
44

5+
from ..errors import BidiExceptionChain
56
from ._task_group import _TaskGroup
67
from ._task_pool import _TaskPool
78

@@ -27,8 +28,4 @@ async def stop_all(*funcs: Callable[..., Awaitable[None]]) -> None:
2728
exceptions.append(exception)
2829

2930
if exceptions:
30-
exceptions.append(RuntimeError("failed stop sequence"))
31-
for i in range(1, len(exceptions)):
32-
exceptions[i].__context__ = exceptions[i - 1]
33-
34-
raise exceptions[-1]
31+
raise BidiExceptionChain("failed stop sequence", exceptions)

src/strands/experimental/bidi/agent/loop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
BidiInterruptionEvent as BidiInterruptionHookEvent,
2121
)
2222
from .._async import _TaskPool, stop_all
23-
from ..models import BidiModelTimeoutError
23+
from ..errors import BidiModelTimeoutError
2424
from ..types.events import (
2525
BidiConnectionCloseEvent,
2626
BidiConnectionRestartEvent,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Custom bidi exceptions."""
2+
from typing import Any
3+
4+
5+
class BidiExceptionChain(Exception):
6+
"""Chain a list of exceptions together.
7+
8+
Useful for chaining together exceptions raised across a multi-step workflow (e.g., the bidi `stop` methods).
9+
Note, this exception is meant to mimic ExceptionGroup released in Python 3.11.
10+
11+
- Docs: https://docs.python.org/3/library/exceptions.html#ExceptionGroup
12+
"""
13+
14+
def __init__(self, message: str, exceptions: list[Exception]) -> None:
15+
"""Chain exceptions.
16+
17+
Args:
18+
message: Top-level exception message.
19+
exceptions: List of exceptions to chain.
20+
"""
21+
super().__init__(self, message)
22+
23+
exceptions.append(self)
24+
for i in range(1, len(exceptions)):
25+
exceptions[i].__context__ = exceptions[i - 1]
26+
27+
28+
class BidiModelTimeoutError(Exception):
29+
"""Model timeout error.
30+
31+
Bidirectional models are often configured with a connection time limit. Nova sonic for example keeps the connection
32+
open for 8 minutes max. Upon receiving a timeout, the agent loop is configured to restart the model connection so as
33+
to create a seamless, uninterrupted experience for the user.
34+
"""
35+
36+
def __init__(self, message: str, **restart_config: Any) -> None:
37+
"""Initialize error.
38+
39+
Args:
40+
message: Timeout message from model.
41+
**restart_config: Configure restart specific behaviors in the call to model start.
42+
"""
43+
super().__init__(self, message)
44+
45+
self.restart_config = restart_config
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""Bidirectional model interfaces and implementations."""
22

3-
from .model import BidiModel, BidiModelTimeoutError
3+
from .model import BidiModel
44
from .nova_sonic import BidiNovaSonicModel
55

66
__all__ = [
77
"BidiModel",
8-
"BidiModelTimeoutError",
98
"BidiNovaSonicModel",
109
]

src/strands/experimental/bidi/models/gemini_live.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from ....types.content import Messages
2626
from ....types.tools import ToolResult, ToolSpec, ToolUse
2727
from .._async import stop_all
28+
from ..errors import BidiModelTimeoutError
2829
from ..types.events import (
2930
AudioChannel,
3031
AudioSampleRate,
@@ -41,7 +42,7 @@
4142
ModalityUsage,
4243
)
4344
from ..types.model import AudioConfig
44-
from .model import BidiModel, BidiModelTimeoutError
45+
from .model import BidiModel
4546

4647
logger = logging.getLogger(__name__)
4748

src/strands/experimental/bidi/models/model.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -112,23 +112,3 @@ async def send(
112112
```
113113
"""
114114
...
115-
116-
117-
class BidiModelTimeoutError(Exception):
118-
"""Model timeout error.
119-
120-
Bidirectional models are often configured with a connection time limit. Nova sonic for example keeps the connection
121-
open for 8 minutes max. Upon receiving a timeout, the agent loop is configured to restart the model connection so as
122-
to create a seamless, uninterrupted experience for the user.
123-
"""
124-
125-
def __init__(self, message: str, **restart_config: Any) -> None:
126-
"""Initialize error.
127-
128-
Args:
129-
message: Timeout message from model.
130-
**restart_config: Configure restart specific behaviors in the call to model start.
131-
"""
132-
super().__init__(self, message)
133-
134-
self.restart_config = restart_config

src/strands/experimental/bidi/models/nova_sonic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from ....types.content import Messages
3838
from ....types.tools import ToolResult, ToolSpec, ToolUse
3939
from .._async import stop_all
40+
from ..errors import BidiModelTimeoutError
4041
from ..types.events import (
4142
AudioChannel,
4243
AudioSampleRate,
@@ -53,7 +54,7 @@
5354
BidiUsageEvent,
5455
)
5556
from ..types.model import AudioConfig
56-
from .model import BidiModel, BidiModelTimeoutError
57+
from .model import BidiModel
5758

5859
logger = logging.getLogger(__name__)
5960

src/strands/experimental/bidi/models/openai_realtime.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ....types.content import Messages
2020
from ....types.tools import ToolResult, ToolSpec, ToolUse
2121
from .._async import stop_all
22+
from ..errors import BidiModelTimeoutError
2223
from ..types.events import (
2324
AudioSampleRate,
2425
BidiAudioInputEvent,
@@ -37,7 +38,7 @@
3738
StopReason,
3839
)
3940
from ..types.model import AudioConfig
40-
from .model import BidiModel, BidiModelTimeoutError
41+
from .model import BidiModel
4142

4243
logger = logging.getLogger(__name__)
4344

src/strands/experimental/bidi/types/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from ....types.streaming import ContentBlockDelta
2828

2929
if TYPE_CHECKING:
30-
from ..models.model import BidiModelTimeoutError
30+
from ..errors import BidiModelTimeoutError
3131

3232
AudioChannel = Literal[1, 2]
3333
"""Number of audio channels.

src/strands/experimental/hooks/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
if TYPE_CHECKING:
1616
from ..bidi.agent.agent import BidiAgent
17-
from ..bidi.models import BidiModelTimeoutError
17+
from ..bidi.errors import BidiModelTimeoutError
1818

1919
warnings.warn(
2020
"BeforeModelCallEvent, AfterModelCallEvent, BeforeToolCallEvent, and AfterToolCallEvent are no longer experimental."

0 commit comments

Comments
 (0)