AI Prompt Engineering · Урок

Обработка ошибок в цепочках запросов

Проверяйте промежуточные результаты и восстанавливайтесь после сбоев цепочки

Урок 4 из 413 шагов

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

Почему цепочки дают сбои

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

Распространённые сценарии сбоев:

  • Модель возвращает некорректный JSON, который невозможно разобрать
  • Модель неправильно понимает задачу и выдаёт результат с неверным смыслом
  • Ограничения частоты запросов или тайм-ауты API приводят к сбоям шагов
  • В длинной цепочке превышается размер контекстного окна
  • Модель выдумывает данные, которые последующие шаги принимают за факты

Проверка результата после каждого шага

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

import json

def validate_json_output(raw_text, required_fields):
    'Parse and validate that required fields are present in model output.'
    try:
        data = json.loads(raw_text.strip())
    except json.JSONDecodeError as e:
        raise ValueError(f'Invalid JSON: {e}. Raw: {raw_text[:200]}')

    missing = [f for f in required_fields if f not in data]
    if missing:
        raise ValueError(f'Missing required fields: {missing}. Got: {list(data.keys())}')

    return data

# Usage after a chain step
raw = '{"sentiment": "positive", "priority": "high"}'
validated = validate_json_output(raw, required_fields=['sentiment', 'priority'])
print('Valid:', validated)

Повторные попытки при временных сбоях

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

import anthropic, time

client = anthropic.Anthropic(api_key='YOUR_API_KEY')

def call_with_retry(prompt, max_retries=3, base_delay=1.0):
    last_error = None
    for attempt in range(max_retries):
        try:
            r = client.messages.create(
                model='claude-opus-4-5', max_tokens=500,
                messages=[{'role': 'user', 'content': prompt}]
            )
            return r.content[0].text
        except anthropic.RateLimitError as e:
            wait = base_delay * (2 ** attempt)
            print(f'Rate limited. Waiting {wait}s before retry {attempt+1}/{max_retries}...')
            time.sleep(wait)
            last_error = e
        except anthropic.APIError as e:
            last_error = e
            if attempt < max_retries - 1:
                time.sleep(base_delay)
    raise RuntimeError(f'All retries exhausted: {last_error}')

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

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

def semantic_validate(data, schema_rules):
    'Apply semantic validation rules to parsed output.'
    errors = []

    for field, rules in schema_rules.items():
        value = data.get(field)

        if rules.get('required') and value is None:
            errors.append(f'{field} is required but missing')
            continue

        if 'allowed_values' in rules and value not in rules['allowed_values']:
            errors.append(f'{field} must be one of {rules["allowed_values"]}, got: {value}')

        if 'min_length' in rules and isinstance(value, list) and len(value) < rules['min_length']:
            errors.append(f'{field} must have at least {rules["min_length"]} items, got {len(value)}')

    if errors:
        raise ValueError('Semantic validation failed: ' + '; '.join(errors))

    return data

rules = {'sentiment': {'allowed_values': ['positive', 'negative', 'mixed']}, 'issues': {'min_length': 1}}
data = {'sentiment': 'positive', 'issues': ['login bug']}
print(semantic_validate(data, rules))

Резервные запросы

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

import json

def call_with_fallback(primary_prompt, fallback_prompt, required_fields):
    # Try primary prompt
    try:
        raw = call_with_retry(primary_prompt)
        return validate_json_output(raw, required_fields)
    except (ValueError, RuntimeError) as e:
        print(f'Primary prompt failed: {e}. Trying fallback...')

    # Try simpler fallback prompt
    try:
        raw = call_with_retry(fallback_prompt)
        return validate_json_output(raw, required_fields)
    except (ValueError, RuntimeError) as e:
        print(f'Fallback also failed: {e}. Returning safe default.')

    # Return safe default — chain continues with minimal data
    return {field: None for field in required_fields}

