Skip to main content

Multi-tenant platform for serving AI agents with PydanticAI

Project description

PydanticAI Multi-Tenant Agent Platform

A production-ready platform for serving AI agents to multiple tenants with PydanticAI.

Who This Is For

Agencies and developers who build AI agents for clients.

You deploy one platform. Each client gets an API key. They call your agents via REST or SDK. You track usage and bill them.

Clients can: Chat with agents you've granted them. Continue conversations. See their usage.

Clients cannot: Create agents. Modify agents. See other tenants.

For Tenants

Tenants receive an API key from the platform admin.

pip install pydanticai-platform-client
from pydanticai_platform_client import PlatformClient

async with PlatformClient("https://platform.example.com", "pk_...") as client:
    response = await client.chat("Hello")
    print(response.text)

Full SDK docs: sdk/README.md

What This Is

This is a multi-tenant platform for serving AI agents. Deploy your agents once, then grant access to different clients/tenants via API keys.

Platform features:

  • Multi-tenant architecture with isolated API keys
  • Agent access control per tenant
  • Conversation persistence per tenant
  • Usage tracking and cost monitoring
  • API key rotation
  • Rate limiting per tenant

Infrastructure:

  • FastAPI app with auth middleware, rate limiting, health checks
  • Conversation persistence (Postgres/Supabase)
  • Type-safe dependency injection
  • Mock implementations for testing
  • CLI for serving and chatting

You provide:

  • Your agent(s) with domain-specific instructions
  • Your tools for your use case
  • Your output models

Quick Start

from pydantic_ai import Agent
from pydanticai_multiagent import BaseDeps, create_mock_base_deps

# 1. Define your agent
my_agent: Agent[BaseDeps, str] = Agent(
    "openai:gpt-4o",
    deps_type=BaseDeps,
    instructions="You are a helpful assistant for my specific domain...",
)

# 2. Run with mock deps (for development)
deps = create_mock_base_deps()
result = await my_agent.run("Hello!", deps=deps)
print(result.output)

Project Structure

src/pydanticai_multiagent/
├── dependencies/     # BaseDeps, SearchDeps, AuthDeps + mocks
├── tools/            # Reusable tools (search, data, external APIs)
├── services/         # Conversation, usage tracking
├── api/              # FastAPI app with middleware
├── db/               # Database pool + migrations
└── cli/              # Command-line interface

examples/
├── starter/          # Simple examples to learn the patterns
├── multi_agent/      # Full router + specialist agents example
└── johnpye/          # Real-world domain-specific example

Dependency Injection

Three levels of typed dependencies:

from pydanticai_multiagent import BaseDeps, SearchDeps, AuthDeps

# BaseDeps: Core infrastructure
#   - http_client, db, cache, user_id

# SearchDeps(BaseDeps): Adds search capabilities
#   - vector_store, search_api_key, domain_toolset

# AuthDeps(BaseDeps): Adds auth context
#   - user_roles, permissions

Convert between them when delegating:

# Your agent uses SearchDeps, sub-agent only needs BaseDeps
result = await sub_agent.run(query, deps=ctx.deps.to_base_deps())

Adding Tools

from pydantic_ai import RunContext
from pydantic_ai.toolsets import FunctionToolset
from pydanticai_multiagent import BaseDeps

my_toolset: FunctionToolset[BaseDeps] = FunctionToolset()

@my_toolset.tool
async def my_tool(ctx: RunContext[BaseDeps], query: str) -> str:
    """Do something useful."""
    # Access deps via ctx.deps.db, ctx.deps.http_client, etc.
    return f"Result for {query}"

# Use in your agent
my_agent = Agent(..., toolsets=[my_toolset])

Structured Outputs

from pydantic import BaseModel, Field
from pydantic_ai import Agent
from pydanticai_multiagent import BaseDeps

class MyOutput(BaseModel):
    answer: str = Field(description="The answer")
    confidence: float = Field(ge=0, le=1)

my_agent: Agent[BaseDeps, MyOutput] = Agent(
    "openai:gpt-4o",
    deps_type=BaseDeps,
    output_type=MyOutput,
    instructions="...",
)

CLI

