Skip to content

Quickstart

This walks through registering server-side tools, building the view, and mounting the AG-UI endpoint. It assumes you are deploying under ASGI (see Installation).

1. Configure the model

Set the Pydantic-AI model in your Django settings:

# settings.py
DJANGO_AG_UI = {
    "MODEL": "anthropic:claude-sonnet-4.6",
}

Any Pydantic-AI model string (or a Model instance passed to the view) works. If you neither set MODEL nor pass model= to the view, building an agent raises ImproperlyConfigured with a clear message.

Under pydantic-ai-slim, the matching provider extra must be installed for your model (see Installation → Model provider extras):

pip install "django-ag-ui[anthropic]"

By default Pydantic-AI infers the provider key from the environment. To pass it explicitly instead, set API_KEY (or PROVIDER for a custom base_url / client):

DJANGO_AG_UI = {
    "MODEL": "anthropic:claude-sonnet-4.6",
    "API_KEY": os.environ["ANTHROPIC_API_KEY"],
}

2. Register tools

A ToolRegistry is an instance — build one and attach tools with the @tool decorator. Tools declare typed parameters and a typed return; the registry derives the JSON Schema for AG-UI from the signature.

# agent_tools.py
from django_ag_ui import ToolCategory, ToolRegistry, tool

registry = ToolRegistry()


@tool(registry, category=ToolCategory.INTROSPECT)
def count_active_users() -> int:
    """Return how many users are currently active."""
    from django.contrib.auth import get_user_model

    return get_user_model().objects.filter(is_active=True).count()


@tool(
    registry,
    destructive=True,
    category=ToolCategory.UI_WRITE,
    confirm="Deactivate this user?",
    summary="Deactivate user",
)
def deactivate_user(user_id: int) -> str:
    """Deactivate the user with the given id."""
    from django.contrib.auth import get_user_model

    user = get_user_model().objects.get(pk=user_id)
    user.is_active = False
    user.save(update_fields=["is_active"])
    return f"deactivated {user_id}"

destructive=True is stamped into the tool's JSON Schema as x-destructive, so an AG-UI client can gate it behind an inline confirmation card. The optional confirm= prompt is stamped as x-confirm (shown in the card), and summary= as x-summary (the card's label). The first paragraph of the docstring becomes the tool description unless you pass description=.

3. Mount the view

DjangoAGUIView is a callable instance that holds one registry. get_urls returns the URL patterns; include them from your root URLconf.

# urls.py
from django.urls import include, path

from django_ag_ui import DjangoAGUIView, get_urls

from agent_tools import registry

agent_view = DjangoAGUIView(registry)

urlpatterns = [
    path("", include(get_urls(agent_view))),
]

This mounts a POST endpoint at agent/ (override with get_urls(view, prefix="chat/")). The endpoint accepts a RunAgentInput JSON body and streams AG-UI events back as text/event-stream.

Because the view is an instance, you can mount several with independent registries — one per surface, each with its own tools.

4. (Optional) override per mount

model, instructions, and audit_logger fall back to DJANGO_AG_UI but can be passed explicitly — handy in tests, where you inject a Pydantic-AI TestModel:

from pydantic_ai.models.test import TestModel

view = DjangoAGUIView(registry, model=TestModel())

CSRF and cookie-authenticated deployments

CSRF is exempt by default — right for header-token auth (Bearer / API key), where CSRF doesn't apply. If your deployment authenticates with session cookies, pass csrf_exempt=False and send the token from the client: tools act as request.user, so a cookie-auth endpoint without CSRF protection lets any third-party page drive the agent as the logged-in user (Django's default SameSite=Lax cookie mitigates, but does not eliminate, the risk).

Authentication is the host's responsibility, but the view offers two hooks. Pass require_authenticated=True to fail closed (anonymous requests get 401), and a get_user= callable — sync or async; a sync ORM lookup is fully supported (it runs off the event loop) — to establish the user. Its return value is assigned onto request.user, so tools and conversation ownership act as that user:

def get_user(request):
    token = request.headers.get("Authorization", "").removeprefix("Bearer ").strip()
    return Token.objects.select_related("user").get(key=token).user


view = DjangoAGUIView(
    registry,
    require_authenticated=True,
    get_user=get_user,
)

The tool and skill catalog views accept the same pair — lock them down whenever the agent endpoint is locked down (the catalogs enumerate every server tool and skill prompt):

ToolsView(registry, require_authenticated=True, get_user=get_user)
SkillsView(skills, require_authenticated=True, get_user=get_user)

5. (Optional) offer skills

Register pre-defined prompts in a SkillRegistry and pass it to get_urls(..., skills=...) to mount a <prefix>skills/ catalog the web component fetches via data-skills-url:

from django_ag_ui import SkillRegistry

skills = SkillRegistry()
skills.add(
    "summarise",
    title="Summarise",
    prompt="Summarise the {selection} for me.",
    chip=True,
)

urlpatterns = [
    *get_urls(DjangoAGUIView(registry), prefix="agent/", skills=skills),
]

6. (Optional) publish the tool catalog

Pass tools=registry (the same registry the view holds) to mount a read-only tool catalog at <prefix>tools/ (GET, JSON). The web component fetches it via data-tools-url to label tool-call cards for server-side tools — whose JSON Schema never reaches the browser:

urlpatterns = [
    *get_urls(DjangoAGUIView(registry), prefix="agent/", tools=registry),
]

Each entry is {"name", "summary", "description"?}; summary falls back from @tool(summary=…) to a prettified tool name. With DRF_MCP_SERVER set, the catalog also surfaces the drf-mcp tools, using their display_name as the label. See Tool metadata catalog.

What next

  • Configuration for every settings key.
  • Key concepts for how the registry, audit logger, streaming, and persistence fit together.