# Usage
primary = 'Analyze this review. Return JSON with 10 fields: {...}'
fallback = 'Classify this review. Return JSON: {"sentiment": "positive|negative|neutral"}'
result = call_with_fallback(primary, fallback, ['sentiment'])
print(result)

Размыкатели цепи

Размыкатель цепи не позволяет неисправной цепочке тратить вызовы API. После N последовательных сбоев он размыкает цепь и немедленно возвращает ошибку, не выполняя дальнейших вызовов API:

class CircuitBreaker:
    def __init__(self, failure_threshold=3, recovery_timeout=60):
        self.failure_count = 0
        self.threshold = failure_threshold
        self.state = 'closed'  # closed = normal, open = blocking
        self.opened_at = None

    def call(self, fn, *args, **kwargs):
        import time
        if self.state == 'open':
            elapsed = time.time() - self.opened_at
            if elapsed > 60:  # recovery_timeout
                self.state = 'half-open'
            else:
                raise RuntimeError('Circuit open — skipping API call')
        try:
            result = fn(*args, **kwargs)
            self.failure_count = 0
            self.state = 'closed'
            return result
        except Exception as e:
            self.failure_count += 1
            if self.failure_count >= self.threshold:
                self.state = 'open'
                self.opened_at = time.time()
                print(f'Circuit opened after {self.failure_count} failures.')
            raise e

cb = CircuitBreaker(failure_threshold=3)
print('Circuit breaker initialized.')

Создание контрольных точек в длинных цепочках

Для цепочек с большим числом шагов или дорогими по ресурсам шагами используйте контрольные точки, чтобы сохранять промежуточные результаты. Если поздний шаг завершится сбоем, продолжите выполнение с контрольной точки, а не начинайте снова с шага 1:

import json, os

CHECKPOINT_DIR = '/tmp/chain_checkpoints'
os.makedirs(CHECKPOINT_DIR, exist_ok=True)

def save_checkpoint(run_id, step_id, data):
    path = os.path.join(CHECKPOINT_DIR, f'{run_id}_step{step_id}.json')
    with open(path, 'w') as f:
        json.dump(data, f)
    print(f'Checkpoint saved: step {step_id}')

def load_checkpoint(run_id, step_id):
    path = os.path.join(CHECKPOINT_DIR, f'{run_id}_step{step_id}.json')
    if os.path.exists(path):
        with open(path) as f:
            return json.load(f)
    return None

def run_with_checkpoints(run_id, input_data):
    step1 = load_checkpoint(run_id, 1) or json.loads(call(f'Step 1 processing: {input_data}'))
    save_checkpoint(run_id, 1, step1)

    step2 = load_checkpoint(run_id, 2) or json.loads(call(f'Step 2 processing: {step1}'))
    save_checkpoint(run_id, 2, step2)

    return step2

print('Checkpointing system defined.')

Плавная деградация

Если шаг цепочки завершается сбоем и восстановить его невозможно, плавная деградация позволяет продолжить цепочку с частичными данными, а не полностью остановить её:

def process_with_degradation(tickets):
    results = []
    for ticket in tickets:
        try:
            # Full chain: extract -> classify -> respond
            extracted = json.loads(call(f'Extract issue from ticket. Return JSON: {{"issue": str}}\n\n{ticket}'))
            classified = json.loads(call(f'Classify priority. Return JSON: {{"priority": str}}\n\n{extracted["issue"]}'))
            response = call(f'Draft response for {classified["priority"]} priority: {extracted["issue"]}')
            results.append({'ticket': ticket, 'response': response, 'degraded': False})
        except Exception as e:
            print(f'Chain failed for ticket, using fallback: {e}')
            # Fallback: simple direct response without classification
            simple_response = call(f'Respond to this support ticket:\n{ticket}')
            results.append({'ticket': ticket, 'response': simple_response, 'degraded': True})
    return results

print('Graceful degradation pipeline defined.')

Структурированное журналирование ошибок

Записывайте ошибки вместе с достаточным контекстом, чтобы определить, какой шаг завершился сбоем, каковы были входные данные и что вернула модель:

