Обработка ошибок в цепочках запросов
Проверяйте промежуточные результаты и восстанавливайтесь после сбоев цепочки
«Обработка ошибок в цепочках запросов» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Что такое цепочка запросов
- Шаблоны «вывод — ввод»
- Цепочки последовательных преобразований
- Обработка ошибок в цепочках запросов