모델 실험 (A/B 테스트)¶
모델 실험을 사용하면 클라이언트는 chat 같은 단일 모델 이름으로 요청을 보내고, 라우터는 지정된 가중치 비율에 따라 트래픽을 여러 업스트림 모델로 나누어 전달합니다. 각 실험은 요청마다 가중치가 부여된 여러 변형(variant) 중 하나로 해석되는 가상 모델 이름입니다. 각 변형은 디스패치할 업스트림 모델 ID를 지정하며, 필요에 따라 해당 변형을 처리할 백엔드 목록을 고정(pin)할 수 있습니다.
모델 이름 변환이 백엔드 엔진이 아닌 라우터에서 이루어지므로 자체 호스팅 엔진뿐 아니라 상용 호스팅 프로바이더(OpenAI, Anthropic, Gemini, Bedrock)에도 동일하게 적용됩니다.
실행 가능한 기본 템플릿은 continuum-router config generate --template ab-testing 명령으로 생성할 수 있습니다.
설정¶
model_experiments:
- name: chat-ab-2026-09 # 실험 이름: 고정 해시의 salt이자 메트릭 레이블
model: chat # 클라이언트가 사용하는 모델 이름
sticky_key: api_key # api_key(기본값) | user | none | header:<name>
sticky_ttl: 7d # 선택: 호출자를 창마다 한 번 재배정(미설정 = 영구)
fallback: within_variant # within_variant(기본값) | cross_variant
response_model: upstream # upstream(기본값) | client
response_headers: true # 귀속 헤더 출력(기본값 false)
hide_variant_models: false # 변형 모델 숨김 및 예약(기본값 false)
variants:
- id: control
model: gpt-5.6 # 백엔드로 보낼 업스트림 모델 ID
weight: 90
- id: candidate
model: qwen3.6-235b
backends: [vllm-pool] # 선택적 백엔드 고정
weight: 10
실험 필드¶
| 필드 | 기본값 | 설명 |
|---|---|---|
name | 필수 | 실험 이름([A-Za-z0-9._-] 문자 1~64자). 운영자가 지정하는 식별자이므로 fallback_chains의 모델 이름과 달리 /를 포함할 수 없습니다(반면 변형의 model은 서빙되는 모델 ID이므로 org/model 형식을 사용할 수 있습니다). 고정 해시의 salt이자 experiment 메트릭 레이블로 사용되며, 이름을 변경하면 모든 호출자의 배정이 새로 시작됩니다. |
model | 필수 | 클라이언트가 요청에 사용하는 모델 이름입니다. 실험 간에 중복될 수 없습니다. |
sticky_key | api_key | 동일한 호출자를 같은 변형에 유지할 때 사용할 요청 속성입니다. 고정 배정을 참고하세요. |
sticky_ttl | 미설정 | 선택적 배정 유지 기간입니다. 60s부터 365d까지 지정할 수 있으며(s, m, h, d 접미사 지원, 단위 생략 시 초), 설정하지 않으면 배정이 영구히 유지됩니다. 배정 유지 기간을 참고하세요. |
fallback | within_variant | 백엔드 실패 시 폴백 동작 방식입니다. 실패 의미론을 참고하세요. |
response_model | upstream | 응답의 model 필드에 반환할 모델 이름입니다. 응답 모델 이름을 참고하세요. |
response_headers | false | x-continuum-experiment 및 x-continuum-experiment-variant 응답 헤더를 반환할지 여부입니다. |
hide_variant_models | false | 모든 변형 모델을 클라이언트용 모델 목록에서 숨기고 실험 전용으로 예약합니다. 변형 모델 숨김과 예약을 참고하세요. |
variants | 필수 | 가중치가 부여된 변형 목록입니다(최대 16개). |
변형 필드¶
| 필드 | 기본값 | 설명 |
|---|---|---|
id | 필수 | 실험 내에서 고유한 변형 식별자([A-Za-z0-9._-] 문자 1~64자). variant 메트릭 레이블로 사용됩니다. |
model | 필수 | 해당 변형에 배정된 요청을 디스패치할 업스트림 모델 ID입니다. |
backends | [] | 선택적 백엔드 고정 목록입니다. 지정하면 여기에 나열된 백엔드만 해당 변형을 처리할 수 있습니다. |
weight | 필수 | 상대 정수 가중치입니다. 0으로 설정하면 변형 항목을 삭제하지 않고 비활성화합니다. |
model_experiments 섹션은 즉시 핫 리로드되며, 매 요청마다 현재 설정 스냅샷에서 읽습니다.
요청 해석 방식¶
모델 실험은 다음 모델 라우팅 인그레스에서 메타데이터 별칭 디스패치와 동일한 단계에서 실행됩니다.
- 채팅 완료(
/v1/chat/completions) 및 텍스트 완료(/v1/completions) - 임베딩(
/v1/embeddings) - Responses API(
/v1/responses,/v1/responses/compact포함) - Anthropic Messages(
/anthropic/v1/messages,count_tokens포함) - 이미지 생성 및 편집 엔드포인트
단계별 해석 순서¶
model_aliases재작성: Anthropic 및 Responses 인그레스에서는model_aliases가 요청 모델 이름을 먼저 변환하므로, 별칭 슬롯이 실험의 클라이언트 모델 이름을 가리키도록 구성할 수 있습니다.- 스마트 라우팅: 채팅 인그레스에서는 이어서 스마트 라우팅이 실행됩니다. 티어 타깃이 실험의 클라이언트 모델 이름이면 해당 실험이 변형을 해석합니다(단, 스마트 라우팅의
virtual_model자체는 실험 모델과 같을 수 없으며 설정 로드 시 거부됩니다). - 정책 및 접근 제어 게이트: 요청 파라미터 정책 스냅샷(
request_params.models.<name>)과 API 키별allowed_models게이트는 변형 모델이 아닌 클라이언트 모델 이름(예:chat)을 기준으로 평가합니다. - 변형 선택: 실험이 가중치에 따라 변형을 선택하고, 대상 모델을 해당 변형의 업스트림
model로 바꿉니다. - 메타데이터 별칭 디스패치: 선택된 업스트림 모델에 대해 메타데이터 별칭 해석이 실행되므로, 변형의
model이 메타데이터 별칭이더라도 정식(canonical) 모델 ID로 디스패치됩니다.
우회 조건, 오류 표시, 제외 인그레스¶
- 고정된 라우팅 결정 우회: 라우터가 변경해서는 안 되는 라우팅 결정이 이미 내려진 요청(AppProxy 인그레스 고정, Continuum Hub 모델 치환, 아비트리지 라우팅, 정확한 모델 예산 폴백 가드)은 실험 해석을 건너뜁니다.
- 선택 오류의 모델 이름 표시: 모델 이름 변환 이후 발생한 백엔드 선택 오류(
404 model not found또는403 model not permitted)에는 변형의 업스트림 모델 ID가 아니라 클라이언트가 요청한 모델 이름이 표시됩니다. - 실험 미적용 인그레스:
/v1/rerank,/embed_sparse,/v1/realtimeWebSocket,/v1/batches는 모델 실험을 해석하지 않고 요청된 모델 이름을 그대로 디스패치합니다. 이 엔드포인트들에는 구체적인 업스트림 모델 ID를 직접 지정해야 합니다.
고정 배정¶
고정 배정(sticky assignment)을 사용하면 동일한 호출자는 여러 라우터 레플리카나 프로세스 재시작 이후에도 항상 같은 변형에 배정됩니다. 라우터는 실험 이름(salt)과 고정 키 값을 SHA-256으로 해싱한 뒤 가중 랑데부 해싱(weighted rendezvous hashing)으로 변형을 결정하므로 프로세스 로컬 난수에 의존하지 않습니다.
sticky_key | 고정 값 기준 |
|---|---|
api_key | 요청에 제시된 API 키(Authorization: Bearer 또는 x-api-key)의 해시값. 원본 자격 증명은 저장되거나 로그에 기록되지 않습니다. |
user | OpenAI 요청의 user 필드 또는 Anthropic 요청의 metadata.user_id. |
header:<name> | 지정한 요청 헤더의 값. 헤더 이름은 유효한 HTTP 토큰이어야 합니다. |
none | 고정 배정을 사용하지 않습니다. |
고정 값이 없는 요청(API 키 없음, user 필드 누락, 지정한 헤더가 없거나 비어 있음)과 sticky_key: none인 모든 요청은 동일한 가중치를 따르는 독립 무작위 추첨으로 배정됩니다. 이러한 요청은 model_experiment_requests_total 메트릭에 assignment="random" 레이블로 기록되므로 무작위 배정 비중이 높은 실험을 쉽게 식별할 수 있습니다.
가중치 변경¶
가중 랑데부 해싱은 호출자마다 각 변형에 대해 독립적인 점수를 계산하므로, 가중치를 수정하면 변경된 비율만큼의 호출자만 이동합니다.
- 특정 변형의 가중치를 높이면 다른 변형에서 해당 변형 안으로만 호출자가 이동합니다.
- 특정 변형의 가중치를 낮추면 해당 변형 밖으로만 호출자가 이동합니다.
- 변형을 추가·제거하거나 가중치를
0으로 설정하면 해당 변형이 얻거나 잃는 호출자만 재배정됩니다.
예를 들어 90/10 분할을 80/20으로 변경하면 전체 호출자의 약 10%만 control에서 candidate로 이동하고 나머지 호출자는 기존 변형을 유지합니다. 모든 호출자를 처음부터 새로 배정하려면 실험의 name을 변경하세요.
배정 유지 기간¶
기본적으로 고정 배정된 호출자는 실험이 유지되는 동안 계속 같은 변형에 머뭅니다. sticky_ttl을 설정하면 별도 상태를 저장하지 않고도 지정된 시간 창(window)마다 호출자를 한 번씩 재배정합니다. 시간 창 인덱스를 호출자의 고정 해시에 함께 결합하므로 모든 레플리카가 동일한 벽시계 시각에 동일한 변형을 계산하며, 라우터를 재시작해도 배정이 흔들리지 않습니다.
- 고정 길이 및 호출자별 오프셋 분산: 각 호출자의 시간 창 길이는
sticky_ttl이며, 호출자 고유 해시에서 파생된 오프셋만큼 시작 시점이 어긋나 있습니다. 따라서 모든 호출자가 동시에 재배정되지 않고 시간 창 전반에 걸쳐 고르게 분산되어 재배정됩니다(창의 시작점은 호출자의 첫 요청 시각이 아닙니다). - 경계 시점의 독립 재추첨: 창 경계를 지날 때 호출자는 각 변형의 가중치 비율에 따라 새로 추첨됩니다. 따라서 매 재배정 시점에 변형이 바뀌는 호출자 비율은
1 - sum(share^2)입니다(90/10 분할에서는 18%, 50/50 분할에서는 50%). - 진행 중인 대화의 변형 전환: 호출자의 시간 창 경계를 넘어 이어지는 다중 턴 대화는 새로 배정된 변형 모델로 계속 처리됩니다.
- 창 내부의 최소 이동 유지: 한 시간 창 안에서 가중치를 변경하면 고정 배정과 동일하게 최소한의 호출자만 이동합니다.
sticky_ttl변경 시 즉시 재배정:sticky_ttl값을 바꾸면 호출자별 오프셋과 창 인덱스가 모두 달라지므로 다음 요청부터 즉시 새 배정이 적용됩니다.- 고정 키가 없을 때는 무효:
sticky_key: none이거나 요청에 고정 값이 없으면 매 요청이 이미 독립 무작위 추첨으로 처리됩니다.sticky_key: none과sticky_ttl을 함께 설정하면config validate가 경고를 출력합니다.
시간 창 인덱스는 응답 캐시 키에 포함되지 않습니다. 캐시 키에는 이미 최종 배정된 변형 식별자가 반영되기 때문입니다.
백엔드 고정¶
변형에 backends 목록을 지정하면 해당 요청이 사용할 수 있는 백엔드 허용 목록이 그 범위로 좁혀집니다. 이는 API 키의 allowed_backends 제약과 동일한 필터에 교집합으로 적용되므로 백엔드 선택, 재시도, 응답 캐시 경로, 모든 폴백 홉에 걸쳐 일관되게 유지됩니다. 만약 API 키의 allowed_backends와 변형의 backends 고정 목록 사이에 겹치는 백엔드가 하나도 없으면, 요청을 다른 변형으로 넘기지 않고 403 model not permitted로 거부합니다.
실패 의미론¶
fallback | 동작 |
|---|---|
within_variant | 변형 모델에 fallback.fallback_chains 항목이 없는 것처럼 동작합니다. 변형에 허용된 백엔드들 사이의 재시도와 백엔드 페일오버는 정상 수행되지만, 다른 모델로 폴백하지는 않습니다. 실험의 실제 트래픽 분할 비율을 왜곡 없이 보존할 때 적합합니다. |
cross_variant | 설정된 폴백 체인이 평소대로 실행되어 다른 변형 모델이나 실험 외부 모델로 넘어갈 수 있습니다. 폴백 홉이 응답을 처리하면 model_experiment_fallbacks_total 카운터가 증가하며, X-Original-Model 응답 헤더에는 실험의 클라이언트 모델 이름이 기록됩니다. |
cross_variant 모드에서도 변형의 백엔드 고정(backends)은 폴백 홉까지 계속 적용됩니다. 따라서 백엔드가 고정된 변형에서 다른 모델로 폴백을 허용하려면 폴백 대상 모델을 서빙하는 백엔드도 변형의 backends 목록에 함께 포함해야 합니다.
캐시 격리¶
배정된 실험 이름과 변형 ID는 exact, prefix, semantic 캐시가 공유하는 응답 캐시 네임스페이스에 포함됩니다. 따라서 두 변형이 동일한 백엔드와 동일한 업스트림 모델을 가리키더라도 한 변형의 요청이 다른 변형의 캐시 항목으로 응답하는 일은 없습니다. 실험 대상이 아닌 일반 요청의 캐시 키는 변경되지 않습니다.
응답 모델 이름¶
response_model | 응답 model 필드 동작 |
|---|---|
upstream | 업스트림 백엔드가 반환한 모델 이름(변형의 모델 ID)을 그대로 유지합니다. 별칭 디스패치의 기본 응답 동작과 동일합니다. |
client | JSON 응답 본문과 SSE data: 이벤트(Anthropic message_start의 message.model 및 Responses API의 response.model 포함)에서 모델 이름을 실험의 클라이언트 모델 이름으로 재작성합니다. |
response_model: client가 설정된 경우에도 32 MiB를 초과하는 JSON 응답 본문이나 16 MiB를 초과하는 단일 SSE 라인은 재작성하지 않고 그대로 통과시킵니다.
응답 본문의 model 필드는 그대로 두면서 클라이언트나 로그 파이프라인에서 어느 변형이 응답했는지 추적하려면 response_headers: true를 활성화하세요.
모델 목록¶
가중치가 양수(> 0)인 변형 모델 중 하나 이상이 활성 백엔드에서 서빙되는 동안 /v1/models, /v1/models/extended, /v1/models/{model}은 실험의 클라이언트 모델 이름을 노출합니다. 해당 모델 항목의 백엔드 목록은 각 변형 모델을 서빙하는 백엔드(변형별 backends 고정 적용 후)의 합집합으로 구성되며, 일반 모델과 동일하게 API 키별 백엔드 및 모델 가시성 필터를 거칩니다.
/anthropic/v1/models 역시 동일한 조건으로 실험의 클라이언트 모델 이름을 노출하며 display_name에도 같은 ID를 사용합니다.
변형 모델 숨김과 예약¶
hide_variant_models: true를 설정하면 클라이언트는 오직 실험의 클라이언트 모델 이름을 통해서만 변형 모델에 접근할 수 있습니다. 가중치가 0인 변형을 포함해 실험에 선언된 모든 변형 모델이 목록에서 숨겨지고 실험 전용으로 예약됩니다.
숨김¶
- 클라이언트 모델 목록 제외: 변형 모델은
/v1/models,/v1/models/extended,/v1/models/proxy,/anthropic/v1/models응답에서 제거되며,/v1/models/{model}을 직접 조회하면 HTTP404를 반환합니다. - 실험 모델 항목 유지: 실험의 클라이언트 모델 이름은 목록에 정상 노출되며, 변형 모델들이 목록에서 숨겨지기 전에 각 변형의 백엔드 정보를 합산해 가져옵니다.
- 운영자 인터페이스 유지: 운영자용 화면인 Admin API 모델 카탈로그(
/admin/models)와 WebUI에서는 변형 모델이 계속 표시됩니다.
예약¶
실험을 거치지 않고 예약된 변형 모델을 직접 지정한 클라이언트 요청은 거부됩니다.
- 존재 여부를 노출하지 않는 404 거부: 라우터에 등록되지 않은 모델을 요청했을 때와 동일한
404 model not found응답 본문을 반환하며, 클라이언트가 보낸 모델 이름을 그대로 표시해 해당 변형 모델의 존재 여부가 드러나지 않게 합니다. - 적용 인그레스 및 검사 시점:
model_aliases변환 직후(API 키별 모델 게이트가 검사하는 것과 동일한 모델 이름)에 chat completions, completions, embeddings, Responses API, Anthropic Messages 및count_tokens, 이미지 엔드포인트,/v1/rerank,/embed_sparse,/v1/realtime에서 실행됩니다. 특히 chat completions와 completions에서는 클라이언트가 보낸 모델 이름(AppProxy 인그레스 고정이 있다면 그 적용 직후 이름)을 기준으로 검사하므로, 이후 단계의 Hub 라우팅 결정이 이 예약을 우회할 수 없습니다. - 오류 및 라우팅 우선순위: 예약 거부는 미등록 모델에 대한
404가 반환될 시점보다 앞서 실행되지 않습니다. 잘못된 파일 참조나 rerank의query누락 같은 요청 유효성 오류, 그리고 API 키allowed_models위반에 따른403오류가 먼저 반환됩니다. 또한 chat completions에서 스마트 라우팅(intercept_all)이 요청 모델 이름을 가로채 대체하는 경우에는 일반 모델 이름과 마찬가지로 예약되지 않은 후보 모델로 정상 라우팅됩니다. - 별칭 우회 차단: 예약된 모델의 메타데이터 별칭으로 요청하거나, 예약된 별칭의 정식 모델 ID로 요청하더라도 동일하게 거부됩니다.
- API 키
allowed_models로 해제 불가: API 키의allowed_models에 변형 모델 이름을 명시하더라도 직접 호출은 허용되지 않습니다. - 실험 트래픽 허용: 실험 자체의 변형 모델 변환과, 예약된 모델에서 시작하는 폴백 체인을 따라 이동하는
fallback: cross_variant홉은 예약된 모델로 정상 디스패치됩니다. - 외부 라우팅 유입 차단: 스마트 라우팅은 후보 풀에서 예약된 모델을 제외하며, 예약되지 않은 일반 모델에서 시작하는 폴백 체인은 런타임에 예약된 모델 홉을 건너뜁니다.
- Hub 라우팅 결정 신뢰: Continuum Hub의 모델 치환, 아비트리지, 예산 폴백이 예약된 모델을 대상으로 지목한 경우 이는 운영자가 의도한 라우팅으로 간주하여 거부하지 않습니다(단, 치환 전 클라이언트가 보낸 원래 모델 이름에 대한 예약 검사는 그대로 수행됩니다).
- 응답 모델 이름 표시: 기본 설정인
response_model: upstream에서는 응답 본문의model필드에 변형의 업스트림 모델 ID가 반환됩니다. 클라이언트에게 실험 모델 이름만 노출하려면response_model: client로 설정하세요. - 배치에서는 실험과 예약을 해석하지 않음:
/v1/batches는 실험 이름을 다시 쓰거나hide_variant_models예약을 적용하지 않습니다. 다만 인라인 Anthropic 모델과 제한된 OpenAI 입력 파일의 모델을 Hub 티어 및 로컬 키의 유효 모델 허용 목록과 대조합니다. 따라서 예약된 변형은 해당 접근 목록이 허용하거나 호출자에게 모델 제한이 없을 때만 배치에 포함할 수 있습니다.
예약된 모델에 대한 직접 호출이 거부될 때마다 model_experiment_reserved_refusals_total{experiment} 카운터가 증가합니다.
예약 모델 관련 설정 검증¶
- 자기 참조 금지:
hide_variant_models: true인 실험은 자신의 클라이언트 모델 이름(model)을 변형의model로 사용할 수 없습니다(클라이언트가 호출해야 할 이름 자체가 예약되어 차단되기 때문이며 설정 로더가 이를 거부합니다). - 우회 경로 경고: 예약된 모델이
model_aliases의 타깃, 비예약 모델에서 시작하는 폴백 체인의 타깃,smart_routing.model_profiles항목, 또는hide_variant_models: false인 다른 실험의 변형으로 동시에 지정되어 있으면continuum-router config validate와 설정 로더가 경고를 남깁니다.
메트릭¶
모델 실험은 기존 요청·지연 시간·토큰 메트릭에 레이블을 덧붙여 카디널리티를 곱으로 늘리는 대신 전용 메트릭 패밀리를 사용합니다. 모든 레이블 값은 설정 파일 또는 고정된 열거값에서 나오며, 라우터당 최대 32개 실험·실험당 최대 16개 변형까지 구성할 수 있습니다.
| 메트릭 | 유형 | 레이블 |
|---|---|---|
model_experiment_requests_total | Counter | experiment, variant, status(success, client_error, server_error), assignment(sticky, random) |
model_experiment_request_duration_seconds | Histogram | experiment, variant |
model_experiment_tokens_total | Counter | experiment, variant, kind(prompt, completion) |
model_experiment_fallbacks_total | Counter | experiment, variant |
model_experiment_reserved_refusals_total | Counter | experiment(해당 모델을 예약한 실험의 설정 이름) |
지연 시간(duration)은 응답 헤더가 반환될 때까지의 시간으로 측정되며, 스트리밍 요청에서는 스트림이 시작되는 시점입니다. status 분류는 라우터가 최종 응답한 HTTP 상태 코드를 따릅니다.
토큰 계산 요청(/anthropic/v1/messages/count_tokens)도 실험을 해석하므로 배정된 변형의 모델로 토큰을 계산하고 백엔드 고정 및 귀속 헤더를 동일하게 적용합니다. 다만 매 호출 전에 토큰 수를 먼저 계산하는 클라이언트가 아래 PromQL 쿼리의 분모(변형별 요청 수)를 부풀리지 않도록 model_experiment_requests_total과 model_experiment_request_duration_seconds 집계에서는 제외됩니다.
# 변형별 오류율
sum by (experiment, variant) (rate(model_experiment_requests_total{status="server_error"}[5m]))
/ sum by (experiment, variant) (rate(model_experiment_requests_total[5m]))
# 변형별 요청당 completion 토큰
sum by (variant) (rate(model_experiment_tokens_total{kind="completion"}[5m]))
/ sum by (variant) (rate(model_experiment_requests_total{status="success"}[5m]))
OTLP 트레이스 내보내기가 활성화되어 있으면 router.request 스팬에 router.experiment와 router.experiment_variant 속성이 기록됩니다(트레이스 내보내기 참고). 컨트롤 플레인 빌드에서는 모델 실험이 해석한 모든 요청의 사용량 레코드에 품질 샘플링 여부와 무관하게 experiment_id(model_experiments[].name)와 experiment_variant_id(model_experiments[].variants[].id)가 포함됩니다.
검증¶
설정 로더는 시작 시점, 핫 리로드, Admin API 설정 적용 경로, 그리고 continuum-router config validate에서 다음 조건을 거부합니다.
- 중복된 실험
name, 또는 두 실험이 동일하게 사용하는 클라이언트model - 활성화된
smart_routing.virtual_model과 동일한 실험model - 변형이 하나도 없거나 모든 변형의 가중치가
0인 실험 1000000을 초과하는 가중치, 실험 내 중복된 변형id, 또는[A-Za-z0-9._-]{1,64}형식을 벗어난name/id- 존재하지 않는 백엔드나
internal: true백엔드를 지정한backends고정 - 고정된 모든 백엔드에 명시적
models목록이 정의되어 있는데 그중 어느 백엔드도 해당 변형의model을 포함하지 않는 경우 - 다른 실험의 클라이언트
model을 가리키는 변형model(실험 간 연쇄 불가) - 유효하지 않은
sticky_key또는 알 수 없는 설정 필드 - 선택적
s,m,h,d접미사가 붙은 정수가 아니거나60s~365d범위를 벗어난sticky_ttl hide_variant_models: true이면서 변형의model이 해당 실험 자신의 클라이언트model과 같은 설정- 백엔드 고정과 API 키
allowed_backends가 겹치지 않는 요청을 차단하기 위해 라우터가 내부 예약한 식별자와 동일한backends[].name(내부 식별자에는 NUL 바이트가 포함되어 있어 일반 이름과 우연히 충돌하지 않으며, 거부 경로가 실제 백엔드와 절대 매칭되지 않도록 보장합니다)
문제 해결¶
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| 사용자의 변형이 매일 바뀌거나, 반대로 전혀 바뀌지 않음 | sticky_ttl이 1d로 설정되어 있거나 미설정(영구 배정) 상태임 | 호출자가 동일한 변형을 유지할 기간(예: 7d)으로 sticky_ttl을 설정하거나, 영구 배정을 원하면 필드를 제거하세요. 창 경계마다 1 - sum(share^2) 비율의 호출자가 다른 변형으로 이동하는 것은 정상 동작입니다. |
| 실제 관측된 트래픽 비율이 가중치와 다름 | 대부분의 트래픽이 단일 고정 키를 공유하거나(예: 모든 최종 사용자가 하나의 API 키를 공용 사용), 고정 키가 누락되어 무작위 배정되고 있음 | 고유 값이 충분히 다양한 sticky_key: user 또는 header:<name>을 사용하고, model_experiment_requests_total{assignment="random"} 비중을 확인하세요. |
| 특정 변형의 오류율이 실제보다 낮게 보임 | fallback: cross_variant 설정으로 인해 실패한 요청이 다른 모델로 폴백되어 성공 처리됨 | fallback: within_variant로 변경하고 model_experiment_fallbacks_total 메트릭을 함께 확인하세요. |
모든 요청이 403 model not permitted로 거부됨 | API 키의 allowed_models에 실험의 클라이언트 모델 이름이 없거나, 변형의 backends 고정 목록과 키의 allowed_backends 사이에 겹치는 백엔드가 없음 | 실험의 model 이름을 allowed_models에 추가하고, 변형의 고정 백엔드와 키의 허용 백엔드가 겹치도록 조정하세요. |
| 실험 활성화 후 변형 모델을 직접 호출하면 404가 반환됨 | hide_variant_models: true가 설정되어 변형 모델이 실험 트래픽 전용으로 예약됨 | 실험의 클라이언트 모델 이름으로 호출하거나, 변형 모델 직접 호출을 허용해야 한다면 hide_variant_models: false로 변경하세요. |
/v1/models 목록에 실험 모델이 나타나지 않음 | 가중치가 양수인 변형 모델 중 현재 가시적인 백엔드에서 서빙되는 모델이 없음 | 각 백엔드에서 변형 모델이 정상적으로 발견(discovery)되고 있는지 확인하세요. |