태그 기반 라우팅
태그 기반 라우팅 (Tag Based Routing)
배포(deployment)에 태그를 붙여서 요청이 특정 태그와 일치하는 배포로만 라우팅되게 하는 기능이에요. 무료/유료 티어를 나누거나, 제공자별로 트래픽을 분리하거나, 특정 배포를 제외하고 싶을 때 유용해요.
빠른 시작
1. config.yaml에 태그 정의
config.yaml
model_list:
- model_name: gpt-5.6-terra
litellm_params:
model: openai/fake
api_key: fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["free"] # 👈 Key Change
- model_name: gpt-5.6-terra
litellm_params:
model: openai/gpt-5.6-terra
api_key: os.environ/OPENAI_API_KEY
tags: ["paid"] # 👈 Key Change
- model_name: gpt-5.6-terra
litellm_params:
model: openai/gpt-5.6-terra
api_key: os.environ/OPENAI_API_KEY
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["default"] # OPTIONAL - All untagged requests will get routed to this
router_settings:
enable_tag_filtering: True # 👈 Key Change
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
2. tags=["free"]로 요청하기
curl -i http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-terra",
"messages": [
{"role": "user", "content": "Hello, Claude gm!"}
],
"tags": ["free"]
}'
응답:
{
"id": "chatcmpl-33c534e3d70148218e2d62496b81270b",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "\n\nHello there, how may I assist you today?",
"role": "assistant"
}
}
],
"model": "gpt-5.6-terra",
"object": "chat.completion",
"usage": {"completion_tokens": 12, "prompt_tokens": 9, "total_tokens": 21}
}
3. tags=["paid"]로 요청하기
curl -i http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-terra",
"messages": [
{"role": "user", "content": "Hello, Claude gm!"}
],
"tags": ["paid"]
}'
응답:
{
"id": "chatcmpl-9maCcqQYTqdJrtvfakIawMOIUbEZx",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "Good morning! How can I assist you today?",
"role": "assistant"
}
}
],
"model": "gpt-5.6-terra",
"object": "chat.completion",
"usage": {"completion_tokens": 10, "prompt_tokens": 12, "total_tokens": 22}
}
요청 헤더로 호출
curl -L -X POST 'http://0.0.0.0:4000/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ***" \
-H 'x-litellm-tags: free,my-custom-tag' \
-d '{
"model": "gpt-5.6-terra",
"messages": [
{
"role": "user",
"content": "Hey, how's it going?"
}
]
}'
기본 태그 설정
1. yaml에 기본 태그 설정
config.yaml
model_list:
- model_name: fake-openai-endpoint
litellm_params:
model: openai/fake
api_key: fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["default"] # 👈 Key Change - All untagged requests will get routed to this
model_info:
id: "default-model"
2. 프록시 시작
$ litellm --config /path/to/config.yaml
3. 태그 없이 요청하기
curl -i http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "fake-openai-endpoint",
"messages": [
{"role": "user", "content": "Hello, Claude gm!"}
]
}'
부정 태그 (Denylist)
태그 앞에 !를 붙이면 정확히 그 태그를 가진 배포를 제외해요. 허용되는 모든 대안을 나열하지 않고 특정 제공자나 모델 계열을 피하고 싶을 때 유용해요.
빠른 예시
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-terra",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["!provider:anthropic"]}
}'
provider:anthropic으로 태그된 모든 배포는 라우팅 전에 후보 풀에서 제거돼요. 남은 모든 배포가 적격이에요.
config 예시
config.yaml
model_list:
- model_name: chat
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["provider:anthropic"]
- model_name: chat
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["provider:openai"]
- model_name: chat
litellm_params:
model: vertex_ai/gemini-3.8-flash
api_key: os.environ/VERTEX_API_KEY
tags: ["provider:vertex"]
router_settings:
enable_tag_filtering: true
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
긍정 태그와 부정 태그 결합
긍정 태그로 티어를 선택하고 부정 태그로 그 티어 안에서 제공자를 제외할 수 있어요:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["paid", "!provider:anthropic"]}
}'
여러 제공자 제외
! 태그를 여러 개 보내면 둘 이상의 배포 그룹을 제외해요:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["!provider:anthropic", "!provider:openai"]}
}'
vertex 배포만 적격으로 남아요.
폴백 체인과의 부정
주 모델 그룹이 금지됐을 때, 라우터는 구성된 폴백으로 자동으로 넘어가요:
config.yaml
model_list:
- model_name: primary
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["provider:anthropic"]
- model_name: fallback
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["provider:openai"]
router_settings:
enable_tag_filtering: true
fallbacks:
- {"primary": ["fallback"]}
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "primary",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["!provider:anthropic"]}
}'
# primary is banned -> falls through to fallback (provider:openai)
부정 시맨틱
| 동작 | 상세 |
|---|---|
| 매칭 | 정확한 태그 문자열 매칭. !provider:anthropic은 정확히 provider:anthropic으로 태그된 배포만 제거 |
| 정규식 아님 | 부정 태그는 평문 문자열이지 regex 패턴이 아님. `!provider:(anthropic |
| 금지 전용 요청 | 요청이 ! 태그만 있고 긍정 태그가 없으면 기본 풀이 태그 없는 요청 동작을 따름: 기본 태그 배포가 있으면 그것들, 없으면 모든 배포. 그 배제 집합이 그 풀 위에 적용됨 |
| 전부 제외 | 부정 태그가 모든 후보를 제거하면 요청은 no_deployments_with_tag_routing으로 실패 |
| 태그 없는 배포 | tags 필드가 없는 배포는 부정 태그로 절대 제외되지 않음 |
| 헤더 | 부정 태그는 x-litellm-tags 헤더로도 동작: -H 'x-litellm-tags: !provider:anthropic' |
필수 태그 (AND)
태그 앞에 &를 붙여 필수로 만들어요. 배포는 요청의 모든 & 접두사 태그를 가져야 후보가 돼요. 일반 태그는 하나만 일치하면 되는 것과 달라요. 이는 독립적인 제약을 결합할 때 유용해요. 예를 들어 "높은 reasoning이어야 하고 정확히 Anthropic 출신이어야 함" 같은 경우인데, 일반 OR 태그는 낮은 reasoning의 Anthropic 배포나 높은 reasoning의 비-Anthropic 배포와 매치할 수 있어요.
빠른 예시
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["&reasoning_type:high", "&provider:anthropic"]}
}'
reasoning_type:high와 provider:anthropic 둘 다를 가진 배포만 적격이에요.
config 예시
config.yaml
model_list:
- model_name: chat
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["reasoning_type:high", "provider:anthropic"]
- model_name: chat
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["reasoning_type:high", "provider:openai"]
router_settings:
enable_tag_filtering: true
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
필수·부정·일반 태그 결합
! 제외가 먼저 적용되고, 그 다음 & 필수 태그가 남은 것을 좁히며, 그 다음 일반 태그가 생존자들에 평소의 OR 선호를 적용해요:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["&reasoning_type:high", "provider:anthropic", "provider:openai", "!inference:cerebras"]}
}'
# must be high-reasoning, AND (anthropic OR openai), AND not cerebras-hosted
필수-AND 시맨틱
| 동작 | 상세 |
|---|---|
| 매칭 | 부정 태그와 같은 정확한 태그 문자열 매칭. Regex 아님 |
| 필수 전용 요청 | 요청이 & 태그만 있으면(일반·부정 태그 없음) 기본 풀이 태그 없는 요청 동작을 따름: 기본 태그 배포가 있으면 그것들, 없으면 모든 배포. 필수 태그 필터가 그 풀 위에 적용됨 |
| Regex/헤더 선호가 필수-AND 요청을 희석하지 않음 | 필수-AND 전용 요청은 필수 태그를 충족하는 모든 배포를 항상 반환함. 그 중 하나가 무관한 tag_regex/User-Agent 선호와 우연히 매치돼도. regex 배포 하나로만 좁혀지지 않음 |
| 전부 제거 | 필수 태그가 모든 후보를 제거하면, 모델 그룹이 allow_fail_open을 선택하지 않는 한 요청은 no_deployments_with_tag_routing으로 실패 |
| 헤더 | 필수 태그는 x-litellm-tags 헤더로도 동작: -H 'x-litellm-tags: &reasoning_type:high' |
실패-열림 폴백 (allow_fail_open)
기본적으로 ! 또는 & 태그가 모델 그룹의 모든 배포를 제거하면 요청은 no_deployments_with_tag_routing으로 실패해요. 그룹의 모든 배포의 model_info에 allow_fail_open: true를 설정하면 요청을 실패시키는 대신 기본 태그 풀로 폴백해요.
이것은 명시적 옵트인이에요. 없으면 동작은 변하지 않아요. 충족할 수 없는 ! 또는 & 제약은 항상 예외를 일으켜요. 현재 부정이 이미 그러하듯이.
config 예시
config.yaml
model_list:
- model_name: chat
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["provider:anthropic"]
model_info:
allow_fail_open: true
- model_name: chat
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["provider:openai", "default"]
model_info:
allow_fail_open: true
router_settings:
enable_tag_filtering: true
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
allow_fail_open 없이
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["!provider:anthropic", "!provider:openai"]}
}'
# both deployments banned, allow_fail_open unset -> fails with no_deployments_with_tag_routing
allow_fail_open 사용 시
위 config를 쓰면 같은 요청이 대신 기본 태그 배포로 폴백하는데, 요청이 금지하려던 것을 포함해요:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["!provider:openai"]}
}'
# openai deployment banned; anthropic deployment is not "default"-tagged, so the
# pool falls back to whichever deployment IS "default"-tagged (openai here) -> the
# ban is treated as advisory rather than failing the request
warning
기본 태그 풀로의 폴백은 요청이 명시적으로 제외하려던 배포를 여전히 반환할 수 있어요. 호출자에 기인할 수 있는 어떤 제약에 대해서도 그래요. 충족할 수 없는 !/& 제약이 실패보다 성능 저하로 받아들여질 수 있는 모델 그룹에만 allow_fail_open을 설정하세요. 제약이 하드 컴플라이언스 요구 사항인 그룹("이 계정의 트래픽을 절대 Provider X로 라우팅하지 마세요")에는 설정하지 마세요.
키 수준이나 팀 수준 정책에서 상속된 제약은 버려지는 것으로부터 보호돼요. 프록시는 요청 자체가 공급한 것과 별도로 어떤 태그가 키/팀 메타데이터에서 왔는지를 추적해요(metadata.inherited_tags). 그래서 allow_fail_open은 호출자가 제어한 제약만 떨어뜨려요. 호출자가 상속 태그의 정확한 값을 충돌하는 값과 함께 재제출해도 그렇고, 이는 순수 집합 뺄셈이 정직한 호출자 전용 태그와 구분할 수 없는 값 충돌이에요. 호출자 제어 부분만 떨어뜨려도 라우팅할 것이 남지 않으면, 요청은 열리지 않고 예외를 일으켜요.
이 보호는 프록시 계층을 요구해요. 프록시를 우회하는 직접 SDK Router 호출(metadata.inherited_tags 미설정)은 무조건 완전히 제약 없는 기본 풀로 폴백해요. 모든 태그가 호출자 공급인 것처럼 말이죠. 이것은 allow_fail_open이 프록시 밖에서 항상 가졌던 동작과 같아요.
allow_fail_open 시맨틱
| 동작 | 상세 |
|---|---|
| 위치 | litellm_params가 아닌 model_info.allow_fail_open. 모델 그룹을 공유하는 모든 배포에 설정해 일관된 동작을 만들 것. 라우터는 아무 한 멤버를 확인 |
| 기본값 | 미설정 (false). 그룹이 옵트인하지 않는 한 기존 ! 부정 동작은 변하지 않음 |
| 범위 | ! 또는 &로 인한 소진만 게이트. 일반 태그 소진은 기존 동작 유지: 기본 풀이 있으면 거기로 폴백, 없으면 예외 |
| 폴백 풀 | 태그 없는 요청과 금지 전용 요청에 쓰는 것과 같은 기본 태그 풀. 요청 자신의 !/& 제약을 재적용하지 않음 |
| 상속 태그 보호 | 버림(discard)은 총량에서 호출자 자신의 태그를 빼는 것이 아니라 태그 출처(metadata.inherited_tags, 프록시가 키/팀 정책에서 채움)에 기반함. 키/팀 상속 제약은 호출자가 동일한 값을 별도로 제출해도 보호됨 |
명시적 라우팅 지시문 (tag_routing_prefix)
태그 기반 라우팅은 어떤 배포의 리터럴 태그 문자열이 우연히 매치하는지 확인해 "이 태그가 라우팅용인가"를 추론해요. 그 휴리스틱은 보통 맞지만, 아무것도 매치하지 않는 호출자 발명의 &/! 태그는 의심스러운 노이즈로 취급되어, 호출자가 정직하지만 충족 불가능한 요청을 진짜 원했어도 allow_fail_open의 폴백을 막을 수 있어요(위 참고). router_settings.tag_routing_prefix를 구성하면 호출자가 특정 태그를 신뢰할 수 있고 모호하지 않은 라우팅 지시문으로 표시할 수 있어, 그 태그들에 대해 그 모호함을 완전히 없앨 수 있어요.
구성된 접두사로 시작하는 모든 요청 태그는 접두사가 벗겨지고 평소 !/&/일반 태그 로직으로 매치돼요. 호출자가 라우팅 의도를 명시적으로 선언했으므로 어휘 검사가 필요 없어요. 접두사 없는 태그는 오늘의 기존 처리를 그대로 계속하므로, 접두사 채택에 마이그레이션이 필요 없어요.
빠른 예시
tag_routing_prefix: "route:"가 구성된 경우:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["feature:demo", "route:!provider:openai"]}
}'
# feature:demo is untouched attribution, never meant for routing; route:!provider:openai
# is stripped to !provider:openai and matched as an explicit ban
config 예시
config.yaml
model_list:
- model_name: chat
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["default", "provider:anthropic"]
model_info:
allow_fail_open: true
- model_name: chat
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["provider:openai"]
router_settings:
enable_tag_filtering: true
tag_routing_prefix: "route:" # opt-in: enables the prefix mechanism
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
접두사 태그와 미지의 태그 실패-열림 가드
접두사 붙은 &/! 태그는 어떤 배포의 리터럴 태그가 그것과 매치하는지와 무관하게 미지의 태그 실패-열림 가드에 알려진 것으로 집계돼요. 호출자가 라우팅 의도를 명시적으로 선언했기 때문이에요:
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["route:&provider:anthropic", "route:&custom-routing-key"]}
}'
# custom-routing-key matches no deployment's tags, but because it is prefix-marked
# the caller has explicitly declared it a routing directive -- allow_fail_open still
# proceeds to the default-tagged pool instead of being blocked by the "does an
# invented tag hide a satisfiable answer" guard
tag_routing_prefix 시맨틱
| 동작 | 상세 |
|---|---|
| 위치 | router_settings.tag_routing_prefix, 문자열, 기본 "" |
| 기본값 | "". str.startswith("")는 모든 문자열과 매치하므로, 구성 전까지 메커니즘은 완전한 no-op |
| 매칭 | 정확한 리터럴 접두사. 구분자가 자동으로 붙지 않음. 자신이 후행 구분자를 구성할 것(예: "route"가 아니라 "route:") — 구분자가 없는 접두사는 우연히 같은 문자로 시작하는 무관한 태그와 매치할 수 있음 |
| 스트리핑 순서 | 접두사가 먼저 벗겨지고 그 다음 !/& 파싱. 그래서 route:!provider:x와 route:&provider:x 둘 다 동작 |
| 접두사 없는 태그 | 오늘의 기존 처리를 그대로 계속: 일반 태그의 알려진-태그 어휘 휴리스틱, !/& 태그의 미지의 태그 실패-열림 가드 |
| 실패-열림과의 상호작용 | 접두사 붙은 &/! 태그는 배포 태그 어휘와 무관하게 allow_fail_open의 미지의 태그 가드에 알려진 것으로 집계됨 |
모델 그룹별 태그 필터링 (enable_tag_filtering)
model_info에 enable_tag_filtering을 설정하면 오직 한 모델 그룹에 대해서만 어느 방향으로든 router_settings.enable_tag_filtering을 오버라이드해요. 모델 그룹 수준에서 확인돼요. 라우터는 요청된 model_name 그룹의 아무 배포를 보고, 그 배포의 model_info.enable_tag_filtering이 설정돼 있으면 그 그룹에 대한 모든 요청에서 라우터 전체 기본값을 대체해요. allow_fail_open이 이미 쓰는 것과 같은 관례인, 그룹을 공유하는 모든 배포에 일관되게 설정하세요.
이것은 서로 무관한 많은 모델 그룹을 서빙하는 프록시에서 중요해요. 한 그룹의 &/! 기반 컴플라이언스 라우팅을 충족시키려고 router_settings.enable_tag_filtering을 전역으로 켜면 다른 모든 그룹의 요청도 태그 평가에 노출돼요. 그 그룹의 배포에만 model_info.enable_tag_filtering: true를 설정하면 그걸 피할 수 있어요. 반대도 동작해요. 프록시의 나머지가 다른 곳에서 태그 필터링을 강제하는 동안, 태그 면역 catch-all 또는 사고 대응 모델 그룹 하나를 분리해 낼 수 있어요.
빠른 예시
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "chat-compliance",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"tags": ["&provider:anthropic"]}
}'
# router_settings.enable_tag_filtering is false, but chat-compliance opts in via
# model_info.enable_tag_filtering: true, so the "&" constraint still applies
config 예시
config.yaml
model_list:
- model_name: chat-compliance
litellm_params:
model: anthropic/claude-sonnet-5
api_key: os.environ/ANTHROPIC_API_KEY
tags: ["provider:anthropic"]
model_info:
enable_tag_filtering: true # opt in for this group only
- model_name: chat-compliance
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
tags: ["provider:openai"]
model_info:
enable_tag_filtering: true
- model_name: incident-response
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEY
model_info:
enable_tag_filtering: false # opt out for this group only
router_settings:
enable_tag_filtering: false # router-wide default; chat-compliance overrides it
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
이 config에서 chat-compliance는 라우터 전체 기본값이 꺼져 있어도 모든 요청에 태그를 평가하고, incident-response를 포함한 다른 모든 모델 그룹은 태그를 무시하고 평범한 로드밸런싱 라우팅으로 폴백해요. 라우터 전체 기본값을 true로 뒤집어도 chat-compliance는 영향받지 않고 태그를 평가하는 반면, incident-response의 명시적 enable_tag_filtering: false는 그것을 면제로 유지해요.
enable_tag_filtering 시맨틱
| 동작 | 상세 |
|---|---|
| 위치 | litellm_params가 아닌 model_info.enable_tag_filtering. 모델 그룹을 공유하는 모든 배포에 설정. 라우터는 아무 한 멤버를 확인 |
| 우선순위 | 낮음→높음: router_settings.enable_tag_filtering(라우터 전체 기본값), 그 다음 요청된 그룹에 설정된 경우 model_info.enable_tag_filtering(어느 방향으로든 그 그룹에 대해서만 라우터 기본값 오버라이드), 그 다음 키/팀 설정의 요청 수준 enable_tag_filtering |
| 요청 수준 상승만 | 키/팀 설정에서 프록시가 설정하는 요청 수준 설정은 필터링을 켤 수만 있음. 요청 수준 enable_tag_filtering=True는 여전히 enable_tag_filtering: false로 스스로 옵트아웃한 그룹을 이김. 라우터와 모델 그룹이 이미 결정한 것 위로 필터링을 끌 요청 수준 방법은 없음 |
| 기본값 | 미설정. 이 오버라이드가 존재하기 전과 똑같이 모델 그룹이 router_settings.enable_tag_filtering에 위임 |
| 범위 | 그룹의 전체 태그 필터링 결정에 적용, &/! 처리만은 아님. 필터링이 비활성화된 그룹은 일반·부정·필수 태그를 모두 무시 |
| 헬스 무관 | 현재 건강한 배포뿐 아니라 모델 그룹에 구성된 모든 배포에서 해석됨. 단일 배포의 쿨다운이 오버라이드를 가진 유일한 배포를 순환에서 빼서 그룹 전체의 태그 정책을 조용히 비활성화(또는 활성화)할 수 없음 |
정규식 기반 태그 라우팅 (tag_regex)
배포에 tag_regex를 사용해 클라이언트가 명시적 태그를 보내지 않아도 그들의 헤더(예: User-Agent)로 들어오는 요청을 매치해요. 패턴은 운영자가 구성하고 서버 쪽에서 컴파일되며 호출자가 공급하지 않아요.
warning
User-Agent는 클라이언트가 공급하는 헤더이고 어떤 호출자도 어떤 값으로든 설정할 수 있어요. 트래픽 분류에는 tag_regex를, 접근 제어 강제에는 사용하지 마세요.
헤더 기반 라우팅은 그 자체로 보안 경계가 아니에요. 요청이 LiteLLM에 도달하기 전에 자격증명을 검증하고 인증되지 않은 트래픽을 거부하는 업스트림 인증 계층(예: API 게이트웨이나 리버스 프록시)을 통과할 때만 의미가 있어요. 그런 계층이 없으면 어떤 클라이언트든 User-Agent를 위조해서 도달해서는 안 되는 배포로 라우팅될 수 있어요.
1. Config
config.yaml
model_list:
# Claude Code traffic → dedicated deployment, matched by User-Agent
- model_name: claude-sonnet
litellm_params:
model: bedrock/converse/anthropic-claude-sonnet-4-6
aws_region_name: us-east-1
aws_role_name: arn:aws:iam::111122223333:role/LiteLLMClaudeCode
tag_regex:
- "^User-Agent: claude-code\\/" # matches claude-code/1.x, 2.x, etc.
model_info:
id: claude-code-deployment
# All other traffic falls back to the default deployment
- model_name: claude-sonnet
litellm_params:
model: bedrock/converse/anthropic-claude-sonnet-4-6
aws_region_name: us-east-1
aws_role_name: arn:aws:iam::444455556666:role/LiteLLMDefault
tags:
- default
model_info:
id: regular-deployment
router_settings:
enable_tag_filtering: true
tag_filtering_match_any: true
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
2. 라우팅 검증
# Claude Code request (User-Agent set automatically by Claude Code)
curl http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer ***" \
-H "User-Agent: claude-code/1.2.3" \
-d '{"model": "claude-sonnet", "messages": [{"role": "user", "content": "hi"}]}'
# -> x-litellm-model-id: claude-code-deployment
# Any other client (no matching User-Agent) -> default deployment
curl http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer ***" \
-d '{"model": "claude-sonnet", "messages": [{"role": "user", "content": "hi"}]}'
# -> x-litellm-model-id: regular-deployment
매칭 시맨틱
| 동작 | 상세 |
|---|---|
| 엔진 | Python re.search — 문자열 시작(^)이나 끝($)에 고정하고 싶지 않으면 패턴을 앵커할 필요 없음 |
| 입력 형식 | 패턴은 "Header-Name: value" 문자열에 매치됨. 현재는 User-Agent만 노출: User-Agent: claude-code/1.2.3 |
| 로직 | 항상 OR — 어떤 단일 패턴이 매치돼도 배포를 선택하기에 충분. tag_filtering_match_any=False는 일반 tags에만 적용되고 tag_regex에는 아님 |
| 잘못된 패턴 | re.compile에 실패하는 패턴은 로그되고 건너뜀. 절대 하드 에러를 일으키지 않음 |
| 일반 태그와의 상호작용 | 배포에 tags와 tag_regex가 둘 있고 tag_filtering_match_any=False면, 엄격 태그 검사가 이미 실패했을 때 regex 경로가 차단됨. Regex는 엄격 태그 정책을 오버라이드할 수 없음 |
| 신뢰된 입력 | 패턴은 config에서 운영자가 설정하며 호출자가 절대 공급하지 않음. 이것이 항상 평문 리터럴로 취급되는 부정 태그(요청 메타데이터의 !foo)와의 핵심 차이 |
부정 태그와의 상호작용
부정 제외는 tag_regex 매칭 전에 실행돼요. 배포가 일반 tags 목록과 tag_regex 둘 다 가질 때 순서가 중요해요:
- 라우터는
tags가 요청의 제외 집합과 교차하는 배포를 제거. tag_regex매칭은 생존 후보에만 실행.
사례 1: 부정이 일반 태그 배포를 제거; tag_regex 배포는 영향받지 않음
model_list:
- model_name: chat
litellm_params:
tag_regex: ["^User-Agent: claude-code\\/"] # no plain tags
model_info: {id: claude-code-deployment}
- model_name: chat
litellm_params:
tags: ["provider:anthropic"]
model_info: {id: anthropic-deployment}
curl ... -H "User-Agent: claude-code/1.2.3" \
-d '{"model":"chat","metadata":{"tags":["!provider:anthropic"]}}'
# anthropic-deployment is excluded; claude-code-deployment is matched by User-Agent
# -> x-litellm-model-id: claude-code-deployment
사례 2: 부정이 tag_regex를 가진 배포를 제거; 금지-전용 경로 발화
부정된 태그가 tag_regex와 같은 배포에 있으면 그 배포가 먼저 제외돼요. 후보 풀에 tag_regex 배포가 남지 않으면 has_tag_filter가 False가 되고, 금지-전용 경로가 발화하여 남은 배포가 직접 반환돼요.
model_list:
- model_name: chat
litellm_params:
tag_regex: ["^User-Agent: claude-code\\/"]
tags: ["group:claude"] # negation target is on the tag_regex deployment
model_info: {id: claude-code-deployment}
- model_name: chat
litellm_params:
tags: ["provider:openai"]
model_info: {id: openai-deployment}
curl ... -H "User-Agent: claude-code/1.2.3" \
-d '{"model":"chat","metadata":{"tags":["!group:claude"]}}'
# claude-code-deployment excluded; no tag_regex deployments remain
# ban-only path returns openai-deployment regardless of User-Agent
# -> x-litellm-model-id: openai-deployment
관측성
{
"tag_routing": {
"matched_via": "tag_regex",
"matched_value": "^User-Agent: claude-code\\/",
"user_agent": "claude-code/1.2.3",
"request_tags": []
}
}
팀 기반 태그 라우팅 (Enterprise)
구성
config.yaml
model_list:
- model_name: fake-openai-endpoint
litellm_params:
model: openai/fake
api_key: fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["teamA"] # 👈 Key Change
model_info:
id: "team-a-model"
- model_name: fake-openai-endpoint
litellm_params:
model: openai/fake
api_key: fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["teamB"] # 👈 Key Change
model_info:
id: "team-b-model"
- model_name: fake-openai-endpoint
litellm_params:
model: openai/fake
api_key: fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
tags: ["default"] # OPTIONAL - All untagged requests will get routed to this
router_settings:
enable_tag_filtering: True # 👈 Key Change
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
태그로 팀 생성
# Create Team A
curl -X POST http://0.0.0.0:4000/team/new \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{"tags": ["teamA"]}'
# Create Team B
curl -X POST http://0.0.0.0:4000/team/new \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{"tags": ["teamB"]}'
팀 멤버용 키 생성
# Generate key for Team A
curl -X POST http://0.0.0.0:4000/key/generate \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{"team_id": "team_a_id_here"}'
# Generate key for Team B
curl -X POST http://0.0.0.0:4000/key/generate \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{"team_id": "team_b_id_here"}'
라우팅 검증
curl -i -X POST http://0.0.0.0:4000/chat/completions \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"model": "fake-openai-endpoint",
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
출처: 문서
더 알아보기 (Learn more)
- 자동 라우터의 폴백과 로드밸런싱 동작 살펴보기
- 배포별
model_info옵션과 태그 필터링 상호작용 이해하기