0Pricing
AI Engineering Academy · Урок

Проверка и повторная обработка некорректного вывода

Реализуйте уровень проверки, сопоставляющий извлечённые данные с бизнес-правилами, автоматически повторяющий попытку с корректирующей обратной связью при сбое проверки и регистрирующий типичные причины ошибок.

«Проверка и повторная обработка некорректного вывода» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.

Зачем проверять результаты LLM

Даже при структурированном выводе и схемах Pydantic извлечение с помощью LLM может давать результаты, синтаксически корректные, но семантически неверные. Оценка уверенности 1.5 (за пределами диапазона 0–1), цена -99.99, строка даты, которую невозможно разобрать, или номер телефона с буквами — всё это проходит разбор JSON, но нарушает ваши бизнес-правила.

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

Уровни проверки

Надёжная система проверки результатов работает на нескольких уровнях:

  1. Проверка схемы (Pydantic): правильные типы полей, наличие обязательных полей, соответствие перечислений допустимым значениям — выполняется автоматически при структурированном выводе
  2. Проверка формата: номера телефонов соответствуют regex, адреса электронной почты корректны, даты можно разобрать, суммы находятся в реалистичных диапазонах
  3. Проверка бизнес-логики: итоговая сумма счёта равна сумме позиций, дата окончания следует за датой начала, количество является положительным целым числом
  4. Проверка взаимосвязанных полей: значение одного поля зависит от значения другого (например, скидка не может превышать 100 процентов)
  5. Семантическая проверка: извлечённое название компании совпадает с известной компанией в вашей базе данных

Валидаторы Pydantic для проверки формата

Декоратор Pydantic field_validator позволяет добавить собственную логику проверки, которая выполняется при создании экземпляра модели. Используйте его для проверок на уровне формата: проверки номеров телефонов и адресов электронной почты с помощью regex, разбора дат и проверки диапазонов числовых полей.

from pydantic import BaseModel, Field, field_validator
from typing import Optional
import re
from datetime import datetime

class ExtractedInvoice(BaseModel):
    vendor: str
    invoice_number: Optional[str]
    amount: float = Field(gt=0, description='Must be positive')
    currency: str = Field(min_length=3, max_length=3)
    invoice_date: str

    @field_validator('currency')
    @classmethod
    def currency_must_be_uppercase(cls, v):
        return v.upper()

    @field_validator('invoice_date')
    @classmethod
    def parse_date(cls, v):
        # Try to parse common date formats
        for fmt in ('%Y-%m-%d', '%d/%m/%Y', '%m/%d/%Y', '%B %d, %Y'):
            try:
                datetime.strptime(v, fmt)
                return v
            except ValueError:
                continue
        raise ValueError(f'Cannot parse date: {v}')

    @field_validator('amount')
    @classmethod
    def reasonable_amount(cls, v):
        if v > 10_000_000:
            raise ValueError(f'Amount {v} seems unreasonably large. Flag for review.')
        return round(v, 2)

Шаблон повторной попытки с корректирующей обратной связью

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

import openai
from pydantic import BaseModel, ValidationError, Field

client = openai.OpenAI()

class PriceExtraction(BaseModel):
    product: str
    price_usd: float = Field(gt=0, lt=100000)
    quantity: int = Field(ge=1)

def extract_with_retry(text: str, max_retries: int = 3) -> PriceExtraction:
    messages = [
        {'role': 'system', 'content': 'Extract product pricing information.'},
        {'role': 'user', 'content': text}
    ]

    for attempt in range(max_retries):
        result = client.beta.chat.completions.parse(
            model='gpt-4o-mini',
            messages=messages,
            response_format=PriceExtraction
        )
        msg = result.choices[0].message
        if msg.refusal:
            raise ValueError(f'Model refused: {msg.refusal}')

        try:
            return msg.parsed  # Pydantic validates on parse
        except ValidationError as e:
            if attempt == max_retries - 1:
                raise
            # Add corrective feedback for the next attempt
            messages.append({'role': 'assistant', 'content': msg.content})
            messages.append({'role': 'user', 'content': f'The previous extraction failed validation: {e}\nPlease correct and try again.'})
            print(f'Attempt {attempt+1} failed. Retrying with feedback...')

Проверка бизнес-логики

Проверка бизнес-логики контролирует свойства, которые охватывают несколько полей или зависят от внешних источников данных. model_validator Pydantic выполняется после всех валидаторов отдельных полей и имеет доступ к полностью заполненной модели, поэтому это подходящее место для проверок взаимосвязанных полей.

from pydantic import BaseModel, Field, model_validator
from typing import List