# Start the API server
pydanticai-multiagent serve
pydanticai-multiagent serve --reload  # with hot reload

# Interactive chat (uses example agents)
pydanticai-multiagent chat --agent router

# Single query
pydanticai-multiagent query "Your question" --agent research

# Run database migrations
pydanticai-multiagent db-migrate

Platform API

The platform provides two API layers: Admin API for tenant management and Platform API for tenant access.

Admin API (/api/v1/admin)

Requires admin authentication via Authorization: Bearer <SECRET_KEY>.

Tenant Management

# Create a tenant
curl -X POST http://localhost:8000/api/v1/admin/tenants \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp", "default_model": "openai:gpt-4o"}'

# Response includes API key (save it - only shown once):
# {"id": "tenant_xxx", "api_key": "sk-tenant-xxx", ...}

# List tenants
curl http://localhost:8000/api/v1/admin/tenants \
  -H "Authorization: Bearer $SECRET_KEY"

# Update tenant
curl -X PATCH "http://localhost:8000/api/v1/admin/tenants/{tenant_id}?is_active=false" \
  -H "Authorization: Bearer $SECRET_KEY"

# Rotate API key (invalidates old key immediately)
curl -X POST http://localhost:8000/api/v1/admin/tenants/{tenant_id}/rotate-key \
  -H "Authorization: Bearer $SECRET_KEY"

Agent Access Control

# Grant tenant access to an agent
curl -X POST http://localhost:8000/api/v1/admin/tenants/{tenant_id}/agents \
  -H "Authorization: Bearer $SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent_name": "support", "is_default": true}'

# List tenant's agents
curl http://localhost:8000/api/v1/admin/tenants/{tenant_id}/agents \
  -H "Authorization: Bearer $SECRET_KEY"

# Revoke agent access
curl -X DELETE http://localhost:8000/api/v1/admin/tenants/{tenant_id}/agents/support \
  -H "Authorization: Bearer $SECRET_KEY"

# List all platform agents
curl http://localhost:8000/api/v1/admin/agents \
  -H "Authorization: Bearer $SECRET_KEY"

Platform API (/api/v1/platform)

Requires tenant authentication via Authorization: Bearer <TENANT_API_KEY>.

Chat

# Send a message (uses default agent)
curl -X POST http://localhost:8000/api/v1/platform/chat \
  -H "Authorization: Bearer sk-tenant-xxx" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Hello!"}'

# Response:
# {"response": "Hi! How can I help?", "conversation_id": "conv_xxx", "agent": "support", "model": "openai:gpt-4o"}

# Continue conversation
curl -X POST http://localhost:8000/api/v1/platform/chat \
  -H "Authorization: Bearer sk-tenant-xxx" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Tell me more", "conversation_id": "conv_xxx"}'

# Use a specific agent
curl -X POST http://localhost:8000/api/v1/platform/chat \
  -H "Authorization: Bearer sk-tenant-xxx" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Analyze this data", "agent": "analyst"}'

# Streaming response (SSE)
curl -X POST http://localhost:8000/api/v1/platform/chat/stream \
  -H "Authorization: Bearer sk-tenant-xxx" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Write a story"}'

Conversations

# List recent conversations
curl http://localhost:8000/api/v1/platform/conversations \
  -H "Authorization: Bearer sk-tenant-xxx"

# Get conversation history
curl http://localhost:8000/api/v1/platform/conversations/{conversation_id} \
  -H "Authorization: Bearer sk-tenant-xxx"

# Clear conversation
curl -X DELETE http://localhost:8000/api/v1/platform/conversations/{conversation_id} \
  -H "Authorization: Bearer sk-tenant-xxx"

Other Endpoints

# List available agents
curl http://localhost:8000/api/v1/platform/agents \
  -H "Authorization: Bearer sk-tenant-xxx"

# Get usage statistics
curl "http://localhost:8000/api/v1/platform/usage?days=30" \
  -H "Authorization: Bearer sk-tenant-xxx"

Database Setup

Run migrations to create the required tables:

pydanticai-multiagent db-migrate

Required tables:

  • tenants - Tenant accounts with API key hashes
  • agents - Registered agents
  • tenant_agents - Agent access per tenant
  • conversation_messages - Conversation history
  • usage_records - Usage tracking

