Skip to content

Commit bf8c0e9

Browse files
author
Murat Kaan Meral
committed
Merge branch 'main' into bidi-gemini-improvements
2 parents f7c18d4 + 30e6b1e commit bf8c0e9

32 files changed

Lines changed: 1266 additions & 961 deletions

File tree

src/strands/agent/agent.py

Lines changed: 3 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from ..session.session_manager import SessionManager
5252
from ..telemetry.metrics import EventLoopMetrics
5353
from ..telemetry.tracer import get_tracer, serialize
54+
from ..tools.caller import _ToolCaller
5455
from ..tools.executors import ConcurrentToolExecutor
5556
from ..tools.executors._executor import ToolExecutor
5657
from ..tools.registry import ToolRegistry
@@ -102,116 +103,6 @@ class Agent:
102103
6. Produces a final response
103104
"""
104105

105-
class ToolCaller:
106-
"""Call tool as a function."""
107-
108-
def __init__(self, agent: "Agent") -> None:
109-
"""Initialize instance.
110-
111-
Args:
112-
agent: Agent reference that will accept tool results.
113-
"""
114-
# WARNING: Do not add any other member variables or methods as this could result in a name conflict with
115-
# agent tools and thus break their execution.
116-
self._agent = agent
117-
118-
def __getattr__(self, name: str) -> Callable[..., Any]:
119-
"""Call tool as a function.
120-
121-
This method enables the method-style interface (e.g., `agent.tool.tool_name(param="value")`).
122-
It matches underscore-separated names to hyphenated tool names (e.g., 'some_thing' matches 'some-thing').
123-
124-
Args:
125-
name: The name of the attribute (tool) being accessed.
126-
127-
Returns:
128-
A function that when called will execute the named tool.
129-
130-
Raises:
131-
AttributeError: If no tool with the given name exists or if multiple tools match the given name.
132-
"""
133-
134-
def caller(
135-
user_message_override: Optional[str] = None,
136-
record_direct_tool_call: Optional[bool] = None,
137-
**kwargs: Any,
138-
) -> Any:
139-
"""Call a tool directly by name.
140-
141-
Args:
142-
user_message_override: Optional custom message to record instead of default
143-
record_direct_tool_call: Whether to record direct tool calls in message history. Overrides class
144-
attribute if provided.
145-
**kwargs: Keyword arguments to pass to the tool.
146-
147-
Returns:
148-
The result returned by the tool.
149-
150-
Raises:
151-
AttributeError: If the tool doesn't exist.
152-
"""
153-
if self._agent._interrupt_state.activated:
154-
raise RuntimeError("cannot directly call tool during interrupt")
155-
156-
normalized_name = self._find_normalized_tool_name(name)
157-
158-
# Create unique tool ID and set up the tool request
159-
tool_id = f"tooluse_{name}_{random.randint(100000000, 999999999)}"
160-
tool_use: ToolUse = {
161-
"toolUseId": tool_id,
162-
"name": normalized_name,
163-
"input": kwargs.copy(),
164-
}
165-
tool_results: list[ToolResult] = []
166-
invocation_state = kwargs
167-
168-
async def acall() -> ToolResult:
169-
async for event in ToolExecutor._stream(self._agent, tool_use, tool_results, invocation_state):
170-
if isinstance(event, ToolInterruptEvent):
171-
self._agent._interrupt_state.deactivate()
172-
raise RuntimeError("cannot raise interrupt in direct tool call")
173-
174-
return tool_results[0]
175-
176-
tool_result = run_async(acall)
177-
178-
if record_direct_tool_call is not None:
179-
should_record_direct_tool_call = record_direct_tool_call
180-
else:
181-
should_record_direct_tool_call = self._agent.record_direct_tool_call
182-
183-
if should_record_direct_tool_call:
184-
# Create a record of this tool execution in the message history
185-
self._agent._record_tool_execution(tool_use, tool_result, user_message_override)
186-
187-
# Apply window management
188-
self._agent.conversation_manager.apply_management(self._agent)
189-
190-
return tool_result
191-
192-
return caller
193-
194-
def _find_normalized_tool_name(self, name: str) -> str:
195-
"""Lookup the tool represented by name, replacing characters with underscores as necessary."""
196-
tool_registry = self._agent.tool_registry.registry
197-
198-
if tool_registry.get(name, None):
199-
return name
200-
201-
# If the desired name contains underscores, it might be a placeholder for characters that can't be
202-
# represented as python identifiers but are valid as tool names, such as dashes. In that case, find
203-
# all tools that can be represented with the normalized name
204-
if "_" in name:
205-
filtered_tools = [
206-
tool_name for (tool_name, tool) in tool_registry.items() if tool_name.replace("-", "_") == name
207-
]
208-
209-
# The registry itself defends against similar names, so we can just take the first match
210-
if filtered_tools:
211-
return filtered_tools[0]
212-
213-
raise AttributeError(f"Tool '{name}' not found")
214-
215106
def __init__(
216107
self,
217108
model: Union[Model, str, None] = None,
@@ -349,7 +240,7 @@ def __init__(
349240
else:
350241
self.state = AgentState()
351242

352-
self.tool_caller = Agent.ToolCaller(self)
243+
self.tool_caller = _ToolCaller(self)
353244

354245
self.hooks = HookRegistry()
355246

@@ -368,7 +259,7 @@ def __init__(
368259
self.hooks.invoke_callbacks(AgentInitializedEvent(agent=self))
369260

370261
@property
371-
def tool(self) -> ToolCaller:
262+
def tool(self) -> _ToolCaller:
372263
"""Call tool as a function.
373264
374265
Returns:
Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
"""Bidirectional streaming package."""
22

