2222import threading
2323from collections .abc import Mapping , Sequence
2424from os import PathLike
25- from typing import Any , Callable , NoReturn
25+ from typing import Any , Callable , ClassVar , NoReturn
2626
2727from pydantic import BaseModel
2828from pydantic_core import PydanticSerializationError , to_jsonable_python
@@ -241,6 +241,22 @@ def my_tool(question: str) -> str:
241241 ```
242242 """
243243
244+ _RESERVED_TOOL_NAMES : ClassVar [set [str ]] = {
245+ "SUBMIT" ,
246+ "FinalOutput" ,
247+ "JsProxy" ,
248+ "_js_tool_call" ,
249+ "buf_stderr" ,
250+ "buf_stdout" ,
251+ "io" ,
252+ "json" ,
253+ "last_exception_args" ,
254+ "old_stderr" ,
255+ "old_stdout" ,
256+ "run_sync" ,
257+ "sys" ,
258+ }
259+
244260 def __init__ (
245261 self ,
246262 deno_command : list [str ] | None = None ,
@@ -286,6 +302,7 @@ def __init__(
286302 self .enable_network_access = enable_network_access or []
287303 self .sync_files = sync_files
288304 self .tools = {}
305+ self ._tool_parameters = {}
289306 self .output_fields = None
290307 self .callbacks = list (callbacks or [])
291308 self ._tools_registered = True
@@ -302,8 +319,8 @@ def __init__(
302319 self ._session_ended = False
303320 self ._state_lock = threading .Lock ()
304321 self ._execution_active = False
305- if tools or output_fields :
306- self .bind (tools = tools or {}, output_fields = output_fields )
322+ if tools is not None or output_fields is not None :
323+ self .bind (tools = tools if tools is not None else {}, output_fields = output_fields )
307324
308325 @property
309326 def execution_instructions (self ) -> str :
@@ -433,14 +450,25 @@ def bind(
433450 raise TypeError (f"tools must be a mapping, not { type (tools ).__name__ } " )
434451
435452 copied_tools : dict [str , Callable [..., Any ]] = {}
453+ copied_tool_parameters = {}
436454 for name , tool in tools .items ():
437455 if not isinstance (name , str ) or not name .isidentifier () or keyword .iskeyword (name ):
438456 raise CodeInterpreterError (f"Invalid tool name: { name !r} " )
439- if name in { "SUBMIT" , "FinalOutput" } :
457+ if name in self . _RESERVED_TOOL_NAMES :
440458 raise CodeInterpreterError (f"Tool name { name !r} is reserved by the interpreter" )
441459 if not callable (tool ):
442460 raise CodeInterpreterError (f"Tool { name !r} must be callable, not { type (tool ).__name__ } " )
461+ previous = self ._tool_parameters .get (name )
462+ if previous is not None and previous [0 ] is tool :
463+ parameters = previous [1 ]
464+ else :
465+ try :
466+ parameters = self ._extract_parameters (tool )
467+ parameters = json .loads (json .dumps (parameters , allow_nan = False ))
468+ except (TypeError , ValueError ) as e :
469+ raise CodeInterpreterError (f"Tool { name !r} has an unsupported signature: { e } " ) from e
443470 copied_tools [name ] = tool
471+ copied_tool_parameters [name ] = (tool , parameters )
444472
445473 copied_output_fields = None
446474 if output_fields is not None :
@@ -471,6 +499,7 @@ def bind(
471499 if self ._execution_active :
472500 raise RuntimeError ("Cannot bind tools while PythonInterpreter execution is active." )
473501 self .tools = copied_tools
502+ self ._tool_parameters = copied_tool_parameters
474503 self .output_fields = copied_output_fields
475504 self ._tools_registered = False
476505
@@ -565,9 +594,10 @@ def _register_tools(self) -> None:
565594 # after a previous typed binding.
566595 tools_info = []
567596 for name , fn in self .tools .items ():
597+ cached = self ._tool_parameters .get (name )
568598 tools_info .append ({
569599 "name" : name ,
570- "parameters" : self ._extract_parameters (fn )
600+ "parameters" : cached [ 1 ] if cached is not None and cached [ 0 ] is fn else self ._extract_parameters (fn )
571601 })
572602 params = {"tools" : tools_info , "outputs" : self .output_fields or []}
573603
0 commit comments