ГлавнаяБлогКак я строил AI-продукт на Python: 5 агентов и 3 компромисса
AI / Нейросети

Как я строил AI-продукт на Python: 5 агентов и 3 компромисса

Разбор архитектуры AI-продукта на Python: мультиагентный пайплайн, RAG, честные метрики и 3 ключевых компромисса. Читайте и применяйте!

Al
Редакция Algolitalgolit.ru
8 мин чтения14 августа 2026 г.

Как я строил AI-продукт на Python: 5 агентов и 3 компромисса

Я потратил 18 дней на создание AI-продукта, который превращает научные статьи в презентации PowerPoint под конкретную аудиторию. Это не игрушка, а реально работающий сервис на Railway, доступный каждому. Самое интересное — не моменты «заработало», а честные компромиссы, на которые мне пришлось пойти, и моменты, когда я устоял перед соблазном добавить «умное» решение, которое только ухудшило бы ситуацию. Эта статья — о тех решениях.

Что я построил: Doc2Slides

Doc2Slides принимает PDF и генерирует файл .pptx, адаптированный под четыре аудитории: ребёнок (весёлые аналогии, простые слова), студент (обучающий материал, определения), инженер (техническая глубина, знание предметной области), руководитель (бизнес-фокус, влияние). Магия в том, что одна и та же статья даёт radically разный результат в зависимости от аудитории. Статья по теории компиляторов для ребёнка превращается в «компиляторы — это волшебные помощники», а для руководителя — в «продвижение технологии компиляторов с помощью формальных фреймворков».

Архитектура: 5 агентов в LangGraph

Я построил мультиагентный пайплайн вместо одного гигантского промпта. Поток данных: загрузка PDF → парсер (извлекает разделы и метаданные) → суммаризатор (RAG-суммаризация разделов) → планировщик (проектирует структуру слайдов для аудитории) → писатель (генерирует адаптивный контент) → сборщик (создаёт редактируемый .pptx). Каждый агент — независимый узел в графе состояний LangGraph, они разделяют состояние TypedDict и читают/пишут определённые поля. Вот как выглядит определение графа:

from langgraph.graph import StateGraph, END
from app.agents.state import AgentState
from app.agents.parser import parser_agent
from app.agents.summarizer import summarizer_agent
from app.agents.planner import planner_agent
from app.agents.writer import writer_agent
from app.agents.builder import builder_agent

def build_pipeline():
    graph = StateGraph(AgentState)
    graph.add_node("parser", parser_agent)
    graph.add_node("summarizer", summarizer_agent)
    graph.add_node("planner", planner_agent)
    graph.add_node("writer", writer_agent)
    graph.add_node("builder", builder_agent)
    graph.set_entry_point("parser")
    graph.add_edge("parser", "summarizer")
    graph.add_edge("summarizer", "planner")
    graph.add_edge("planner", "writer")
    graph.add_edge("writer", "builder")
    graph.add_edge("builder", END)
    return graph.compile()

Почему LangGraph, а не последовательная цепочка? Добавление нового агента — это изменение двух строк в графе. В последовательной цепочке добавление шага часто требует рефакторинга предыдущих. Государственный мультиагентный подход масштабируется лучше.

Компромисс №1: моя RAG top-1 точность — 42%

Я создал оценочный стенд, потому что хотел измерять качество, а не просто заявлять о нём. Три типа оценок: парсер (детерминированные проверки ground-truth), RAG (top-K точность на вручную размеченных парах запрос-раздел), суммаризатор (LLM-судья оценивает точность, полноту, ясность). Парсер показал 100% (34/34 проверок), суммаризатор — 4.4/5. Но RAG top-1 точность составила 42%: только 3 из 7 запросов вернули правильный раздел как лучший результат. Моя первая мысль: скрыть число, сообщить top-3 (57%). Вместо этого я опубликовал оба числа и объяснил, почему так вышло. Анализ ошибок выявил реальное ограничение RAG: запрос «как работает генетический алгоритм?» ожидал раздел «Методология», а получил подраздел «3.6 Критерии остановки». Генетические алгоритмы обсуждаются в шести подразделах (3.1–3.6). Векторный поиск возвращает чанк с наивысшим баллом, а не раздел. Для запросов по широким темам подразделы часто обходят родительский раздел, потому что упоминают конкретный термин плотнее. Это известная проблема RAG. Решения включают иерархический поиск, переписывание запросов, выбор раздела через LLM. Ни одно из них пока не реализовано, но я точно знаю, что сломано и почему — это полезнее, чем притворяться, что всё работает. Урок: детерминированные метрики лучше ощущений. Ощущения позволяют убедить себя, что AI умный. Метрики показывают, где он глупый.

Компромисс №2: я отказался использовать количество слов как прокси для плотности контента

