The Veto Python SDK provides guardrails for AI agent tool calls. It intercepts and validates tool calls before execution, blocking, allowing, or routing to human approval based on your policies.
Installation
Quick Start
import asyncio
from veto import Veto
async def main ():
# Initialize Veto
veto = await Veto.init()
# Wrap your tools
wrapped_tools = veto.wrap(my_tools)
# Use with your agent framework
agent = create_agent( tools = wrapped_tools)
if __name__ == "__main__" :
asyncio.run(main())
Core API
Veto.init()
Initialize a Veto instance by loading configuration and rules.
@ classmethod
async def init ( cls , options : Optional[VetoOptions] = None ) -> Veto
Optional configuration options API key for Veto Cloud (can also use VETO_API_KEY env var)
base_url
str
default: "https://api.runveto.com"
Base URL for Veto Cloud API
mode
'strict' | 'log' | 'shadow'
default: "strict"
Operating mode:
strict: Block denied tool calls
log: Only log denials, allow execution
shadow: Compute decisions but never block
log_level
'debug' | 'info' | 'warn' | 'error' | 'silent'
default: "info"
Log verbosity level
Session ID for tracking (can also use VETO_SESSION_ID env var)
Agent ID for tracking (can also use VETO_AGENT_ID env var)
User ID for tracking (can also use VETO_USER_ID env var)
Role for tracking (can also use VETO_ROLE env var)
Path to the veto directory containing config and rules
validation_mode
'cloud' | 'local'
default: "cloud"
Validation mode:
cloud: Use Veto Cloud API
local: Local YAML-based evaluation only
validators
List[Union[Validator, NamedValidator]]
Additional custom validators to run
API timeout in milliseconds
Number of retries for API calls
Callback fired when a tool call requires human approval
Seconds between approval poll requests
Max seconds to wait for approval resolution
Examples
Default (local rules)
Custom directory
Cloud mode
Custom endpoint
With tracking
veto.wrap()
Wrap a list of tools with Veto validation. Types are preserved.
def wrap ( self , tools : List[T]) -> List[T]
List of tools to wrap. Works with any tool format (LangChain, custom).
Wrapped tools with validation injected. Same type as input.
Examples
LangChain
PydanticAI
Custom tools
from langchain.tools import tool
from veto import Veto
@tool
def search_database ( query : str ) -> str :
"""Search the database."""
return perform_search(query)
tools = [search_database]
veto = await Veto.init()
wrapped_tools = veto.wrap(tools)
# Use with LangChain agent
agent = create_react_agent(llm, wrapped_tools)
Wrap a single tool with Veto validation.
def wrap_tool ( self , tool : T) -> T
Wrapped tool with validation injected
safe_tool = veto.wrap_tool(my_tool)
veto.guard()
Standalone validation without execution. Useful for checking if a call would be allowed.
async def guard (
self ,
tool_name : str ,
args : dict[ str , Any],
* ,
session_id : Optional[ str ] = None ,
agent_id : Optional[ str ] = None ,
user_id : Optional[ str ] = None ,
role : Optional[ str ] = None
) -> GuardResult
Name of the tool to validate
Session ID for this check
decision
'allow' | 'deny' | 'require_approval'
required
Validation decision
Human-readable reason for the decision
severity
'critical' | 'high' | 'medium' | 'low' | 'info'
Severity level from the matched rule
Approval ID if decision is require_approval
Whether this was evaluated in shadow mode
result = await veto.guard( 'transfer_funds' , {
'amount' : 5000 ,
'recipient' : 'vendor-123'
})
if result.decision == 'deny' :
print ( f 'Would be blocked: { result.reason } ' )
History & Export
veto.get_history_stats()
Get statistics about tool call history.
def get_history_stats ( self ) -> HistoryStats
Total number of tool calls validated
stats = veto.get_history_stats()
print ( f 'Blocked { stats.denied_calls } out of { stats.total_calls } calls' )
veto.clear_history()
Clear the call history.
def clear_history ( self ) -> None
veto.export_decisions()
Export decision history in JSON or CSV format.
def export_decisions ( self , format : DecisionExportFormat) -> str
Serialized decision history
json_data = veto.export_decisions( 'json' )
csv_data = veto.export_decisions( 'csv' )
with open ( 'decisions.json' , 'w' ) as f:
f.write(json_data)
with open ( 'decisions.csv' , 'w' ) as f:
f.write(csv_data)
protect() Function
One-step wrapper for simple use cases. Automatically detects configuration source and applies appropriate policies.
from veto import protect
async def protect (
tools : Union[T, List[T]],
* ,
config_dir : Optional[ str ] = None ,
pack : Optional[ str ] = None ,
rules : Optional[List[ dict ]] = None ,
api_key : Optional[ str ] = None ,
endpoint : Optional[ str ] = None ,
mode : Optional[ str ] = None ,
log_level : Optional[ str ] = None ,
session_id : Optional[ str ] = None ,
agent_id : Optional[ str ] = None ,
user_id : Optional[ str ] = None ,
role : Optional[ str ] = None ,
on_approval_required : Optional[Callable] = None
) -> Union[T, List[T]]
Tool or list of tools to protect
Built-in policy pack to apply (e.g., ‘financial’, ‘browser-automation’)
mode
'strict' | 'log' | 'shadow'
Operating mode
Examples
Auto-detect (heuristic packs)
Explicit pack
Inline rules
Cloud mode
from veto import protect
tools = [
{ 'name' : 'transfer_funds' , 'handler' : transfer_funds_handler},
{ 'name' : 'navigate' , 'handler' : navigate_handler}
]
# Automatically applies @veto/financial and @veto/browser-automation packs
protected = await protect(tools)
Framework Integrations
LangChain
Middleware
Tool Node
Callback
from veto.integrations.langchain import create_veto_middleware
from veto import Veto
veto = await Veto.init()
middleware = create_veto_middleware(
veto,
throw_on_deny = True ,
on_allow = lambda name , args : print ( f 'Allowed: { name } ' ),
on_deny = lambda name , args , reason : print ( f 'Denied: { name } ' )
)
# Use with LangChain agent
agent = create_react_agent(llm, tools = [middleware])
from veto.integrations.langchain import create_veto_tool_node
from langgraph.prebuilt import ToolNode
veto = await Veto.init()
tool_node = ToolNode(tools)
veto_node = create_veto_tool_node(veto, tool_node)
# Use in LangGraph
from langgraph.graph import StateGraph
graph = StateGraph()
graph.add_node( 'tools' , veto_node)
graph.add_edge( 'agent' , 'tools' )
from veto.integrations.langchain import create_veto_callback_handler
handler = create_veto_callback_handler(
on_tool_start = lambda name , input : print ( f 'Starting: { name } ' ),
on_tool_end = lambda name , output : print ( f 'Completed: { name } ' ),
on_tool_error = lambda name , error : print ( f 'Error: { name } ' )
)
result = await agent.ainvoke(
{ 'input' : 'Transfer funds' },
config = { 'callbacks' : [handler]}
)
PydanticAI
from pydantic_ai import Agent
from veto.integrations.pydanticai import wrap_pydanticai
from veto import Veto
agent = Agent( 'openai:gpt-4o' , tools = [transfer_funds, send_email])
veto = await Veto.init()
# Wrap the agent
safe_agent = wrap_pydanticai(agent, veto)
# Tool calls are now validated
result = await safe_agent.run( 'Transfer $5000 to vendor-123' )
CrewAI
from crewai import Agent, Task, Crew
from veto.integrations.crewai import wrap_crewai_agent
from veto import Veto
veto = await Veto.init()
# Create agent with tools
agent = Agent(
role = 'Financial Assistant' ,
goal = 'Manage financial operations' ,
tools = [transfer_funds, check_balance]
)
# Wrap agent with Veto
safe_agent = wrap_crewai_agent(agent, veto)
# Use in Crew
crew = Crew( agents = [safe_agent], tasks = [ ... ])
result = crew.kickoff()
OpenAI Agents SDK
from openai import OpenAI
from veto.integrations.openai_agents import wrap_openai_agent
from veto import Veto
client = OpenAI()
veto = await Veto.init()
# Wrap the client
safe_client = wrap_openai_agent(client, veto)
# Tool calls are now validated
response = await safe_client.agents.run(
agent_id = 'asst_123' ,
messages = [{ 'role' : 'user' , 'content' : 'Transfer funds' }]
)
Provider Adapters
Convert between Veto’s tool format and provider-specific formats.
OpenAI
from veto import to_openai, from_openai, to_openai_tools
# Convert to OpenAI format
openai_tools = to_openai_tools(veto_definitions)
# Convert from OpenAI tool call
veto_call = from_openai(openai_tool_call)
Anthropic
from veto import to_anthropic, from_anthropic, to_anthropic_tools
anthropic_tools = to_anthropic_tools(veto_definitions)
veto_call = from_anthropic(anthropic_tool_use)
Google
from veto import to_google_tool, from_google_function_call
google_tool = to_google_tool(veto_definition)
veto_call = from_google_function_call(google_call)
Error Handling
Raised when a tool call is blocked in strict mode.
from veto import ToolCallDeniedError
try :
await wrapped_tool.handler({ 'amount' : 10000 })
except ToolCallDeniedError as e:
print ( f 'Tool call denied: { e } ' )
print ( f 'Reason: { e.reason } ' )
print ( f 'Tool: { e.tool_name } ' )
ApprovalTimeoutError
Raised when approval flow times out.
from veto import ApprovalTimeoutError
try :
await wrapped_tool.handler(args)
except ApprovalTimeoutError:
print ( 'Approval timed out' )
Configuration Types
VetoOptions
from dataclasses import dataclass
from typing import Optional, List, Union, Callable
@dataclass
class VetoOptions :
api_key: Optional[ str ] = None
base_url: Optional[ str ] = None
mode: Optional[ str ] = None
log_level: Optional[ str ] = None
session_id: Optional[ str ] = None
agent_id: Optional[ str ] = None
user_id: Optional[ str ] = None
role: Optional[ str ] = None
config_dir: Optional[ str ] = None
validation_mode: Optional[ str ] = None
validators: Optional[List[Union[Validator, NamedValidator]]] = None
timeout: Optional[ int ] = None
retries: Optional[ int ] = None
on_approval_required: Optional[Callable] = None
approval_poll_interval: Optional[ float ] = None
approval_timeout: Optional[ float ] = None
Validator
Custom validation function.
from typing import Callable, Union
from dataclasses import dataclass
Validator = Callable[[ValidationContext], Union[ValidationResult, Awaitable[ValidationResult]]]
@dataclass
class NamedValidator :
name: str
validate: Validator
description: Optional[ str ] = None
priority: Optional[ int ] = None
tool_filter: Optional[List[ str ]] = None
Example
from veto import Veto, VetoOptions, NamedValidator
async def no_delete_validator ( context ):
if 'delete' in context.tool_name:
return {
'decision' : 'deny' ,
'reason' : 'Delete operations are not allowed'
}
return { 'decision' : 'allow' }
validator = NamedValidator(
name = 'no-delete' ,
description = 'Block all delete operations' ,
priority = 10 ,
validate = no_delete_validator
)
veto = await Veto.init(VetoOptions(
validators = [validator]
))
Advanced Features
Async/Await Patterns
The Python SDK is fully async. All validation and execution happens asynchronously.
import asyncio
from veto import Veto
async def main ():
veto = await Veto.init()
wrapped_tools = veto.wrap(tools)
# Execute tool calls
results = await asyncio.gather(
wrapped_tools[ 0 ].handler({ 'query' : 'test1' }),
wrapped_tools[ 1 ].handler({ 'query' : 'test2' })
)
print (results)
if __name__ == '__main__' :
asyncio.run(main())
Event Webhooks
# In veto/veto.config.yaml:
# events:
# webhook:
# url: https://example.com/veto-events
# on:
# - decision
# - approval_required
# min_severity: high
# format: slack
veto = await Veto.init()
# Events automatically sent to webhook
Output Validation
Validate tool outputs against patterns.
# In veto/rules/output.yaml
output_rules :
- id : redact-ssn
name : Redact SSNs from output
action : redact
tools :
- fetch_user_data
patterns :
- '\d{3}-\d{2}-\d{4}'
Type Hints
The SDK provides full type hints for better IDE support:
from veto import (
Veto,
VetoOptions,
VetoMode,
ValidationMode,
WrappedTools,
WrappedHandler,
GuardResult,
ToolDefinition,
ToolCall,
ToolResult,
ValidationContext,
ValidationResult,
ValidationDecision,
LogLevel
)