class LineItem(BaseModel):
    description: str
    quantity: int = Field(ge=1)
    unit_price: float = Field(ge=0)
    line_total: float

    @model_validator(mode='after')
    def check_line_total(self):
        expected = round(self.quantity * self.unit_price, 2)
        actual = round(self.line_total, 2)
        if abs(expected - actual) > 0.02:  # Allow 2-cent rounding tolerance
            raise ValueError(
                f'Line total {actual} does not match quantity*price={expected}'
            )
        return self

class Invoice(BaseModel):
    line_items: List[LineItem]
    subtotal: float
    tax: float
    total: float

    @model_validator(mode='after')
    def check_invoice_total(self):
        expected_total = round(self.subtotal + self.tax, 2)
        if abs(expected_total - round(self.total, 2)) > 0.02:
            raise ValueError(
                f'Invoice total {self.total} != subtotal+tax ({expected_total})'
            )
        return self

Журналирование сбоев проверки

Каждый сбой проверки указывает, где именно конвейер даёт сбой. Записывайте для каждого сбоя: входной текст (или его хеш для защиты конфиденциальности), извлечённый результат, конкретную ошибку проверки и номер попытки. Обобщайте эти записи, чтобы выявлять систематические закономерности: постоянно ли модель ошибается в одном поле? Есть ли класс документов, вызывающий сбои? Эти данные помогают целенаправленно улучшать запросы.

import logging
from pydantic import ValidationError

logger = logging.getLogger(__name__)

def extract_with_logging(text: str, doc_id: str) -> dict:
    result = None
    for attempt in range(3):
        try:
            result = run_extraction(text)  # Your extraction function
            logger.info('Extraction success', extra={
                'doc_id': doc_id,
                'attempt': attempt + 1
            })
            return result
        except ValidationError as e:
            logger.warning('Validation failure', extra={
                'doc_id': doc_id,
                'attempt': attempt + 1,
                'errors': e.errors(),
                'error_count': len(e.errors())
            })
    # All retries failed
    logger.error('Extraction failed after max retries', extra={'doc_id': doc_id})
    return {'error': 'extraction_failed', 'doc_id': doc_id}

def run_extraction(text):
    pass  # Placeholder for actual extraction logic

Оценки уверенности и пороговые значения

Добавьте в схему извлечения поле confidence и попросите модель оценивать уверенность в каждом извлечённом значении от 0 до 1. Затем применяйте бизнес-правила с учётом уверенности: результаты с высокой уверенностью сразу передаются в базу данных, результаты со средней уверенностью помечаются для выборочной проверки, а результаты с низкой уверенностью направляются в очередь на проверку человеком.

Такой вероятностный подход гораздо практичнее, чем требование стопроцентной точности от LLM: вы проектируете конвейер так, чтобы он корректно обрабатывал неопределённость, а не делал вид, что её не существует.

from pydantic import BaseModel, Field
from typing import Optional

class ExtractedWithConfidence(BaseModel):
    value: Optional[str]
    confidence: float = Field(ge=0.0, le=1.0)
    reason: Optional[str] = None  # Why confidence is low, if below threshold

class DocumentExtraction(BaseModel):
    vendor_name: ExtractedWithConfidence
    invoice_amount: ExtractedWithConfidence
    due_date: ExtractedWithConfidence

def route_by_confidence(extraction: DocumentExtraction, threshold=0.85):
    low_confidence_fields = []
    for field_name, field_val in extraction.model_dump().items():
        if isinstance(field_val, dict) and field_val.get('confidence', 1.0) < threshold:
            low_confidence_fields.append(field_name)

    if not low_confidence_fields:
        return 'auto_approve'
    elif len(low_confidence_fields) > 2:
        return 'human_review'
    else:
        return f'spot_check: {low_confidence_fields}'

Стратегии отката при неудаче повторных попыток

Когда все повторные попытки исчерпаны, а проверка по-прежнему завершается ошибкой, нужна стратегия отката. В порядке предпочтения доступны такие варианты:

  1. Частичный результат: вернуть поля, которые прошли проверку, а поля с ошибками отметить значением null
  2. Очередь на проверку человеком: добавить документ в очередь для ручной проверки, особенно если документ имеет высокую ценность
  3. Извлечение с меньшей детализацией: перейти к более простой схеме, запрашивающей меньше полей, пожертвовав структурированностью ради надёжности
  4. Хранение исходного текста: сохранить исходный текст с метаданными, чтобы повторно обработать его позднее после улучшения конвейера извлечения

Никогда не удаляйте документ молча. Всегда записывайте сведения о сбое и обеспечивайте возможность вернуться к этому документу.

Семантическая проверка по внешним данным

Некоторые правила проверки требуют обращения к внешним данным, которое невозможно выполнить внутри валидаторов Pydantic. Например, нужно проверить, есть ли извлечённое название компании в вашей CRM или существует ли извлечённый SKU товара в вашем складском учёте. Такие проверки следует выполнять на этапе проверки после извлечения, который запускается после успешного прохождения проверки Pydantic.

