An agent runtime for Django, with an optional Wagtail integration. Define agents in code, configure models and credentials in the database, and drop a streaming chat widget into any Django, Django-admin, or Wagtail page.
- litellm under the hood — talk to OpenAI, Anthropic, Google, Mistral, Ollama, or any OpenAI-compatible endpoint.
- Streaming over SSE — no Channels, no ASGI requirement, no WebSockets.
- Tools are just methods — decorate a method with
@tooland the JSON schema is generated from its type hints and docstring. - Secrets stay on the server — API keys are encrypted at rest and write-only through every admin surface.
Screenshots: placeholder — add chat widget / admin screenshots here.
pip install django-agents # add ".[wagtail]" for the Wagtail integrationAdd the app and mount the URLs:
# settings.py
INSTALLED_APPS = [
# ...
"django_agents",
]
DJANGO_AGENTS = {
"DEFAULT_MODEL": "anthropic/claude-sonnet-4-5",
}# urls.py
urlpatterns = [
# ...
path("agents/", include("django_agents.urls")),
]python manage.py migrate1. Register an agent in any installed app's agents.py (autodiscovered):
from django_agents.base import Agent, register, tool
@register
class WeatherAgent(Agent):
slug = "weather"
name = "Weather assistant"
system_prompt = "You help with the weather."
@tool
def forecast(self, city: str) -> str:
"""Return the forecast for a city.
:param city: The city to look up.
"""
return f"It's sunny in {city}."python manage.py startagent WeatherAgent --app myapp scaffolds this for you.
2. Add a credential. In the Django admin, add a Provider credential with your API key and mark it default. (Keys are encrypted at rest and never returned by the API.)
3. Embed the widget in a template:
{% load agent_tags %}
<head>{% agent_chat_assets %}</head>
<body>{% agent_chat "weather" %}</body>Or link to the built-in standalone page at /agents/chat/weather/.
cd sandbox
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
# visit /django-admin/ → add a Provider credential (mark default)
# visit /agents/chat/assistant/ and chatSet WAGTAIL=1 before runserver to explore the Wagtail integration instead.
All settings live under a single DJANGO_AGENTS dict.
| Key | Default | Meaning |
|---|---|---|
DEFAULT_MODEL |
None |
litellm model string used when an agent doesn't set model (e.g. "anthropic/claude-sonnet-4-5"). |
BACKEND |
"django_agents.runtime.LiteLLMBackend" |
Dotted path to the runtime backend. Swap for FakeBackend in tests. |
MAX_TOOL_ROUNDS |
10 |
Max tool-execution rounds per run before the run is stopped. |
REQUEST_TIMEOUT |
60.0 |
Seconds before a single backend call is aborted, so a hung provider can't wedge a worker indefinitely. |
ENCRYPTION_KEY |
None |
Overrides SECRET_KEY-derived encryption for stored credentials. Set this to rotate SECRET_KEY without invalidating keys. |
REQUIRE_AUTH |
True |
Require an authenticated user on every endpoint. Disable only for public demo agents (and understand the risks). |
RATE_LIMIT |
{"PER_MINUTE": 20, "PER_DAY": 200} |
Per-user message budget for the chat endpoint. Set a window to None to disable it. |
RATE_LIMITER |
"django_agents.ratelimit.CacheRateLimiter" |
Dotted path to the rate limiter class. Needs a shared cache backend (Redis/Memcached) in multi-process deployments — see Deployment notes. |
MAX_CONCURRENT_RUNS |
3 |
Per-user cap on simultaneously in-flight runs, across all of a user's threads. None/0 disables it. |
TRACE |
"auto" |
Build run traces: "auto" (on iff django_agents.observability is installed), True, or False. |
TRACE_CONTENT |
"full" |
"full" stores prompts/outputs; "metadata" stores only char counts. |
TRACE_SAMPLE |
1.0 |
Fraction of runs (0–1) to build a trace for; unsampled runs still get a row. |
OBSERVABILITY_PERMISSION |
None |
Dotted path to a callable(user) -> bool gating the run browser; default is a staff check. |
OTEL |
False |
Emit OpenTelemetry spans (needs opentelemetry-api and OTEL_HOOK). |
OTEL_HOOK |
"django_agents.observability.otel.span_factory" |
Dotted path to the OTel span factory. |
JUDGE_MODEL |
None |
Default model for llm_judge eval checks. |
The last seven keys are used only by the optional django_agents.observability
app (tracing, a run browser, OpenTelemetry export, and evals). See the docs.
Streaming uses Server-Sent Events, which need a worker that does not buffer the
response. It works under runserver and gunicorn's sync worker for modest
concurrency. Any reverse proxy in front must have response buffering disabled —
the X-Accel-Buffering: no header covers nginx; other proxies need their own
configuration.
The default RATE_LIMIT (20/min, 200/day per user) is enforced by counting
messages in CACHES["default"]. That count is only accurate when the cache is
shared across worker processes — Redis or Memcached. On Django's default
per-process LocMemCache, each worker keeps its own counter, so the effective
limit becomes configured limit × worker count. A system check
(django_agents.W001) warns about this whenever DEBUG = False and no shared
cache is configured — point CACHES["default"] at Redis/Memcached in any
deployment running more than one worker process.
uv venv --python 3.12
uv pip install -e ".[dev,wagtail]"
pytest # plain Django
DJANGO_SETTINGS_MODULE=tests.settings_wagtail pytest # with WagtailSee sandbox/README.md for running the demo project.