본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 18:24

LLM 파이프라인을 위한 상태 유지형 프로바이더 폴백(Stateful provider fallback): FSM 패턴

요약

다단계 LLM 파이프라인에서 프로바이더 장애 시 상태를 유지하며 폴백을 구현하는 FSM 패턴을 소개합니다. llm-nano-vm을 활용하여 예외가 아닌 도구 결과로서 실패를 처리하는 방법과 구현 시 주의할 버그를 다룹니다.

핵심 포인트

  • 단일 요청 단위의 게이트웨이 폴백과 다단계 파이프라인의 차이점 설명
  • FSM(상태 머신) 내에서 실패를 도구(TOOL) 결과로 처리하여 분기 구현
  • llm-nano-vm 사용 시 비동기 실행(async) 처리의 중요성
  • AST 엔진의 조건문 내 문자열 리터럴 평가 오류 주의

게이트웨이 수준의 LLM 폴백 (LiteLLM, Bifrost, Kong AI Gateway)은 개별 HTTP 요청 단위로 작동합니다. 한 프로바이더(provider)에 대한 요청이 실패하면, 게이트웨이는 다른 프로바이더를 대상으로 재시도합니다. 작업 단위가 단일 완료 호출(single completion call)인 경우에는 이것이 적절한 도구입니다.

하지만 작업 단위가 다단계 파이프라인(multi-step pipeline)인 경우에는 적절한 도구가 아닙니다. 게이트웨이는 "3단계 중 2단계"라는 개념이 없기 때문입니다. 게이트웨이는 요청을 볼 뿐, 상태 머신(state machine) 내의 위치를 인식하지 못합니다.

이 포스트에서는 llm-nano-vm 0.8.6을 사용하여 명시적인 FSM 전이(transition)로서 프로바이더 폴백을 구현하는 과정을 살펴봅니다. 여기에는 실제 패키지(모의 객체가 아닌)를 사용하면서 마주친 두 가지 버그에 대한 내용도 포함되어 있습니다.

문제 정의 (Problem statement)

3단계 파이프라인:

collect_application → verify_income → policy_decision

verify_income은 LLM을 호출합니다. 파이프라인 중간에 LLM 프로바이더가 사용 불가능해질 수 있습니다. 우리는 파이프라인이 다른 프로바이더를 통해 완료되기를 원하며, 영수증(Receipt, nano-vm의 결정론적 실행 후 아티팩트)에 정확히 어떤 일이 일어났는지 표시되기를 원합니다.

메커니즘: 예외(exception)가 아닌 도구(TOOL) 결과로서의 실패

llm-nano-vm의 네이티브 LLM 단계(step) 유형은 실패 시 분기점(branch point)을 제공하지 않습니다. 어댑터(adapter)에서 오류가 발생하면 해당 단계는 FAILED로 표시되고 트레이스(trace)가 중단됩니다. 분기를 생성하려면, 예외를 포착(catch)하고 센티널 값(sentinel value)을 반환하는 TOOL 단계 내에 LLM 호출을 작성해야 합니다:

async def attempt_llm_step(**kwargs):
    step_id = kwargs["step_id"]
    try:
...

그러면 FSM 프로그램은 해당 센티널을 기준으로 분기합니다:

