22
33import functools
44import inspect
5- from collections .abc import Callable
5+ from collections .abc import Awaitable , Callable
66from functools import cached_property
77from typing import TYPE_CHECKING , Any , Literal
88
@@ -39,6 +39,9 @@ class Tool(BaseModel):
3939 default = ["sync" ], description = "Supported invocation modes (sync/async)"
4040 )
4141 meta : dict [str , Any ] | None = Field (description = "Optional additional tool information." , default = None )
42+ immediate_result : Callable [..., Awaitable [list [Any ]]] | None = Field (
43+ None , exclude = True , description = "Optional immediate result function for async tools"
44+ )
4245
4346 @cached_property
4447 def output_schema (self ) -> dict [str , Any ] | None :
@@ -55,7 +58,9 @@ def from_function(
5558 annotations : ToolAnnotations | None = None ,
5659 structured_output : bool | None = None ,
5760 invocation_modes : list [InvocationMode ] | None = None ,
61+ keep_alive : int | None = None ,
5862 meta : dict [str , Any ] | None = None ,
63+ immediate_result : Callable [..., Awaitable [list [Any ]]] | None = None ,
5964 ) -> Tool :
6065 """Create a Tool from a function."""
6166 func_name = name or fn .__name__
@@ -80,6 +85,39 @@ def from_function(
8085 if invocation_modes is None :
8186 invocation_modes = ["sync" ]
8287
88+ # Set appropriate default keep_alive based on async compatibility
89+ # if user didn't specify custom keep_alive
90+ if keep_alive is None and "async" in invocation_modes :
91+ keep_alive = 3600 # Default for async-compatible tools
92+
93+ # Validate keep_alive is only used with async-compatible tools
94+ if keep_alive is not None and "async" not in invocation_modes :
95+ raise ValueError (
96+ f"keep_alive parameter can only be used with async-compatible tools. "
97+ f"Tool '{ func_name } ' has invocation_modes={ invocation_modes } "
98+ f"but specifies keep_alive={ keep_alive } . "
99+ f"Add 'async' to invocation_modes to use keep_alive."
100+ )
101+
102+ # Process meta dictionary and add keep_alive if specified
103+ meta = meta or {}
104+ if keep_alive is not None :
105+ meta = meta .copy () # Don't modify the original dict
106+ meta ["_keep_alive" ] = keep_alive
107+
108+ # Validate immediate_result usage
109+ if immediate_result is not None :
110+ # Check if tool supports async invocation
111+ if "async" not in invocation_modes :
112+ raise ValueError (
113+ "immediate_result can only be used with async-compatible tools. "
114+ "Add 'async' to invocation_modes to use immediate_result."
115+ )
116+
117+ # Validate that immediate_result is an async callable
118+ if not _is_async_callable (immediate_result ):
119+ raise ValueError ("immediate_result must be an async callable that returns list[ContentBlock]" )
120+
83121 return cls (
84122 fn = fn ,
85123 name = func_name ,
@@ -92,6 +130,7 @@ def from_function(
92130 annotations = annotations ,
93131 invocation_modes = invocation_modes ,
94132 meta = meta ,
133+ immediate_result = immediate_result ,
95134 )
96135
97136 async def run (
0 commit comments