@@ -232,6 +232,38 @@ def _find_first_mcp_error_nested(exc: BaseException) -> McpError | None:
232232    tool .coroutine  =  wrapped_mcp_coroutine 
233233    return  tool 
234234
235+ def  load_custom_tools (config : RunnableConfig , existing_tool_names : set [str ]) ->  List [BaseTool ]:
236+     """Load custom Python functions as tools""" 
237+     configurable  =  Configuration .from_runnable_config (config )
238+     if  not  configurable .custom_tools :
239+         return  []
240+     
241+     tools  =  []
242+     for  tool_func  in  configurable .custom_tools :
243+         # Check if tool already exists 
244+         tool_name  =  getattr (tool_func , 'name' , str (tool_func ))
245+         if  tool_name  in  existing_tool_names :
246+             warnings .warn (f"Tool { tool_name }  )
247+             continue 
248+         
249+         # Ensure it's a proper LangChain tool 
250+         if  isinstance (tool_func , BaseTool ):
251+             tools .append (tool_func )
252+         elif  callable (tool_func ):
253+             # If it's a callable but not a BaseTool, wrap it 
254+             try :
255+                 # If it's already decorated with @tool, it should be a BaseTool 
256+                 if  hasattr (tool_func , 'name' ) and  hasattr (tool_func , 'description' ):
257+                     tools .append (tool_func )
258+                 else :
259+                     warnings .warn (f"Tool { tool_name }  )
260+             except  Exception  as  e :
261+                 warnings .warn (f"Error processing custom tool { tool_name } { e }  )
262+         else :
263+             warnings .warn (f"Invalid tool type for { tool_name }  )
264+     
265+     return  tools 
266+ 
235267async  def  load_mcp_tools (
236268    config : RunnableConfig ,
237269    existing_tool_names : set [str ],
@@ -292,6 +324,13 @@ async def get_all_tools(config: RunnableConfig):
292324    search_api  =  SearchAPI (get_config_value (configurable .search_api ))
293325    tools .extend (await  get_search_tool (search_api ))
294326    existing_tool_names  =  {tool .name  if  hasattr (tool , "name" ) else  tool .get ("name" , "web_search" ) for  tool  in  tools }
327+     
328+     # Add custom tools 
329+     custom_tools  =  load_custom_tools (config , existing_tool_names )
330+     tools .extend (custom_tools )
331+     existing_tool_names .update ({tool .name  for  tool  in  custom_tools  if  hasattr (tool , "name" )})
332+     
333+     # Keep MCP tools last 
295334    mcp_tools  =  await  load_mcp_tools (config , existing_tool_names )
296335    tools .extend (mcp_tools )
297336    return  tools 
0 commit comments