Step(
    id="try_s2",
    type=StepType.TOOL,
...

이것이 핵심 메커니즘입니다. 프로바이더의 실패는 런타임이 전파하는 예외가 아니라, FSM이 평가하는 하나의 값이 됩니다.

버그 #1: ExecutionVM.run은 비동기(async)입니다

README를 대충 훑어본다면 놓치기 쉽습니다. vm.run()Trace가 아니라 코루틴 (coroutine)을 반환합니다. 해결 방법은 최상위 수준에서 asyncio.run(vm.run(program, context=...))를 사용하는 것이며, LLM 어댑터를 호출하는 모든 도구 (tool) 함수는 async def로 정의해야 합니다. ExecutionVM은 도구별로 inspect.iscoroutinefunction(fn)을 확인하여 그에 따라 await를 수행합니다.

버그 #2: ASTEngine 조건문에서 문자열 리터럴(string literals)이 작동하지 않음

우리의 첫 번째 조건문 버전은 다음과 같았습니다:

condition="try_s2.output == 'PROVIDER_FAILED'"

이것은 오류 없이 파싱됩니다. 하지만 항상 False로 평가됩니다. 엔진을 직접 테스트하여 이를 확인했습니다:

from nano_vm.vm import eval_condition
ctx = {"try_s2": {"output": "PROVIDER_FAILED"}}
eval_condition("try_s2.output == 'PROVIDER_FAILED'", ctx)
...

llm-nano-vm의 ASTEngine (v0.8.6)은 ==, !=, >, <, in, not_in, and, or, not, contains를 지원하지만, 비교 연산의 우항 (right-hand side)은 따옴표로 묶인 문자열 리터럴이 아니라 숫자 또는 $var 참조여야 합니다. 작동하는 패턴은 숫자 센티널 (numeric sentinel)을 사용하는 것입니다:

condition="$provider_ok < 1"

이 사항은 이제 단순한 구전 지식이 아니라 프로젝트의 엄격한 제약 사항으로 문서화되었습니다.

두 가지 실패 시나리오

python receipt_demo.py --failure-mode retry   # 3번의 시도 동안 단계적으로 성능이 저하된 후 전환됨
python receipt_demo.py --failure-mode hard     # 한 번 실패하면 즉시 전환됨

hard 모드의 출력:

S2  verify_income
  EVENT: ProviderUnavailable (CLAUDE)
  ACTION: switch_provider  claude → gpt
...

두 시나리오 모두에서 trace_hash가 동일한 이유

trace_hash는 단계별 결과의 머클 체인 (Merkle chain)에 대한 SHA-256 값입니다. retryhard 모드 모두 정확히 동일한 FSM 경로를 탐색합니다. 재시도 루프 (retry loop)가 attempt_llm_step 도구 (TOOL) 내부에 포함되어 있기 때문에, FSM은 어떤 경우에도 단 하나의 도구 단계 결과만을 보게 됩니다. 동일한 경로 → 동일한 해시 (hash). 이것은 우연히 설명해야 할 현상이 아니라 구조적 특성입니다. 만약 경로가 갈라졌다면 해시도 달라졌을 것입니다.

현재의 한계

  • 폴백 체인(Fallback chain)은 점수가 매겨지거나 순위가 지정된 선택지가 아니라 고정된 목록(claude → gpt → qwen)입니다.
  • 활성 상태 확인 폴링(Active health-check polling)이 없습니다. Bifrost가 명시한 ~11μs 오버헤드 수준의 활성 탐지(active detection)와 달리, 실패는 시도 시에만 감지됩니다.
  • 데모의 MockAdapter는 실제 프로바이더 API를 호출하지 않습니다. 이는 API 키 없이도 데모를 재현할 수 있도록 설계상 결정론적(deterministic)으로 동작합니다.

이것이 대체하는 것이 아니라, 무엇과 결합되는가

LiteLLM과 같은 게이트웨이는 여전히 HTTP 계층에서 모델 라우팅(model routing), 속도 제한(rate limiting), 비용 추적(cost tracking)을 담당합니다. 이 FSM 패턴은 파이프라인 상태를 인식하는(pipeline-state-aware) 폴백을 담당합니다. 즉, "프로바이더가 중단되었을 때 파이프라인이 무엇을 하고 있었는가, 그리고 완료되었는가?"라는 질문에 답합니다. 이 둘은 서로 다른 계층이며, 동일한 질문에 대한 경쟁적인 해답이 아닙니다.

Repo: provider-fallback-demo

pip install "llm-nano-vm[litellm]"
python receipt_demo.py --both

다음 단계: switch_provider를 OpenTelemetry 스팬(span)으로 방출하여, Receipt JSON에서만 보이는 대신 기존 대시보드에 나타나도록 하는 것입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0