-
-
Notifications
You must be signed in to change notification settings - Fork 753
Description
Advanced tools
minimal code changes in the main feature and minimal code in the client side code.
1. Pre/Post Execution Hooks πͺ
π‘ Proposed PraisonAI Agents Implementation
from praisonaiagents.tools import tool
Simple pre/post hooks
def log_start(context):
print(f"Starting {context.tool_name} with args: {context.args}")def log_end(context):
print(f"Finished {context.tool_name}, result: {context.result}")@tool(
before=log_start,
after=log_end
)
def calculate_metrics(data: list) -> dict:
return {"mean": sum(data)/len(data)}Multiple hooks with priority
@tool(
before=[
(validate_input, priority=1), # Runs first
(log_start, priority=2) # Runs second
],
after=[log_end, save_to_cache]
)
def process_data(input: str) -> str:
return input.upper()Hook with error handling
def error_handler(context):
if context.error:
logger.error(f"Tool {context.tool_name} failed: {context.error}")
# Can modify the error or suppress it
context.error = None
context.result = "default_value"@tool(after=error_handler)
def risky_operation():
# Some code that might fail
passGlobal hooks for all tools
from praisonaiagents import set_global_hooks
set_global_hooks(
before=lambda ctx: logger.info(f"Tool call: {ctx.tool_name}"),
after=lambda ctx: metrics.record(ctx.tool_name, ctx.execution_time)
)
Integration with existing approval system:Combine with existing @require_approval
@require_approval(risk_level="high")
@tool(
before=validate_permissions,
after=audit_log
)
def delete_user(user_id: str):
# Dangerous operation with hooks and approval
pass2. Tool-Level Caching with TTL π
π‘ Proposed PraisonAI Agents Implementation
from praisonaiagents.tools import tool, cache
Simple caching decorator
@tool
@cache(ttl=300) # 5 minutes
def fetch_weather(city: str) -> dict:
return requests.get(f"api.weather.com/{city}").json()Advanced caching with custom key
@tool
@cache(
ttl=3600, # 1 hour
key=lambda city, date: f"{city}:{date}", # Custom cache key
condition=lambda result: result['status'] == 'success' # Only cache successful results
)
def get_historical_weather(city: str, date: str) -> dict:
return fetch_historical_data(city, date)Cache with different backends
from praisonaiagents.tools import RedisCache, MemoryCache
@tool
@cache(backend=RedisCache(host='localhost'), ttl=1800)
def expensive_calculation(x: int, y: int) -> int:
time.sleep(5)
return x ** yCache invalidation
@tool
@cache(ttl=600, tags=['user_data'])
def get_user_profile(user_id: str) -> dict:
return db.fetch_user(user_id)Invalidate cache by tags
from praisonaiagents.tools import invalidate_cache
invalidate_cache(tags=['user_data'])Per-agent caching
agent = Agent(
name="DataAgent",
cache_config={
'enabled': True,
'ttl': 300,
'backend': 'redis'
}
)
Built-in cache management:Cache statistics
from praisonaiagents.tools import get_cache_stats
stats = get_cache_stats()
print(f"Cache hits: {stats.hits}, misses: {stats.misses}")Clear all caches
from praisonaiagents.tools import clear_all_caches
clear_all_caches()3. External Execution Markers π
π‘ Proposed PraisonAI Agents Implementation
from praisonaiagents.tools import tool, external
Simple external marker
@tool
@external
def run_on_gpu(model_path: str, data: list) -> dict:
# Execution pauses, returns control to handler
passExternal with metadata
@tool
@external(
executor="gpu_cluster",
requirements=["cuda>=11.0", "torch>=2.0"],
estimated_time=300 # 5 minutes
)
def train_model(dataset: str, hyperparams: dict):
passConditional external execution
@tool
@external(when=lambda args: args['size'] > 1000000)
def process_data(data: list, size: int):
# Only external if data is large
passIntegration with agent
agent = Agent(
name="MLAgent",
tools=[train_model, run_on_gpu],
external_handler=my_external_executor # Custom handler
)External execution flow
async def my_external_executor(tool_call):
if tool_call.executor == "gpu_cluster":
# Send to GPU cluster
job_id = await gpu_cluster.submit(tool_call)
result = await gpu_cluster.wait_for_result(job_id)
return result
Built-in external patterns:Human-in-the-loop
@tool
@external(type="human_approval")
def delete_production_data(table: str):
passWebhook-based
@tool
@external(
type="webhook",
endpoint="https://api.company.com/execute",
auth_token="${EXTERNAL_API_TOKEN}"
)
def trigger_deployment(version: str):
pass4. Structured User Input Fields π
π‘ Proposed PraisonAI Agents Implementation
from praisonaiagents.tools import tool, user_input, Field
Simple user input
@tool
@user_input(
Field(name="confirm", type=bool, description="Proceed with deletion?"),
Field(name="reason", type=str, description="Reason for deletion", required=False)
)
def delete_records(confirm: bool, reason: str = None):
if confirm:
# Proceed with deletion
passAdvanced field types
from praisonaiagents.tools import Choice, Range, Pattern
@tool
@user_input(
Field(
name="priority",
type=Choice(["low", "medium", "high"]),
description="Task priority",
default="medium"
),
Field(
name="budget",
type=Range(min=0, max=10000),
description="Budget in USD"
),
Field(
name="email",
type=Pattern(r"^[\w.-]+@[\w.-]+.\w+$"),
description="Contact email"
)
)
def create_project(priority: str, budget: float, email: str):
passDynamic fields based on context
@tool
def process_order(order_type: str):
if order_type == "custom":
# Dynamically request additional fields
details = tool.request_input(
Field(name="specifications", type=str),
Field(name="quantity", type=int),
Field(name="delivery_date", type="date")
)
return handle_custom_order(details)
return handle_standard_order()Form-like input groups
from praisonaiagents.tools import InputGroup
@tool
@user_input(
InputGroup(
"Personal Information",
Field(name="first_name", type=str),
Field(name="last_name", type=str),
Field(name="age", type=int, optional=True)
),
InputGroup(
"Preferences",
Field(name="newsletter", type=bool, default=True),
Field(name="language", type=Choice(["en", "es", "fr"]))
)
)
def register_user(**kwargs):
pass
Integration with agents:Agent with input handling
agent = Agent(
name="InteractiveAgent",
on_user_input_required=lambda fields: display_form(fields),
input_timeout=60 # Wait 60 seconds for input
)Async input handling
async def handle_with_input():
agent = Agent(name="Assistant")# Will pause and wait for user input result = await agent.run( "Process the user registration", on_input=lambda fields: get_user_response(fields) )π Implementation Recommendations
1. Unified Tool Decorator System
Create a comprehensive
@tooldecorator that supports all features:@tool(
name="fetch_data",
description="Fetches data from API",
# Hooks
before=validate_input,
after=log_result,
# Caching
cache=True,
cache_ttl=300,
# External execution
external=conditional_external,
# User input
inputs=[Field(name="api_key", type=str, secret=True)],
# Integration with existing
require_approval=True,
risk_level="medium"
)
def fetch_data(api_key: str, endpoint: str):
pass2. Tool Context Object
Provide rich context to hooks and handlers:
class ToolContext:
tool_name: str
args: dict
kwargs: dict
result: Any
error: Exception | None
agent: Agent | None
execution_time: float
metadata: dict3. Progressive Enhancement
Start with simple APIs that can be progressively enhanced:
Simple
@tool
@cache(300)
def simple_tool(): passAdvanced
@tool(
cache={'ttl': 300, 'backend': 'redis', 'key': custom_key},
hooks={'before': [validate, log], 'after': [audit]},
external={'when': is_large, 'executor': 'gpu'}
)
def advanced_tool(): pass4. Backwards Compatibility
Ensure existing tools continue to work:
Old style still works
def my_tool(x: int) -> int:
return x * 2agent = Agent(tools=[my_tool]) # Works without @tool decorator