Проверка и повторная обработка некорректного вывода
Реализуйте уровень проверки, сопоставляющий извлечённые данные с бизнес-правилами, автоматически повторяющий попытку с корректирующей обратной связью при сбое проверки и регистрирующий типичные причины ошибок.
«Проверка и повторная обработка некорректного вывода» — бесплатный урок 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 извлекает данные, а ваш проверяющий код принимает или отклоняет их.
Уровни проверки
Надёжная система проверки результатов работает на нескольких уровнях:
- Проверка схемы (Pydantic): правильные типы полей, наличие обязательных полей, соответствие перечислений допустимым значениям — выполняется автоматически при структурированном выводе
- Проверка формата: номера телефонов соответствуют regex, адреса электронной почты корректны, даты можно разобрать, суммы находятся в реалистичных диапазонах
- Проверка бизнес-логики: итоговая сумма счёта равна сумме позиций, дата окончания следует за датой начала, количество является положительным целым числом
- Проверка взаимосвязанных полей: значение одного поля зависит от значения другого (например, скидка не может превышать 100 процентов)
- Семантическая проверка: извлечённое название компании совпадает с известной компанией в вашей базе данных
Валидаторы 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}'Стратегии отката при неудаче повторных попыток
Когда все повторные попытки исчерпаны, а проверка по-прежнему завершается ошибкой, нужна стратегия отката. В порядке предпочтения доступны такие варианты:
- Частичный результат: вернуть поля, которые прошли проверку, а поля с ошибками отметить значением null
- Очередь на проверку человеком: добавить документ в очередь для ручной проверки, особенно если документ имеет высокую ценность
- Извлечение с меньшей детализацией: перейти к более простой схеме, запрашивающей меньше полей, пожертвовав структурированностью ради надёжности
- Хранение исходного текста: сохранить исходный текст с метаданными, чтобы повторно обработать его позднее после улучшения конвейера извлечения
Никогда не удаляйте документ молча. Всегда записывайте сведения о сбое и обеспечивайте возможность вернуться к этому документу.
Семантическая проверка по внешним данным
Некоторые правила проверки требуют обращения к внешним данным, которое невозможно выполнить внутри валидаторов 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 — локальная установка не требуется.
Все уроки этого курса
- Режим JSON и response_format
- Структурированный вывод с Pydantic
- Извлечение данных из неструктурированного текста
- Проверка и повторная обработка некорректного вывода