В один прекрасный понедельник я понял, что больше не могу читать pull request'ы. Чужие - еще туда-сюда, но свои... Когда команда разрослась до 20 человек, уткнуться в 400 строк кода перед мержем стало рутиной. Мы хотели ускорить код-ревью, не теряя качества. Идея создать AI-агента витала в воздухе - на дворе 2026, LLM уже умеют писать неплохие ревью. Оставалось заставить их делать это стабильно, дешево и без галлюцинаций. Спойлер: не вышло с первого раза. Вообще не вышло. Но после трех итераций у нас получился агент, который прошел 1000+ PR. Рассказываю, как мы до этого дошли и где наступили на грабли.
Если вы еще не знакомы с архитектурой современных AI-агентов, советую прочитать мой материал "Как спроектировать современного AI-агента: от planner/executor до stateful memory" - там заложена база для понимания графов.
Почему LangGraph, а не просто промпт к ChatGPT?
Код-ревью - это не линейная задача. Сначала нужно понять контекст PR, потом проверить логику, затем стиль, потом безопасность, иногда запустить тесты. Простой вызов LLM с промптом "проверь код" дает много шума. Нужен четкий pipeline, где каждый этап может ветвиться, возвращаться назад, запускать инструменты. LangGraph (текущая версия 0.4.x) позволяет построить граф состояний - идеально для такого сценария.
Сейчас многие инженеры переходят от LangChain к нативным решениям - подробно разобрал в статье "Почему AI-инженеры бегут от LangChain к нативным архитектурам агентов". Но мы остались на LangGraph - на тот момент он давал достаточную гибкость и уже был обкатан в production кейсах, например как Vodafone и Fastweb автоматизировали поддержку клиентов.
Архитектура нашего агента
Мы разделили процесс на четыре узла в графе:
- Context Collector - загружает diff, связанные файлы, историю коммитов, комментарии.
- Static Analyzer - запускает линтеры (eslint, pylint) и type-checker.
- LLM Reviewer - основной узел: отправляет контекст в GPT-4 (мы использовали gpt-4o-mini для экономии, потом перешли на gpt-4o).
- Checklist Runner - проверяет, покрыты ли все пункты ревью (безопасность, производительность, архитектура).
Граф выглядел так: Collector -> Analyzer (ветвление) -> LLM Reviewer -> Runner -> (если не хватает -> возврат к LLM Reviewer с дополнительным контекстом). Вот упрощенный код на Python с LangGraph:
from langgraph.graph import StateGraph, END
from typing import Dict, List, TypedDict
class ReviewState(TypedDict):
pr_diff: str
analysis_results: Dict
review_comments: List[str]
checklist: Dict[str, bool]
def collector(state: ReviewState) -> ReviewState:
# загружаем diff и метаданные
state['pr_diff'] = fetch_diff(state['pr_id'])
return state
def analyzer(state: ReviewState) -> ReviewState:
# запускаем статический анализ
state['analysis_results'] = run_linters(state['pr_diff'])
return state
def llm_reviewer(state: ReviewState) -> ReviewState:
prompt = build_review_prompt(state['pr_diff'], state['analysis_results'])
response = call_gpt4o(prompt)
state['review_comments'] = parse_response(response)
return state
def checklist_runner(state: ReviewState) -> ReviewState:
# проверяем coverage чеклиста
state['checklist'] = check_coverage(state['review_comments'])
if not all(state['checklist'].values()):
# возвращаемся в llm_reviewer с недостающей информацией
return {"next": "llm_reviewer", "context_hint": "...", **state}
return {"next": END, **state}
graph = StateGraph(ReviewState)
graph.add_node("collector", collector)
graph.add_node("analyzer", analyzer)
graph.add_node("llm_reviewer", llm_reviewer)
graph.add_node("checklist_runner", checklist_runner)
graph.set_entry_point("collector")
graph.add_edge("collector", "analyzer")
graph.add_edge("analyzer", "llm_reviewer")
graph.add_conditional_edges("checklist_runner", lambda state: state.get("next", "end"))
# ... запуск через app = graph.compile()
Ошибка №1: не делайте так с самого начала. В первой версии мы пытались в одном узле LLM сделать всё. Граф превращался в спагетти, а стоимость вызовов росла экспоненциально из-за возвратов. Разделение на узлы - база, но упрощение графа до 3-4 узлов спасает.
Что пошло не так - три главных грабля
1 Галлюцинации и ложные срабатывания
Первая версия агента засыпала разработчиков комментариями. "Вы забыли обработать null", "этот метод не используется", "у вас утечка памяти". 90% из них были ложными. LLM высасывала из пальца проблемы, которых нет. Причина - слишком общий промпт. Мы просили "найти все возможные баги", а надо было "найди только реальные проблемы, используя следующие критерии".
Решение: перешли на structured output (Pydantic модели) и строгие критерии. Пример промпта: "Если ты не уверен на 80% - не пиши комментарий". И добавили верификацию через второй LLM-вызов, который оценивает уверенность. Это удвоило latency, но сократило ложные срабатывания в 5 раз.
2 Проблемы с длинным контекстом
PR на 2000+ строк - обычное дело. Даже с контекстным окном в 128k токенов мы упирались в лимит, если добавляли весь файл целиком. Приходилось сжимать diff, вырезать нерелевантные строки. Но сжатие теряло важные детали. Агент начинал предлагать неправильные рефакторинги.
Решение: разбили большой PR на логические блоки (по файлам или по изменениям), прогоняли каждый блок отдельно, потом агрегировали результаты. Это похоже на LangSmith подход с трейсингом - кстати, для отладки таких сценариев очень помогает визуализация, как в инструменте Codag.
3 Стоимость и производительность
Каждый PR обходился в $0.5-$1.5 на вызовы GPT-4o. Для команды из 20 человек с 10 PR в день - $2000 в месяц. Жирно. Мы оптимизировали: использовали gpt-4o-mini для предварительного анализа, а gpt-4o только для критических проверок. Добавили кэширование (одинаковые diff'ы часто повторяются). И запускали агента асинхронно через webhook'и, а не синхронно в pipeline.
Деплой агента на production - отдельная история. Мы использовали LangGraph Deploy CLI, чтобы развернуть агента одной командой с Docker и CI/CD. Если хотите запустить своего агента быстро - рекомендую посмотреть в ту сторону.
Результаты и статистика (актуально на май 2026)
После всех фиксов мы получили:
| Метрика | До оптимизации | После |
|---|---|---|
| Ложные срабатывания | ~20% | <3% |
| Среднее время ответа | 45 сек | 15 сек (с кэшем - 2 сек) |
| Стоимость за PR | $1.20 | $0.35 |
| Хороших ревью (приняты без изменений) | - | 68% |
Итог: стоит ли овчинка выделки?
Да, если делать правильно. Мы сэкономили ~40% времени команды на ревью. Но это потребовало двух месяцев итераций. Основные уроки:
- Не верьте LLM на слово - добавляйте верификацию.
- Разбивайте граф на мелкие узлы, но не увлекайтесь.
- Кэшируйте и используйте разные модели по цене.
- Следите за latency - 10 секунд еще норма, 30 уже раздражает.
- Логируйте все в трассировщик - мы использовали Daggr для визуализации графов, но есть и open-source аналоги.
Если вы только начинаете строить AI-агентов - присмотритесь к нативным подходам. Возможно, вам вообще не нужен LangGraph, а хватит простого if-else с вызовом API. Но для сложных сценариев вроде код-ревью графовая архитектура незаменима.
Кстати, мы выложили core-часть агента в open source - ссылка в комментариях. А пока, если хотите глубже погрузиться в тему stateful memory - читайте ту же статью. Там много пересечений.