Эволюция схем и обратная совместимость
Управляйте несовместимыми изменениями схем в длительно работающих конвейерах извлечения: версионируйте схемы, переносите исторические извлечения и выполняйте параллельную проверку во время переходов.
«Эволюция схем и обратная совместимость» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
Почему схемы со временем меняются
Схемы извлечения не остаются неизменными. Меняются бизнес-требования, появляются новые типы документов, и вы обнаруживаете поля, которые стоило извлекать с самого начала. Изменение схемы в работающем конвейере создаёт проблему обратной совместимости: существующие извлечённые записи используют старую схему, а новые — новую. Безопасное управление этим переходом и называется развитием схемы.
Версионирование схем
Назначайте каждой схеме номер версии и сохраняйте его вместе с каждой извлечённой записью. При изменении схемы увеличивайте номер версии. Это позволяет выбирать записи по версии схемы, выполнять миграции старых записей и поддерживать отдельную логику проверки для каждой версии. Для этого достаточно простого строкового поля schema_version в каждой выходной модели.
from pydantic import BaseModel
from typing import Literal
class InvoiceV1(BaseModel):
schema_version: Literal['1.0'] = '1.0'
vendor: str
total_amount: float
class InvoiceV2(BaseModel):
schema_version: Literal['2.0'] = '2.0'
vendor: str
vendor_tax_id: str | None = None # new field
total_amount: float
currency: str = 'USD' # new field with defaultДополняющие и несовместимые изменения
Дополняющие изменения безопасны: добавление поля Optional или поля со значением по умолчанию не нарушает работу старого кода извлечения и старых записей. Несовместимые изменения рискованны: переименование поля, изменение типа со строки на целое число или удаление поля нарушит работу последующих потребителей. Всегда отдавайте предпочтение дополняющим изменениям. Если несовместимое изменение неизбежно, создайте новую основную версию схемы и выполните контролируемую миграцию.
# Safe: additive change - add optional field
class ProductV2(BaseModel):
name: str
price: float
sku: str | None = None # NEW optional field - backward safe
category: str = 'general' # NEW with default - backward safe
# Risky: breaking change - rename or retype
# class ProductV2(BaseModel):
# product_name: str # RENAMED from name - breaks consumers
# price_cents: int # RETYPED from float - breaks dataХранение версии схемы в базе данных
Добавьте версию схемы в таблицу результатов извлечения, чтобы всегда знать, какая версия создала каждую запись. Распространённый подход — столбец jsonb с полными извлечёнными данными и текстовый столбец schema_version. Это позволяет писать запросы с учётом версии и выборочно переносить старые записи в периоды низкой нагрузки.
-- PostgreSQL table design
CREATE TABLE extractions (
doc_id TEXT PRIMARY KEY,
schema_version TEXT NOT NULL,
extracted_data JSONB NOT NULL,
extracted_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_schema_version ON extractions(schema_version);
-- Query old records needing migration
SELECT doc_id, extracted_data
FROM extractions
WHERE schema_version = '1.0'
LIMIT 1000;Написание скриптов миграции
Для каждого перехода между версиями схемы пишите скрипт миграции, который читает старые записи, преобразует их в новый формат и записывает обратно с новой версией. Выполняйте миграции небольшими партиями в транзакциях, чтобы сбой не оставил базу данных в частично перенесённом состоянии. Старую схему всегда следует сохранять доступной, пока завершение миграции не будет проверено.
import asyncpg
import json
async def migrate_v1_to_v2(pool, batch_size=100):
async with pool.acquire() as conn:
rows = await conn.fetch(
'SELECT doc_id, extracted_data FROM extractions WHERE schema_version=$1 LIMIT $2',
'1.0', batch_size
)
for row in rows:
old = row['extracted_data']
new_data = {
'schema_version': '2.0',
'vendor': old['vendor'],
'vendor_tax_id': None, # unknown for old records
'total_amount': old['total_amount'],
'currency': 'USD' # assume USD for old records
}
await conn.execute(
'UPDATE extractions SET extracted_data=$1, schema_version=$2 WHERE doc_id=$3',
json.dumps(new_data), '2.0', row['doc_id']
)Параллельная проверка во время переходов
Во время миграции схемы выполняйте параллельную проверку: одновременно извлекайте данные по старой и новой схеме для выборки входящих документов. Сравнивайте результаты, чтобы убедиться, что новая схема извлекает всё, что извлекала старая, а также новые поля. Отказывайтесь от старой схемы только после того, как параллельная проверка покажет стабильное соответствие на статистически значимой выборке.
async def parallel_validate(text: str) -> dict:
v1_result, v2_result = await asyncio.gather(
extract_with_schema(text, InvoiceV1),
extract_with_schema(text, InvoiceV2)
)
discrepancy = (
v1_result.vendor != v2_result.vendor or
abs(v1_result.total_amount - v2_result.total_amount) > 0.01
)
if discrepancy:
log_discrepancy(text, v1_result, v2_result)
return {'v1': v1_result, 'v2': v2_result, 'discrepancy': discrepancy}Переключатели функций для внедрения схемы
Используйте переключатели функций, чтобы управлять моментом перехода конвейера со старой схемы на новую. Это позволяет постепенно подключать новую схему для определённой доли трафика, отслеживать частоту ошибок и мгновенно отменять изменения при проблемах — без повторного развёртывания кода. Подойдут как сервисы переключателей функций вроде LaunchDarkly, так и простая строка в базе данных.
import os
def get_active_schema():
version = os.environ.get('EXTRACTION_SCHEMA_VERSION', '1.0')
schemas = {
'1.0': InvoiceV1,
'2.0': InvoiceV2,
}
return schemas.get(version, InvoiceV1)
async def extract_document(text: str):
SchemaClass = get_active_schema()
return await extract_with_schema(text, SchemaClass)Совместимость потребителей с объединёнными типами
Последующие потребители, читающие извлечённые данные, должны корректно обрабатывать несколько версий схемы. Используйте в коде потребителя размеченное объединение, которое выбирает правильную логику разбора на основе поля schema_version. Это надёжнее, чем цепочки условных операторов if-else, и проще расширяется при появлении версии 3.
from pydantic import BaseModel
from typing import Union, Annotated
from typing import Literal
def parse_extraction(raw: dict) -> Union[InvoiceV1, InvoiceV2]:
version = raw.get('schema_version', '1.0')
if version == '1.0':
return InvoiceV1(**raw)
elif version == '2.0':
return InvoiceV2(**raw)
else:
raise ValueError(f'Unknown schema version: {version}')Проверка изменений схемы перед развёртыванием
Перед развёртыванием новой схемы запустите её на всём наборе регрессионных проверок — подготовленной коллекции типичных документов с известными ожидаемыми результатами. Сравните показатели F1 для каждого поля у старой и новой схемы. Снижение F1 для любого поля означает, что описание новой схемы ввело модель в заблуждение: исправьте описание поля до выпуска.
def eval_schema_on_test_set(test_cases: list, SchemaClass) -> dict:
field_f1 = {}
for case in test_cases:
result = extract_with_schema(case['text'], SchemaClass)
for field in case['expected']:
expected = case['expected'][field]
actual = getattr(result, field, None)
# Update precision/recall counters
update_metrics(field_f1, field, expected, actual)
return {k: compute_f1(v) for k, v in field_f1.items()}Обработка устаревания схемы
Когда версия схемы больше не используется для новых извлечений, её можно объявить устаревшей. Устаревание означает: прекратить принимать новые записи этой версии, сохранить возможность чтения старых записей и назначить дату окончательного вывода, к которой старые записи будут перенесены или заархивированы. Зафиксируйте устаревание в журнале изменений, чтобы все потребители знали о необходимости обновить код разбора.
DEPRECATED_VERSIONS = {'1.0'}
SUNSET_DATE = '2026-09-01'
def warn_if_deprecated(version: str):
if version in DEPRECATED_VERSIONS:
import warnings
warnings.warn(
f'Schema version {version} is deprecated. '
f'It will be removed after {SUNSET_DATE}. '
'Migrate consumers to version 2.0.',
DeprecationWarning,
stacklevel=2
)Журнал изменений и информирование
Каждое изменение схемы должно сопровождаться записью в журнале изменений с описанием того, что изменилось, почему это произошло, как выполнить миграцию и каковы ожидаемые последствия. Перед развёртыванием изменения отправьте записи журнала всем командам, использующим извлечённые данные. Многие проблемы при миграции схем возникают не из-за технических сбоев, а потому, что потребителей не предупредили о предстоящем изменении.
# CHANGELOG.md entry format:
# ## Schema v2.0 (2026-07-01)
# ### Changes
# - ADDED: vendor_tax_id (Optional[str]) - VAT/EIN extracted from header
# - ADDED: currency (str, default='USD') - detected from symbol/code
# ### Migration
# Run: python scripts/migrate_v1_to_v2.py --batch-size=500
# ### Consumers
# - billing-service: update parse_extraction() to handle v2
# - audit-service: query now supports currency filterБыстрая проверка
Проверьте, насколько хорошо вы поняли развитие схем и обратную совместимость в конвейерах извлечения.
Итоги урока
В этом уроке вы узнали, что версионирование схем сохраняет идентификатор версии вместе с каждой извлечённой записью и позволяет выборочно выполнять миграцию, дополняющие изменения безопасны, тогда как переименование или изменение типа полей требует тщательной миграции, а параллельная проверка позволяет проверить новую схему до отказа от старой. Далее мы измерим задержку LLM с помощью метрик TTFT и TPOT.
Часто задаваемые вопросы
Урок «Эволюция схем и обратная совместимость» бесплатный?
Да — полный текст урока «Эволюция схем и обратная совместимость» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 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 — локальная установка не требуется.
Все уроки этого курса
- Instructor: типизированное извлечение с Pydantic
- Обработка частичных и отсутствующих данных
- Пакетная обработка с асинхронностью и очередями
- Эволюция схем и обратная совместимость