@@ -447,6 +447,241 @@ def test_create_tool_with_local_source(nexent_agent_instance):
447447 assert result == "local_tool"
448448
449449
450+ def test_create_local_tool_success (nexent_agent_instance ):
451+ """Test successful creation of a local tool."""
452+ mock_tool_class = MagicMock ()
453+ mock_tool_instance = MagicMock ()
454+ mock_tool_class .return_value = mock_tool_instance
455+
456+ tool_config = ToolConfig (
457+ class_name = "DummyTool" ,
458+ name = "dummy" ,
459+ description = "desc" ,
460+ inputs = "{}" ,
461+ output_type = "string" ,
462+ params = {"param1" : "value1" , "param2" : 42 },
463+ source = "local" ,
464+ metadata = {},
465+ )
466+
467+ # Patch the module's globals to include our mock tool class
468+ import sdk .nexent .core .agents .nexent_agent as nexent_agent_module
469+ original_value = nexent_agent_module .__dict__ .get ("DummyTool" )
470+ nexent_agent_module .__dict__ ["DummyTool" ] = mock_tool_class
471+
472+ try :
473+ result = nexent_agent_instance .create_local_tool (tool_config )
474+ finally :
475+ # Restore original value
476+ if original_value is not None :
477+ nexent_agent_module .__dict__ ["DummyTool" ] = original_value
478+ elif "DummyTool" in nexent_agent_module .__dict__ :
479+ del nexent_agent_module .__dict__ ["DummyTool" ]
480+
481+ mock_tool_class .assert_called_once_with (param1 = "value1" , param2 = 42 )
482+ assert result == mock_tool_instance
483+
484+
485+ def test_create_local_tool_class_not_found (nexent_agent_instance ):
486+ """Test create_local_tool raises ValueError when class is not found."""
487+ tool_config = ToolConfig (
488+ class_name = "NonExistentTool" ,
489+ name = "dummy" ,
490+ description = "desc" ,
491+ inputs = "{}" ,
492+ output_type = "string" ,
493+ params = {},
494+ source = "local" ,
495+ metadata = {},
496+ )
497+
498+ with pytest .raises (ValueError , match = "NonExistentTool not found in local" ):
499+ nexent_agent_instance .create_local_tool (tool_config )
500+
501+
502+ def test_create_local_tool_knowledge_base_search_tool_success (nexent_agent_instance ):
503+ """Test successful creation of KnowledgeBaseSearchTool with metadata."""
504+ mock_kb_tool_class = MagicMock ()
505+ mock_kb_tool_instance = MagicMock ()
506+ mock_kb_tool_class .return_value = mock_kb_tool_instance
507+
508+ mock_vdb_core = MagicMock ()
509+ mock_embedding_model = MagicMock ()
510+
511+ tool_config = ToolConfig (
512+ class_name = "KnowledgeBaseSearchTool" ,
513+ name = "knowledge_base_search" ,
514+ description = "desc" ,
515+ inputs = "{}" ,
516+ output_type = "string" ,
517+ params = {"top_k" : 10 },
518+ source = "local" ,
519+ metadata = {
520+ "index_names" : ["index1" , "index2" ],
521+ "vdb_core" : mock_vdb_core ,
522+ "embedding_model" : mock_embedding_model ,
523+ },
524+ )
525+
526+ import sdk .nexent .core .agents .nexent_agent as nexent_agent_module
527+ original_value = nexent_agent_module .__dict__ .get ("KnowledgeBaseSearchTool" )
528+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = mock_kb_tool_class
529+
530+ try :
531+ result = nexent_agent_instance .create_local_tool (tool_config )
532+ finally :
533+ # Restore original value
534+ if original_value is not None :
535+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = original_value
536+ elif "KnowledgeBaseSearchTool" in nexent_agent_module .__dict__ :
537+ del nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ]
538+
539+ # Verify only non-excluded params are passed to __init__
540+ mock_kb_tool_class .assert_called_once_with (
541+ top_k = 10 , # Only non-excluded params passed to __init__
542+ )
543+ # Verify excluded parameters were set directly as attributes after instantiation
544+ assert result == mock_kb_tool_instance
545+ assert mock_kb_tool_instance .observer == nexent_agent_instance .observer
546+ assert mock_kb_tool_instance .index_names == ["index1" , "index2" ]
547+ assert mock_kb_tool_instance .vdb_core == mock_vdb_core
548+ assert mock_kb_tool_instance .embedding_model == mock_embedding_model
549+
550+
551+ def test_create_local_tool_knowledge_base_search_tool_with_conflicting_params (nexent_agent_instance ):
552+ """Test KnowledgeBaseSearchTool creation filters out conflicting params from params dict."""
553+ mock_kb_tool_class = MagicMock ()
554+ mock_kb_tool_instance = MagicMock ()
555+ mock_kb_tool_class .return_value = mock_kb_tool_instance
556+
557+ mock_vdb_core = MagicMock ()
558+ mock_embedding_model = MagicMock ()
559+
560+ tool_config = ToolConfig (
561+ class_name = "KnowledgeBaseSearchTool" ,
562+ name = "knowledge_base_search" ,
563+ description = "desc" ,
564+ inputs = "{}" ,
565+ output_type = "string" ,
566+ params = {
567+ "top_k" : 10 ,
568+ "index_names" : ["conflicting_index" ], # This should be filtered out
569+ "vdb_core" : "conflicting_vdb" , # This should be filtered out
570+ "embedding_model" : "conflicting_model" , # This should be filtered out
571+ "observer" : "conflicting_observer" , # This should be filtered out
572+ },
573+ source = "local" ,
574+ metadata = {
575+ "index_names" : ["index1" , "index2" ], # These should be used instead
576+ "vdb_core" : mock_vdb_core ,
577+ "embedding_model" : mock_embedding_model ,
578+ },
579+ )
580+
581+ import sdk .nexent .core .agents .nexent_agent as nexent_agent_module
582+ original_value = nexent_agent_module .__dict__ .get ("KnowledgeBaseSearchTool" )
583+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = mock_kb_tool_class
584+
585+ try :
586+ result = nexent_agent_instance .create_local_tool (tool_config )
587+ finally :
588+ # Restore original value
589+ if original_value is not None :
590+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = original_value
591+ elif "KnowledgeBaseSearchTool" in nexent_agent_module .__dict__ :
592+ del nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ]
593+
594+ # Verify conflicting params were filtered out from __init__ call
595+ # Only non-excluded params should be passed to __init__ due to smolagents wrapper restrictions
596+ mock_kb_tool_class .assert_called_once_with (
597+ top_k = 10 , # From filtered_params (not in conflict list)
598+ )
599+ # Verify excluded parameters were set directly as attributes after instantiation
600+ assert result == mock_kb_tool_instance
601+ assert mock_kb_tool_instance .observer == nexent_agent_instance .observer
602+ assert mock_kb_tool_instance .index_names == ["index1" , "index2" ] # From metadata, not params
603+ assert mock_kb_tool_instance .vdb_core == mock_vdb_core # From metadata, not params
604+ assert mock_kb_tool_instance .embedding_model == mock_embedding_model # From metadata, not params
605+
606+
607+ def test_create_local_tool_knowledge_base_search_tool_with_none_defaults (nexent_agent_instance ):
608+ """Test KnowledgeBaseSearchTool creation with None defaults when metadata is missing."""
609+ mock_kb_tool_class = MagicMock ()
610+ mock_kb_tool_instance = MagicMock ()
611+ mock_kb_tool_class .return_value = mock_kb_tool_instance
612+
613+ tool_config = ToolConfig (
614+ class_name = "KnowledgeBaseSearchTool" ,
615+ name = "knowledge_base_search" ,
616+ description = "desc" ,
617+ inputs = "{}" ,
618+ output_type = "string" ,
619+ params = {"top_k" : 5 },
620+ source = "local" ,
621+ metadata = {}, # No metadata provided
622+ )
623+
624+ import sdk .nexent .core .agents .nexent_agent as nexent_agent_module
625+ original_value = nexent_agent_module .__dict__ .get ("KnowledgeBaseSearchTool" )
626+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = mock_kb_tool_class
627+
628+ try :
629+ result = nexent_agent_instance .create_local_tool (tool_config )
630+ finally :
631+ # Restore original value
632+ if original_value is not None :
633+ nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ] = original_value
634+ elif "KnowledgeBaseSearchTool" in nexent_agent_module .__dict__ :
635+ del nexent_agent_module .__dict__ ["KnowledgeBaseSearchTool" ]
636+
637+ # Verify only non-excluded params are passed to __init__
638+ mock_kb_tool_class .assert_called_once_with (
639+ top_k = 5 ,
640+ )
641+ # Verify excluded parameters were set directly as attributes with None defaults when metadata is missing
642+ assert result == mock_kb_tool_instance
643+ assert mock_kb_tool_instance .observer == nexent_agent_instance .observer
644+ assert mock_kb_tool_instance .index_names == [] # Empty list when None
645+ assert mock_kb_tool_instance .vdb_core is None
646+ assert mock_kb_tool_instance .embedding_model is None
647+ assert result == mock_kb_tool_instance
648+
649+
650+ def test_create_local_tool_with_observer_attribute (nexent_agent_instance ):
651+ """Test create_local_tool sets observer attribute on tool if it exists."""
652+ mock_tool_class = MagicMock ()
653+ mock_tool_instance = MagicMock ()
654+ mock_tool_instance .observer = None # Initially no observer
655+ mock_tool_class .return_value = mock_tool_instance
656+
657+ tool_config = ToolConfig (
658+ class_name = "ToolWithObserver" ,
659+ name = "tool" ,
660+ description = "desc" ,
661+ inputs = "{}" ,
662+ output_type = "string" ,
663+ params = {},
664+ source = "local" ,
665+ metadata = {},
666+ )
667+
668+ import sdk .nexent .core .agents .nexent_agent as nexent_agent_module
669+ original_value = nexent_agent_module .__dict__ .get ("ToolWithObserver" )
670+ nexent_agent_module .__dict__ ["ToolWithObserver" ] = mock_tool_class
671+
672+ try :
673+ result = nexent_agent_instance .create_local_tool (tool_config )
674+ finally :
675+ # Restore original value
676+ if original_value is not None :
677+ nexent_agent_module .__dict__ ["ToolWithObserver" ] = original_value
678+ elif "ToolWithObserver" in nexent_agent_module .__dict__ :
679+ del nexent_agent_module .__dict__ ["ToolWithObserver" ]
680+
681+ # Verify observer was set on the tool instance
682+ assert result .observer == nexent_agent_instance .observer
683+
684+
450685def test_create_tool_with_mcp_source (nexent_agent_instance ):
451686 """Ensure create_tool dispatches to create_mcp_tool for mcp source."""
452687 tool_config = ToolConfig (
0 commit comments