- BrainTools - https://www.braintools.ru -
Недавно я делал учебный проект про автоматизацию документирования инцидентов. Поначалу планы были грандиозными: инциденты, таймлайны, интеграции с мониторингами, чатами, постмортемы, подсказки дежурным инженерам.
Но довольно быстро стало понятно, что с временными и ресурсными ограничениями лучше не пытаться написать маленький PagerDuty. Поэтому я сузил задачу до более реалистичного ядра: локального RAG-сервиса, который ищет по документации, ранбукам и коду, а затем передаёт найденный контекст в LLM.
Так появился llmortem — FastAPI-сервис, который можно подключить к OpenWebUI как OpenAI-compatible backend.
В статье расскажу, как устроена архитектура, почему я начал с BM25, зачем индексировать docstring’и и какие ограничения у такого подхода.
Репозиторий на Гитхабе [1].
Во время инцидента инженер редко работает с одним источником информации [2]. Обычно нужно быстро найти и сопоставить:
документацию проекта;
SRE-документацию;
ранбуки;
фрагменты кода;
логи;
метрики;
сообщения из чатов;
описание релиза.
Часть информации уже есть в проекте, но она разбросана по разным файлам и директориям. Например, инструкция по queue lag лежит в ранбуке, описание регистрации — в пользовательской документации, а детали поведения [3] функции — только в docstring’е.
LLM тут кажется хорошим помощником: можно спросить “что делать, если растёт queue lag?” или “сгенерируй черновик постмортема”. Но если просто отправить вопрос в модель, она не знает внутреннюю документацию проекта и может ответить слишком общо или начать придумывать детали.
Поэтому вместо обычного “чата с моделью” я сделал RAG-прослойку:
пользователь задаёт вопрос;
сервис ищет релевантные фрагменты в локальных источниках;
найденный контекст добавляется в prompt;
prompt отправляется в локальную LLM;
пользователь получает ответ, привязанный к материалам проекта.
llmortem умеет:
индексировать Markdown-документацию;
индексировать SRE-документацию и ранбуки;
извлекать из кода docstring’и, комментарии и сигнатуры;
строить BM25-индекс;
искать релевантный контекст;
определять тип запроса: документационный, SRE/incident или общий;
формировать prompt для LLM;
обращаться к локальной модели через Ollama;
отдавать ответы через OpenAI-compatible endpoint;
подключаться к OpenWebUI;
генерировать черновик постмортема.
Конечно же это не полноценная платформа инцидент-менеджмента. Тут нет on-call, эскалаций, автоматического таймлайна и интеграций с Grafana/Slack/GitLab. Это именно RAG-ядро, которое можно развивать дальше.
Общая схема выглядит так:
Пользователь
↓
OpenWebUI
↓
/v1/chat/completions
↓
FastAPI-сервис llmortem
↓
Intent detection
↓
BM25 retrieval по docs / runbooks / code
↓
Prompt builder
↓
Ollama
↓
Ответ пользователю
OpenWebUI ничего не знает о внутренней логике [4] сервиса. Для него llmortem выглядит как обычный OpenAI-compatible backend. А уже внутри FastAPI-приложения происходит поиск по локальным источникам, сборка prompt’а и обращение к Ollama.
Это оказалось удобным решением: не нужно писать отдельный frontend, а RAG-логику можно развивать независимо.
Когда говорят про RAG, часто сразу вспоминают embeddings и vector database. Я решил начать с BM25. Причина простая: для технической документации часто важны точные совпадения.
Например:
конкретные слова;
код ошибки;
названия endpoint’ов;
имена конфигов;
названия метрик.
В таких случаях обычный лексический поиск может работать достаточно хорошо. При этом BM25 сильно проще: не нужна отдельная векторная база, не нужно выбирать embedding-модель и не нужно пересчитывать эмбеддинги.
Для прототипа это был хороший компромисс: сначала проверить архитектуру целиком, а semantic search оставить как развитие.
Ограничение тоже очевидное: если пользователь формулирует вопрос сильно иначе, чем написано в документации, BM25 может не найти правильный фрагмент. Поэтому в будущем сюда хорошо ложится гибридный поиск: BM25 + embeddings.
В прототипе индексируются три типа источников:
docs/
registration.md
sre/
queue-lag.md
runbooks/
api-5xx-errors.md
auth-401-403.md
database-connection-errors.md
disk-space-full.md
external-api-timeout.md
high-cpu-service.md
queue-lag.md
release-rollback.md
llmortem/
*.py
Для документационных вопросов приоритет получает docs.
Для SRE/incident-запросов — docs/sre и runbooks.