Пользователи могут запросить любое количество слайдов от 3 до 50. Когда фактическая плотность контента не соответствует запрошенному числу слайдов, LLM либо раздувает короткие разделы, либо сжимает плотные. Это создаёт лёгкую избыточность при большом числе слайдов. Очевидное решение: распределять слайды на основе количества слов в разделе. Длинный раздел — больше слайдов, короткий — меньше. Я почти построил это, но осознал: количество слов — это не плотность контента. Раздел из 100 слов с тремя разными концепциями должен получить несколько слайдов, а раздел из 2000 слов, болтающий вокруг одной идеи, — один слайд. Количество слов систематически вознаграждало бы многословные разделы и наказывало краткие. Это не фикс, а баг с математикой. Вместо этого я задокументировал компромисс и выпустил без эвристики. В testing_notes.md проекта написано: «Отклонено быстрое решение: использовать количество слов в разделе как прокси для плотности контента. Количество слов — не плотность: короткий раздел может содержать несколько разных идей, а длинный — болтать вокруг одной. Правильное решение отложено: распределение слайдов с учётом контента с помощью LLM, проверенное оценочным стендом. Требует инфраструктурной работы, не подходящей для первой версии». Урок: правильный ответ на вопрос «стоит ли добавить эту эвристику?» часто «нет». Эвристики ощущаются как прогресс. Иногда это анти-прогресс, замаскированный под прагматизм.

Компромисс №3: SQLite в разработке → PostgreSQL в проде — одна переменная

Я разрабатывал локально с SQLite, но задеплоил на Railway с PostgreSQL. Миграция заняла одну строку: DATABASE_URL = os.getenv("DATABASE_URL"). Для локальной разработки в .env: DATABASE_URL=sqlite:///./doc2slides.db. Для Railway переменная окружения: DATABASE_URL=postgresql+psycopg2://postgres:xxx@host:5432/railway. Больше ничего не меняется. Модели SQLAlchemy независимы от бэкенда. Это скучная инженерия. Но скучная инженерия позволяет спать по ночам. Когда кто-то спрашивает, как вы обрабатываете миграции БД, ответ — не хитрый хак, а «конфигурация через переменные окружения и паттерн репозитория».

Стек технологий

  • Язык: Python 3.13 — экосистема AI
  • API: FastAPI — асинхронность, авто Swagger-документация
  • Оркестрация: LangGraph — государственный мультиагент
  • LLM: OpenAI GPT-4o-mini — дёшево для итераций, умно для структурированного вывода
  • Векторная БД: ChromaDB — локально, без облачной зависимости
  • Структурированный вывод: JSON mode + Pydantic — двухуровневая валидация
  • База данных: SQLAlchemy + PostgreSQL — переносимость через окружение
  • Фронтенд: ванильные HTML/CSS/JS — без шага сборки, портативно
  • Деплой: Railway — GitHub CI/CD, управляемый Postgres

Фронтенд стоит отметить отдельно. Я не использовал фреймворк — только HTML, CSS и ванильный JavaScript в ~500 строк. Ноль шага сборки. Любой может склонировать репозиторий, открыть файл и понять его за 5 минут. Для MVP это фича, а не ограничение.

Что я не построил (и почему это нормально)

Я пропустил: аутентификацию пользователей, мультитенантные рабочие пространства, пользовательские шаблоны презентаций, стриминг ответов, очередь задач с Celery/Redis. Причина: MVP. Каждая фича имеет стоимость. Доставка основной ценности (PDF → слайды под аудиторию) важнее, чем доставка всех возможных фич. Для портфолио-проекта ответ «я мог добавить X, но не стал по таким-то причинам» сильнее, чем «я добавил X плохо».

Уроки, которые я бы рассказал себе в прошлом

  1. Создавайте оценочные стенды до оптимизации. Я сначала построил пайплайн, потом стенды. Если бы сделал наоборот, узнал бы о проблемах RAG раньше. Теперь я вношу улучшения на основе оценок на третьей неделе.
  2. Сопротивляйтесь эвристикам. Каждый раз, когда я думал «это быстрый фикс», на самом деле это был технический долг, который я собирался встроить. Количество слов как плотность. Тихие переопределения числа слайдов AI. Булевы флаги статуса вместо нормальных enum.
  3. Деплойте рано. Я задеплоил на 16-й день из 18. Надо было на 8-й. Деплой выявляет реальные баги: опечатки в переменных окружения, отсутствующие зависимости, захардкоженные localhost URL. Чем раньше найдёте, тем дешевле.
  4. Документируйте компромиссы, а не фичи. Любой может прочитать код и узнать, что он делает. Почти никто не оставляет заметок о том, почему выбрано такое решение. Мой файл testing_notes.md — место, где живёт большая часть инженерного мышления.

Что дальше

Проект жив, но не закончен. Будущая работа: распределение слайдов с учётом контента (с оценочным стендом), поддержка многоязычных PDF, пользовательские шаблоны презентаций, исправление RAG для иерархических разделов (подраздел → родитель). Если хотите попробовать Doc2Slides: живое демо на web-production-6eded.up.railway.app, код на github.com/manasviboineypally/doc2slides, 60-секундное видео на Loom. Загрузите любой PDF, выберите аудиторию, получите презентацию. Одна и та же статья — radically разный результат в зависимости от того, кому вы представляете.

Практический вывод

Прямо сейчас: возьмите свой проект и честно оцените, какие метрики вы используете для измерения качества. Если вы полагаетесь на «ощущения», создайте хотя бы простой оценочный стенд. Затем задокументируйте компромиссы, которые вы приняли, — это будет ценнее, чем любой код. И, наконец, задеплойте свой проект раньше, чем планировали. Деплой вскроет реальные проблемы, о которых вы даже не подозревали.

#AI-продукт#LangGraph#RAG#Python#архитектура
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →