Compare commits

...
5 changed files with 66 additions and 57 deletions

No files matched your search

+1 -1
View File
@@ -1377,7 +1377,7 @@ export { // Python parity additions
MinimalTelemetry, AgentTelemetry, PerformanceMonitor, TelemetryCollector, TelemetryIntegration, cleanupTelemetry, cleanupTelemetryResources, createAgentTelemetry, createConsoleSink, createHTTPSink, createPerformanceMonitor, createTelemetryIntegration, disablePerformanceMode, disableTelemetry, enablePerformanceMode, enableTelemetry, getMinimalTelemetry, getTelemetry } from "./telemetry";
export { // Subagent Tool (agent-as-tool pattern)
SubagentTool, BaseTool, FunctionTool, ToolRegistry, ToolResult, ToolValidationError, createDelegator, createSubagentTool, createSubagentTools, createTool, getRegistry, getTool, registerTool, tool, validateTool } from "./tools";
export { airweaveSearch, bedrockBrowserClick, bedrockBrowserFill, bedrockBrowserNavigate, bedrockCodeInterpreter, codeExecution, codeMode, createCustomTool, exaSearch, firecrawlCrawl, firecrawlScrape, parallelSearch, perplexitySearch, registerCustomTool, registerLocalTool, registerNpmTool, superagentGuard, superagentRedact, superagentVerify, tavilyCrawl, tavilyExtract, tavilySearch, valyuBioSearch, valyuCompanyResearch, valyuEconomicsSearch, valyuFinanceSearch, valyuPaperSearch, valyuPatentSearch, valyuSecSearch, valyuWebSearch } from "./tools/builtins";
export { airweaveSearch, bedrockBrowserClick, bedrockBrowserFill, bedrockBrowserNavigate, bedrockCodeInterpreter, codeExecution, codeMode, createCustomTool, crwCrawl, crwScrape, exaSearch, firecrawlCrawl, firecrawlScrape, parallelSearch, perplexitySearch, registerCustomTool, registerLocalTool, registerNpmTool, superagentGuard, superagentRedact, superagentVerify, tavilyCrawl, tavilyExtract, tavilySearch, valyuBioSearch, valyuCompanyResearch, valyuEconomicsSearch, valyuFinanceSearch, valyuPaperSearch, valyuPatentSearch, valyuSecSearch, valyuWebSearch } from "./tools/builtins";
export { BudgetExceededError, MissingDependencyError, MissingEnvVarError, ToolsRegistry, composeMiddleware, createLoggingMiddleware, createRateLimitMiddleware, createRedactionMiddleware, createRetryMiddleware, createTimeoutMiddleware, createToolsRegistry, createTracingMiddleware, createValidationMiddleware, getToolsRegistry, get_registry, get_tool, register_tool, resetToolsRegistry, validate_tool } from "./tools/registry";
export type { InstallHints, PraisonTool, RedactionHooks, RegisteredTool, ToolCapabilities, ToolExecutionContext, ToolExecutionResult, ToolFactory, ToolHooks, ToolInstallStatus, ToolLimits, ToolLogger, ToolMetadata, ToolMiddleware, ToolParameterProperty, ToolParameterSchema } from "./tools/registry";
export { registerBuiltinTools, tools } from "./tools/tools";
@@ -119,7 +119,6 @@ class TestMemoryConfigHistoryFields:
assert agent._history_limit == 15
@pytest.mark.skip(reason="History injection implementation differs from test expectations - needs API alignment")
class TestHistoryInjection:
"""Test that history is actually injected into messages."""
@@ -8,7 +8,7 @@ instead of direct TOOL_MAPPINGS access.
import pytest
from unittest.mock import Mock, patch, MagicMock
from praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
class TestCLIToolResolverSmoke:
@@ -16,10 +16,10 @@ class TestCLIToolResolverSmoke:
def test_load_tools_imports_tool_resolver(self):
"""Test that _load_tools imports ToolResolver from correct module."""
cli = PraisonCLI()
cli = PraisonAI()
# Mock the ToolResolver import
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.return_value = None # No tools found
@@ -33,9 +33,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_resolver_instantiation_pattern(self):
"""Test that ToolResolver is instantiated correctly."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.return_value = Mock() # Mock tool
@@ -47,9 +47,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_calls_resolve_with_instantiate_true(self):
"""Test that resolve is called with instantiate=True."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_tool = Mock()
@@ -63,9 +63,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_multiple_comma_separated(self):
"""Test loading multiple comma-separated tools."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
@@ -85,9 +85,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_whitespace_handling(self):
"""Test that whitespace around tool names is handled correctly."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_tool = Mock()
@@ -104,9 +104,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_empty_string_filtering(self):
"""Test that empty strings are filtered out."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_tool = Mock()
@@ -122,9 +122,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_none_return_handling(self):
"""Test handling when resolver returns None (tool not found)."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.return_value = None # Tool not found
@@ -137,9 +137,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_exception_handling(self):
"""Test that exceptions during tool resolution are handled gracefully."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.side_effect = Exception("Tool resolution error")
@@ -153,13 +153,13 @@ class TestCLIToolResolverSmoke:
def test_load_tools_file_path_bypass(self):
"""Test that file paths bypass the ToolResolver and use file loading."""
cli = PraisonCLI()
cli = PraisonAI()
# Mock os.path.isfile to return True for file path test
with patch('os.path.isfile', return_value=True):
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
# Also need to mock the file loading parts
with patch('praisonai.cli.main.load_user_module', return_value=None):
with patch('praisonai._safe_loader.load_user_module', return_value=None):
result = cli._load_tools("/path/to/tools.py")
# ToolResolver should NOT be called for file paths
@@ -167,9 +167,9 @@ class TestCLIToolResolverSmoke:
def test_load_tools_empty_input(self):
"""Test handling of empty or None input."""
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
# Test empty string
result = cli._load_tools("")
assert result == []
@@ -182,7 +182,7 @@ class TestCLIToolResolverSmoke:
def test_load_tools_preserves_tool_instances(self):
"""Test that tool instances returned by resolver are preserved."""
cli = PraisonCLI()
cli = PraisonAI()
# Create mock tools with specific attributes
mock_tool1 = Mock()
@@ -190,7 +190,7 @@ class TestCLIToolResolverSmoke:
mock_tool2 = Mock()
mock_tool2.name = "tool2"
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.side_effect = [mock_tool1, mock_tool2]
@@ -213,23 +213,23 @@ class TestCLIToolResolverConsistency:
# This test ensures the import path is correct and consistent
try:
from praisonai.tool_resolver import ToolResolver
from praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
# If both imports work, the path is consistent
assert ToolResolver is not None
assert PraisonCLI is not None
assert PraisonAI is not None
except ImportError as e:
pytest.fail(f"Import path inconsistency detected: {e}")
def test_resolver_instantiation_consistency(self):
"""Test that CLI instantiates ToolResolver the same way as other components."""
from praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
cli = PraisonCLI()
cli = PraisonAI()
# Mock to capture how ToolResolver is instantiated
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.return_value = Mock()
@@ -241,11 +241,11 @@ class TestCLIToolResolverConsistency:
def test_resolve_method_signature_consistency(self):
"""Test that resolve method is called with expected signature."""
from praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_instance = Mock()
MockResolver.return_value = mock_instance
mock_instance.resolve.return_value = Mock()
@@ -93,8 +93,10 @@ class TestAsyncIODaemonStateUpdates:
# Mock the _update_state_if_daemon method
with patch.object(scheduler, '_update_state_if_daemon') as mock_update:
with patch('asyncio.to_thread') as mock_to_thread:
mock_to_thread.return_value = asyncio.create_future()
mock_to_thread.return_value.set_result(None)
future = asyncio.Future()
future.set_result(None)
mock_to_thread.return_value = future
# Execute with retry - should call _update_state_if_daemon via asyncio.to_thread
await scheduler._execute_with_retry(max_retries=1)
@@ -110,8 +112,10 @@ class TestAsyncIODaemonStateUpdates:
scheduler = AsyncAgentScheduler(mock_agent, "test task")
with patch('asyncio.to_thread') as mock_to_thread:
mock_to_thread.return_value = asyncio.create_future()
mock_to_thread.return_value.set_result(None)
future = asyncio.Future()
future.set_result(None)
mock_to_thread.return_value = future
await scheduler._execute_with_retry(max_retries=1)
@@ -127,9 +131,11 @@ class TestAsyncIODaemonStateUpdates:
scheduler = AsyncAgentScheduler(mock_agent, "test task")
with patch('asyncio.to_thread') as mock_to_thread:
mock_to_thread.return_value = asyncio.create_future()
mock_to_thread.return_value.set_result(None)
future = asyncio.Future()
future.set_result(None)
mock_to_thread.return_value = future
await scheduler._execute_with_retry(max_retries=1)
# Should be called once on failure path
@@ -271,12 +277,12 @@ class TestCLIToolResolverIntegration:
def test_cli_load_tools_uses_resolver(self):
"""Test that _load_tools uses ToolResolver.resolve instead of direct TOOL_MAPPINGS."""
from praisonai.praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
cli = PraisonCLI()
cli = PraisonAI()
# Mock ToolResolver
with patch('praisonai.praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_resolver_instance = Mock()
MockResolver.return_value = mock_resolver_instance
mock_resolver_instance.resolve.return_value = Mock() # Mock tool
@@ -294,11 +300,11 @@ class TestCLIToolResolverIntegration:
def test_cli_load_tools_handles_empty_strings(self):
"""Test that _load_tools filters out empty strings from tool names."""
from praisonai.praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_resolver_instance = Mock()
MockResolver.return_value = mock_resolver_instance
mock_resolver_instance.resolve.return_value = Mock()
@@ -313,11 +319,11 @@ class TestCLIToolResolverIntegration:
def test_cli_load_tools_error_handling(self):
"""Test that _load_tools handles resolution errors gracefully."""
from praisonai.praisonai.cli.main import PraisonCLI
from praisonai.cli.main import PraisonAI
cli = PraisonCLI()
cli = PraisonAI()
with patch('praisonai.praisonai.cli.main.ToolResolver') as MockResolver:
with patch("praisonai.tool_resolver.ToolResolver") as MockResolver:
mock_resolver_instance = Mock()
MockResolver.return_value = mock_resolver_instance
# Simulate tool resolution failure
@@ -335,7 +341,7 @@ class TestFrameworkAdapterDuplication:
def test_no_duplicate_arun_in_protocol(self):
"""Test that FrameworkAdapter protocol has single arun method."""
from praisonai.praisonai.framework_adapters.base import FrameworkAdapter
from praisonai.framework_adapters.base import FrameworkAdapter
# Get all method names from the protocol
import inspect
@@ -348,7 +354,7 @@ class TestFrameworkAdapterDuplication:
def test_framework_adapter_has_cleanup_method(self):
"""Test that FrameworkAdapter protocol includes cleanup method."""
from praisonai.praisonai.framework_adapters.base import FrameworkAdapter
from praisonai.framework_adapters.base import FrameworkAdapter
# Check that cleanup method is defined in the protocol
import inspect
+10 -6
View File
@@ -1415,18 +1415,22 @@ def custom_search(query: str) -> str:
# After context, original state should be restored
# (implementation detail - registry should be isolated)
def test_default_custom_dirs(self):
"""Test that default custom tool directories are defined."""
from praisonai.templates.tool_override import ToolOverrideLoader
loader = ToolOverrideLoader()
default_dirs = loader.get_default_tool_dirs()
assert len(default_dirs) >= 2
assert any(".praison/tools" in str(d) for d in default_dirs)
assert any(".config/praison/tools" in str(d) or ".praison/tools" in str(d) for d in default_dirs)
assert any(".praison/tools" in Path(d).as_posix() for d in default_dirs)
assert any(
".config/praison/tools" in Path(d).as_posix()
or ".praison/tools" in Path(d).as_posix()
for d in default_dirs
)
def test_no_scanning_on_import(self):
"""Test that no filesystem scanning occurs on import."""
import sys