Fail Closed, Not Open: 규제 대상 기업을 위한 AI 게이트웨이 설계
요약
규제 준수가 중요한 기업 환경을 위해 AI 요청의 모델 선택 이유를 증명하고 거버넌스를 유지하는 'Aegis Gateway' 설계 방식을 소개합니다. 가용성을 위해 보안 규정을 무시하는 '조용한 거버넌스 저하'를 방지하기 위해 정책 필터링을 하드 게이트로 구현하는 아키텍처를 제안합니다.
핵심 포인트
- 가용성보다 컴플라이언스를 우선시하는 'Fail Closed' 설계 원칙
- 조용한 거버넌스 저하(silent governance downgrade) 방지 전략
- 정책 엔진을 통한 엄격한 하드 게이트 방식의 모델 필터링
- 모델 선택의 근거를 기록하여 규제 대응력 강화
"왜 이 요청이 저 모델로 전송되었나요?"
만약 귀사가 둘 이상의 팀에서 LLM (Large Language Models)을 사용하고 있다면, 언젠가 누군가 당신에게 이 질문을 던질 것입니다. 금융 서비스, 의료 분야, 또는 규제 기관이 존재하는 그 어떤 곳에서 일하고 있다면, 누군가는 선서에 준하는 엄격한 상황에서 이 질문을 던질 것입니다.
대부분의 조직은 이에 답변하지 못합니다. 팀마다 선호하는 제공업체와 직접 통합하고, 민감한 데이터 처리 방식은 팀마다 다르며, 지역적 제한 사항은 일관성 없이 적용됩니다. 또한 특정 모델이 왜 선택되었는지, 혹은 더 중요한 점으로서 왜 대안 모델들이 선택되지 않았는지에 대한 기록이 없습니다.
저는 최근 적절한 답변이 어떤 모습인지 탐구하기 위해 Aegis Gateway를 구축했습니다. 이는 AI 요청을 위한 단일 출입구로서, 각 요청에 어떤 모델이 서비스를 제공할 수 있는지 결정하고 그 이유를 증명할 수 있는 시스템입니다. 이 글은 중요한 세 가지 설계 결정과, 제가 전체 아키텍처를 구성하며 방지하고자 했던 단 하나의 실패 모드(failure mode)에 관한 것입니다.
중요한 실패 모드: 조용한 거버넌스 저하 (silent governance downgrade)
다음은 컴플라이언스 담당자(compliance officers)를 밤잠 설치게 만드는 시나리오입니다.
정상적인 경로(happy path)에서는 게이트웨이가 잘 작동합니다. 제한된 클라이언트 데이터는 온프레미스 (on-prem) 모델로 전송되고 모든 것이 원활합니다. 그러던 어느 화요일, 온프레미스 클러스터가 다운됩니다. 귀하의 폴백 (fallback) 로직이 작동하며, 폴백 로직이 하는 일 그대로 — _작동하는 무언가_를 찾아 — 제한된 데이터를 외부 제공업체로 조용히 라우팅합니다. 요청은 성공합니다. 모두가 행복해합니다. 아무도 눈치채지 못합니다.
이것이 바로 **조용한 거버넌스 저하 (silent governance downgrade)**입니다. 즉, 불편해지기 전까지만 규정을 준수하는 시스템입니다. 그리고 이 교활한 점은 이것이 대개 버그가 아니라는 것입니다. 가용성 (availability)과 컴플라이언스 (compliance)가 동일한 결정 과정에서 경쟁할 때, 가용성이 승리하게 되는 — 즉, 가용성 문제가 발생해야 호출(page)을 받기 때문에 발생하는 — _합리적으로 보이는 설계_라는 점입니다.
저의 설계 목표는 이러한 실패 모드를 구조적으로 불가능하게 (structurally impossible) 만드는 것이었습니다. 코드 리뷰를 통해 권장하지 않는 수준이나 알림(alert)으로 잡아내는 수준이 아니라, 구조적으로 도달할 수 없도록 만드는 것입니다.
결정 1: 정책(Policy)은 하드 게이트이고, 순위 매기기(Ranking)는 소프트 최적화이다 — 그리고 그 순서가 아키텍처다
라우팅 코어는 엄격하고 협상 불가능한 세 단계를 가집니다:
catalogue ──► Policy Engine ──► Ranking ──► Fallback Executor
(하드 게이트) (적격 모델만)
...
1단계 — 정책 필터링(Policy filtering). 카탈로그의 모든 모델은 거버넌스 규칙과 비교됩니다: 이 작업 유형에 대한 역량을 가지고 있는가? 제공업체가 이 사업 단위에 승인되었는가? 이 데이터 분류에 대해 인증을 받았는가? 제한된 데이터는 사설 인프라에 머무르는가? 이 지역에서 서비스되는가? 모든 모델이 실패하면 _실패한 특정 규칙_을 기록합니다.
def evaluate(self, model: ModelSpec, ctx: RequestContext) -> list[str]:
"""위반된 규칙 목록을 반환합니다 (빈 목록 = 적격)."""
reasons = []
...
2단계 — 순위 매기기(Ranking). 정규화된 비용, 품질, 지연 시간(latency)에 대한 가중치 점수를 계산하며, 이 가중치는 요청이 선언한 우선순위(비용/균형/품질)에 의해 결정됩니다. 단순 산술입니다. 핵심 속성은 다음과 같습니다: 랭커의 입력은 정책 게이트의 출력입니다. 랭커는 규정 준수하지 않는 모델을 볼 수 없으며, 따라서 부활시킬 수도 없습니다.
3단계 — 폴백(Fallback). 선택된 모델이 호출 시점에 실패하면, 순위가 매겨진 목록을 따라 내려갑니다. 어떤 목록인가요? 적격 목록입니다. 이 목록이 소진되면:
if trace.status != "ROUTED":
trace.rationale = (
"No model satisfies current policy for this request context. "
...
NO_COMPLIANT_ROUTE. 게이트웨이는 거부합니다 — 심지어 건강한 외부 모델이 한 번의 함수 호출만 남았더라도 말입니다.
이것이 전체 트릭이며, 거의 당황스러울 정도로 간단합니다: 규정 준수 보장은
장애는 복구할 수 있지만, 규정 준수 위반(compliance breach)은 복구할 수 없습니다. 아키텍처는 그러한 선택(trade-off)을 할 수 없어야 하며, 이 설계에서는 불가능합니다.
결정 2: 설명 가능성(Explainability)은 로그 한 줄이 아니라 출력값이다
대부분의 시스템은 결정 근거(decision rationale)를 사고가 발생한 후 grep으로 찾아내야 하는 무언가로 취급합니다. Aegis는 이를 API 계약(API contract)의 일부로 취급합니다. 모든 응답에는 결정 추적(decision trace)이 포함됩니다:
{
"request_id": "f188db34-…",
"policy_version": "2026.07-r4",
...
두 가지 사항에 주목하십시오.
첫째, 추적 정보는 "허용되지 않음(not allowed)"과 "필요하지 않음(not needed)"을 구분합니다. 비용 우선순위가 높은 공개 요청이 저렴한 모델로 라우팅될 때, 프리미엄 모델들은 eligible_ranked에 나타납니다. 즉, 허용은 되었으나 점수가 낮았을 뿐입니다. 반면 제한된 요청이 외부 모델을 거부할 때는, 위반한 정확한 규칙과 함께 rejected_models에 나타납니다. 이는 "왜 GPT가 아닌가?"라는 질문에 대한 서로 다른 답변이며, 감사관(auditors)은 이 차이를 중요하게 여깁니다.
둘째, 추적 정보에는 **정책 버전(policy version)**이 포함됩니다. 정책은 변경됩니다. 6개월 후 감사 질문은 "당시 시행 중이던 규칙 하에서 이 결정이 옳았는가?"가 될 것입니다. 버전 관리(versioning)가 없다면 이 질문에는 답할 수 없습니다.
감사용 추적 복사본에는 의도적인 누락 사항이 하나 있습니다: 바로 프롬프트(prompt)입니다. 대신 SHA-256 지문(fingerprint)과 글자 수를 저장합니다. 이는 감사 로그가 가장 민감한 데이터의 보호되지 않은 두 번째 복사본이 되지 않으면서도, 해당 결정이 어느 요청에 속하는지를 증명하기에 충분한 정보입니다. 귀하의 감사 추적(audit trail)이 데이터 유출의 경로가 되어서는 안 됩니다.
결정 3: 테넌트(tenant)별 특화 사항은 코드가 아니라 데이터다
모든 멀티 테넌트(multi-tenant) 거버넌스 시스템에 던져지는 결정적인 질문은 다음과 같습니다: "두 번째 사업부가 다른 제공업체 승인, 다른 지역적 제한, 다른 라우팅 가중치를 원한다면, 사업부별 전용 코드 경로(code paths)를 만들지 않고 어떻게 이를 지원할 것인가?"
만약 귀하의 답변에 if business_unit == "wealth-mgmt"와 같은 코드가 포함된다면, 당신은 이미 실패한 것입니다. 새로운 테넌트가 추가될 때마다 코드 변경, 릴리스, 그리고 거버넌스 버그가 숨어들 새로운 장소가 필요하게 되기 때문입니다.
Aegis에서 테넌트는 하나의 정책 항목(policy entry)입니다:
business_units:
retail-banking:
approved_providers: [InHouseAI, OpenAI, Anthropic, Google]
...
모델(models)도 마찬가지입니다. 새로운 모델은 제공자(provider), 기능(capabilities), 지역(regions), 인증 상한선(certification ceiling), 그리고 비용/품질/지연 시간(cost/quality/latency) 프로필을 선언하는 하나의 YAML 항목입니다. 라우팅 코어(routing core)는 테넌트(tenant)나 모델의 이름을 직접 언급하지 않습니다. 이들 중 어느 것을 온보딩(onboarding)하더라도 배포(deployment)가 아닌, 정책 검토를 동반한 설정 변경(config change)일 뿐입니다.
(고백하자면: 제 데모 UI의 첫 번째 버전은 비즈니스 유닛(business-unit) 드롭다운을 하드코딩했습니다. 라우팅 코어는 깔끔했지만, 프레젠테이션 레이어(presentation layer)에는 아키텍처가 금지하는 테넌트 특정적 코드 경로(tenant-specific code path)가 정확히 생성되어 있었습니다. 이는 거버넌스 불변성(governance invariants)이 코어(core)가 아니라 UI, 배치 작업(batch job), "임시" 스크립트와 같은 가장자리(edges)로부터 침식된다는 사실을 상기시켜 주는 좋은 사례입니다.)
제가 가장 강력하게 옹호할 결정: 라우팅 루프(routing loop) 내 LLM 배제
지금은 2026년이므로, 반사적인 설계 방식은 "최적의 모델을 선택하기 위해 LLM을 사용한다"는 것입니다. 저는 이 계층(layer)에서 그것이 정확히 틀렸다고 생각하며, 그 이유를 명확히 짚고 넘어갈 가치가 있다고 봅니다.
거버넌스 게이트웨이(governance gateway)의 라우팅 결정은 반드시 다음과 같아야 합니다:
- 결정론적(Deterministic) — 동일한 요청, 동일한 설정에 대해 매번 동일한 결정이 내려져야 합니다.
- 규칙별 설명 가능성(Explainable rule-by-rule) — "모델이 이 경로가 준수된다고 느꼈습니다"라는 말은 규제 기관(regulator)에 할 수 있는 말이 아닙니다.
- 빠르고 저렴함(Fast and cheap) — 이는 모든 요청의 크리티컬 패스(critical path)에 위치합니다.
규칙(rules)과 산술(arithmetic)은 이 세 가지를 모두 제공합니다. 하지만 LLM 판사(LLM judge)는 그 중 어느 것도 제공하지 못합니다. 이 아키텍처에서 LLM은 워크로드(workload)일 뿐, 결코 판사(judge)가 아닙니다.
여기에는 정당하게 어려운 하위 문제(sub-problem)가 하나 있습니다. 바로 요청(request) 자체를 분류하는 것입니다(이것이 텍스트 작업인가 아니면 이미지 작업인가? 이 데이터가 정말 "내부용(Internal)"인가?). 저의 입장은 이렇습니다. 분류는 게이트웨이의 추측이 아니라 상류(upstream)의 계약(contract)이어야 합니다. 애플리케이션은 통합 시점에 작업 유형과 데이터 분류를 선언해야 하며, 이는 검증되고 버전 관리되며 감사(auditable) 가능해야 합니다. 프롬프트(prompt) 내용으로부터 이를 추론하는 것은 _누가 프롬프트를 읽을 수 있는지 결정하기 전에 프롬프트를 읽는 것_을 의미하며, 이는 당신이 결코 원치 않을 프라이버시 역설(privacy paradox)입니다. 만약 반드시 추론해야 한다면, 신뢰 경계(trust boundary) 내부에서 수행하고, 추적(trace)에 신뢰도(confidence)를 기록하며, 추론 결과가 거버넌스 규칙(governance rules)에 직접 영향을 주지 않도록 하십시오. 오직 기능 일치(capability match) 여부에만 영향을 주어야 합니다. 잘못된 추측으로 인한 폭발 반경(blast radius)을 "최적화되지 않은 경로(suboptimal route)"로 제한해야 하며, 결코 "규정 위반(compliance breach)"이 되게 해서는 안 됩니다.
무엇이 시뮬레이션되었는가, 그리고 왜 그것이 기능(feature)인가
솔직히 말씀드리면, 저장소(repo)를 호출하는 제공자(provider) 부분은 시뮬레이션되었습니다. execute()는 정해진 텍스트를 반환하며, 장애(outage)는 테스트 훅(test hook)을 통해 주입됩니다. 이는 의도된 것입니다. 시뮬레이션 경계는 정확히 함수 하나입니다. 이 프로젝트가 실제로 보여주는 모든 것 — 엄격한 게이트(hard gate), 랭킹(ranking), 페일 클로즈(fail-closed) 폴백(fallback), 추적(trace) — 은 실제적이고 결정론적(deterministic)이며 테스트로 검증되었고, API 키 없이도 어디에서나 몇 초 안에 실행됩니다.
프로덕션 강화(production hardening) 경로는 예측 가능합니다. YAML 파일은 승인 워크플로(approval workflows)를 갖춘 레지스트리(registry) 및 정책(policy) 서비스가 되고, 가용성 플래그(availability flag)는 자격 게이트(eligibility gate)에 피딩(feeding)되는 상태 확인(health checks) 및 서킷 브레이커(circuit breakers)가 되며, JSONL 감사(audit) 파일은 추가 전용 저장소(append-only store)가 되고, 비즈니스 유닛(business unit)은 요청 본문(request body) 대신 호출자의 인증된 신원(authenticated identity)으로부터 가져오게 됩니다. 이 중 그 어떤 것도 라우팅 코어(routing core)를 변경하지 않으며, 이것이 바로 이음새(seams)를 올바르게 맞추는 핵심입니다.
당신이 꼭 기억했으면 하는 세 문장
- 구조적으로 최적화보다 정책을 우선시하십시오 — 랭커 (ranker)와 폴백 (fallback)이 규정을 준수하지 않는 옵션을 물리적으로 볼 수 없도록 설계해야 합니다. 그래야 트레이드오프 (trade-off)가 발생할 여지조차 차단할 수 있습니다.
- Fail closed (실패 시 차단) — 이유를 명시한 거절은 기능(feature)이지만, 조용히 성능을 낮추는 것(silent downgrade)은 규제 기관으로부터 통보를 받게 될 사고(incident)입니다.
- 설명 가능성 (explainability)을 계약으로 만드십시오 — 응답 자체에서 정책 버전 (policy version)과 함께 "왜 이 모델인가?"라는 질문에 답할 수 없다면, 그것은 거버넌스 (governance)가 아니라 그저 느낌 (vibes)일 뿐입니다.
라우팅 코어 (routing core), Streamlit UI, FastAPI 엔드포인트 (endpoint), CLI 데모, 그리고 불변량 (invariants)을 고정하는 테스트 스위트 (test suite)를 포함한 전체 구현체는 **github.com/abhijatchaturvedi/aegis-gateway**에서 확인할 수 있습니다. 제가 가장 먼저 권장하는 시나리오는 다음과 같습니다: 모든 규정 준수 모델을 '다운(down)' 상태로 표시한 뒤, 건강한 외부 모델들이 바로 옆에 있음에도 불구하고 라우팅을 거부하는 모습을 지켜보십시오. 그 거절이야말로 전체 아키텍처 (architecture)를 하나의 응답으로 보여주는 핵심입니다.
"왜 이 요청이 해당 모델로 전송되었는가?"라는 질문에 대해 귀하의 조직은 어떤 답을 내놓고 있습니까? 다른 분들은 이 문제를 어떻게 다루고 있는지 진심으로 듣고 싶습니다. 특히 태스크 분류 (task-classification) 계약은 이 분야에서 가장 정립되지 않은 부분이라고 생각합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기