18
18
from ..tools .executor import run_tools , validate_and_prepare_tools
19
19
from ..types .content import Message , Messages
20
20
from ..types .event_loop import ParallelToolExecutorInterface
21
- from ..types .exceptions import ContextWindowOverflowException , EventLoopException , ModelThrottledException
21
+ from ..types .exceptions import (
22
+ ContextWindowOverflowException ,
23
+ EventLoopException ,
24
+ ModelThrottledException ,
25
+ )
22
26
from ..types .models import Model
23
27
from ..types .streaming import Metrics , StopReason
24
28
from ..types .tools import ToolConfig , ToolHandler , ToolResult , ToolUse
28
32
29
33
logger = logging .getLogger (__name__ )
30
34
35
+ MAX_ATTEMPTS = 6
36
+ INITIAL_DELAY = 4
37
+ MAX_DELAY = 240 # 4 minutes
31
38
32
- def initialize_state (** kwargs : Any ) -> Any :
39
+
40
+ def initialize_state (kwargs : Dict [str , Any ]) -> Dict [str , Any ]:
33
41
"""Initialize the request state if not present.
34
42
35
43
Creates an empty request_state dictionary if one doesn't already exist in the
@@ -51,7 +59,7 @@ def event_loop_cycle(
51
59
system_prompt : Optional [str ],
52
60
messages : Messages ,
53
61
tool_config : Optional [ToolConfig ],
54
- callback_handler : Any ,
62
+ callback_handler : Callable [..., Any ] ,
55
63
tool_handler : Optional [ToolHandler ],
56
64
tool_execution_handler : Optional [ParallelToolExecutorInterface ] = None ,
57
65
** kwargs : Any ,
@@ -100,10 +108,12 @@ def event_loop_cycle(
100
108
# Initialize cycle state
101
109
kwargs ["event_loop_cycle_id" ] = uuid .uuid4 ()
102
110
103
- event_loop_metrics : EventLoopMetrics = kwargs .get ("event_loop_metrics" , EventLoopMetrics ())
111
+ event_loop_metrics : EventLoopMetrics = kwargs .get (
112
+ "event_loop_metrics" , EventLoopMetrics ()
113
+ )
104
114
105
115
# Initialize state and get cycle trace
106
- kwargs = initialize_state (** kwargs )
116
+ kwargs = initialize_state (kwargs )
107
117
cycle_start_time , cycle_trace = event_loop_metrics .start_cycle ()
108
118
kwargs ["event_loop_cycle_trace" ] = cycle_trace
109
119
@@ -130,9 +140,9 @@ def event_loop_cycle(
130
140
stop_reason : StopReason
131
141
usage : Any
132
142
metrics : Metrics
133
- max_attempts = 6
134
- initial_delay = 4
135
- max_delay = 240 # 4 minutes
143
+ max_attempts = MAX_ATTEMPTS
144
+ initial_delay = INITIAL_DELAY
145
+ max_delay = MAX_DELAY
136
146
current_delay = initial_delay
137
147
138
148
# Retry loop for handling throttling exceptions
@@ -145,13 +155,15 @@ def event_loop_cycle(
145
155
)
146
156
147
157
try :
148
- stop_reason , message , usage , metrics , kwargs ["request_state" ] = stream_messages (
149
- model ,
150
- system_prompt ,
151
- messages ,
152
- tool_config ,
153
- callback_handler ,
154
- ** kwargs ,
158
+ stop_reason , message , usage , metrics , kwargs ["request_state" ] = (
159
+ stream_messages (
160
+ model ,
161
+ system_prompt ,
162
+ messages ,
163
+ tool_config ,
164
+ callback_handler ,
165
+ ** kwargs ,
166
+ )
155
167
)
156
168
if model_invoke_span :
157
169
tracer .end_model_invoke_span (model_invoke_span , message , usage )
@@ -177,7 +189,13 @@ def event_loop_cycle(
177
189
178
190
# Handle throttling errors with exponential backoff
179
191
should_retry , current_delay = handle_throttling_error (
180
- e , attempt , max_attempts , current_delay , max_delay , callback_handler , kwargs
192
+ e ,
193
+ attempt ,
194
+ max_attempts ,
195
+ current_delay ,
196
+ max_delay ,
197
+ callback_handler ,
198
+ kwargs ,
181
199
)
182
200
if should_retry :
183
201
continue
@@ -204,80 +222,29 @@ def event_loop_cycle(
204
222
205
223
# If the model is requesting to use tools
206
224
if stop_reason == "tool_use" :
207
- tool_uses : List [ToolUse ] = []
208
- tool_results : List [ToolResult ] = []
209
- invalid_tool_use_ids : List [str ] = []
210
-
211
- # Extract and validate tools
212
- validate_and_prepare_tools (message , tool_uses , tool_results , invalid_tool_use_ids )
213
-
214
- # Check if tools are available for execution
215
- if tool_uses :
216
- if tool_handler is None :
217
- raise ValueError ("toolUse present but tool handler not set" )
218
- if tool_config is None :
219
- raise ValueError ("toolUse present but tool config not set" )
220
-
221
- # Create the tool handler process callable
222
- tool_handler_process : Callable [[ToolUse ], ToolResult ] = partial (
223
- tool_handler .process ,
224
- messages = messages ,
225
- model = model ,
226
- system_prompt = system_prompt ,
227
- tool_config = tool_config ,
228
- callback_handler = callback_handler ,
229
- ** kwargs ,
225
+ if not tool_handler :
226
+ raise EventLoopException (
227
+ "Model requested tool use but no tool handler provided" ,
228
+ kwargs ["request_state" ],
230
229
)
231
230
232
- # Execute tools (parallel or sequential)
233
- run_tools (
234
- handler = tool_handler_process ,
235
- tool_uses = tool_uses ,
236
- event_loop_metrics = event_loop_metrics ,
237
- request_state = cast (Any , kwargs ["request_state" ]),
238
- invalid_tool_use_ids = invalid_tool_use_ids ,
239
- tool_results = tool_results ,
240
- cycle_trace = cycle_trace ,
241
- parent_span = cycle_span ,
242
- parallel_tool_executor = tool_execution_handler ,
243
- )
244
-
245
- # Update state for the next cycle
246
- kwargs = prepare_next_cycle (kwargs , event_loop_metrics )
247
-
248
- # Create the tool result message
249
- tool_result_message : Message = {
250
- "role" : "user" ,
251
- "content" : [{"toolResult" : result } for result in tool_results ],
252
- }
253
- messages .append (tool_result_message )
254
- callback_handler (message = tool_result_message )
255
-
256
- if cycle_span :
257
- tracer .end_event_loop_cycle_span (
258
- span = cycle_span , message = message , tool_result_message = tool_result_message
259
- )
260
-
261
- # Check if we should stop the event loop
262
- if kwargs ["request_state" ].get ("stop_event_loop" ):
263
- event_loop_metrics .end_cycle (cycle_start_time , cycle_trace )
264
- return (
265
- stop_reason ,
266
- message ,
267
- event_loop_metrics ,
268
- kwargs ["request_state" ],
269
- )
270
-
271
- # Recursive call to continue the conversation
272
- return recurse_event_loop (
273
- model = model ,
274
- system_prompt = system_prompt ,
275
- messages = messages ,
276
- tool_config = tool_config ,
277
- callback_handler = callback_handler ,
278
- tool_handler = tool_handler ,
279
- ** kwargs ,
280
- )
231
+ # Handle tool execution
232
+ return _handle_tool_execution (
233
+ stop_reason ,
234
+ message ,
235
+ model ,
236
+ system_prompt ,
237
+ messages ,
238
+ tool_config ,
239
+ tool_handler ,
240
+ callback_handler ,
241
+ tool_execution_handler ,
242
+ event_loop_metrics ,
243
+ cycle_trace ,
244
+ cycle_span ,
245
+ cycle_start_time ,
246
+ kwargs ,
247
+ )
281
248
282
249
# End the cycle and return results
283
250
event_loop_metrics .end_cycle (cycle_start_time , cycle_trace )
@@ -359,7 +326,9 @@ def recurse_event_loop(
359
326
)
360
327
361
328
362
- def prepare_next_cycle (kwargs : Dict [str , Any ], event_loop_metrics : EventLoopMetrics ) -> Dict [str , Any ]:
329
+ def prepare_next_cycle (
330
+ kwargs : Dict [str , Any ], event_loop_metrics : EventLoopMetrics
331
+ ) -> Dict [str , Any ]:
363
332
"""Prepare state for the next event loop cycle.
364
333
365
334
Updates the keyword arguments with the current event loop metrics and stores the current cycle ID as the parent
@@ -377,3 +346,81 @@ def prepare_next_cycle(kwargs: Dict[str, Any], event_loop_metrics: EventLoopMetr
377
346
kwargs ["event_loop_parent_cycle_id" ] = kwargs ["event_loop_cycle_id" ]
378
347
379
348
return kwargs
349
+
350
+
351
+ def _handle_tool_execution (
352
+ stop_reason : StopReason ,
353
+ message : Message ,
354
+ model : Model ,
355
+ system_prompt : Optional [str ],
356
+ messages : Messages ,
357
+ tool_config : ToolConfig ,
358
+ tool_handler : ToolHandler ,
359
+ callback_handler : Callable [..., Any ],
360
+ tool_execution_handler : Optional [ParallelToolExecutorInterface ],
361
+ event_loop_metrics : EventLoopMetrics ,
362
+ cycle_trace : Trace ,
363
+ cycle_span : Any ,
364
+ cycle_start_time : float ,
365
+ kwargs : Dict [str , Any ],
366
+ ) -> Tuple [StopReason , Message , EventLoopMetrics , Dict [str , Any ]]:
367
+ tool_uses : List [ToolUse ] = []
368
+ tool_results : List [ToolResult ] = []
369
+ invalid_tool_use_ids : List [str ] = []
370
+
371
+ validate_and_prepare_tools (message , tool_uses , tool_results , invalid_tool_use_ids )
372
+
373
+ if not tool_uses :
374
+ return stop_reason , message , event_loop_metrics , kwargs ["request_state" ]
375
+
376
+ tool_handler_process = partial (
377
+ tool_handler .process ,
378
+ messages = messages ,
379
+ model = model ,
380
+ system_prompt = system_prompt ,
381
+ tool_config = tool_config ,
382
+ callback_handler = callback_handler ,
383
+ ** kwargs ,
384
+ )
385
+
386
+ run_tools (
387
+ handler = tool_handler_process ,
388
+ tool_uses = tool_uses ,
389
+ event_loop_metrics = event_loop_metrics ,
390
+ request_state = cast (Any , kwargs ["request_state" ]),
391
+ invalid_tool_use_ids = invalid_tool_use_ids ,
392
+ tool_results = tool_results ,
393
+ cycle_trace = cycle_trace ,
394
+ parent_span = cycle_span ,
395
+ parallel_tool_executor = tool_execution_handler ,
396
+ )
397
+
398
+ kwargs = prepare_next_cycle (kwargs , event_loop_metrics )
399
+
400
+ tool_result_message : Message = {
401
+ "role" : "user" ,
402
+ "content" : [{"toolResult" : result } for result in tool_results ],
403
+ }
404
+
405
+ messages .append (tool_result_message )
406
+ callback_handler (message = tool_result_message )
407
+
408
+ if cycle_span :
409
+ tracer = get_tracer ()
410
+ tracer .end_event_loop_cycle_span (
411
+ span = cycle_span , message = message , tool_result_message = tool_result_message
412
+ )
413
+
414
+ if kwargs ["request_state" ].get ("stop_event_loop" , False ):
415
+ event_loop_metrics .end_cycle (cycle_start_time , cycle_trace )
416
+ return stop_reason , message , event_loop_metrics , kwargs ["request_state" ]
417
+
418
+ return recurse_event_loop (
419
+ model = model ,
420
+ system_prompt = system_prompt ,
421
+ messages = messages ,
422
+ tool_config = tool_config ,
423
+ callback_handler = callback_handler ,
424
+ tool_handler = tool_handler ,
425
+ ** kwargs ,
426
+ )
0 commit comments