ДокументацияПрактические руководства (Cookbooks)Каскадное извлечение данных (SDE Cascade)

Каскадное извлечение данных (SDE Cascade)

Uses a 2-stage structured-data-extraction cascade (mini → verify → reasoning) to get most of the quality of a big reasoning model at a fraction of the cost.

Использование двухэтапного каскада извлечения структурированных данных (mini → верификация → reasoning), позволяющего получить качество большой рассуждающей модели за малую часть ее стоимости.

  • Обзор
    • Большие рассуждающие модели (reasoning models) отлично извлекают структурированные данные, но работают медленно и стоят дорого.
    • Маленькие модели стоят дешево, но совершают ошибки.
    • Каскад позволяет получить большую часть качества за малую часть стоимости.
    • Используемые модели и их стоимость ($ за 1 млн токенов, вход / выход; стандартные тарифы по состоянию на 15 сентября 2026 г.):
      • уровень 0 (mini): gpt-5.4-mini — $0.75 / $4.50
      • уровень 1 (reasoning): gpt-5.5 — $5.00 / $30.00 (примерно в 7 раз дороже mini)
      • верификатор: TypeSafe jev-1.12 — $0.042 / $0.00 (выходные токены бесплатны; опубликованные тарифы Jev)
  • Алгоритм
    1. Извлечение с помощью дешевой/маленькой модели.
    2. Верификация с помощью примитивов TypeSafe: набор вопросов типа да/нет для каждого поля («вопрос Noul»), например: «отсутствует ли это значение в источнике?», «взято ли оно из постороннего текста?», каждый из которых возвращает P(что-то не так).
    3. Эскалация к дорогой модели рассуждений, если срабатывает сигнал верификатора; в противном случае сохраняется дешевый ответ.
  • Содержание руководства
    • Подробный разбор одного реального примера от начала до конца, а затем демонстрация баланса затрат и качества на 100 промптах.
    • Примечание: оба уровня извлечения используют обычный текстовый режим OpenAI.
    • Мы не используем structured outputs, tool calls или json mode, потому что:
      • ошибка следования схеме — это не та ошибка, которой мы ждем от LLM (для этого легко создать синтетические данные);
      • если LLM не способна соблюсти схему, она, как правило, фундаментально запуталась во входных данных, поэтому принудительное декодирование по схеме не решает суть проблемы;
      • тем не менее мы рекомендуем попробовать эти режимы самостоятельно!

Настройка

  • Установите зависимости (клиент верификатора TypeSafe загружается из индекса пакетов TypeSafe):
BASH THEME={NULL} api.wedstack.ru/v1
pip install openai datasets jsonschema ipython "typesafe-sdk>=0.5.7" cooksafe --extra-index-url https://pypi.typesafe.ai/
  • Затем задайте переменные окружения OPENAI_API_KEY и TYPESAFE_API_KEY:
PYTHON THEME={NULL} api.wedstack.ru/v1
import json
import os
from pathlib import Path

import jsonschema
from cooksafe import JsonCache, make_playground_link
from datasets import load_dataset
from IPython.display import Markdown, display
from openai import OpenAI
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient

MINI = "gpt-5.4-mini"  # rung 0: cheap + fast
REASONING = "gpt-5.5"  # rung 1: strong, run with reasoning_effort="high"
TS_MODEL = "jev-1.12"  # the TypeSafe verifier model
FIRE_T = 0.7  # escalate if any per-field P(wrong) exceeds this; also the "<== FIRES" display marker

oai = OpenAI()

ts = TypeSafeClient(api_key=os.environ["TYPESAFE_API_KEY"], timeout=30.0)

Шаг 1: Данные

Мы выбираем датасет из HuggingFace под названием scrapegraphai:

PYTHON THEME={NULL} api.wedstack.ru/v1
SCRAPEGRAPHAI_REVISION = "4bb9fba1dff9181c5acdb60a5a26fea62fa54fe9"
row = load_dataset(
    "scrapegraphai/scrapegraphai-100k",
    revision=SCRAPEGRAPHAI_REVISION,
    split="train",
)[516]
schema = json.loads(row["schema"])
prompt = row["prompt"]
content = row["content"]

print(
    f"""
PROMPT
===========
{prompt}

SCHEMA
===========
{json.dumps(schema, indent=2)}

CONTENT
===========
{content}
""".strip()
)
TEXT EXPANDABLE THEME={NULL} api.wedstack.ru/v1
  • Данная строка представляет собой страницу календаря мероприятий NYU ("Fall 2024 Census Date"):
    • схема запрашивает только два поля: registration_open_date и description;
    • парсинг страницы захватил только навигацию календаря и стандартный шаблонный текст: даты регистрации и описания на странице нет;
    • обратите внимание: описание поля description в схеме даже содержит пример ("Registration opens for the fall semester");
  • корректный экстрактор должен отказаться придумывать значения полей, которых нет на странице;
  • давайте посмотрим, справится ли маленькая модель!

