Veto Python SDK

Native Python SDK for AI agent authorization. Async-native, framework-agnostic, and works with LangChain, CrewAI, PydanticAI, AutoGen, FastAPI, and Django. Two lines of code to add runtime guardrails.

Python AI agent guardrails, veto Python SDK, Python agent authorization, AI safety Python, LangChain guardrails, CrewAI authorization, PydanticAI guardrails, FastAPI agent security, Django AI agent authorization

Installation

Terminalbash
pip install veto

Requires Python 3.9+. The SDK is async-native but provides sync wrappers for Django and other synchronous frameworks.

Quick start

Initialize Veto, wrap your tools, and call them normally. Blocked calls raise ToolCallDeniedError with the policy reason attached.

agent.pypython
import asyncio
from veto import Veto, ToolCallDeniedError

async def transfer_money(amount: float, to_account: str) -> str:
    return f"Transferred $" + str(amount) + f" to {to_account}"

async def delete_file(path: str) -> str:
    return f"Deleted {path}"

async def main():
    veto = await Veto.init()  # reads VETO_API_KEY from env

    wrapped = veto.wrap([transfer_money, delete_file])

    try:
        result = await wrapped[0].handler({"amount": 500, "to_account": "ACC123"})
        print(f"Allowed: {result}")
    except ToolCallDeniedError as e:
        print(f"Blocked: {e.reason}")

asyncio.run(main())

Shorthand with protect()

The protect() function combines initialization and wrapping into a single call. Best for quick setup and scripts.

One-line wrappingpython
from veto import protect

# One-line wrapping. Reads VETO_API_KEY from env.
safe_tools = await protect([transfer_money, delete_file])

# Equivalent to Veto.init() + veto.wrap()
# Use protect() for quick setup, Veto.init() for advanced config.

Web framework integrations

Use the SDK in your API layer to authorize tool calls before they execute. Works with any Python web framework.

FastAPI

Initialize Veto once on startup. Use veto.guard() in your route handlers for pre-flight validation without wrapping tools.

FastAPI integrationpython
from fastapi import FastAPI, HTTPException
from veto import Veto, ToolCallDeniedError
from pydantic import BaseModel

app = FastAPI()
veto: Veto | None = None

@app.on_event("startup")
async def startup():
    global veto
    veto = await Veto.init(api_key="veto_live_...")

class AgentRequest(BaseModel):
    tool_name: str
    arguments: dict

@app.post("/agent/execute")
async def execute_tool(req: AgentRequest):
    decision = await veto.guard(req.tool_name, req.arguments)

    if decision.decision == "deny":
        raise HTTPException(status_code=403, detail=decision.reason)

    if decision.decision == "require_approval":
        return {
            "status": "pending_approval",
            "approval_id": decision.approval_id,
            "message": decision.reason,
        }

    result = await run_tool(req.tool_name, req.arguments)
    return {"status": "executed", "result": result}

Django

Django's synchronous views work with Veto through asyncio.run(). For async views, use await directly.

Django integrationpython
# views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from veto import Veto
import asyncio
import json

_veto = None

async def get_veto():
    global _veto
    if _veto is None:
        _veto = await Veto.init()
    return _veto

@csrf_exempt
def agent_execute(request):
    if request.method != "POST":
        return JsonResponse({"error": "Method not allowed"}, status=405)

    body = json.loads(request.body)
    veto = asyncio.run(get_veto())

    decision = asyncio.run(
        veto.guard(body["tool_name"], body["arguments"])
    )

    if decision.decision == "deny":
        return JsonResponse({"error": decision.reason}, status=403)

    result = execute_tool(body["tool_name"], body["arguments"])
    return JsonResponse({"result": result})

Provider adapters

Built-in adapters convert between Veto's tool format and provider-specific formats. Define tools once, use them with any LLM provider.

OpenAI

OpenAI function callingpython
from veto import Veto, to_openai_tools, from_openai_tool_call
from openai import AsyncOpenAI

client = AsyncOpenAI()
veto = await Veto.init()

# Define tools and wrap with Veto
my_tools = [search_web, send_email, read_database]
wrapped_tools = veto.wrap(my_tools)

# Convert to OpenAI format
openai_tools = to_openai_tools(wrapped_tools)

response = await client.chat.completions.create(
    model="gpt-5.4",
    messages=[{"role": "user", "content": "Search for Q3 revenue data"}],
    tools=openai_tools,
)

# When OpenAI returns a tool call, convert and validate
for tool_call in response.choices[0].message.tool_calls:
    veto_call = from_openai_tool_call(tool_call)
    # Authorization already enforced by wrapped tools
    result = await execute_wrapped(veto_call)

Anthropic Claude

Claude tool usepython
from veto import Veto, to_anthropic_tools, from_anthropic_tool_use
import anthropic

client = anthropic.AsyncAnthropic()
veto = await Veto.init()

wrapped_tools = veto.wrap([search_web, send_email])
claude_tools = to_anthropic_tools(wrapped_tools)

response = await client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Send the report to the team"}],
    tools=claude_tools,
)

for block in response.content:
    if block.type == "tool_use":
        veto_call = from_anthropic_tool_use(block)
        result = await execute_wrapped(veto_call)

Agent framework patterns

Veto wraps tools at the boundary between your agent framework and the outside world. The framework keeps working normally. Authorization happens transparently.

LangChain / LangGraph

