プロンプトチェーンのエラー処理
中間出力を検証し、チェーンの失敗から復旧します。
「プロンプトチェーンのエラー処理」はCoddyKit上の無料AI Prompt Engineeringレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これは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呼び出しの浪費を止めます
- 高コストなステップをチェックポイント保存し、後半のステップが失敗しても長いチェーンを再開できるようにします
- グレースフルデグラデーションによって、クラッシュさせずに不完全なデータでパイプラインを実行し続けます
- 本番環境でステップ成功率、フォールバック率、劣化率を追跡します
よくある質問
「プロンプトチェーンのエラー処理」レッスンは無料ですか?
はい。「プロンプトチェーンのエラー処理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Prompt Engineeringコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Prompt Engineeringコースには全4レッスンが含まれています。
「プロンプトチェーンのエラー処理」で何を学びますか?
中間出力を検証し、チェーンの失敗から復旧します。 ブラウザで直接実行するハンズオンコードでAI Prompt Engineeringを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Prompt Engineeringを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Prompt Engineeringは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「プロンプトチェーンのエラー処理」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Prompt Engineeringレッスンでコードを書いて実行できますか?
はい。すべてのAI Prompt Engineeringレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- プロンプトチェーンとは
- 出力から入力へ渡すパターン
- 逐次変換チェーン
- プロンプトチェーンのエラー処理