Шаг 2: Извлечение с помощью модели mini (текстовый режим)

  • примечание: gpt-5.4-mini ведет себя крайне стохастично на этом тексте — даже при temperature=0 она придумывает разное description практически при каждом запуске. Для обеспечения воспроизводимости мы жестко задали один типичный пример галлюцинации, который далее разбирается в ноутбуке (и который верификатор отмечает с P(wrong) > 0.8). В рабочем коде вы бы напрямую вызывали extract(MINI, prompt, schema, content, temperature=0).
PYTHON EXPANDABLE THEME={NULL} api.wedstack.ru/v1
PLAINTEXT api.wedstack.ru/v1
mini extraction:
 {
  "registration_open_date": "",
  "description": "Registration opens for the fall semester"
}

schema-valid: True
  • Запись соответствует схеме (строка выше выводит True), но при этом ошибочна:
    • registration_open_date оставлена пустой, что соответствует странице, на которой дата отсутствует;
    • однако description сфабриковано: страница нигде не описывает дату регистрации, поэтому mini придумывает правдоподобное значение. Она может повторить пример из схемы ("Registration opens for the fall semester") или написать что-то вроде "...was not found in the document";
    • валидация через JSON Schema не способна обнаружить такую ошибку. Дешевая модель генерирует уверенные, валидные по структуре галлюцинации, и выявление подобных ошибок — прямая задача семантического верификатора.

Шаг 3: Верификация с помощью TypeSafe

  • в качестве верификатора выступает TypeSafe; для каждого поля мы формируем вопрос Noul:
    • точный вопрос да/нет, сформулированный так, чтобы true означало наличие ошибки (необходимость эскалации);
  • TypeSafe возвращает калиброванное значение noul = P(true) по каждому вопросу за один вызов system_one;
  • набор вопросов включает:
    • одну общую оценку __overall__::judge («следует ли эскалировать эту запись?»). Мы вычисляем и выводим ее для сопоставления общей оценки записи с детальными оценками отдельных полей, однако решающий гейт на Шаге 4 ее не использует — решение об эскалации принимается на основе набора полей;
    • набор проверок по каждому полю:
      • непустые поля проходят полный спектр проверок;
      • пустые поля (null / "" / []) проверяются только вопросом absence_wrong;
    • (полный конвейер также включает проверку spurious для контейнеров и общую оценку difficulty; здесь они опущены для наглядности);
  • Подход TypeSafe: декомпозиция
    • Обратите внимание, как все программно декомпозировано — в этом заключается подход TypeSafe.
    • Декомпозиция максимизирует интеллектуальный потенциал каждого промпта и делает алгоритм гибко настраиваемым и интерпретируемым.
    • this is the way
PYTHON EXPANDABLE THEME={NULL} api.wedstack.ru/v1

Запуск полного набора проверок на извлечении модели mini

PYTHON THEME={NULL} api.wedstack.ru/v1
checks = verify(mini_record)
playground_link = checks.pop("playground_link")
display(
    Markdown(
        f"🔗 [Open this verification in the TypeSafe playground]({playground_link})"
    )
)

print(f"{'qid':<40}{'P(wrong)':>9}")
print("-" * 50)
for fld, p in sorted(checks.items(), key=lambda c: -c[-1]):
    flag = "  <== FIRES" if p > FIRE_T else ""
    print(f"{fld:<40}{p:>9.2f}{flag}")
PLAINTEXT api.wedstack.ru/v1
qid                                      P(wrong)
--------------------------------------------------
description::hallucinated                    0.95  <== FIRES
description::off_target                      0.85  <== FIRES
description::unreasonable                    0.58
__overall__::judge                           0.56
description::incomplete                      0.16
registration_open_date::absence_wrong        0.14
description::format_violation                0.10
description::name_desc_mismatch              0.08
description::type_mismatch                   0.02

Открыть эту верификацию в TypeSafe Playground →

  • TypeSafe фокусирует сигнал именно на тех полях, где действительно есть ошибки.
  • Результаты калиброваны: высокие значения для ошибочного поля, низкие для правильного поля, средние для поля, которое выглядит сомнительно, но не является явно ошибочным.
  • В этом и заключается преимущество типизированного верификатора над прямолинейной общей оценкой «хороша ли запись целиком?».

Шаг 4: Гейт эскалации

  • теперь настраиваем гейт по условию any_flag: эскалировать, если хотя бы один флаг поля превышает FIRE_T (0.7, заданный выше и отмеченный маркером <== FIRES на Шаге 3);
  • это логика типа max (эскалация при срабатывании любого флага), а не среднее значение, поэтому одного уверенного тревожного сигнала достаточно, чтобы запустить эскалацию, вместо его размытия усреднением.
PYTHON THEME={NULL} api.wedstack.ru/v1
# any_flag is a per-field gate: the holistic __overall__ head is shown above but not part of it
fired = {
    qid: p
    for qid, p in checks.items()
    if not qid.startswith("__overall__") and p > FIRE_T
}
escalate = bool(fired)

