에러 처리 — 실패한 스텝을 안전하게 재시도하기
에러 처리 — 실패한 스텝을 안전하게 재시도하기
스텝이 실패하면 워크플로 전체가 실패로 끝날 수 있어요. 그런데 실패가 예상 가능한 경우(예: 일시적인 네트워크 오류, rate limit)는 안전하게 재시도할 수 있는 게 훨씬 낫죠. 워크플로의 재시도 모듈은 세 가지 조립식 블록으로 구성돼요.
출처: 공식문서
세 가지 기본 블록
- 재시도 조건(retry conditions) — 어떤 예외가 재시도 가능한지 결정해요.
- 대기 전략(wait strategies) — 다음 시도 전에 얼마나 기다릴지 결정해요.
- 중단 조건(stop conditions) — 언제 포기할지 결정해요.
이들을 retry_policy(retry=..., wait=..., stop=...)로 조합해 정책을 만들고 @step 데코레이터에 넘기면 돼요. 재시도 조건은 |와 &를, 대기 전략은 +를, 중단 조건은 |와 &를 지원해요.
기본 사용법
from llama_index.core.workflow import Workflow, step
from llama_index.core.workflow.events import StartEvent, StopEvent
from llama_index.core.workflow.retry_policy import retry_policy, stop_after_attempt, wait_fixed
class MyWorkflow(Workflow):
@step(retry_policy=retry_policy(wait=wait_fixed(5), stop=stop_after_attempt(3)))
async def my_step(self, ev: StartEvent) -> StopEvent:
...
인자 없이 retry_policy()를 쓰면 모든 예외를 최대 3회 시도, 시도 사이 5초 고정 대기로 재시도해요. retry=, wait=, stop=로 각 구성 요소를 바꿀 수 있어요.
재시도할 예외 골라내기
재시도 조건으로 어떤 예외를 재시도할지 제어할 수 있어요. 타입으로 걸러내거나(retry_if_exception_type) 메시지로 매칭하거나(retry_if_exception_message) 하고, | 나 &로 조합해요. 대기 전략도 +로 이어 붙일 수 있어요(예: wait_fixed(1) + wait_random(0, 1)).
@step(
retry_policy=retry_policy(
retry=retry_if_exception_type((TimeoutError, ConnectionError))
| retry_if_exception_message(match="rate limit|temporarily unavailable"),
wait=wait_fixed(1) + wait_random(0, 1),
stop=stop_after_attempt(5),
)
)
지수 백오프
LLM 프로바이더나 rate limit이 있는 API를 호출하는 스텝에는 지수 백오프가 뭉칫값 효과(thundering herd)를 피하는 데 도움돼요. wait_exponential_jitter(initial=1, exp_base=2, max=30, jitter=1) 같은 전략을 쓰면 초기 대기에서부터 점점 늘리며, 지터로 동시 폭주를 흩어줍니다. API 레퍼런스에 훨씬 많은 재시도 조건·대기·중단 블록이 있으니 필요할 때 참고하면 돼요.
커스텀 재시도 정책
조립식 API로 안 되는 경우가 있다면 직접 정책을 쓸 수도 있어요. 요구사항은 RetryPolicy 프로토콜을 따르는 next 메서드를 가진 클래스 하나예요. 다음 재시도까지 기다릴 초(seconds)를 돌려주고, 멈추려면 None을 돌려주면 됩니다. attempts는 지금까지의 실패 횟수(첫 실패는 1)이고, 선택적 seed는 내구성 있는 런타임이 재생(replay) 중에 지터를 결정적으로 만들 때 써요.
재시도 소진 시 복구: @catch_error
정책이 포기하면 예외가 전파되어 워크플로가 실패해요. @catch_error 핸들러를 선언하면 그 실패를 붙잡아 복구할 수 있습니다.
- 와일드카드 — 데코레이터만 붙인
@catch_error는 재시도를 다 소진한 어느 스텝이든, 더 구체적인 핸들러가 주장하지 않는 한 잡아요. 워크플로당 하나만 허용돼요. - 특정 스텝만 —
for_steps=[...]로 핸들러가 덮을 스텝을 한정해요. 한 스텝 이름은 최대 하나의 스코프 핸들러에만 등장 가능하고, 알 수 없는 이름은 생성 시점에 거부돼요.
핸들러는 스스로 그래프로 다시 흘러드는 이벤트를 내보낼 수 있어서, 같은 혈통(lineage)이 같은 핸들러를 다시 만날 수 있어요. max_recoveries(기본 1)는 이벤트 혈통당 핸들러 재진입 횟수 상한을 정해요. 유한 재시도 루프에 참여하는 핸들러는 좀 더 높이고, 종료형 핸들러는 1로 두면 됩니다.
스텝 안에서 재시도 상태 확인하기
Context.retry_info()는 현재 시도의 RetryInfo를 돌려줘요. 재시도 사이에 동작을 바꾸는 데 써요(검색 범위 넓히기, 타임아웃 줄이기, 경고 로그 남기기). retry_number는 첫 실행에서 0이고, last_exception·last_failed_at은 첫 실패 전까지 None이었다가 그 다음부터 최근 실패를 설명해요.
더 알아보기
- 상태 관리 — 스텝 간 공유 상태
- 이벤트 스트리밍 — 종료 이벤트로 실패 감지
- Agent Workflows 입문 — 기본 모델 이해하기