992. Method-style for direct tool access: `agent.tool.tool_name(param1="value")`
1010"""
1111
12+ import asyncio
1213import json
1314import logging
1415import os
1516import random
1617from concurrent .futures import ThreadPoolExecutor
17- from typing import Any , AsyncIterator , Callable , Generator , List , Mapping , Optional , Type , TypeVar , Union , cast
18+ from typing import Any , AsyncGenerator , AsyncIterator , Callable , Mapping , Optional , Type , TypeVar , Union , cast
1819
1920from opentelemetry import trace
2021from pydantic import BaseModel
@@ -418,33 +419,43 @@ def __call__(self, prompt: str, **kwargs: Any) -> AgentResult:
418419 - metrics: Performance metrics from the event loop
419420 - state: The final state of the event loop
420421 """
421- callback_handler = kwargs .get ("callback_handler" , self .callback_handler )
422422
423- self ._start_agent_trace_span (prompt )
423+ def execute () -> AgentResult :
424+ return asyncio .run (self .invoke_async (prompt , ** kwargs ))
424425
425- try :
426- events = self ._run_loop (prompt , kwargs )
427- for event in events :
428- if "callback" in event :
429- callback_handler (** event ["callback" ])
426+ with ThreadPoolExecutor () as executor :
427+ future = executor .submit (execute )
428+ return future .result ()
430429
431- stop_reason , message , metrics , state = event [ "stop" ]
432- result = AgentResult ( stop_reason , message , metrics , state )
430+ async def invoke_async ( self , prompt : str , ** kwargs : Any ) -> AgentResult :
431+ """Process a natural language prompt through the agent's event loop.
433432
434- self ._end_agent_trace_span (response = result )
433+ This method implements the conversational interface (e.g., `agent("hello!")`). It adds the user's prompt to
434+ the conversation history, processes it through the model, executes any tool calls, and returns the final result.
435435
436- return result
436+ Args:
437+ prompt: The natural language prompt from the user.
438+ **kwargs: Additional parameters to pass through the event loop.
437439
438- except Exception as e :
439- self ._end_agent_trace_span (error = e )
440- raise
440+ Returns:
441+ Result object containing:
442+
443+ - stop_reason: Why the event loop stopped (e.g., "end_turn", "max_tokens")
444+ - message: The final message from the model
445+ - metrics: Performance metrics from the event loop
446+ - state: The final state of the event loop
447+ """
448+ events = self .stream_async (prompt , ** kwargs )
449+ async for event in events :
450+ _ = event
451+
452+ return cast (AgentResult , event ["result" ])
441453
442454 def structured_output (self , output_model : Type [T ], prompt : Optional [str ] = None ) -> T :
443455 """This method allows you to get structured output from the agent.
444456
445457 If you pass in a prompt, it will be added to the conversation history and the agent will respond to it.
446458 If you don't pass in a prompt, it will use only the conversation history to respond.
447- If no conversation history exists and no prompt is provided, an error will be raised.
448459
449460 For smaller models, you may want to use the optional prompt string to add additional instructions to explicitly
450461 instruct the model to output the structured data.
@@ -453,25 +464,52 @@ def structured_output(self, output_model: Type[T], prompt: Optional[str] = None)
453464 output_model: The output model (a JSON schema written as a Pydantic BaseModel)
454465 that the agent will use when responding.
455466 prompt: The prompt to use for the agent.
467+
468+ Raises:
469+ ValueError: If no conversation history or prompt is provided.
470+ """
471+
472+ def execute () -> T :
473+ return asyncio .run (self .structured_output_async (output_model , prompt ))
474+
475+ with ThreadPoolExecutor () as executor :
476+ future = executor .submit (execute )
477+ return future .result ()
478+
479+ async def structured_output_async (self , output_model : Type [T ], prompt : Optional [str ] = None ) -> T :
480+ """This method allows you to get structured output from the agent.
481+
482+ If you pass in a prompt, it will be added to the conversation history and the agent will respond to it.
483+ If you don't pass in a prompt, it will use only the conversation history to respond.
484+
485+ For smaller models, you may want to use the optional prompt string to add additional instructions to explicitly
486+ instruct the model to output the structured data.
487+
488+ Args:
489+ output_model: The output model (a JSON schema written as a Pydantic BaseModel)
490+ that the agent will use when responding.
491+ prompt: The prompt to use for the agent.
492+
493+ Raises:
494+ ValueError: If no conversation history or prompt is provided.
456495 """
457496 self ._hooks .invoke_callbacks (StartRequestEvent (agent = self ))
458497
459498 try :
460- messages = self .messages
461- if not messages and not prompt :
499+ if not self .messages and not prompt :
462500 raise ValueError ("No conversation history or prompt provided" )
463501
464502 # add the prompt as the last message
465503 if prompt :
466- messages .append ({"role" : "user" , "content" : [{"text" : prompt }]})
504+ self . messages .append ({"role" : "user" , "content" : [{"text" : prompt }]})
467505
468- # get the structured output from the model
469- events = self .model .structured_output (output_model , messages )
470- for event in events :
506+ events = self .model .structured_output (output_model , self .messages )
507+ async for event in events :
471508 if "callback" in event :
472509 self .callback_handler (** cast (dict , event ["callback" ]))
473510
474511 return event ["output" ]
512+
475513 finally :
476514 self ._hooks .invoke_callbacks (EndRequestEvent (agent = self ))
477515
@@ -511,21 +549,22 @@ async def stream_async(self, prompt: str, **kwargs: Any) -> AsyncIterator[Any]:
511549
512550 try :
513551 events = self ._run_loop (prompt , kwargs )
514- for event in events :
552+ async for event in events :
515553 if "callback" in event :
516554 callback_handler (** event ["callback" ])
517555 yield event ["callback" ]
518556
519- stop_reason , message , metrics , state = event ["stop" ]
520- result = AgentResult (stop_reason , message , metrics , state )
557+ result = AgentResult (* event ["stop" ])
558+ callback_handler (result = result )
559+ yield {"result" : result }
521560
522561 self ._end_agent_trace_span (response = result )
523562
524563 except Exception as e :
525564 self ._end_agent_trace_span (error = e )
526565 raise
527566
528- def _run_loop (self , prompt : str , kwargs : dict [str , Any ]) -> Generator [dict [str , Any ], None , None ]:
567+ async def _run_loop (self , prompt : str , kwargs : dict [str , Any ]) -> AsyncGenerator [dict [str , Any ], None ]:
529568 """Execute the agent's event loop with the given prompt and parameters."""
530569 self ._hooks .invoke_callbacks (StartRequestEvent (agent = self ))
531570
@@ -539,13 +578,15 @@ def _run_loop(self, prompt: str, kwargs: dict[str, Any]) -> Generator[dict[str,
539578 self .messages .append (new_message )
540579
541580 # Execute the event loop cycle with retry logic for context limits
542- yield from self ._execute_event_loop_cycle (kwargs )
581+ events = self ._execute_event_loop_cycle (kwargs )
582+ async for event in events :
583+ yield event
543584
544585 finally :
545586 self .conversation_manager .apply_management (self )
546587 self ._hooks .invoke_callbacks (EndRequestEvent (agent = self ))
547588
548- def _execute_event_loop_cycle (self , kwargs : dict [str , Any ]) -> Generator [dict [str , Any ], None , None ]:
589+ async def _execute_event_loop_cycle (self , kwargs : dict [str , Any ]) -> AsyncGenerator [dict [str , Any ], None ]:
549590 """Execute the event loop cycle with retry logic for context window limits.
550591
551592 This internal method handles the execution of the event loop cycle and implements
@@ -583,7 +624,7 @@ def _execute_event_loop_cycle(self, kwargs: dict[str, Any]) -> Generator[dict[st
583624
584625 try :
585626 # Execute the main event loop cycle
586- yield from event_loop_cycle (
627+ events = event_loop_cycle (
587628 model = self .model ,
588629 system_prompt = self .system_prompt ,
589630 messages = self .messages , # will be modified by event_loop_cycle
@@ -594,11 +635,15 @@ def _execute_event_loop_cycle(self, kwargs: dict[str, Any]) -> Generator[dict[st
594635 event_loop_parent_span = self .trace_span ,
595636 kwargs = kwargs ,
596637 )
638+ async for event in events :
639+ yield event
597640
598641 except ContextWindowOverflowException as e :
599642 # Try reducing the context size and retrying
600643 self .conversation_manager .reduce_context (self , e = e )
601- yield from self ._execute_event_loop_cycle (kwargs )
644+ events = self ._execute_event_loop_cycle (kwargs )
645+ async for event in events :
646+ yield event
602647
603648 def _record_tool_execution (
604649 self ,
@@ -623,7 +668,7 @@ def _record_tool_execution(
623668 messages: The message history to append to.
624669 """
625670 # Create user message describing the tool call
626- user_msg_content : List [ContentBlock ] = [
671+ user_msg_content : list [ContentBlock ] = [
627672 {"text" : (f"agent.tool.{ tool ['name' ]} direct tool call.\n Input parameters: { json .dumps (tool ['input' ])} \n " )}
628673 ]
629674
0 commit comments