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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23