33
# Main components - Primary user interface
4-
from .agent.agent import BidirectionalAgent
4+
from .agent.agent import BidiAgent
5+
6+
# IO channels - Hardware abstraction
7+
from .io.audio import AudioIO
58

69
# Model interface (for custom implementations)
7-
from .models.bidirectional_model import BidirectionalModel
10+
from .models.bidirectional_model import BidiModel
811

912
# Model providers - What users need to create models
10-
from .models.gemini_live import GeminiLiveModel
11-
from .models.novasonic import NovaSonicModel
12-
from .models.openai import OpenAIRealtimeModel
13+
from .models.gemini_live import BidiGeminiLiveModel
14+
from .models.novasonic import BidiNovaSonicModel
15+
from .models.openai import BidiOpenAIRealtimeModel
1316

1417
# Event types - For type hints and event handling
15-
from .types.bidirectional_streaming import (
16-
AudioInputEvent,
17-
AudioStreamEvent,
18-
ConnectionCloseEvent,
19-
ConnectionStartEvent,
20-
ErrorEvent,
21-
ImageInputEvent,
22-
InputEvent,
23-
InterruptionEvent,
18+
from .types.events import (
19+
BidiAudioInputEvent,
20+
BidiAudioStreamEvent,
21+
BidiConnectionCloseEvent,
22+
BidiConnectionStartEvent,
23+
BidiErrorEvent,
24+
BidiImageInputEvent,
25+
BidiInputEvent,
26+
BidiInterruptionEvent,
2427
ModalityUsage,
25-
UsageEvent,
26-
OutputEvent,
27-
ResponseCompleteEvent,
28-
ResponseStartEvent,
29-
TextInputEvent,
30-
TranscriptStreamEvent,
28+
BidiUsageEvent,
29+
BidiOutputEvent,
30+
BidiResponseCompleteEvent,
31+
BidiResponseStartEvent,
32+
BidiTextInputEvent,
33+
BidiTranscriptStreamEvent,
3134
)
3235

3336
# Re-export standard agent events for tool handling
@@ -39,37 +42,38 @@
3942

4043
__all__ = [
4144
# Main interface
42-
"BidirectionalAgent",
43-
45+
"BidiAgent",
46+
# IO channels
47+
"AudioIO",
4448
# Model providers
45-
"GeminiLiveModel",
46-
"NovaSonicModel",
47-
"OpenAIRealtimeModel",
49+
"BidiGeminiLiveModel",
50+
"BidiNovaSonicModel",
51+
"BidiOpenAIRealtimeModel",
4852

4953
# Input Event types
50-
"TextInputEvent",
51-
"AudioInputEvent",
52-
"ImageInputEvent",
53-
"InputEvent",
54+
"BidiTextInputEvent",
55+
"BidiAudioInputEvent",
56+
"BidiImageInputEvent",
57+
"BidiInputEvent",
5458

5559
# Output Event types
56-
"ConnectionStartEvent",
57-
"ConnectionCloseEvent",
58-
"ResponseStartEvent",
59-
"ResponseCompleteEvent",
60-
"AudioStreamEvent",
61-
"TranscriptStreamEvent",
62-
"InterruptionEvent",
63-
"UsageEvent",
60+
"BidiConnectionStartEvent",
61+
"BidiConnectionCloseEvent",
62+
"BidiResponseStartEvent",
63+
"BidiResponseCompleteEvent",
64+
"BidiAudioStreamEvent",
65+
"BidiTranscriptStreamEvent",
66+
"BidiInterruptionEvent",
67+
"BidiUsageEvent",
6468
"ModalityUsage",
65-
"ErrorEvent",
66-
"OutputEvent",
69+
"BidiErrorEvent",
70+
"BidiOutputEvent",
6771

6872
# Tool Event types (reused from standard agent)
6973
"ToolUseStreamEvent",
7074
"ToolResultEvent",
7175
"ToolStreamEvent",
7276

7377
# Model interface
74-
"BidirectionalModel",
78+
"BidiModel",
7579
]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Bidirectional agent for real-time streaming conversations."""
22

3-
from .agent import BidirectionalAgent
3+
from .agent import BidiAgent
44

5-
__all__ = ["BidirectionalAgent"]
5+
__all__ = ["BidiAgent"]

0 commit comments

Comments
 (0)