실행 도중 서비스 제공자 장애를 견뎌내는 LLM 파이프라인 구축 방법: FSM 패턴 소개
요약
LLM 서비스 제공자의 장애에 대응하기 위해 FSM(유한 상태 기계) 패턴을 활용한 견고한 파이프라인 구축 방법을 소개합니다. 단순 HTTP 재시도 방식의 한계를 극복하고, 실패를 상태 전이로 처리하여 다단계 파이프라인의 안정성을 높이는 전략을 다룹니다.
핵심 포인트
- 단순 게이트웨이 재시도는 다단계 파이프라인의 맥락을 파악하지 못함
- FSM을 통해 LLM 실패를 예외가 아닌 상태 전이(State Transition)로 처리
- 실패를 TOOL 결과값으로 반환하여 FSM이 정상적인 분기 로직을 수행하도록 설계
- llm-nano-vm의 제약 사항을 고려한 숫자 센티널 방식의 조건문 활용
2025년에는 모든 주요 LLM 제공업체(provider)가 적어도 한 번 이상의 중대한 장애를 겪었습니다. Anthropic, OpenAI, Gemini 모두 어느 시점에는 요청 도중 응답을 중단했습니다.
대부분의 폴백(fallback) 솔루션은 게이트웨이 계층(gateway layer)에 위치합니다: LiteLLM, Bifrost, Kong AI Gateway 등이 있습니다. 이들은 실패한 HTTP 요청을 포착하여 다른 제공업체를 대상으로 재시도(retry)합니다. 이는 단일 호출(single call)에는 효과적입니다. 하지만 다단계 파이프라인(multi-step pipeline)에는 작동하지 않습니다. 게이트웨이는 실패한 호출이 3단계 중 2단계라는 사실을 알지 못하며, 그저 재시도가 필요한 요청으로만 인식하기 때문입니다.
우리는 다음과 같은 질문을 던지고 싶었습니다: 상태 저장 방식의 FSM(Finite State Machine, 유한 상태 기계) 런타임이 상태 비저장(stateless) 방식의 HTTP 재시도보다 더 나은 성능을 보여줄 수 있을까?
설정
3단계 신용 신청 파이프라인:
collect_application → verify_income → policy_decision
verify_income은 실패할 수 있는 LLM 단계입니다. 우리는 두 가지 실패 모드를 테스트했습니다:
retry: 제공업체의 성능이 저하되어 3번 실패한 후 포기함
hard: 제공업체가 완전히 사라져 첫 번째 호출이 실패함
첫 번째 시도 — LLM 단계를 자연스럽게 실패하게 두기
우리의 첫 번째 본능은 FSM의 네이티브 LLM 단계가 예외(exception)를 발생시키도록 두고, 이를 FSM 레벨에서 포착하는 것이었습니다. 하지만 이는 llm-nano-vm의 현재 단계 모델에서는 작동하지 않습니다. LLM 단계에서 예외가 발생하면 FSM은 이를 FAILED로 표시하고 트레이스(trace)를 종료합니다. 분기점(branching point)이 존재하지 않습니다.
해결책 — 실패를 예외가 아닌 TOOL 결과로 만들기
TOOL attempt_llm_step → 1(성공) 또는 0(실패) 반환
CONDITION $provider_ok < 1 이면: switch_provider 실행, 그렇지 않으면: 계속 진행
TOOL do_switch_provider → 현재 제공업체(current_provider) 업데이트
TOOL attempt_llm_step → 새로운 제공업체에서 재시도
LLM 호출은 내부적으로 제공업체 예외를 포착하고 센티널(sentinel) 값을 반환하는 TOOL 단계 내부에서 발생합니다. FSM은 예외를 전혀 보지 못하며, 대신 정상적인 CONDITION 분기를 보게 됩니다. 이것이 실제 메커니즘입니다: FSM은 제공업체의 실패를 복구해야 할 오류가 아니라 상태 전이(state transition)로 취급합니다.
우리가 직면한 실제 버그: 이 ASTEngine에서는 문자열 리터럴(string literals)이 작동하지 않음
우리는 다음과 같이 시도했습니다:
condition: try_s2.output == "PROVIDER_FAILED"
파싱은 되지만, 항상 False를 반환합니다.
llm-nano-vm 0.8.6의 ASTEngine은 비교 연산의 우변(right-hand side)으로 문자열 리터럴(string literals)을 지원하지 않습니다. 숫자와 $var 참조만 작동합니다. 따라서 우리는 다음과 같은 숫자 센티널(numeric sentinel)로 전환했습니다:
condition: $provider_ok < 1
이것은 이제 추측이 아닌, 프로젝트에 문서화된 제약 사항입니다.
결과
RECEIPT: { "final_status": "SUCCESS", "provider_final": "gpt" }
=== Scenario: HARD === S2 verify_income EVENT: ProviderUnavailable (CLAUDE) ACTION: switch_provider claude → gpt S3 policy_decision ✓ GPT
RECEIPT: { "final_status": "SUCCESS", "provider_final": "gpt" } ```
두 시나리오 모두 동일한 trace_hash를 생성합니다. 이는 우연이 아닙니다. 두 실행 모두 동일한 FSM 경로(collect → attempt → fail → switch → attempt → decide)를 통과하기 때문입니다. trace_hash = SHA-256(Merkle(step_results)). 구조적으로 동일한 경로를 거치면 동일한 해시가 생성됩니다.
이 방식이 수행하지 않는 것
- "최적의" 제공자를 선택하지 않습니다 — 폴백 체인(fallback chain)은 고정된 목록(claude → gpt → qwen)입니다.
- Bifrost의 능동적 탐지(active detection)와 같은 상태 확인 폴링(health-check polling)을 수행하지 않습니다 — 실패는 시도(attempt) 시에만 감지됩니다.
- 데모의 MockAdapter는 실제 API를 호출하지 않습니다 — 재현성을 위해 응답이 하드코딩되어 있습니다.
다단계 에이전트 파이프라인(multi-step agent pipelines)을 실행하는 모든 이들에게 이것이 중요한 이유
게이트웨이 수준의 폴백(LiteLLM, Bifrost)은 "이 HTTP 호출이 성공했는가?"라는 질문에 답합니다. 상태 기반(stateful) FSM 폴백은 "제공자가 실패했을 때 파이프라인이 어떤 상태였으며, 그 후에 어떤 일이 일어났는가?"라는 질문에 답합니다.
영수증(Receipt)이 그 차이점입니다. 영수증에는 "3회 재시도함"이라고 적힌 로그 한 줄 대신, switch_event, rejected_transitions, 그리고 재계산 가능한 trace_hash가 포함되어 있습니다.
코드: provider-fallback-demo — python receipt_demo.py --both, API 키는 필요하지 않으며, 모킹된 제공자(mocked providers)를 사용하는 실제 llm-nano-vm 스택입니다.
다음 단계: OpenTelemetry 스팬(spans)으로 스위치 이벤트(switch events)를 가져와, 기존의 관측성 스택(observability stacks)을 대체하는 대신 이들과 결합할 수 있도록 합니다.
제출자: /u/ale007xd / 커뮤니티: r/LocalLLaMA
[link] [comments]
AI 자동 생성 콘텐츠
본 콘텐츠는 r/OpenAI Codex (search)의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기