FastAPI vs Django: Выбираем фреймворк для AI-продуктов

MVP и продукты
Две таблички с надписями FastAPI и Django над схемой ML-сервиса: API-слой, очередь задач и воркеры инференса

TL;DR: Если бэкенд в основном обслуживает вызовы LLM и других моделей — берите FastAPI: нативный async/await, стриминг ответов, типизированные контракты через Pydantic. Если AI — лишь часть большого приложения с базой, пользователями и админкой — берите Django, а тяжёлый инференс выносите в воркеры. Оптимум для многих проектов — гибрид: Django как ядро, FastAPI как сервис инференса.

От чего зависит выбор

Вопрос «FastAPI или Django» для AI-продукта — это не вопрос «какой фреймворк быстрее». Это вопрос о том, что делает бэкенд большую часть времени. Два крайних случая:

  1. Тонкий API-слой над моделями. Сервер принимает запрос, зовёт LLM по сети (OpenAI, Anthropic, self-hosted vLLM), стримит ответ клиенту. Основная нагрузка — I/O-bound: тысячи одновременных соединений ждут ответа от внешних сервисов.
  2. Полноценное приложение. Пользователи, роли, база данных с историей запросов, админка для операторов, платёжка, отчёты. Модель — один из десяти компонентов.

В первом случае асинхронность — ключевое свойство фреймворка, и это территория 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-сервиса в прод? Напишите мне — обсудим ваш проект.

Обсудить проект

Есть идея или задача? Давайте обсудим, как можно её реализовать с помощью современных AI-технологий.

Написать мне
Вернуться к блогу