11
11
import logging
12
12
import time
13
13
import uuid
14
- from typing import TYPE_CHECKING , Any , AsyncGenerator , cast
14
+ from typing import TYPE_CHECKING , Any , AsyncGenerator
15
15
16
16
from opentelemetry import trace as trace_api
17
17
18
18
from ..experimental .hooks import (
19
19
AfterModelInvocationEvent ,
20
- AfterToolInvocationEvent ,
21
20
BeforeModelInvocationEvent ,
22
- BeforeToolInvocationEvent ,
23
21
)
24
22
from ..hooks import (
25
23
MessageAddedEvent ,
26
24
)
27
25
from ..telemetry .metrics import Trace
28
26
from ..telemetry .tracer import get_tracer
29
- from ..tools .executor import run_tools , validate_and_prepare_tools
27
+ from ..tools .validator import validate_and_prepare_tools
30
28
from ..types .content import Message
31
29
from ..types .exceptions import (
32
30
ContextWindowOverflowException ,
35
33
ModelThrottledException ,
36
34
)
37
35
from ..types .streaming import Metrics , StopReason
38
- from ..types .tools import ToolChoice , ToolChoiceAuto , ToolConfig , ToolGenerator , ToolResult , ToolUse
36
+ from ..types .tools import ToolResult , ToolUse
39
37
from ._recover_message_on_max_tokens_reached import recover_message_on_max_tokens_reached
40
38
from .streaming import stream_messages
41
39
@@ -212,7 +210,7 @@ async def event_loop_cycle(agent: "Agent", invocation_state: dict[str, Any]) ->
212
210
if stop_reason == "max_tokens" :
213
211
"""
214
212
Handle max_tokens limit reached by the model.
215
-
213
+
216
214
When the model reaches its maximum token limit, this represents a potentially unrecoverable
217
215
state where the model's response was truncated. By default, Strands fails hard with an
218
216
MaxTokensReachedException to maintain consistency with other failure types.
@@ -306,122 +304,6 @@ async def recurse_event_loop(agent: "Agent", invocation_state: dict[str, Any]) -
306
304
recursive_trace .end ()
307
305
308
306
309
- async def run_tool (agent : "Agent" , tool_use : ToolUse , invocation_state : dict [str , Any ]) -> ToolGenerator :
310
- """Process a tool invocation.
311
-
312
- Looks up the tool in the registry and streams it with the provided parameters.
313
-
314
- Args:
315
- agent: The agent for which the tool is being executed.
316
- tool_use: The tool object to process, containing name and parameters.
317
- invocation_state: Context for the tool invocation, including agent state.
318
-
319
- Yields:
320
- Tool events with the last being the tool result.
321
- """
322
- logger .debug ("tool_use=<%s> | streaming" , tool_use )
323
- tool_name = tool_use ["name" ]
324
-
325
- # Get the tool info
326
- tool_info = agent .tool_registry .dynamic_tools .get (tool_name )
327
- tool_func = tool_info if tool_info is not None else agent .tool_registry .registry .get (tool_name )
328
-
329
- # Add standard arguments to invocation_state for Python tools
330
- invocation_state .update (
331
- {
332
- "model" : agent .model ,
333
- "system_prompt" : agent .system_prompt ,
334
- "messages" : agent .messages ,
335
- "tool_config" : ToolConfig ( # for backwards compatability
336
- tools = [{"toolSpec" : tool_spec } for tool_spec in agent .tool_registry .get_all_tool_specs ()],
337
- toolChoice = cast (ToolChoice , {"auto" : ToolChoiceAuto ()}),
338
- ),
339
- }
340
- )
341
-
342
- before_event = agent .hooks .invoke_callbacks (
343
- BeforeToolInvocationEvent (
344
- agent = agent ,
345
- selected_tool = tool_func ,
346
- tool_use = tool_use ,
347
- invocation_state = invocation_state ,
348
- )
349
- )
350
-
351
- try :
352
- selected_tool = before_event .selected_tool
353
- tool_use = before_event .tool_use
354
- invocation_state = before_event .invocation_state # Get potentially modified invocation_state from hook
355
-
356
- # Check if tool exists
357
- if not selected_tool :
358
- if tool_func == selected_tool :
359
- logger .error (
360
- "tool_name=<%s>, available_tools=<%s> | tool not found in registry" ,
361
- tool_name ,
362
- list (agent .tool_registry .registry .keys ()),
363
- )
364
- else :
365
- logger .debug (
366
- "tool_name=<%s>, tool_use_id=<%s> | a hook resulted in a non-existing tool call" ,
367
- tool_name ,
368
- str (tool_use .get ("toolUseId" )),
369
- )
370
-
371
- result : ToolResult = {
372
- "toolUseId" : str (tool_use .get ("toolUseId" )),
373
- "status" : "error" ,
374
- "content" : [{"text" : f"Unknown tool: { tool_name } " }],
375
- }
376
- # for every Before event call, we need to have an AfterEvent call
377
- after_event = agent .hooks .invoke_callbacks (
378
- AfterToolInvocationEvent (
379
- agent = agent ,
380
- selected_tool = selected_tool ,
381
- tool_use = tool_use ,
382
- invocation_state = invocation_state , # Keep as invocation_state for backward compatibility with hooks
383
- result = result ,
384
- )
385
- )
386
- yield after_event .result
387
- return
388
-
389
- async for event in selected_tool .stream (tool_use , invocation_state ):
390
- yield event
391
-
392
- result = event
393
-
394
- after_event = agent .hooks .invoke_callbacks (
395
- AfterToolInvocationEvent (
396
- agent = agent ,
397
- selected_tool = selected_tool ,
398
- tool_use = tool_use ,
399
- invocation_state = invocation_state , # Keep as invocation_state for backward compatibility with hooks
400
- result = result ,
401
- )
402
- )
403
- yield after_event .result
404
-
405
- except Exception as e :
406
- logger .exception ("tool_name=<%s> | failed to process tool" , tool_name )
407
- error_result : ToolResult = {
408
- "toolUseId" : str (tool_use .get ("toolUseId" )),
409
- "status" : "error" ,
410
- "content" : [{"text" : f"Error: { str (e )} " }],
411
- }
412
- after_event = agent .hooks .invoke_callbacks (
413
- AfterToolInvocationEvent (
414
- agent = agent ,
415
- selected_tool = selected_tool ,
416
- tool_use = tool_use ,
417
- invocation_state = invocation_state , # Keep as invocation_state for backward compatibility with hooks
418
- result = error_result ,
419
- exception = e ,
420
- )
421
- )
422
- yield after_event .result
423
-
424
-
425
307
async def _handle_tool_execution (
426
308
stop_reason : StopReason ,
427
309
message : Message ,
@@ -431,18 +313,12 @@ async def _handle_tool_execution(
431
313
cycle_start_time : float ,
432
314
invocation_state : dict [str , Any ],
433
315
) -> AsyncGenerator [dict [str , Any ], None ]:
434
- tool_uses : list [ToolUse ] = []
435
- tool_results : list [ToolResult ] = []
436
- invalid_tool_use_ids : list [str ] = []
437
-
438
- """
439
- Handles the execution of tools requested by the model during an event loop cycle.
316
+ """Handles the execution of tools requested by the model during an event loop cycle.
440
317
441
318
Args:
442
319
stop_reason: The reason the model stopped generating.
443
320
message: The message from the model that may contain tool use requests.
444
- event_loop_metrics: Metrics tracking object for the event loop.
445
- event_loop_parent_span: Span for the parent of this event loop.
321
+ agent: Agent for which tools are being executed.
446
322
cycle_trace: Trace object for the current event loop cycle.
447
323
cycle_span: Span object for tracing the cycle (type may vary).
448
324
cycle_start_time: Start time of the current cycle.
@@ -456,24 +332,25 @@ async def _handle_tool_execution(
456
332
- The updated event loop metrics,
457
333
- The updated request state.
458
334
"""
459
- validate_and_prepare_tools (message , tool_uses , tool_results , invalid_tool_use_ids )
335
+ tool_uses : list [ToolUse ] = []
336
+ tool_results : list [ToolResult ] = []
337
+ invalid_tool_use_ids : list [str ] = []
460
338
339
+ validate_and_prepare_tools (message , tool_uses , tool_results , invalid_tool_use_ids )
340
+ tool_uses = [tool_use for tool_use in tool_uses if tool_use .get ("toolUseId" ) not in invalid_tool_use_ids ]
461
341
if not tool_uses :
462
342
yield {"stop" : (stop_reason , message , agent .event_loop_metrics , invocation_state ["request_state" ])}
463
343
return
464
344
465
- def tool_handler (tool_use : ToolUse ) -> ToolGenerator :
466
- return run_tool (agent , tool_use , invocation_state )
467
-
468
- tool_events = run_tools (
469
- handler = tool_handler ,
470
- tool_uses = tool_uses ,
471
- event_loop_metrics = agent .event_loop_metrics ,
472
- invalid_tool_use_ids = invalid_tool_use_ids ,
473
- tool_results = tool_results ,
474
- cycle_trace = cycle_trace ,
475
- parent_span = cycle_span ,
345
+ invocation_state .update (
346
+ {
347
+ "event_loop_cycle_trace" : cycle_trace ,
348
+ "event_loop_cycle_span" : cycle_span ,
349
+ "tool_results" : tool_results ,
350
+ }
476
351
)
352
+
353
+ tool_events = agent .tool_executor .execute (agent , tool_uses , invocation_state )
477
354
async for tool_event in tool_events :
478
355
yield tool_event
479
356
0 commit comments