Integrate an LLM API into a Django Project
Flask's minimal structure from part 3 worked for a small demo, but a real Django project — like the one the Django From Scratch series built — needs a genuinely different, more structured integration pattern. This part covers it correctly, as the foundation every remaining Django part in this series builds on.
Real, practical setup
pip install anthropic python-dotenv django-environdjango-environ is a real, widely used package for reading environment variables into Django settings cleanly — a genuine, Django-idiomatic alternative to python-dotenv used directly, fitting the Django series' own settings conventions.
Real, correct settings configuration
# settings.py
import environ
env = environ.Env()
environ.Env.read_env()
ANTHROPIC_API_KEY = env("ANTHROPIC_API_KEY")# .env (never committed, exactly per this series' part 1 discipline)
ANTHROPIC_API_KEY=your-real-api-key-hereStoring the real key in settings.py, read from the environment, is the genuinely correct Django-idiomatic location — every real Django app can then reference settings.ANTHROPIC_API_KEY, rather than each individual view or module independently reading environment variables.
A real, separate service module — not AI logic inside a view
# support/services.py
from anthropic import Anthropic
from django.conf import settings
def get_client() -> Anthropic:
return Anthropic(api_key=settings.ANTHROPIC_API_KEY)
def ask_support_assistant(user_question: str, plan_data: str) -> str:
client = get_client()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=(
f"You are a customer support assistant for Bright Leaf "
f"Coffee. Real, current plan data:\n{plan_data}\nOnly "
f"answer using this real data."
),
messages=[{"role": "user", "content": user_question}],
)
return response.content[0].textThis real, separate services.py module — a genuine, standard Django pattern for business logic that doesn't belong directly in a view or model — is what keeps a real view (built in part 5) thin and focused on handling the actual HTTP request/response cycle, exactly the same separation-of-concerns discipline the Django REST Framework series' own serializer and view separation already established.
Putting AI logic directly inside a real Django view — rather than in a separate service module — genuinely works for a first, small prototype, but it becomes a real, practical problem the moment the same AI logic needs to be reused from a second view, a management command, or a Celery background task. A real service module is callable from any of those contexts identically; logic embedded in one specific view is not.
A real, minimal model for storing conversation history
# support/models.py
from django.db import models
class ChatMessage(models.Model):
session_id = models.CharField(max_length=64)
role = models.CharField(max_length=20)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["created_at"]This directly resolves part 3's honest limitation — rather than storing conversation history in a browser cookie via Flask's session, a real Django model persists it in the actual database, exactly the durable storage pattern the Django series' own models coverage established, and genuinely necessary once a conversation needs to survive longer than a single browser session or be inspected server-side.
A real, minimal, working view using this integration
# support/views.py
from django.shortcuts import render
from .services import ask_support_assistant
from .models import ChatMessage
PLAN_DATA = "- Gift Subscription: $18/mo\n- Monthly Subscription: $16/mo"
def chat_view(request):
if request.method == "POST":
question = request.POST["message"]
answer = ask_support_assistant(question, PLAN_DATA)
ChatMessage.objects.create(session_id=request.session.session_key, role="user", content=question)
ChatMessage.objects.create(session_id=request.session.session_key, role="assistant", content=answer)
messages = ChatMessage.objects.filter(session_id=request.session.session_key)
return render(request, "support/chat.html", {"messages": messages})This is a genuine, minimal working integration — part 5 builds directly on this exact services.py and models.py, adding a real, complete chatbot UI, proper session handling, and the production concerns this intentionally minimal version leaves out.
Next: building a real, complete AI chatbot with Django — the full application built directly on this part's integration foundation.