Привет! Меня зовут Стас, я занимаюсь 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.

Сервис состоит из двух веток:

  1. Extractor - извлекает структуру данных и параметры операций.

  2. Summarizer - формирует AI-инсайты по результатам экстракции.

Так как сервис должен был интегрироваться с почтой и корпоративным дашбордом, я изначально закладывал асинхронную архитектуру: клиент получает 200 OK, а обработка продолжается в фоне через пул процессов.

Интересно, что отказ от специализированных SDK для S3 (например, boto3) позволил легко перенести загрузочный модуль на Azure Blob Storage без переписывания кода - решение, которое оказалось неожиданно дальновидным.

Extractor: из PDF в структурированные данные

Экстрактор проходит несколько этапов:

  1. Загрузка файла из S3.

  2. Извлечение текстового слоя. Так как большинство рапортов сохраняются с уже встроенным OCR-слоем, мы не выполняем дополнительное распознавание изображений. Для извлечения текста и таблиц используется pdfplumber, который работает напрямую с текстовым слоем PDF. Эксперименты показали, что LLM воспринимает табличные документы точнее в HTML, чем в Markdown.

  3. 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.

Результаты и метрики

Дашборд с operations и summary
Дашборд с operations и summary

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

Полный вид дашборда с виджетом AI Insights
Полный вид дашборда с виджетом AI Insights

За время работы над этим сервисом я понял, что LLM‑интеграция — это не магия, а инженерная дисциплина.

Чтобы модель работала надёжно, ей нужно дать структуру, контекст и границы рассуждения.

В этом смысле схема вывода ответа стала для меня способом не просто парсить отчёты, а формализовать мышление инженера.

Думаю, в ближайшие годы именно такие подходы сделают LLM‑системы по‑настоящему промышленными.

Комментарии (0)