Python SDK for building Agentpack tools
Project description
agentpack
Zero-Config Python SDK for Agentpack Tools
Drop a .py file in your repo and get a production-ready, containerized, gRPC-enabled tool—instantly. No servers. No Docker. No infrastructure code.
Installation
pip install agentpack
# or
poetry add agentpack
# or
uv pip install agentpack
Quick Start
1. Write ONLY Business Logic
# tools/greet.py
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class GreetInput(BaseModel):
name: str
class GreetOutput(BaseModel):
greeting: str
@tool(
name="greet",
description="Greet a person by name",
input_schema=GreetInput,
output_schema=GreetOutput,
)
async def greet(input: GreetInput, ctx: ExecutionContext) -> GreetOutput:
ctx.logger.info("Greeting user", name=input.name)
return GreetOutput(greeting=f"Hello, {input.name}!")
2. That's It!
No step 2. Agentpack Builder automatically:
- Discovers tools in
tools/directory - Generates
.agentpack-manifest.json - Creates optimized Dockerfile
- Builds OCI image with gRPC runtime
- Deploys to container backend
Agentpack Runtime automatically:
- Loads tools from manifest
- Starts gRPC server (port 50051)
- Handles execution via Rust core
- Provides full observability
Zero-Config Architecture
┌─────────────────────────────────────────────────┐
│ YOU: Write tools/calculator.py │
│ ↓ │
│ BUILDER: Scan → Manifest → Dockerfile → Image │
│ ↓ │
│ RUNTIME: Load → gRPC Server → Execute │
│ ↓ │
│ RUST CORE: Orchestrate with Tower middleware │
└─────────────────────────────────────────────────┘
You own: Business logic (100% focus) Agentpack owns: Servers, containers, orchestration, observability
Features
Built-In, Zero Setup:
- ✅ Type-Safe: Full type inference with Pydantic
- ✅ Validated: Automatic input/output validation
- ✅ Observable: OpenTelemetry tracing (no config)
- ✅ Secure: Secret management via dashboard
- ✅ Resilient: HTTP client with automatic retries
- ✅ Structured Logging: Request-scoped context
- ✅ gRPC: High-performance protocol (auto-configured)
- ✅ Containerized: OCI images with base runtime
- ✅ Sandboxed: gVisor/Firecracker isolation
Execution Context
Every tool handler receives ctx with production-ready utilities:
Secrets
# Get optional secret
api_key = await ctx.secrets.get("API_KEY")
# Get required secret (raises if missing)
api_key = await ctx.secrets.require("API_KEY")
# Secrets configured via Agentpack Dashboard:
# - vault://kv/my-secret
# - aws-sm://secret-name
# - gcp-sm://projects/*/secrets/*
HTTP Client
# GET with automatic retries
response = await ctx.http.get(
"https://api.example.com/data",
params={"limit": 10},
headers={"Authorization": f"Bearer {token}"},
)
# POST, PUT, DELETE also available
response = await ctx.http.post(
"https://api.example.com/data",
json={"name": "value"},
)
Logger
ctx.logger.info("Processing request", user_id="123")
ctx.logger.error("Something went wrong", error=str(err))
# Logs automatically include:
# - Request ID
# - Trace ID
# - Tool name
# - Timestamp
Tracer (OpenTelemetry)
span = ctx.trace.start_span("external-api-call")
try:
# ... do work ...
span.set_attribute("items.count", len(items))
finally:
span.end()
# Traces sent to OpenTelemetry Collector automatically
Examples
See examples/ directory:
Simple Tool (examples/calculator.py)
from enum import Enum
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class Operation(str, Enum):
ADD = "add"
SUBTRACT = "subtract"
MULTIPLY = "multiply"
DIVIDE = "divide"
class CalculatorInput(BaseModel):
operation: Operation
a: float
b: float
class CalculatorOutput(BaseModel):
result: float
operation: str
@tool(
name="calculator",
description="Perform basic arithmetic operations",
input_schema=CalculatorInput,
output_schema=CalculatorOutput,
)
async def calculator(input: CalculatorInput, ctx: ExecutionContext) -> CalculatorOutput:
# Pure business logic
if input.operation == Operation.ADD:
result = input.a + input.b
elif input.operation == Operation.SUBTRACT:
result = input.a - input.b
elif input.operation == Operation.MULTIPLY:
result = input.a * input.b
elif input.operation == Operation.DIVIDE:
if input.b == 0:
raise ValueError("Division by zero")
result = input.a / input.b
return CalculatorOutput(
result=result,
operation=f"{input.a} {input.operation.value} {input.b}"
)
External API Tool (examples/weather.py)
Demonstrates:
- Secret management (
ctx.secrets.require()) - HTTP client with retries (
ctx.http.get()) - Structured logging
- Input validation with Pydantic
API Reference
@tool Decorator
Create a tool definition.
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class MyInput(BaseModel):
value: int
class MyOutput(BaseModel):
result: int
@tool(
name="my_tool", # Unique tool identifier
description="My tool description", # Human-readable description
input_schema=MyInput, # Pydantic input model
output_schema=MyOutput, # Pydantic output model
)
async def my_tool(input: MyInput, ctx: ExecutionContext) -> MyOutput:
return MyOutput(result=input.value * 2)
Parameters:
name(str): Unique tool namedescription(str): Human-readable descriptioninput_schema(BaseModel): Pydantic input validation modeloutput_schema(BaseModel): Pydantic output validation model
Returns: Tool instance (internal use by runtime)
Error Handling
from agentpack import (
ToolError,
ValidationError,
SecretNotFoundError,
HttpError,
)
async def my_handler(input: MyInput, ctx: ExecutionContext) -> MyOutput:
# Validation errors raised automatically by Pydantic
# Secret not found
key = await ctx.secrets.require("API_KEY") # raises SecretNotFoundError
# HTTP errors from external APIs
response = await ctx.http.get("...") # raises HttpError on failure
# Custom errors
if not input.valid:
raise ToolError("Invalid input", "INVALID_INPUT")
Multi-File Tools
Tools can have dependencies:
tools/
├── calculator/
│ ├── __init__.py # Entry point (exports tool)
│ ├── operations.py # Helper functions
│ └── types.py # Shared types
└── weather.py # Single-file tool
Builder discovers both patterns automatically:
- Single-file:
tools/weather.py - Multi-file:
tools/calculator/__init__.py
Testing
import pytest
from pydantic import BaseModel
from agentpack import tool, ExecutionContext
class TestInput(BaseModel):
value: int
class TestOutput(BaseModel):
result: int
@tool(
name="test_tool",
description="Test tool",
input_schema=TestInput,
output_schema=TestOutput,
)
async def test_tool(input: TestInput, ctx: ExecutionContext) -> TestOutput:
return TestOutput(result=input.value * 2)
@pytest.mark.asyncio
async def test_my_tool():
# Create mock context
from agentpack.context import create_execution_context
from agentpack.types import RequestMetadata
metadata = RequestMetadata(request_id="123")
ctx = create_execution_context(metadata)
# Execute tool
result = await test_tool.execute({"value": 5}, ctx)
assert result.result == 10
Deployment
Nothing to do! Builder + Runtime handle everything:
- Build:
agentpack build(or automatic via GitHub App) - Deploy: Automatic to Agentpack infrastructure
- Scale: Automatic based on load
- Monitor: Traces/logs in dashboard
Configuration
Optional agentpack.yaml for advanced settings:
version: 0.1
project: my-tools
agents:
- name: default
model: { provider: anthropic, name: claude-3-5-sonnet }
tools: ["*"] # All tools in tools/
resources:
- name: stripe_secret
type: secret
source: vault://kv/stripe
policies:
outbound_network:
allow: ["*.stripe.com", "*.openai.com"]
Thin SDK Philosophy
This SDK is intentionally minimal:
- ✅
@tooldecorator for definitions - ✅
ExecutionContexttypes - ✅ Error classes
- ❌ NO server creation
- ❌ NO Docker setup
- ❌ NO deployment code
All orchestration lives in Rust core for:
- Consistent cross-language behavior
- Performance (Tower middleware)
- Security (policy enforcement)
- Observability (OpenTelemetry)
Type Checking
The SDK is fully typed and works with mypy:
mypy tools/ --strict
Troubleshooting
"Tool not found" error
- Ensure tool is in
tools/directory - Check file exports tool with
@tooldecorator - Verify
.agentpack-manifest.jsonwas generated
Validation errors
- Check Pydantic model matches input
- Use
Optional[T]or= Field(default=...)for optional fields - Test with mock data
Secret not found
- Configure secrets in Agentpack Dashboard
- Use correct secret key name
- Check environment variables in development
Related Packages
agentpack-runtime-python- Internal gRPC runtime (private)@agentpack/sdk- TypeScript SDK (same zero-config approach)
License
MIT OR Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentpack-0.1.0.tar.gz.
File metadata
- Download URL: agentpack-0.1.0.tar.gz
- Upload date:
- Size: 25.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d398dee12a6ca0b54ff89bfdb4f3e80df590c9d776a73a6f93d7851211667503
|
|
| MD5 |
0d205fa53ceff257b97758a6ffd17519
|
|
| BLAKE2b-256 |
9d2b3a18e00408ccf04f4649f3ed926decb10f06706c057ec47970373725e488
|
File details
Details for the file agentpack-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agentpack-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a69f8b08cf6c959d30d0cb8cc0adb802fc222a85cb125f8eacac450ce3634e40
|
|
| MD5 |
4afee69b766b1eeaab92b5a27c19ca32
|
|
| BLAKE2b-256 |
ce3f1a74ff566f882fb24eff8652bcfca02a5e6517d433dca4479ce410ffc320
|