Articles

Где заканчивается вызов LLM и начинается backend система: локальный RAG на FastAPI и Ollama

Вызов LLM заканчивается на этапе генерации ответа: модель получает сформированный промпт и возвращает токены. Всё, что до и после — ответственность backend-системы.

· 2 мин чтения · · AI и LLM

TL;DR

  • Вызов LLM заканчивается на этапе генерации ответа: модель получает сформированный промпт и возвращает токены. Всё, что до и после — ответственность backend-системы.
  • Backend на FastAPI управляет цепочкой: эмбеддинг запроса → поиск по векторному индексу → сборка контекста → вызов Ollama → постобработка и логирование. Без этого RAG превращается в «чёрный ящик» без прозрачности.
  • Обновление индекса, кэширование, контроль версий документов и мониторинг — чистая backend-логика, не имеющая отношения к инференсу модели.

Разбор

На практике граница проходит ровно в одной точке: когда сформирован промпт и передан в ollama.generate(). Всё, что до — подготовка контекста (поиск чанков, фильтрация, сортировка по релевантности). Всё, что после — разбор ответа, сбор метаданных (какие документы использованы, сколько токенов, время генерации).

Пример типичной архитектуры локального RAG:

@app.post("/ask")
async def ask(query: str):
    # 1. Эмбеддинг запроса (backend)
    query_emb = embed(query)
    
    # 2. Поиск по FAISS (backend)
    chunks = index.search(query_emb, k=5)
    
    # 3. Сборка промпта (backend)
    context = "\n".join([c.text for c in chunks])
    prompt = f"Контекст:\n{context}\n\nВопрос: {query}"
    
    # 4. Вызов LLM (граница)
    response = ollama.generate(model="llama3", prompt=prompt)
    
    # 5. Постобработка и логирование (backend)
    log_entry = {
        "query": query,
        "sources": [c.metadata for c in chunks],
        "prompt_tokens": len(prompt.split()),
        "response_time": response["total_duration"]
    }
    return {"answer": response["response"], "meta": log_entry}

Ключевой момент: модель не знает, откуда взялся контекст. Она просто генерирует текст на основе переданного промпта. Вся ответственность за актуальность данных, размер контекстного окна и фильтрацию шума лежит на backend. Если индекс не обновлён — модель будет отвечать по устаревшим документам, и это проблема инфраструктуры, а не LLM.

Для управления версиями документов и переиндексации удобно добавить отдельный endpoint:

curl -X POST http://localhost:8000/reindex \
  -H "Content-Type: application/json" \
  -d '{"path": "/docs/v2"}'

Этот вызов не трогает модель — он перестраивает FAISS-индекс и обновляет метаданные. Backend также отвечает за кэширование частых запросов (например, через Redis) и мониторинг времени каждого этапа через middleware.

Выводы

  • Чёткое разделение: LLM отвечает только за генерацию, backend — за контекст, логику и инфраструктуру. Смешивать их — путь к неконтролируемому поведению.
  • Локальный RAG на FastAPI + Ollama — это не «вызов модели», а полноценная backend-система с поиском, кэшем и мониторингом. Без этого вы не сможете отлаживать ответы и гарантировать актуальность.
  • Инвестиция в backend-слой (индексация, логирование, управление сессиями) окупается сразу, как только проект выходит за рамки демо.

Нужна такая автоматизация? Обсудить проект в Telegram: https://t.me/Cvister0

FAQ

С чего начать?

Смотрите раздел «Разбор» и блок «Выводы».

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