멀티 에이전트 라우팅 설계 — 클래시파이어 전략, 프로바이더 추상화, 코스트 라이프사이클
라우팅 레이어를 설계할 때 가장 비싼 실수는 모델 선택을 런타임까지 미루는 것이다
핵심 요약
- 요청 분류(classifier) 전략이 라우팅 레이어의 실질적 품질을 결정한다
- 멀티 프로바이더 추상화는 인터페이스 통일이 아니라 폴백 체인 설계가 핵심이다
- 코스트 라이프사이클을 모델 선택 루프에 포함하지 않으면 라우팅은 비용 최적화가 아니라 복잡도만 추가된다
설계 배경
Node.js + TypeScript 기반의 멀티 프로바이더 LLM 라우팅 레이어(Router_Control)를 설계하면서 얻은 구조적 교훈입니다. 대상 프로바이더는 OpenAI, Claude, Ollama, oMLX. 목표는 요청 유형에 따라 적합한 모델로 자동 분배하는 것이었습니다.
이 글은 시스템이 "잘 됐다"는 성공담이 아니라, 설계 단계에서 어떤 판단이 결과를 갈랐는지에 집중합니다.
본문
1. 클래시파이어 전략 — 라우팅의 실질적 품질 결정 요소
라우팅 레이어의 첫 번째 설계 결정: 요청을 어떻게 분류할 것인가.
초기 설계는 키워드 기반 분류였습니다. 요청 텍스트에 "코드", "번역", "요약" 같은 키워드가 포함되면 해당 모델로 라우팅하는 방식입니다. 빠르고 구현이 단순합니다.
문제는 키워드가 의도를 대표하지 않는다는 것입니다. "이 코드 어떻게 생각해?"는 코드 생성이 아니라 해석 요청입니다. 키워드 기반 분류는 이 둘을 구분하지 못합니다.
효과적으로 동작한 접근:
interface ClassifierResult {
taskType: 'code-gen' | 'code-review' | 'translation' | 'embedding' | 'general';
complexity: 'low' | 'medium' | 'high';
latencyRequirement: 'realtime' | 'batch';
costBudget: 'local-only' | 'cloud-ok' | 'premium-ok';
}
분류 결과를 단일 레이블이 아니라 다차원 구조체로 만드는 것입니다. 작업 유형, 복잡도, 레이턴시 요구사항, 비용 예산을 조합해야 라우팅 결정이 실질적으로 정밀해집니다.
클래시파이어 자체도 LLM에게 위임할 수 있지만, 이 경우 클래시파이어 호출 비용이 발생합니다. 단순 요청은 규칙 기반, 복잡한 요청만 LLM 클래시파이어로 2단 분류하는 구조가 비용 대비 정확도 균형점이었습니다.
2. 멀티 프로바이더 추상화 — 인터페이스 통일의 함정
멀티 프로바이더 라우팅의 표준적인 접근은 공통 인터페이스를 정의하는 것입니다:
interface LLMProvider {
complete(prompt: string, options: CompletionOptions): Promise<CompletionResult>;
}
이 추상화는 코드 차원에서는 깔끔합니다. 실제 운영에서는 구조적 문제가 있습니다.
각 프로바이더는 실패 모드가 다릅니다. OpenAI는 rate limit 429로 실패합니다. Ollama는 모델이 로드되지 않았을 때 타임아웃으로 실패합니다. oMLX는 메모리 초과 시 프로세스가 종료됩니다. 공통 인터페이스로 이 차이를 숨기면, 폴백 로직이 실패 원인을 구분하지 못합니다.
구조적으로 더 나은 접근:
interface ProviderError {
type: 'rate-limit' | 'model-unavailable' | 'resource-exhausted' | 'network';
retryable: boolean;
fallbackSuggestion: ProviderId | null;
}
실패를 프로바이더별로 타입화하고, 폴백 체인을 실패 유형에 따라 분기합니다. rate limit은 같은 프로바이더의 다른 엔드포인트로 재시도하고, 모델 미로드는 즉시 다음 프로바이더로 넘기고, 리소스 초과는 더 가벼운 모델로 강등합니다.
인터페이스 통일이 목표가 아니라 실패 유형별 폴백 체인 설계가 핵심입니다.
3. 코스트 라이프사이클 — 선택 루프에 포함되지 않으면 의미 없다
라우팅 레이어가 비용 최적화를 목표로 한다면, 비용 정보는 모델 선택 루프 안에 있어야 합니다.
초기 설계의 문제: 비용 추적이 로깅이었습니다. 토큰 소비량을 기록하고 대시보드에 표시했지만, 그 정보가 다음 요청의 모델 선택에 영향을 주지 않았습니다. 모니터링과 제어가 분리된 상태입니다.
코스트 라이프사이클을 라우팅 루프에 포함하는 설계:
interface CostBudgetTracker {
sessionBudget: number; // 세션 전체 예산
consumed: number; // 현재까지 소비량
remainingRatio: number; // 잔여 비율
nextModelConstraint(): CostBudget; // 잔여 예산에 따른 모델 제약
}
잔여 예산이 50% 이상이면 premium 모델 허용, 20% 이하면 local-only 강제. 이 제약을 클래시파이어 결과와 조합해 최종 모델을 결정합니다.
이 구조 없이는 라우팅 레이어가 비용 최적화가 아니라 라우팅 복잡도만 추가하는 레이어가 됩니다.
4. OpenClaw-Hermes 통합 이력 — 사실 기록
라우팅 레이어는 OpenClaw(OC) 멀티 에이전트 플랫폼과의 통합을 목표로 설계되었습니다. 이 맥락에서 발생한 구조적 전환을 사실 그대로 기록합니다.
흐름: OC 안정 운영 → Hermes(HM) 1차 마이그레이션 시도 → 토큰 폭주 발생 → OC 회귀 → OC 재설계 → HM 재시도(현재 검증 단계).
라우팅 레이어(Router_Control)는 OC→HM 전환 과정에서 우선순위 재조정으로 Suspended 상태가 되었습니다. 기술적 실패가 아닙니다. 토큰 폭주 문제는 HM 측 이슈였고, 라우팅 레이어 자체는 프로토타입 수준에서 동작했습니다.
HM 재시도 검증이 완료되면, 이 글에서 다룬 설계 패턴(다차원 클래시파이어, 폴백 체인 타입화, 코스트 루프 통합)을 기반으로 라우팅 레이어 재개를 검토합니다.
관련 PR: 하네스 git PR#12497.
5. Suspended 상태의 설계적 가치
온톨로지에서 에이전트/컴포넌트를 "Suspended"로 명시하는 것은 단순한 상태 표시 이상의 역할을 합니다.
- 맥락 보존: 왜 시작했고, 무엇을 달성했고, 왜 멈췄는지의 설계 근거가 보존된다
- 재개 비용 감소: 재시작 시 처음부터 설계하지 않아도 된다. 이전 결정 지점에서 재개 가능하다
- 의사결정 참고: 동일한 문제를 다시 만났을 때 이전 판단의 맥락을 즉시 조회할 수 있다
- 시스템 지도의 정직성: Active와 Completed만 있는 온톨로지는 중단된 컴포넌트를 숨긴다. Suspended 상태를 명시하면 시스템의 실제 상태가 드러난다
멀티 에이전트 시스템에서 각 컴포넌트는 관리 비용을 가집니다. 가치 대비 비용을 지속 평가하고, 낮아진 컴포넌트를 솔직하게 Suspended로 전환하는 것이 전체 시스템 효율을 유지하는 방법입니다.
시행착오
클래시파이어를 단일 레이블로 설계한 것: 초기에 taskType 하나로만 라우팅했습니다. 복잡도와 비용 예산을 별도 차원으로 추가했을 때 라우팅 정확도가 의미있게 올라갔습니다.
폴백 로직이 실패 원인을 구분하지 않은 것: 어떤 실패든 단순히 다음 프로바이더로 넘겼습니다. 실패 유형을 타입화하고 나서야 rate limit과 리소스 초과에 대한 대응이 달라졌습니다.
코스트 추적이 로깅에만 머문 것: 비용 정보를 기록하면서도 모델 선택 루프에 피드백하지 않았습니다. 모니터링과 제어의 분리는 최적화가 아니라 가시성에 그칩니다.
마무리
멀티 프로바이더 라우팅 레이어의 실질적 품질은 인터페이스 추상화의 깔끔함이 아니라 세 가지 설계 결정에 달려 있습니다: 다차원 클래시파이어, 실패 유형별 폴백 체인, 코스트 루프 통합. 이 세 가지가 선택 루프 안에 있지 않으면 라우팅 레이어는 복잡도만 추가하는 미들웨어가 됩니다.
Router_Control은 현재 Suspended 상태이지만, 설계 패턴은 유효합니다. HM 검증 완료 이후 이 기반 위에서 재개할 예정입니다.
댓글
댓글 쓰기