Привет! Меня зовут Стас, я занимаюсь R&D в компании ROGII.
Я пришёл в ROGII после нескольких лет работы «в поле» — от тундры Уренгойских месторождений до Сахалина. Там я понял, что буровые данные живут в хаосе: у каждого вендора — свой формат, у каждой скважины — свой стиль отчёта.
Когда я оказался в компании, которая консолидирует буровые данные в облаке, задача встала ребром: нужно научить машину понимать суточные рапорты так же, как это делает инженер.
Мы собрали 507 PDF‑файлов (всего 14 678 страниц) и выделили 23 типа отчётов по признаку компании и структуры.
Но традиционные подходы: ручной ввод, регулярки, rule‑based и классический NLP — оказались или неэффективными, или нежизнеспособными.
Тогда я обратился к LLM.

В этой статье я расскажу, как построил микросервис, с какими проблемами столкнулся, как их решил и что планирую дальше.
Данные и цели
Для описания данных мы решили не изобретать формат, а использовать существующий стандарт WITSML — отраслевой протокол обмена буровыми данными.
В процессе обсуждения с бизнесом список полей рос, и к извлечению данных добавился AI‑summary операций — выделение ключевых событий, на которые должен обратить внимание инженер по бурению.
Архитектура сервиса
Микросервис построен на Python с использованием FastAPI и httpx. CPU-bound задачи по работе с PDF выполняются через pdfplumber, а взаимодействие с LLM реализовано на OpenAI Python SDK. Файлы хранятся в S3-совместимом хранилище, а результаты возвращаются в формате JSON.
Сервис состоит из двух веток:
Extractor - извлекает структуру данных и параметры операций.
Summarizer - формирует AI-инсайты по результатам экстракции.
Так как сервис должен был интегрироваться с почтой и корпоративным дашбордом, я изначально закладывал асинхронную архитектуру: клиент получает 200 OK
, а обработка продолжается в фоне через пул процессов.
Интересно, что отказ от специализированных SDK для S3 (например, boto3) позволил легко перенести загрузочный модуль на Azure Blob Storage без переписывания кода - решение, которое оказалось неожиданно дальновидным.
Extractor: из PDF в структурированные данные
Экстрактор проходит несколько этапов:
Загрузка файла из S3.
Извлечение текстового слоя. Так как большинство рапортов сохраняются с уже встроенным OCR-слоем, мы не выполняем дополнительное распознавание изображений. Для извлечения текста и таблиц используется pdfplumber, который работает напрямую с текстовым слоем PDF. Эксперименты показали, что LLM воспринимает табличные документы точнее в HTML, чем в Markdown.
LLM-процессинг. Реализован базовый класс для запросов к модели с поддержкой function calling и structured output.
Рапорты приходят на разных языках (английский, русский, испанский), с различными форматами дат. Первым шагом выполняется region recognition - определение языка документа. Затем извлекаются даты отчёта и начала строительства скважины, нормализуемые в ISO-формат.
Основная сложность при работе с буровыми суточными сводками - это операции.
Таблицы в рапортах крайне непредсказуемы: в одних указывается начало или конец операции, в других только длительность, часто значения разбросаны между несколькими столбцами.
Наивная генерация через LLM приводит к галлюцинациям: модель «додумывает» поля, которых нет, либо некорректно интерпретирует дублирующиеся значения.
Чтобы этого избежать, я применил подход с использованием structured output. Суть подхода — не позволять модели «думать свободно», а направлять её рассуждение в контекст заранее заданной схемы данных. Фактически, схема (в моём случае — Pydantic‑модель) становится каркасом мышления модели: все ответы она должна укладывать в определённую структуру и типизацию.
class Operation(BaseModel):
dTimStart: str
dTimEnd: str
duration: float
depthReached: float | None
operationCode: str | None
comments: str | None
Эта схема определяет не только формат вывода, но и логику рассуждения модели. В следующих проектах я активно продолжаю развивать этот подход. Для некоторых груп полей LLM сначала получает контекст таблицы, затем список функций — «инструмент» с аргументами, с заданным описаннием.
Модель не может вернуть произвольный текст — она должна обосновать каждое поле. Такой подход уменьшил количество галлюцинаций в операциях примерно на 40-60%, особенно при сложных таблицах, где встречаются пустые поля, объединённые ячейки и условные обозначения.
Отдельная головная боль - глубина забоя.
Объяснить модели, что глубина инструмента ≠ глубина скважины, оказалось не тривиально. Некоторые операции (например, спуско-подъёмные) не меняют забой вовсе, а при цементировании он даже уменьшается. LLM изначально не понимает этих контекстных зависимостей, поэтому я встроил семантические правила валидации ответа прямо в схему рассуждения.
class ValidateOperation(BaseModel):
d_tim_start: str = Field()
d_tim_end: str = Field()
depth_reached: float = Field()
comments: str | None = Field(
description="Return all relevant data on the operation that you can find"
)
duration: float
non_productive_time_identified: bool
class ChainOfThought(BaseModel):
completeness: bool = Field(
description="The operations follow each other in sequence without time gaps and cover a period of 24 hours"
)
details: bool = Field(
description="Availability of additional columns in the operations table to be included in the `description` field"
)
class ValidateOperationsResponse(BaseModel):
chain_of_thought: ChainOfThought
operation: list[ValidateOperation]
Это не просто валидация - это управляемое рассуждение по схеме, где логика заложена на уровне данных.
Summarizer: AI-инсайты для инженеров
Summarizer формирует AI-инсайты: короткие выжимки из суточного отчёта, где инженер видит, что произошло важного за сутки - без чтения десятков страниц.
Модель не просто суммирует текст, а связывает инсайты с временными рамками и глубиной скважины. Это превращает статический отчёт в интерактивный слой данных для анализа. Чтобы не допустить ошибок вычислений, агрегирующие функции выполняются через tools, а не "чёрным ящиком" LLM.
Результаты и метрики

Сервис работает в продакшене уже три месяца.
Контроль затрат ведётся через дашборды OpenAI, а ключевая метрика - фидбэк пользователей.
Следующий этап - внедрение LLM-tracing через Langfuse и CI-job в пайплайн для автоматического контроля качества извлечения. Так же в планах развивать пайплайн экстракции, улучшать резолверы дат и глубин, добавить метрики качества.

За время работы над этим сервисом я понял, что LLM‑интеграция — это не магия, а инженерная дисциплина.
Чтобы модель работала надёжно, ей нужно дать структуру, контекст и границы рассуждения.
В этом смысле схема вывода ответа стала для меня способом не просто парсить отчёты, а формализовать мышление инженера.
Думаю, в ближайшие годы именно такие подходы сделают LLM‑системы по‑настоящему промышленными.