Tool Configuration
Complete reference for tool timeout settings and performance tuning
Tool Configuration
This page provides comprehensive documentation for configuring tools in PraisonAI, including timeout settings, performance optimization, error handling, and resource management.
Tool Timeout Configuration
Basic Timeout Settings
from praisonaiagents import Agent, Tool, ToolConfig
# Configure tool with timeout
tool = Tool(
name="web_scraper",
description="Scrape web pages",
function=scrape_webpage,
config={
"timeout": 30, # seconds
"retry_on_timeout": True,
"timeout_message": "Web scraping timed out"
}
)
agent = Agent(
name="Researcher",
tools=[tool],
tool_config=ToolConfig(timeout=30),
)Consolidated ToolConfig (new in PraisonAI 0.0.6x)
PraisonAI now provides a unified ToolConfig dataclass that consolidates tool-related parameters into a single configuration object. This replaces the previous pattern of separate tool_timeout, parallel_tool_calls, and tool_retry_policy parameters.
from praisonaiagents import Agent, ExecutionConfig, ToolConfig
from praisonaiagents.tools.retry import RetryPolicy
agent = Agent(
name="researcher",
tools=[web_search],
tool_config=ToolConfig(
timeout=30,
retry_policy=RetryPolicy(max_attempts=3),
),
execution=ExecutionConfig(parallel_tool_calls=True),
)ToolConfig Options
| Option | Type | Default | Description |
|---|---|---|---|
timeout | int | None | Tool execution timeout in seconds |
parallel | bool | False | Deprecated alias for ExecutionConfig.parallel_tool_calls. Prefer execution=ExecutionConfig(parallel_tool_calls=True). Conflicting values across both spellings raise TypeError. |
retry_policy | RetryPolicy | None | Authoritative tool-retry budget. When set, overrides the ExecutionConfig retry spelling (max_retry_limit / retry_*). See Tool Retry Policy. |
output_limit | int | 16000 | Maximum bytes of tool output before spilling to the artifact store |
output_max_lines | int | None | Max lines kept from a tool’s output (None = no line cap). Sync tool loop only |
output_direction | str | "both" | Which end(s) survive the line cap and the output_limit char cap: "head", "tail", or "both" (with a marker between) |
enable_artifacts | bool | False | Master switch — enables artifact spillover for large tool outputs |
artifact_retention_days | int | 7 | Days to keep artifacts before garbage collection on agent destruction |
artifact_store | Any | None | Custom store instance implementing ArtifactStoreProtocol |
redact_secrets | bool | True | Redact common secret patterns before writing artifacts to disk |
allow_global_tools | bool | False | Allow the agent to resolve tools from the process-global registry. When enabled, a global tool’s idempotent flag is also consulted for retry-on-timeout decisions. |
Artifact Storage
When enable_artifacts=True, tool outputs that exceed output_limit are saved to disk and the agent gains tools to page through them on demand — preventing silent data loss for large web scrapes, file reads, or query results.
from praisonaiagents import Agent, ToolConfig
agent = Agent(tool_config=ToolConfig(enable_artifacts=True))Full documentation: how overflow is stored, the six retrieval tools the agent gains, storage layout, and best practices.
Configuration Shortcuts
ToolConfig supports convenient shorthand notation:
from praisonaiagents import Agent, ToolConfig
# Boolean shorthand
agent = Agent(tool_config=True) # Uses default ToolConfig()
agent = Agent(tool_config=False) # Disables tool config
# Dict expansion
agent = Agent(tool_config={
"timeout": 30,
"retry_policy": {"max_attempts": 3},
"enable_artifacts": True,
})Removed legacy kwargs
The standalone tool_timeout, parallel_tool_calls, and tool_retry_policy keyword arguments on Agent(...) have been removed. Passing them raises TypeError. The error now includes an inline migration hint pointing at the ToolConfig field:
TypeError: Agent.__init__() got an unexpected keyword argument 'tool_timeout'
tool_timeout: tool timeout moved into tool_config=; use
tool_config=ToolConfig(timeout=...).Pass these settings through tool_config=ToolConfig(...) instead.
The TypeError now carries a “Did you mean?” hint naming the tool_config=ToolConfig(...) replacement, so callers upgrading from an older release see the fix inline rather than having to search the 41-parameter signature.
| Removed kwarg | New location |
|---|---|
tool_timeout=30 | tool_config=ToolConfig(timeout=30) |
parallel_tool_calls=True | execution=ExecutionConfig(parallel_tool_calls=True) (canonical) or tool_config=ToolConfig(parallel=True) (deprecated alias) |
tool_retry_policy=RetryPolicy(...) | tool_config=ToolConfig(retry_policy=RetryPolicy(...)) |
Passing tool_retry_policy and/or tool_timeout now names the replacement inline in the TypeError, so the exact error string is searchable:
Agent.__init__() got unexpected keyword argument(s): tool_retry_policy, tool_timeout
tool_retry_policy: tool retry moved into tool_config=; use tool_config=ToolConfig(retry_policy=RetryPolicy(...)).
tool_timeout: tool timeout moved into tool_config=; use tool_config=ToolConfig(timeout=...).YAML and the CLI are unaffected — tool_timeout: in YAML and --tool-timeout on the CLI remain valid; the wrapper translates them into a ToolConfig before constructing the Agent.
Agent-Level Tool Timeout
Each agent can set tool_config=ToolConfig(timeout=…) (or tool_config=True for defaults) which applies to all tool executions. When a tool times out, it returns a standardized error dict:
from praisonaiagents import Agent, ToolConfig
agent = Agent(
name="Assistant",
tools=["web_search"],
tool_config=ToolConfig(timeout=30) # seconds
)
# If web_search takes >30s, it returns:
# {"error": "Tool timed out after 30s", "timeout": True}Timeout behavior details:
- Each agent has its own 2-thread tool executor (reused across calls)
- Timed-out tools return
{"error": "Tool timed out after Ns", "timeout": True}rather than raising - Thread names are prefixed
tool-<agent_name>— useful for debugging
Why the change?
The consolidation groups timeout + retry + parallel execution settings into one config object instead of three loose kwargs. This provides better organization and prevents parameter conflicts.
Wrapper-Level Tool Timeout (YAML / CLI — Defense-in-Depth)
When you set tool_timeout in YAML (framework: praisonai) or pass --tool-timeout on the CLI, the wrapper now wraps every tool callable with a timeout-enforcing shim before handing the agent to the SDK.
This means the tool_timeout knob is enforced even if the downstream SDK ignores it, even if the tool is a blocking C extension, and even if the tool is a subprocess that does not honour CancelledError.
The wrapper handles sync and async tools differently:
- Sync tools run in an instance-owned
ThreadPoolExecutor(see_get_tool_timeout_executorinagents_generator.py); on timeout the future is best-effort cancelled. A call that already started cannot be interrupted, so the worker may keep running. - Async tools are wrapped with
asyncio.wait_for(...), which cancels the underlying task cleanly.
Exception raised on wrapper-level timeout: on timeout the wrapper raises ToolTimeoutError (a TimeoutError subclass), not a JSON dict. This preserves each tool’s declared return-type contract — a typed return value is never silently downgraded to a string. Framework adapters catch it and translate it per framework.
from praisonaiagents import Agent
from praisonai.agents_generator import ToolTimeoutError
def slow_lookup(query: str) -> str:
... # a tool that may hang
agent = Agent(
name="Researcher",
instructions="Look things up.",
tools=[slow_lookup],
tool_config={"timeout": 5}, # ToolConfig(timeout=5) also works
)
try:
agent.start("Look up X")
except ToolTimeoutError as e:
print(e.tool_name, e.timeout_seconds, e.background_work_may_continue)ToolTimeoutError carries three attributes:
| Attribute | Type | Description |
|---|---|---|
tool_name | str | Name of the tool that timed out |
timeout_seconds | float | The per-call limit that was exceeded |
background_work_may_continue | bool | True only when a started sync worker could not be cancelled; async tools cancel cleanly (False) |
On any adapter whose SUPPORTS_CORE_TOOL_TIMEOUT flag is True (the built-in PraisonAIAdapter and its subclasses, plus any third-party adapter opting in) with a plain callable, the wrapper forwards the resolved per-agent tool_timeout to core (via Agent(tool_config=ToolConfig(timeout=...))) and leaves the callable unwrapped — core is the single enforcement path, and a timeout surfaces core’s structured dict rather than the wrapper’s ToolTimeoutError. On non-native frameworks (crewai, autogen, ag2), and for framework tool objects on any framework, the wrapper still wraps the tool itself and raises ToolTimeoutError. See PR #5652 for the rationale. Subsecond budgets (tool_timeout: 0.4) are forwarded to core as a float, not rounded to 0.
Example usage:
framework: praisonai
topic: Fetch and summarise a slow upstream API
config:
tool_timeout: 15 # Wrapper-level + SDK-level enforcement
roles:
fetcher:
role: API Fetcher
goal: Pull data without wedging the crew on a slow upstream
backstory: Reliability-focused engineer.
tools:
- fetch_url
tasks:
fetch:
description: Fetch {topic}
expected_output: Summarised payload, or a translated timeout message if the upstream is slow.When fetch_url blocks for more than 15 s, the wrapper raises ToolTimeoutError instead of hanging indefinitely; the framework adapter catches it and translates it into a value its framework understands, so the crew keeps moving.
Precedence note: Three timeout layers can apply, in different units:
| Layer | Configure with | Unit |
|---|---|---|
| Wrapper boundary | YAML tool_timeout: N / CLI --tool-timeout N | seconds |
| SDK Agent executor | Agent(tool_config=ToolConfig(timeout=N)) | seconds |
| SDK tool-call executor | Agent(llm={"tool_timeout_ms": N}) | milliseconds |
On framework: praisonai, native plain callables route through the SDK Agent executor row only — the Wrapper boundary row applies to framework tool objects or non-native frameworks (see PR #5652).
When more than one applies, the shortest effective timeout wins in practice — whichever fires first short-circuits the tool call. The three layers are independent, so mind the units: tool_timeout_ms: 5000 equals a 5-second wrapper tool_timeout: 5. See Tool Call Executor Timeout for the millisecond layer.
As of PR #3176, the wrapper’s shared-tool-dict resolver picks the smallest declared tool_timeout across roles/agents (previously the largest). Combined with the executor’s own short-circuit, the effective timeout is genuinely the tightest across all three layers.
Which value the wrapper uses
The wrapper resolves a single effective timeout per invocation:
| Source | Precedence |
|---|---|
--tool-timeout N on the CLI | Wins outright |
tool_timeout: N inside any roles: or agents: entry in YAML | min(N) (tightest) across all entries, used when the CLI does not specify one; every overridden agent is warned in the logs |
| Nothing declared | No wrapper-layer wrapping (SDK-level timeout still applies if tool_config is set in Python) |
Boolean YAML values (tool_timeout: yes, tool_timeout: true) are explicitly ignored — they used to be silently coerced to a 1-second cap because bool subclasses int. Use integers or floats (e.g. tool_timeout: 30).
Advanced Timeout Configuration
timeout_config = {
# Per-tool timeout settings
"tool_timeouts": {
"web_search": {
"timeout": 30,
"connect_timeout": 5,
"read_timeout": 25,
"retry_on_timeout": True,
"max_retries": 2
},
"database_query": {
"timeout": 120,
"statement_timeout": 60, # SQL statement timeout
"lock_timeout": 10, # Lock acquisition timeout
"idle_timeout": 300 # Connection idle timeout
},
"api_call": {
"timeout": 45,
"dynamic_timeout": {
"base": 30,
"per_item": 0.5, # Additional time per item
"max": 120
}
}
},
# Global timeout settings
"global_config": {
"default_timeout": 60,
"max_total_time": 300, # Maximum time for all tool executions
"parallel_timeout": 120, # Timeout for parallel executions
"timeout_buffer": 5 # Buffer time for cleanup
}
}Dynamic Timeout Calculation
def calculate_dynamic_timeout(tool_name, input_data):
"""Calculate timeout based on input complexity"""
base_timeouts = {
"data_processor": 30,
"ml_model": 60,
"web_scraper": 45
}
base = base_timeouts.get(tool_name, 60)
# Adjust based on input size
if hasattr(input_data, '__len__'):
size_factor = len(input_data) / 100
return min(base + (size_factor * 10), 300)
return base
# Configure dynamic timeouts
dynamic_timeout_config = {
"timeout_calculator": calculate_dynamic_timeout,
"enable_dynamic": True,
"cache_calculations": True
}Performance Tuning
Resource Management
performance_config = {
# CPU and memory limits
"resource_limits": {
"max_cpu_percent": 80,
"max_memory_mb": 2048,
"max_threads": 10,
"max_processes": 4
},
# Execution optimization
"execution": {
"parallel_tools": True,
"max_parallel": 5,
"queue_size": 100,
"priority_queue": True,
"batch_processing": True,
"batch_size": 10
},
# Caching configuration
"cache": {
"enabled": True,
"cache_size": 1000,
"ttl": 3600,
"cache_strategy": "lru",
"serialize_method": "pickle"
}
}Tool Execution Strategies
# Sequential execution (default)
sequential_config = {
"execution_strategy": "sequential",
"fail_fast": True,
"continue_on_error": False
}
# Parallel execution
parallel_config = {
"execution_strategy": "parallel",
"max_workers": 5,
"thread_pool": True, # Use threads instead of processes
"chunk_size": 10,
"ordered_results": True
}
# Async execution
async_config = {
"execution_strategy": "async",
"event_loop": "asyncio",
"max_concurrent": 10,
"semaphore_limit": 5,
"gather_errors": True
}
# Pipeline execution
pipeline_config = {
"execution_strategy": "pipeline",
"stages": [
{"name": "fetch", "parallel": True},
{"name": "process", "parallel": False},
{"name": "store", "parallel": True}
],
"buffer_size": 100,
"backpressure": True
}Performance Monitoring
monitoring_config = {
"metrics": {
"track_execution_time": True,
"track_memory_usage": True,
"track_cpu_usage": True,
"track_io_operations": True
},
"profiling": {
"enabled": False, # Enable for debugging
"profile_type": "cProfile",
"output_format": "stats",
"top_functions": 20
},
"alerts": {
"slow_execution": {
"threshold": 30, # seconds
"action": "log_warning"
},
"high_memory": {
"threshold": 1024, # MB
"action": "garbage_collect"
},
"repeated_timeouts": {
"threshold": 3,
"window": 300, # 5 minutes
"action": "circuit_break"
}
}
}Tool-Specific Configurations
Web Scraping Tools
web_scraping_config = {
"timeout": 45,
"page_load_timeout": 30,
"script_timeout": 20,
"implicit_wait": 10,
"retry_config": {
"max_retries": 3,
"retry_on": ["timeout", "connection_error"],
"backoff_factor": 2
},
"performance": {
"headless": True,
"disable_images": True,
"disable_javascript": False,
"use_cache": True,
"connection_pool_size": 10
},
"rate_limiting": {
"requests_per_second": 2,
"burst_size": 5,
"respect_robots_txt": True
}
}Database Tools
database_config = {
"timeout": 60,
"connection_timeout": 10,
"statement_timeout": 50,
"pool_config": {
"min_connections": 2,
"max_connections": 20,
"connection_lifetime": 3600,
"idle_timeout": 600,
"retry_on_connection_failure": True
},
"query_optimization": {
"use_prepared_statements": True,
"batch_size": 1000,
"fetch_size": 100,
"use_compression": True
},
"transaction_config": {
"isolation_level": "READ_COMMITTED",
"auto_commit": False,
"lock_timeout": 5
}
}API Tools
api_tool_config = {
"timeout": 30,
"connect_timeout": 5,
"read_timeout": 25,
"retry_config": {
"max_retries": 3,
"retry_on_status": [429, 500, 502, 503],
"exponential_backoff": True,
"respect_retry_after": True
},
"rate_limiting": {
"calls_per_minute": 60,
"calls_per_hour": 1000,
"burst_allowance": 10,
"queue_excess": True
},
# Note: Tool circuit breakers are automatic - see /features/tool-circuit-breaker
"circuit_breaker": {
"failure_threshold": 5,
"recovery_timeout": 60.0,
"success_threshold": 2,
"timeout": 30.0,
"graceful_degradation": True
}
}File Processing Tools
file_processing_config = {
"timeout": 120,
"chunk_size": 8192, # bytes
"file_limits": {
"max_file_size": 104857600, # 100MB
"allowed_extensions": [".txt", ".csv", ".json", ".xml"],
"scan_for_malware": True
},
"processing": {
"use_memory_mapping": True,
"parallel_processing": True,
"compression": "auto", # auto-detect
"encoding": "utf-8",
"error_handling": "replace"
},
"optimization": {
"buffer_size": 65536,
"read_ahead": True,
"use_native_operations": True
}
}Error Handling Configuration
error_handling_config = {
"global_error_handler": "graceful", # or "strict", "logging"
"error_strategies": {
"TimeoutError": {
"retry": True,
"max_retries": 2,
"increase_timeout": 1.5,
"fallback_tool": "simple_fetcher"
},
"RateLimitError": {
"retry": True,
"use_exponential_backoff": True,
"switch_endpoint": True
},
"ValidationError": {
"retry": False,
"log_level": "ERROR",
"user_message": "Invalid input provided"
}
},
"recovery_strategies": {
"checkpoint_enabled": True,
"save_partial_results": True,
"resume_on_restart": True
}
}Resource Pooling
pooling_config = {
"connection_pools": {
"http": {
"pool_size": 20,
"max_overflow": 10,
"timeout": 30,
"recycle": 3600,
"pre_ping": True
},
"database": {
"pool_size": 10,
"max_overflow": 5,
"timeout": 30,
"recycle": 1800
}
},
"thread_pools": {
"default": {
"min_threads": 2,
"max_threads": 10,
"queue_size": 100,
"thread_name_prefix": "tool-"
}
},
"resource_sharing": {
"share_connections": True,
"share_sessions": True,
"cleanup_interval": 300
}
}Complete Tool Configuration Example
from praisonaiagents import Agent, Tool
# Configure a web search tool with comprehensive settings
web_search_tool = Tool(
name="advanced_web_search",
description="Advanced web search with optimizations",
function=perform_web_search,
config={
# Timeout settings
"timeout": 30,
"connect_timeout": 5,
"read_timeout": 25,
# Performance settings
"cache_enabled": True,
"cache_ttl": 3600,
"parallel_requests": 5,
"batch_size": 10,
# Error handling
"retry_on_timeout": True,
"max_retries": 3,
"exponential_backoff": True,
# Rate limiting
"rate_limit": 60, # per minute
"burst_size": 10,
# Resource limits
"max_memory_mb": 512,
"max_response_size": 10485760 # 10MB
}
)
# Create agent with tool configuration
from praisonaiagents import Agent, ExecutionConfig, ToolConfig
from praisonaiagents.tools.retry import RetryPolicy
agent = Agent(
name="SearchAgent",
tools=[web_search_tool],
tool_config=ToolConfig(
timeout=30,
retry_policy=RetryPolicy(max_attempts=3),
),
execution=ExecutionConfig(parallel_tool_calls=True),
)Environment Variables
# Tool timeout settings
export PRAISONAI_TOOL_TIMEOUT="60"
export PRAISONAI_TOOL_TIMEOUT_WORKERS="32" # cap sync tool timeout thread pool (default 32)
export PRAISONAI_TOOL_CONNECT_TIMEOUT="5"
export PRAISONAI_TOOL_READ_TIMEOUT="55"
# Performance settings
export PRAISONAI_TOOL_PARALLEL="true"
export PRAISONAI_TOOL_MAX_WORKERS="5"
export PRAISONAI_TOOL_CACHE_ENABLED="true"
export PRAISONAI_TOOL_CACHE_SIZE="1000"
# Resource limits
export PRAISONAI_TOOL_MAX_MEMORY="2048" # MB
export PRAISONAI_TOOL_MAX_CPU="80" # Percent
# Error handling
export PRAISONAI_TOOL_MAX_RETRIES="3"
export PRAISONAI_TOOL_RETRY_DELAY="2"
# Monitoring
export PRAISONAI_TOOL_METRICS="true"
export PRAISONAI_TOOL_PROFILE="false"
# Tool Security & Filtering
export ALLOWED_TOOLS="search,send_message" # Whitelist specific tools (prevents collisions)
export HERMES_ONLY_TOOLS="search,send_message" # Deprecated alias for ALLOWED_TOOLSBest Practices
Timeout Guidelines
- Set appropriate timeouts based on tool type
- Fast operations: 5-15 seconds
- API calls: 15-30 seconds
- Web scraping: 30-60 seconds
- Data processing: 60-300 seconds
- Use dynamic timeouts for variable workloads
timeout = base_timeout + (item_count * per_item_timeout)- Always set connection timeouts lower than read timeouts
connect_timeout = 5 # Quick failure if can't connect
read_timeout = 25 # More time for actual operationPerformance Optimization
- Enable caching for idempotent operations
- Use connection pooling for repeated operations
- Circuit breakers for tools are automatic – see Tool Circuit Breaker
- Monitor and alert on performance degradation
See Also
- Tool Development – Creating custom tools
- Agent Configuration – Agent-level tool settings
- Best Practices – Configuration guidelines