Testing

from pydantic_ai.models.test import TestModel
from pydanticai_multiagent import create_mock_base_deps

async def test_my_agent():
    deps = create_mock_base_deps()

    with my_agent.override(model=TestModel()):
        result = await my_agent.run("test query", deps=deps)
        assert "expected" in result.output

Configuration

Create a .env file:

OPENAI_API_KEY=sk-...
DATABASE_URL=postgresql://...  # Optional, uses mocks if not set
SECRET_KEY=change-me           # For auth middleware

Examples

Example Description
examples/starter/ Simple agent, custom output, tools
examples/multi_agent/ Router + specialist delegation pattern
examples/johnpye/ Real-world auction tracking agent

Built-in Agents

The platform includes example agents you can grant to tenants:

Agent Description
router Routes requests to specialist agents
research Web research and information gathering
analyst Data analysis and insights
code Code generation and review
writer Content writing and editing
support Customer support assistance

Register your own agents in app.py:

registry.register_builtin("my_agent", my_agent, "Description")

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     Admin API                                │
│  POST /admin/tenants    - Create tenant (returns API key)   │
│  POST /admin/tenants/{id}/agents - Grant agent access       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Tenant Database                           │
│  tenants (id, api_key_hash, default_model, rate_limit)      │
│  tenant_agents (tenant_id, agent_id, model_override)        │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Platform API                              │
│  POST /platform/chat - Send message (tenant API key auth)   │
│  GET /platform/conversations - List tenant's conversations  │
└─────────────────────────────────────────────────────────────┘

Platform Status

Production-Ready

Feature Implementation
Tenant API keys SHA256 hashed, secure storage
Tenant CRUD Full create/read/update via Admin API
Agent registry Database-backed with foreign key integrity
Agent access control Per-tenant permissions via tenant_agents
Conversation persistence PostgreSQL JSONB with PydanticAI serialization
API key rotation Immediate invalidation of old keys
Database migrations Tracked, idempotent migrations
Usage tracking ConversationService calls record_usage() after each chat
Token limit enforcement monthly_token_limit checked before chat, returns 429 when exceeded
Cost monitoring Token usage tracked per tenant via /usage endpoint

Partial Implementation

Feature Status Notes
Rate limiting In-memory only Works for single instance. For multi-instance deployments, implement Redis-based rate limiting.
Per-tenant rate limits Config stored, not enforced rate_limit_per_minute field exists on Tenant but rate limiter uses global config.

To Reach Production

  1. Enforce tenant rate limits - Read tenant.rate_limit_per_minute in rate limit middleware
  2. Distributed rate limiting - Replace in-memory rate limiter with Redis for multi-instance deployments

What's NOT Included

  • Vendor lock-in (swap models, databases, etc.)
  • Tenant-defined agents (agents are server-defined for security)

Define your agents in code, grant access to tenants via Admin API.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydanticai_platform-0.3.0.tar.gz (357.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydanticai_platform-0.3.0-py3-none-any.whl (86.0 kB view details)

Uploaded Python 3

File details

Details for the file pydanticai_platform-0.3.0.tar.gz.

File metadata

  • Download URL: pydanticai_platform-0.3.0.tar.gz
  • Upload date:
  • Size: 357.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.5

File hashes

Hashes for pydanticai_platform-0.3.0.tar.gz
Algorithm Hash digest
SHA256 7cfe29b03c7c1b6d5f45fb8e9918bc0fbce2033c39ac6e0d1ac2d61be43ce797
MD5 f9720bf09291cbe42f7b973f072ad155
BLAKE2b-256 a514e33a2ff4872ee02ce29d87aee42452870f89cb6b408fa6303e4b2e25f5f6

See more details on using hashes here.

File details

Details for the file pydanticai_platform-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydanticai_platform-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a800b9571a9f738d0ec08253eb028b2250440c9cb316863e2a57c81a8d989f18
MD5 0416ccb625457d954fc298a51e0e72a1
BLAKE2b-256 ef0d7eafa550637a7d432b93d1fcdce579a0edcb2f5304b703a6af1b27c07931

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page