Для вопросов по реализации дополнительно используется поиск по коду.
Например, запрос:
queue lag, what to do?
должен в первую очередь попасть в ранбук по очередям, а не в пользовательскую документацию.
Отдельно я добавил поиск по коду. Идея простая: документация может отставать от реализации, а в коде иногда остаются полезные docstring’и и комментарии.
Для Python можно использовать AST и извлекать:
docstring модуля;
docstring класса;
docstring функции;
сигнатуры функций;
сигнатуры классов;
комментарии.
Например, если добавить файл:
"""
Authentication helper module.
Password reset flow:
1. User opens the password reset page.
2. User enters email.
3. System sends a reset link.
4. User opens the link and sets a new password.
"""
def reset_password(email: str) -> None:
"""Send password reset link to the user's email address."""
pass
то после переиндексации можно спросить:
curl -X POST http://localhost:8000/search
-H "Content-Type: application/json"
-d '{"query":"How to change the password?", "top_k": 5}'
| python3 -m json.tool
и получить релевантный фрагмент именно из кода.

Конечно, это не полноценный поиск по коду. Но даже такой простой подход полезен, если документация неполная, а в коде есть хотя бы базовые описания.
Основные endpoint’ы сервиса:
GET /health
POST /reindex
POST /search
POST /ask/docs
POST /draft-doc
POST /postmortem
POST /v1/chat/completions
/health проверяет состояние сервиса.
/reindex пересобирает индекс после изменения документации, ранбуков или кода.
/search позволяет проверить retrieval отдельно от LLM.
/ask/docs отвечает на вопросы по документации и SRE-сценариям.
/postmortem генерирует черновик постмортема.
/v1/chat/completions нужен для подключения OpenWebUI.
Чтобы не заморачиваться со своим интерфейсом, я реализовал OpenAI-совместимый endpoint.
В OpenWebUI можно добавить backend:
Base URL: http://host.docker.internal:8000/v1
API key: any value
После этого пользователь работает с llmortem как с обычной моделью в чате. Но внутри запрос проходит через retrieval:
OpenWebUI → llmortem → поиск контекста → Ollama → ответ
Это удобно, потому что можно сосредоточиться на backend-логике и не тратить время на UI.
В моём случае основная задержка была связана не с RAG-прослойкой, а с ожиданием ответа локальной LLM.
Индекс строится заранее: при старте сервиса или через /reindex. Поэтому на каждый пользовательский запрос не происходит полного обхода репозитория.
Во время запроса выполняются только:
определение цели запроса;
BM25-поиск;
сборка prompt’а;
вызов Ollama.
Если сервис начнёт упираться именно в поиск, можно добавить кэширование или гибридный поиск. Но в текущем прототипе узким местом была модель, запущенная локально, ибо она не очень-то и мощная(по понятным причинам). В любом случае хорошая RAG прослойка дает довольно хорошие ответы даже со слабенькой моделью.
BM25 оказался нормальным стартом для технической документации. Для терминов вроде queue lag, 5xx, database connection, reset_password точный поиск работает достаточно хорошо.
OpenAI-совместимый API сильно упростил интеграцию с OpenWebUI. Не пришлось писать frontend.
Поиск по коду тоже оказался полезным. Даже docstring’и и сигнатуры могут дать модели контекст, которого нет в документации.
RAG-подход делает ответы более привязанными к проекту. Даже компактная локальная модель отвечает лучше, если передать ей релевантный фрагмент ранбука или документации.
BM25 не понимает семантику. Если вопрос сформулирован не так, как написано в документации, правильный фрагмент может не попасть в контекст.
Локальная LLM может быть медленной, особенно на слабом железе.
В системе пока нет полноценной модели инцидента, хранения истории, интеграций с мониторингами и автоматического таймлайна.
Дальше наверное я бы развивал проект в таких направлениях:
добавить embeddings и сделать гибридный поиск;
подключить Grafana или другой источник алертов;
добавить импорт сообщений из Slack или Mattermost;
подключить GitLab/GitHub для анализа релизов и merge request’ов;
сохранять инциденты и постмортемы в БД;
искать похожие прошлые инциденты;
Я начинал с идеи системы для автоматизации документирования инцидентов, но в итоге пришёл к более простой и полезной задаче: сделать локальное RAG-ядро для работы с инженерными знаниями проекта.
llmortem индексирует документацию, ранбуки и код, ищет релевантные фрагменты через BM25 и передаёт их локальной LLM через Ollama. Через OpenAI-compatible API сервис подключается к OpenWebUI и выглядит для пользователя как обычный чат.
Это не замена PagerDuty или incident.io [5]. Скорее, это небольшой слой поверх внутренних знаний проекта, который помогает быстрее находить инструкции, задавать вопросы к документации и получать черновики постмортемов.
Главный вывод для меня: даже простая RAG-архитектура с BM25 и локальной моделью может быть полезной, если аккуратно выбрать источники знаний и не пытаться выдавать LLM за абсолютный источник истины.
Автор: chisi
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/31304
URLs in this post:
[1] на Гитхабе: https://github.com/syubogdanov/llmortem/tree/LM-4
[2] источником информации: http://www.braintools.ru/article/8616
[3] поведения: http://www.braintools.ru/article/9372
[4] логике: http://www.braintools.ru/article/7640
[5] incident.io: http://incident.io
[6] Источник: https://habr.com/ru/articles/1043928/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1043928
Нажмите здесь для печати.