import logging, traceback
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('chain')

def logged_step(step_name, prompt, validator=None):
    start = datetime.utcnow()
    try:
        raw = call_with_retry(prompt)
        result = validator(raw) if validator else raw
        logger.info(f'[{step_name}] SUCCESS in {(datetime.utcnow()-start).total_seconds():.2f}s')
        return result
    except Exception as e:
        logger.error(f'[{step_name}] FAILED after {(datetime.utcnow()-start).total_seconds():.2f}s')
        logger.error(f'[{step_name}] PROMPT: {prompt[:200]}')
        logger.error(f'[{step_name}] ERROR: {traceback.format_exc()}')
        raise

print('Structured error logging defined.')

Проверка сценариев ошибок

Проверяйте обработку ошибок явно, искусственно вызывая сбои. Используйте имитированные объекты для моделирования ошибок API и некорректных результатов:

from unittest.mock import patch, MagicMock

def test_fallback_on_json_error():
    with patch('__main__.call') as mock_call:
        # First call returns malformed JSON, fallback returns valid JSON
        mock_call.side_effect = [
            'This is not JSON at all',
            '{"sentiment": "positive"}'
        ]

        result = call_with_fallback(
            primary_prompt='Analyze review with 10 fields',
            fallback_prompt='Just classify sentiment as JSON',
            required_fields=['sentiment']
        )

        assert result['sentiment'] == 'positive'
        print('PASS: fallback activated correctly on JSON parse error')

def test_circuit_breaker_opens():
    cb = CircuitBreaker(failure_threshold=2)
    for i in range(2):
        try:
            cb.call(lambda: (_ for _ in ()).throw(RuntimeError('API fail')))
        except RuntimeError:
            pass
    assert cb.state == 'open'
    print('PASS: circuit breaker opened after 2 failures')

print('Error handling tests defined.')

Мониторинг состояния цепочки в рабочей среде

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

  • Доля успешных шагов: процент запусков, в которых каждый шаг успешно выполняется с первой попытки
  • Доля активации резервного варианта: как часто используется резервный запрос?
  • Доля деградации: какая часть запусков цепочки завершается в режиме деградации?
  • Задержка шага: отслеживайте задержку p50/p95 для каждого шага — медленный шаг указывает на проблемы со сложностью запроса
  • Доля сбоев проверки: высокий показатель означает, что запрос требует доработки

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

Каково назначение размыкателя цепи в цепочке запросов?

Обработка ошибок в цепочках — основные выводы

Надёжная обработка ошибок отличает прототипные цепочки от боевых систем:

  • Проверяйте результат каждого шага перед передачей дальше — никогда не предполагайте, что модель вернула правильные данные
  • Повторяйте запросы при временных сбоях API с экспоненциальной задержкой — ограничения частоты запросов и тайм-ауты можно устранить повтором
  • Используйте резервные запросы для получения более простого результата, если основной запрос не проходит семантическую проверку
  • Размыкатели цепи прекращают бесполезные вызовы API после повторяющихся сбоев
  • Создавайте контрольные точки для дорогих по ресурсам шагов, чтобы длинные цепочки можно было продолжить после сбоя позднего шага
  • Плавная деградация поддерживает работу конвейера с частичными данными вместо полной остановки
  • В рабочей среде отслеживайте долю успешных шагов, долю использования резервного варианта и долю деградации
Можно начать бесплатно

Изучай AI Prompt Engineering с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
53
Уроки
199

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

Урок «Обработка ошибок в цепочках запросов» бесплатный?

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

Чему я научусь в уроке «Обработка ошибок в цепочках запросов»?

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

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

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

Сколько времени занимает урок «Обработка ошибок в цепочках запросов»?

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

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

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

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

  1. Что такое цепочка запросов
  2. Шаблоны «вывод — ввод»
  3. Цепочки последовательных преобразований
  4. Обработка ошибок в цепочках запросов
← Назад к AI Prompt Engineering