LangChain tool wrappingpython
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from pydantic import BaseModel, Field
from veto import Veto

class TransferInput(BaseModel):
    amount: float = Field(description="Amount to transfer")
    to_account: str = Field(description="Destination account ID")

@tool("transfer_money", args_schema=TransferInput)
def transfer_money(amount: float, to_account: str) -> str:
    """Transfer money to a bank account."""
    return f"Transferred $" + str(amount) + f" to {to_account}"

veto = await Veto.init()
wrapped_tools = veto.wrap([transfer_money])

llm = ChatOpenAI(model="gpt-5.4")
agent = create_react_agent(llm, wrapped_tools)

Dataclass tools

Veto automatically detects tools defined as dataclasses with a name, description, and handler method.

Dataclass tool wrappingpython
from dataclasses import dataclass
from typing import Any

@dataclass
class DatabaseQuery:
    name: str = "query_database"
    description: str = "Run a SQL query against the production database"

    async def handler(self, args: dict[str, Any]) -> str:
        query = args["sql"]
        # Execute query...
        return f"Query returned {len(rows)} rows"

@dataclass
class FileWriter:
    name: str = "write_file"
    description: str = "Write content to a file on disk"

    async def handler(self, args: dict[str, Any]) -> str:
        path, content = args["path"], args["content"]
        # Write file...
        return f"Wrote {len(content)} bytes to {path}"

veto = await Veto.init()
safe = veto.wrap([DatabaseQuery(), FileWriter()])

Async patterns

The SDK is async-native. Validate multiple tool calls in parallel with asyncio.gather for minimal latency.

Parallel validationpython
import asyncio
from veto import Veto

async def main():
    veto = await Veto.init()

    # Parallel tool validation
    tools = ["send_email", "delete_user", "export_data"]
    args_list = [
        {"to": "user@external.com", "body": "Report"},
        {"user_id": "usr_123"},
        {"format": "csv", "scope": "all"},
    ]

    decisions = await asyncio.gather(*[
        veto.guard(tool, args)
        for tool, args in zip(tools, args_list)
    ])

    for tool, decision in zip(tools, decisions):
        if decision.decision == "allow":
            print(f"{tool}: allowed")
        elif decision.decision == "deny":
            print(f"{tool}: blocked - {decision.reason}")
        elif decision.decision == "require_approval":
            print(f"{tool}: needs approval (id: {decision.approval_id})")

asyncio.run(main())

Policy rules

Define authorization policies in YAML. Rules match tool calls by name, evaluate conditions against arguments and context, and enforce actions.

veto/policies.yamlyaml
version: "1.0"
name: Python agent policies

rules:
  - id: block-sql-mutations
    action: block
    tools: [query_database]
    conditions:
      - field: arguments.sql
        operator: matches
        value: "(?i)(DROP|DELETE|TRUNCATE|ALTER|UPDATE|INSERT)"
    message: "Mutation queries blocked. Use read-only queries."

  - id: limit-transfer-amount
    action: require_approval
    tools: [transfer_money]
    conditions:
      - field: arguments.amount
        operator: greater_than
        value: 1000
    message: "Transfers over $1000 require human approval"

  - id: restrict-file-paths
    action: block
    tools: [write_file]
    conditions:
      - field: arguments.path
        operator: not_matches
        value: "^/app/data/"
    message: "File writes restricted to /app/data/"

Features

Runtime authorization

Every tool call is validated against your policies before execution. The agent cannot bypass guardrails regardless of its reasoning.

Framework agnostic

Works with LangChain, CrewAI, PydanticAI, AutoGen, or custom agents. Adapters for OpenAI, Anthropic, and Google function calling formats.

Deterministic policies

Policies are code, not prompts. Argument limits, regex matching, rate limits. Evaluate locally without network latency.

Cloud sync

Policies managed in Veto Cloud. Real-time updates, team collaboration, approval workflows, and audit logging.

Build vs buy

CapabilityDIYVeto Python SDK
Initial setup2-4 weeks5 minutes
Policy engine
Approval workflows
Audit logging
Provider adapters
Async-nativeManual
Maintenance burdenOngoingNone

Frequently asked questions

How do I install the Python SDK?
Install via pip: pip install veto. Set the VETO_API_KEY environment variable with your API key from the dashboard. Initialize with await Veto.init() in async code, or use the protect() shorthand for scripts.
Does it work with async frameworks like FastAPI?
Yes. The SDK is async-native. All methods (init, wrap, guard) are async and work directly in FastAPI, Starlette, and any asyncio-based framework. For synchronous frameworks like Django, use asyncio.run() or async views.
Can I use Veto without a network connection?
For deterministic policies, yes. Define constraints locally and they evaluate without network calls. Cloud policies require connectivity for real-time updates and approval workflows.
How do I handle blocked tool calls?
Blocked calls raise ToolCallDeniedError with a reason attribute. Catch it and return a fallback response, log the attempt, or escalate to human review. Use veto.guard() for pre-flight checks that return a decision object instead of raising.
Which Python agent frameworks are supported?
LangChain, LangGraph, CrewAI, PydanticAI, AutoGen, and any framework that uses callable tools. The SDK wraps plain functions, dataclass tools, and LangChain tool objects. Provider adapters handle OpenAI, Anthropic, and Google format conversion.

Related integrations

View all integrations

Ship Python agents with confidence.