print(
    f"any_flag gate (threshold {FIRE_T}): {'ESCALATE' if escalate else 'ACCEPT cheap result'}"
)
for qid, p in sorted(fired.items(), key=lambda c: -c[1]):
    print(f"  fired: {qid}  (P={p:.2f})")
PLAINTEXT api.wedstack.ru/v1
any_flag gate (threshold 0.7): ESCALATE
  fired: description::hallucinated  (P=0.95)
  fired: description::off_target  (P=0.85)

Шаг 5: Эскалация на модель рассуждений (reasoning model)

Поскольку сигнал сработал, мы подключаем сильную модель (gpt-5.5, reasoning_effort="high"):

PYTHON THEME={NULL} api.wedstack.ru/v1
final_record = (
    extract(REASONING, prompt, schema, content, reasoning_effort="high")
    if escalate
    else mini_record
)

print("mini      :", json.dumps(mini_record))
print("reasoning :", json.dumps(final_record))
print("\nfield-level diff (mini -> final):")
for name in mini_record:
    if mini_record[name] != final_record.get(name):
        print(f"  {name}: {mini_record[name]!r}  ->  {final_record.get(name)!r}")
PLAINTEXT api.wedstack.ru/v1
mini      : {"registration_open_date": "", "description": "Registration opens for the fall semester"}
reasoning : {"description": "", "registration_open_date": ""}

field-level diff (mini -> final):
  description: 'Registration opens for the fall semester'  ->  ''
  • Результат оптимизации
    • Рассуждающая модель отбрасывает выдуманное поле description, возвращая пустую строку "".
    • Она распознала, что на странице отсутствует информация о дате регистрации, и отказалась ее придумывать.
    • Каскад превратил уверенную, валидную по схеме галлюцинацию в честное пустое поле.
    • При этом затраты на мощную модель были совершены только потому, что верификатор прямо указал на ошибку.

Шаг 6: Как это выглядит на 100 промптах

  • Это внутренние результаты TypeSafe, полученные по описанной выше методике:
    • тот же цикл извлечение → верификация → эскалация, связка gpt-5.4-mini → gpt-5.5-reasoning, гейт any_flag по полям, протестированные на 100 промптах scrapegraphai;
    • для каждого примера дешевое извлечение оценивается в TypeSafe; порог срабатывания гейта варьируется от 0 до 1, и каждая конфигурация наносится на график в координатах (стоимость, качество);
    • график представляет собой исторический снимок; затраты не пересчитывались по текущим тарифам модели Jev.
internal results: cost/quality frontier over 100 prompts
  • Как читать этот график:
    • черные ромбы — четыре модели, запущенные самостоятельно (стоимость растет по мере возможностей; самая мощная, gpt-5.5-reasoning, находится в верхнем правом углу с качеством ≈0.81 при стоимости ≈$0.10 за извлечение);
    • синие точки — каскад при различных порогах срабатывания гейта; пунктирная линия обозначает границу Парето (pareto frontier);
    • граница каскада проходит выше и левее каждой отдельной модели: варьирование порога позволяет получить большую часть качества лучшей модели за малую долю ее стоимости;
    • дешевый уровень берет на себя простые примеры практически бесплатно, и лишь отмеченные сомнительные записи оплачивают работу модели рассуждений.

Приложение A: Что делает сигнал верификатора качественным

  • эффективность каскада напрямую определяется качеством его верификатора; вот что отличает полезный сигнал от бесполезного:
    • Узкий и привязанный к источнику (grounded).
      • четкий проверяемый вопрос да/нет по одному полю относительно источника (например, «отсутствует ли это значение в источнике?»), а не размытый вопрос «хорошо ли выполнено извлечение?»;
      • неконкретные вопросы дают размытые, некалиброванные оценки.
    • Ошибка = TRUE, с явными критериями.
      • формулируйте каждый вопрос так, чтобы случай необходимости эскалации соответствовал ответу true, и явно описывайте значения true/false.
    • По полям с последующей агрегацией через max.
      • проверка по отдельным полям локализует ошибку и сохраняет четкость сигнала;
      • функция max («сработал хотя бы один флаг») гарантирует эскалацию при уверенном обнаружении одной ошибки вместо ее сглаживания усреднением.
    • Независимый и недорогой.
      • специализированный верификатор (в данном случае TypeSafe), оценивающий результат со стороны, компенсирует слепые зоны экстрактора;
      • верификатор обязан быть дешевым, иначе вся финансовая выгода каскада сойдет на нет.
    • Разделяющий и калиброванный.
      • качественный сигнал дает высокие значения на реальных ошибках и низкие на корректных данных, благодаря чему один фиксированный порог четко разделяет приемлемый результат и эскалацию;
      • именно эта разделительная способность сдвигает кривую Парето вверх и влево.