from typing import Optional

# Simulated external data source
KNOWN_VENDORS = {'acme corp', 'techsupplies inc', 'globex corporation'}

def validate_against_crm(extraction: dict) -> dict:
    vendor = extraction.get('vendor', '').lower()
    warnings = []

    if vendor and vendor not in KNOWN_VENDORS:
        warnings.append({
            'field': 'vendor',
            'issue': f'Vendor "{vendor}" not found in CRM',
            'severity': 'warning'
        })
        # Optionally suggest closest match
        # from difflib import get_close_matches
        # matches = get_close_matches(vendor, KNOWN_VENDORS, n=1, cutoff=0.8)
        # if matches: warnings[-1]['suggestion'] = matches[0]

    return {
        'extraction': extraction,
        'warnings': warnings,
        'requires_review': len(warnings) > 0
    }

print('Semantic validation pattern defined')

Тестирование конвейера проверки

Для логики проверки нужен собственный набор тестов, отдельный от тестов извлечения. Напишите модульные тесты, которые передают вашим валидаторам заведомо некорректные результаты и проверяют, что возникают правильные ошибки. Проверьте граничные случаи: суммы на граничных значениях, даты в необычных форматах, поля с неожиданными пробелами и поля, содержащие числовые строки вместо чисел.

Этот набор тестов проверки выполняется без каких-либо обращений к API, поэтому его быстро и недорого запускать после каждого изменения кода. Кроме того, это лучшая документация для ваших правил проверки: тестовые случаи явно описывают каждый формат и каждое бизнес-ограничение, которые применяет ваш конвейер.

from pydantic import ValidationError

def test_extraction_validation():
    # Test cases: (input_data, should_pass)
    test_cases = [
        ({'vendor': 'ACME', 'amount': 150.00, 'currency': 'USD', 'invoice_date': '2025-01-15'}, True),
        ({'vendor': 'ACME', 'amount': -50.00, 'currency': 'USD', 'invoice_date': '2025-01-15'}, False),  # negative
        ({'vendor': 'ACME', 'amount': 150.00, 'currency': 'EURO', 'invoice_date': '2025-01-15'}, False), # 4-char
        ({'vendor': 'ACME', 'amount': 150.00, 'currency': 'USD', 'invoice_date': 'yesterday'}, False),   # bad date
    ]
    passed = failed = 0
    for data, should_pass in test_cases:
        try:
            # ExtractedInvoice(**data)  # Your Pydantic model
            if should_pass:
                passed += 1
            else:
                print(f'MISSED: Should have failed for {data}')
                failed += 1
        except (ValidationError, ValueError):
            if not should_pass:
                passed += 1
            else:
                print(f'UNEXPECTED FAIL for {data}')
                failed += 1
    print(f'Tests: {passed} passed, {failed} failed')

test_extraction_validation()

Анализ типичных сбоев и настройка промптов

После запуска конвейера извлечения на выборке реальных документов и анализа сбоев проверки вы, скорее всего, обнаружите, что 80% сбоев вызваны небольшим числом первопричин. Типичные причины: модель постоянно неправильно форматирует даты из определённой локали, путает сумму налога с итоговой суммой или выдаёт номера телефонов без дефисов, хотя валидатор ожидает дефисы.

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

Быстрая проверка

Проверьте, насколько хорошо вы усвоили концепции разработки ИИ из этого урока.

Итоги урока

В этом уроке вы узнали, что проверка выполняется на нескольких уровнях — схема, формат, бизнес-логика и семантика, — и для каждого требуются свои подходы к реализации; повторная попытка с корректирующей обратной связью предоставляет модели конкретный контекст ошибки, чтобы эффективно исправить результат; а оценки уверенности позволяют вероятностно направлять результаты в очереди автоматического одобрения, выборочной проверки или проверки человеком в зависимости от уверенности в извлечении. Вы завершили курс «Структурированный вывод и режим JSON». Далее мы рассмотрим векторные представления — основу каждой системы RAG.

Часто задаваемые вопросы

Урок «Проверка и повторная обработка некорректного вывода» бесплатный?

Да — полный текст урока «Проверка и повторная обработка некорректного вывода» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.

Чему я научусь в уроке «Проверка и повторная обработка некорректного вывода»?

Реализуйте уровень проверки, сопоставляющий извлечённые данные с бизнес-правилами, автоматически повторяющий попытку с корректирующей обратной связью при сбое проверки и регистрирующий типичные причи… Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Engineering Academy?

Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Проверка и повторная обработка некорректного вывода»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Engineering Academy?

Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Режим JSON и response_format
  2. Структурированный вывод с Pydantic
  3. Извлечение данных из неструктурированного текста
  4. Проверка и повторная обработка некорректного вывода
← Назад к AI Engineering Academy