14
14
import os
15
15
import random
16
16
from concurrent .futures import ThreadPoolExecutor
17
- from typing import Any , AsyncIterator , Callable , Generator , Mapping , Optional , Type , TypeVar , Union , cast
17
+ from typing import Any , AsyncIterator , Callable , Generator , List , Mapping , Optional , Type , TypeVar , Union , cast
18
18
19
19
from opentelemetry import trace
20
20
from pydantic import BaseModel
31
31
from ..types .content import ContentBlock , Message , Messages
32
32
from ..types .exceptions import ContextWindowOverflowException
33
33
from ..types .models import Model
34
- from ..types .tools import ToolConfig
34
+ from ..types .tools import ToolConfig , ToolResult , ToolUse
35
35
from ..types .traces import AttributeValue
36
36
from .agent_result import AgentResult
37
37
from .conversation_manager import (
@@ -98,104 +98,56 @@ def __getattr__(self, name: str) -> Callable[..., Any]:
98
98
AttributeError: If no tool with the given name exists or if multiple tools match the given name.
99
99
"""
100
100
101
- def find_normalized_tool_name () -> Optional [str ]:
102
- """Lookup the tool represented by name, replacing characters with underscores as necessary."""
103
- tool_registry = self ._agent .tool_registry .registry
104
-
105
- if tool_registry .get (name , None ):
106
- return name
107
-
108
- # If the desired name contains underscores, it might be a placeholder for characters that can't be
109
- # represented as python identifiers but are valid as tool names, such as dashes. In that case, find
110
- # all tools that can be represented with the normalized name
111
- if "_" in name :
112
- filtered_tools = [
113
- tool_name for (tool_name , tool ) in tool_registry .items () if tool_name .replace ("-" , "_" ) == name
114
- ]
115
-
116
- # The registry itself defends against similar names, so we can just take the first match
117
- if filtered_tools :
118
- return filtered_tools [0 ]
119
-
120
- raise AttributeError (f"Tool '{ name } ' not found" )
121
-
122
- def caller (** kwargs : Any ) -> Any :
101
+ def caller (
102
+ user_message_override : Optional [str ] = None ,
103
+ record_direct_tool_call : Optional [bool ] = None ,
104
+ ** kwargs : Any ,
105
+ ) -> Any :
123
106
"""Call a tool directly by name.
124
107
125
108
Args:
109
+ user_message_override: Optional custom message to record instead of default
110
+ record_direct_tool_call: Whether to record direct tool calls in message history. Overrides class
111
+ attribute if provided.
126
112
**kwargs: Keyword arguments to pass to the tool.
127
113
128
- - user_message_override: Custom message to record instead of default
129
- - tool_execution_handler: Custom handler for tool execution
130
- - event_loop_metrics: Custom metrics collector
131
- - messages: Custom message history to use
132
- - tool_config: Custom tool configuration
133
- - callback_handler: Custom callback handler
134
- - record_direct_tool_call: Whether to record this call in history
135
-
136
114
Returns:
137
115
The result returned by the tool.
138
116
139
117
Raises:
140
118
AttributeError: If the tool doesn't exist.
141
119
"""
142
- normalized_name = find_normalized_tool_name ( )
120
+ normalized_name = self . _find_normalized_tool_name ( name )
143
121
144
122
# Create unique tool ID and set up the tool request
145
123
tool_id = f"tooluse_{ name } _{ random .randint (100000000 , 999999999 )} "
146
- tool_use = {
124
+ tool_use : ToolUse = {
147
125
"toolUseId" : tool_id ,
148
126
"name" : normalized_name ,
149
127
"input" : kwargs .copy (),
150
128
}
151
129
152
- # Extract tool execution parameters
153
- user_message_override = kwargs .get ("user_message_override" , None )
154
- tool_execution_handler = kwargs .get ("tool_execution_handler" , self ._agent .thread_pool_wrapper )
155
- event_loop_metrics = kwargs .get ("event_loop_metrics" , self ._agent .event_loop_metrics )
156
- messages = kwargs .get ("messages" , self ._agent .messages )
157
- tool_config = kwargs .get ("tool_config" , self ._agent .tool_config )
158
- callback_handler = kwargs .get ("callback_handler" , self ._agent .callback_handler )
159
- record_direct_tool_call = kwargs .get ("record_direct_tool_call" , self ._agent .record_direct_tool_call )
160
-
161
- # Process tool call
162
- handler_kwargs = {
163
- k : v
164
- for k , v in kwargs .items ()
165
- if k
166
- not in [
167
- "tool_execution_handler" ,
168
- "event_loop_metrics" ,
169
- "messages" ,
170
- "tool_config" ,
171
- "callback_handler" ,
172
- "tool_handler" ,
173
- "system_prompt" ,
174
- "model" ,
175
- "model_id" ,
176
- "user_message_override" ,
177
- "agent" ,
178
- "record_direct_tool_call" ,
179
- ]
180
- }
181
-
182
130
# Execute the tool
183
131
tool_result = self ._agent .tool_handler .process (
184
132
tool = tool_use ,
185
133
model = self ._agent .model ,
186
134
system_prompt = self ._agent .system_prompt ,
187
- messages = messages ,
188
- tool_config = tool_config ,
189
- callback_handler = callback_handler ,
190
- tool_execution_handler = tool_execution_handler ,
191
- event_loop_metrics = event_loop_metrics ,
192
- agent = self ._agent ,
193
- ** handler_kwargs ,
135
+ messages = self ._agent .messages ,
136
+ tool_config = self ._agent .tool_config ,
137
+ callback_handler = self ._agent .callback_handler ,
138
+ kwargs = kwargs ,
194
139
)
195
140
196
- if record_direct_tool_call :
141
+ if record_direct_tool_call is not None :
142
+ should_record_direct_tool_call = record_direct_tool_call
143
+ else :
144
+ should_record_direct_tool_call = self ._agent .record_direct_tool_call
145
+
146
+ if should_record_direct_tool_call :
197
147
# Create a record of this tool execution in the message history
198
- self ._agent ._record_tool_execution (tool_use , tool_result , user_message_override , messages )
148
+ self ._agent ._record_tool_execution (
149
+ tool_use , tool_result , user_message_override , self ._agent .messages
150
+ )
199
151
200
152
# Apply window management
201
153
self ._agent .conversation_manager .apply_management (self ._agent )
@@ -204,6 +156,27 @@ def caller(**kwargs: Any) -> Any:
204
156
205
157
return caller
206
158
159
+ def _find_normalized_tool_name (self , name : str ) -> str :
160
+ """Lookup the tool represented by name, replacing characters with underscores as necessary."""
161
+ tool_registry = self ._agent .tool_registry .registry
162
+
163
+ if tool_registry .get (name , None ):
164
+ return name
165
+
166
+ # If the desired name contains underscores, it might be a placeholder for characters that can't be
167
+ # represented as python identifiers but are valid as tool names, such as dashes. In that case, find
168
+ # all tools that can be represented with the normalized name
169
+ if "_" in name :
170
+ filtered_tools = [
171
+ tool_name for (tool_name , tool ) in tool_registry .items () if tool_name .replace ("-" , "_" ) == name
172
+ ]
173
+
174
+ # The registry itself defends against similar names, so we can just take the first match
175
+ if filtered_tools :
176
+ return filtered_tools [0 ]
177
+
178
+ raise AttributeError (f"Tool '{ name } ' not found" )
179
+
207
180
def __init__ (
208
181
self ,
209
182
model : Union [Model , str , None ] = None ,
@@ -411,7 +384,7 @@ def __call__(self, prompt: str, **kwargs: Any) -> AgentResult:
411
384
412
385
Args:
413
386
prompt: The natural language prompt from the user.
414
- **kwargs: Additional parameters to pass to the event loop.
387
+ **kwargs: Additional parameters to pass through the event loop.
415
388
416
389
Returns:
417
390
Result object containing:
@@ -577,35 +550,35 @@ def _execute_event_loop_cycle(
577
550
# Get dynamic tool config (now simple and sync!)
578
551
tool_config = self ._select_tools_for_context (current_prompt , messages )
579
552
580
- kwargs .pop ("agent" , None ) # Remove agent to avoid conflicts
553
+ # Add `Agent` to kwargs to keep backwards-compatibility
554
+ kwargs ["agent" ] = self
581
555
582
556
try :
583
557
# Execute the main event loop cycle
584
558
yield from event_loop_cycle (
585
- model = model ,
586
- system_prompt = system_prompt ,
587
- messages = messages , # will be modified by event_loop_cycle
588
- tool_config = tool_config ,
589
- callback_handler = callback_handler_override ,
590
- tool_handler = tool_handler ,
591
- tool_execution_handler = tool_execution_handler ,
592
- event_loop_metrics = event_loop_metrics ,
593
- agent = self ,
559
+ model = self .model ,
560
+ system_prompt = self .system_prompt ,
561
+ messages = self .messages , # will be modified by event_loop_cycle
562
+ tool_config = self .tool_config ,
563
+ callback_handler = callback_handler ,
564
+ tool_handler = self .tool_handler ,
565
+ tool_execution_handler = self .thread_pool_wrapper ,
566
+ event_loop_metrics = self .event_loop_metrics ,
594
567
event_loop_parent_span = self .trace_span ,
595
- ** kwargs ,
568
+ kwargs = kwargs ,
596
569
)
597
570
598
571
except ContextWindowOverflowException as e :
599
572
# Try reducing the context size and retrying
600
573
self .conversation_manager .reduce_context (self , e = e )
601
- yield from self ._execute_event_loop_cycle (callback_handler_override , kwargs )
574
+ yield from self ._execute_event_loop_cycle (callback_handler , kwargs )
602
575
603
576
def _record_tool_execution (
604
577
self ,
605
- tool : dict [ str , Any ] ,
606
- tool_result : dict [ str , Any ] ,
578
+ tool : ToolUse ,
579
+ tool_result : ToolResult ,
607
580
user_message_override : Optional [str ],
608
- messages : list [ dict [ str , Any ]] ,
581
+ messages : Messages ,
609
582
) -> None :
610
583
"""Record a tool execution in the message history.
611
584
@@ -623,7 +596,7 @@ def _record_tool_execution(
623
596
messages: The message history to append to.
624
597
"""
625
598
# Create user message describing the tool call
626
- user_msg_content = [
599
+ user_msg_content : List [ ContentBlock ] = [
627
600
{"text" : (f"agent.tool.{ tool ['name' ]} direct tool call.\n Input parameters: { json .dumps (tool ['input' ])} \n " )}
628
601
]
629
602
@@ -632,19 +605,19 @@ def _record_tool_execution(
632
605
user_msg_content .insert (0 , {"text" : f"{ user_message_override } \n " })
633
606
634
607
# Create the message sequence
635
- user_msg = {
608
+ user_msg : Message = {
636
609
"role" : "user" ,
637
610
"content" : user_msg_content ,
638
611
}
639
- tool_use_msg = {
612
+ tool_use_msg : Message = {
640
613
"role" : "assistant" ,
641
614
"content" : [{"toolUse" : tool }],
642
615
}
643
- tool_result_msg = {
616
+ tool_result_msg : Message = {
644
617
"role" : "user" ,
645
618
"content" : [{"toolResult" : tool_result }],
646
619
}
647
- assistant_msg = {
620
+ assistant_msg : Message = {
648
621
"role" : "assistant" ,
649
622
"content" : [{"text" : f"agent.{ tool ['name' ]} was called" }],
650
623
}
0 commit comments