TL;DR: Если бэкенд в основном обслуживает вызовы LLM и других моделей — берите FastAPI: нативный async/await, стриминг ответов, типизированные контракты через Pydantic. Если AI — лишь часть большого приложения с базой, пользователями и админкой — берите Django, а тяжёлый инференс выносите в воркеры. Оптимум для многих проектов — гибрид: Django как ядро, FastAPI как сервис инференса.
От чего зависит выбор
Вопрос «FastAPI или Django» для AI-продукта — это не вопрос «какой фреймворк быстрее». Это вопрос о том, что делает бэкенд большую часть времени. Два крайних случая:
- Тонкий API-слой над моделями. Сервер принимает запрос, зовёт LLM по сети (OpenAI, Anthropic, self-hosted vLLM), стримит ответ клиенту. Основная нагрузка — I/O-bound: тысячи одновременных соединений ждут ответа от внешних сервисов.
- Полноценное приложение. Пользователи, роли, база данных с историей запросов, админка для операторов, платёжка, отчёты. Модель — один из десяти компонентов.
В первом случае асинхронность — ключевое свойство фреймворка, и это территория FastAPI. Во втором важнее готовая инфраструктура — и это территория Django. Дальше разберу оба стека и мои критерии выбора.
FastAPI: async-ввод/вывод и типизированные контракты
FastAPI построен поверх Starlette и ASGI. Любой эндпоинт — async def, и пока один запрос ждёт ответа от LLM-клиента, event loop обслуживает другие. На I/O-bound нагрузках, характерных для LLM-сервисов, это даёт принципиальную разницу против синхронного кода: один процесс держит сотни-тысячи конкурентных соединений без потоков на каждый запрос.
Второе его свойство, которое я ценю именно в AI-продуктах, — контракты на Pydantic v2. Схема запроса и ответа описана один раз, дальше FastAPI сам валидирует входные данные и генерирует OpenAPI-документацию (Swagger UI и ReDoc из коробки, в актуальных версиях — OpenAPI 3.1). Для сервиса, к которому будут подключать фронтенд, мобильное приложение или сторонних интеграторов, это экономит заметное время.
Пример: эндпоинт со стримингом ответа LLM
Типичная ошибка в демках — принимать текст query-параметром. Текст для модели — это тело запроса, и его надо описывать Pydantic-моделью. Вот скелет сервиса, который стримит ответ через Server-Sent Events:
import json
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
app = FastAPI()
class ChatRequest(BaseModel):
text: str = Field(min_length=1, max_length=20_000)
model: str = "my-model"
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
async def stream_llm(request: ChatRequest):
"""Генератор чанков от LLM-клиента (llm_client.stream — ваш код).
Ошибки ловим здесь, а не вокруг StreamingResponse: генератор
итерируется уже после того, как обработчик вернул ответ, и внешний
try/except его исключений не увидит. Клиенту уходит SSE-событие error.
"""
try:
async for chunk in llm_client.stream(
input=request.text,
model=request.model,
temperature=request.temperature,
):
yield f"data: {json.dumps({'delta': chunk}, ensure_ascii=False)}\n\n"
yield "data: [DONE]\n\n"
except Exception as exc: # провайдер упал, таймаут, лимит
yield f"data: {json.dumps({'error': str(exc)}, ensure_ascii=False)}\n\n"
@app.post("/chat")
async def chat(request: ChatRequest):
return StreamingResponse(
stream_llm(request),
media_type="text/event-stream",
)
StreamingResponse принимает асинхронный генератор и отдаёт чанки клиенту по мере поступления — пользователь видит ответ LLM в реальном времени, а не ждёт полной генерации. Частота хартбитов и формат SSE фронтенд обрабатывает через EventSource или fetch-стриминг.
Обратите внимание: в коде нет никакой «модели в процессе». llm_client — это сетевой вызов к провайдеру или к отдельному сервису инференса. Если у вас локальная модель (PyTorch, vLLM, ONNX), держать её в том же процессе, что и API, — плохая идея: один тяжёлый запрос займёт GPU и память, заблокирует масштабирование и сделает деплой монолитным. Тяжёлый инференс выносится в отдельный воркер или отдельный сервис, и это отдельный раздел ниже.
Запуск — штатно через ASGI-сервер: uvicorn main:app --workers 4 для небольших деплоев или gunicorn main:app -k uvicorn.workers.UvicornWorker, если нужен менеджмент воркеров Gunicorn.
Django: батарейки, которых нет у FastAPI
Django выигрывает не в скорости эндпоинтов, а в количестве готового кода, который не нужно писать:
- ORM и миграции. Django ORM плюс система миграций — до сих пор эталон. Хранение пользователей, истории запросов к модели, версий промптов, фидбека — всё это в Django делается декларативно и с накатом изменений в прод без ручного SQL.
- Админка. Автогенерируемая панель для управления данными. Для AI-продукта это, например, интерфейс, где оператор смотрит логи генераций, помечает плохие ответы, правит промпты — без единой строчки фронтенда.
- Auth и пермишены. Сессии, токены, группы, права — готовые и проверенные годами. В FastAPI всё это собирается руками из сторонних библиотек.
- Экосистема. DRF для API, django-allauth для OAuth, тысячи пакетов под типовые задачи.
Пример: лог запросов к модели и постановка задачи в очередь
from django.db import models
from rest_framework import permissions, serializers, status, viewsets
from rest_framework.response import Response
from .tasks import run_inference # Celery-задача
class GenerationLog(models.Model):
prompt = models.TextField()
result = models.TextField(blank=True)
status = models.CharField(max_length=20, default="pending")
created_at = models.DateTimeField(auto_now_add=True)
class GenerationSerializer(serializers.ModelSerializer):
class Meta:
model = GenerationLog
fields = ["id", "prompt", "result", "status", "created_at"]
read_only_fields = ["result", "status", "created_at"]
class GenerationViewSet(viewsets.ModelViewSet):
queryset = GenerationLog.objects.all().order_by("-created_at")
serializer_class = GenerationSerializer
permission_classes = [permissions.IsAuthenticated] # без этого DRF пускает всех
http_method_names = ["get", "post"]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = serializer.save()
run_inference.delay(instance.id)
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
Клиент сразу получает 202 Accepted с id задачи и опрашивает статус (или получает push через WebSocket/Channels). Тяжёлая работа ушла в Celery и не держит HTTP-соединение.
Как Django выполняет sync-код в async-стеке
С версии 3.0 Django умеет async def-вью, и с этого начинаются вопросы. Суть простая: если вы написали асинхронную вью, но вызвали внутри синхронный код (ORM-запрос, обычный requests.get, любую sync-библиотеку), Django через asgiref оборачивает этот вызов и гонит его в пул потоков — sync_to_async с threadpool под капотом. Это работает, но:
- каждый sync-вызов — это переключение в поток и обратно, накладные расходы на высокой конкуренции ощутимы;
- легко получить скрытый контекст-свитчинг и насыщение пула потоков под нагрузкой;
- сам ORM в асинхронном режиме предлагает async-методы (
afirst,aget,acreateи т.д.), но не всякая сторонняя библиотека готова к async и не всякий код легко портировать.
Практический вывод: Django в async-режиме — норм рабочий вариант для умеренной нагрузки, но если весь смысл сервиса в тысячах одновременных стриминговых соединений, вы будете постоянно бороться с синхронным наследием. FastAPI проектировался ровно под этот паттерн.
Куда девать тяжёлый инференс: отдельный воркер
Независимо от фреймворка в моих проектах действует правило: модель не живёт в процессе API. Аргументация:
- Масштабирование по-разному. API-слой масштабируется по числу соединений, инференс — по GPU и памяти. Смешивая их, вы платите за GPU-инстанс ради веб-сервера.
- Деплой. Обновили веб-слой — не нужно перезагружать модель (её старт может занимать минуты).
- Отказоустойчивость. Воркер упал — очередь копит задачи, API продолжает отвечать.
Механика: API принимает запрос, кидает задачу в брокер (Redis или RabbitMQ), возвращает 202. Воркер — Celery или arq (лёгкая async-очередь, если весь стек на asyncio) — забирает задачу, считает, пишет результат в базу. Стратегия целиком совместима и с Django, и с FastAPI; она же пригодится, когда дойдёте до многошаговых агентных сценариев — я разбирал их в статье AI-агенты: хайп или реальный инструмент?.
Гибридная архитектура: мой дефолт для AI-продуктов
Если продукт не «голый AI-API», комбинация почти всегда сильнее каждого фреймворка по отдельности:
- Django (с DRF) — ядро: пользователи, подписки, логирование, админка, миграции, бизнес-логика, отчёты.
- FastAPI — отдельный сервис инференса: стримит LLM-ответы, держит long-lived соединения, валидирует AI-контракты.
- Очередь (Celery/arq + Redis/RabbitMQ) — связка и тяжёлые задачи.
- Общая PostgreSQL, аутентификация между сервисами — внутренний токен или mTLS.
Такой разрез даёт и скорость стрима у FastAPI, и админку Django, и чистые границы деплоя. На старте MVP это уместно даже в одном репозитории — монорепо с двумя сервисами; про сам подход к MVP я писал в MVP в 2026: от минимального продукта к минимальному доказательству ценности. Если продукт — вокруг LLM-оркестрации, заодно посмотрите выбор фреймворка для LLM-логики: LangChain vs LlamaIndex.
Когда что брать: прямая рекомендация
Берите FastAPI, если:
- сервис в основном проксирует и стримит вызовы LLM (SSE, WebSockets);
- нужен максимум конкурентных I/O-bound соединений на один процесс;
- вы хотите строгие контракты API и автосгенерённую OpenAPI-схему без дополнительных настроек;
- команда готова сама собирать auth, админку и инфраструктуру вокруг.
Берите Django, если:
- AI — один модуль из многих в большом приложении (данные, роли, платежи, операторы);
- нужна админка и зрелый ORM «из коробки» — это самый дешёвый способ дать операторам интерфейс;
- модель можно вызывать фоново через очередь, стриминг не требуется.
Берите оба (гибрид), если:
- продукт и большой, и с горячими AI-путями: стриминг ответов пользователю плюс аналитика, биллинг, контент-менеджмент;
- команда умеет оперировать несколькими сервисами.
Настоящий критерий — не бенчмарки hello-world, а профиль нагрузки. Считайте пиковое число одновременных стримов, время ответа модели, требования к данным. Ответ на вопрос «FastAPI или Django» почти всегда следует из этих чисел.
FAQ
Можно ли держать LLM-модель прямо в Django-процессе?
Технически да, практически — нет. Модель съест память и GPU-время, привязав ресурсы API-инстанса к ресурсам инференса. Масштабирование, обновление и отладка становятся монолитными. Правильнее — отдельный воркер (Celery/arq) или самостоятельный inference-сервис.
FastAPI точно быстрее Django?
Для I/O-bound API — да, заметно: нативный ASGI без обёрток sync-кода в threadpool. Но «быстрее» не равно «лучше»: вы теряете ORM, админку и готовые модули, которые придётся собирать по кусочкам. Считайте и нагрузку, и стоимость разработки.
Что делать, если уже есть Django-монолит и нужен стриминг LLM?
Не переписывайте всё. Вынесите стриминг в отдельный FastAPI-сервис на том же домене (за reverse proxy), поделите сессию/токен аутентификации. Django останется ядром, а стримы уйдут в инструмент, спроектированный под них.
Как тестировать SSE-стриминг от FastAPI?
Штатно: TestClient из Starlette (или httpx.AsyncClient с stream()) умеет читать чанки ответа по мере поступления. Проверяйте и формат кадров data: ..., и корректный финальный маркер, и поведение при обрыве соединения.
Нужен ли DRF, если проект на Django, но API минимальный?
Нет, это необязательно. Для двух-трёх эндпоинтов хватит обычных Django-вью с JsonResponse. DRF окупается, когда API растёт: сериализаторы, пермишены, пагинация, версии, документация через drf-spectacular.
Нужна помощь с выбором фреймворка, архитектурой AI-продукта или выводом ML-сервиса в прод? Напишите мне — обсудим ваш проект.