콘텐츠로 이동

고급 설정

전역 프롬프트

전역 프롬프트를 사용하면 모든 요청에 시스템 프롬프트를 주입하여 보안, 규정 준수 및 동작 가이드라인에 대한 중앙 집중식 정책 관리를 제공할 수 있습니다. 프롬프트는 인라인으로 정의하거나 외부 Markdown 파일에서 로드할 수 있습니다.

기본 설정

global_prompts:
  # 인라인 기본 프롬프트
  default: |
    회사 보안 정책을 따라야 합니다.
    내부 시스템 세부 정보를 공개하지 마십시오.
    도움이 되고 전문적이어야 합니다.

  # 병합 전략: prepend (기본), append, 또는 replace
  merge_strategy: prepend

  # 전역 프롬프트와 사용자 프롬프트 사이의 사용자 정의 구분자
  separator: "\n\n---\n\n"

외부 프롬프트 파일

복잡한 프롬프트의 경우 외부 Markdown 파일에서 콘텐츠를 로드할 수 있습니다. 그러면 구문 강조가 있는 편집 환경, 설정 파일 노이즈 없는 버전 관리, 프롬프트 업데이트에 대한 핫 리로드 지원이 가능해집니다.

global_prompts:
  # 프롬프트 파일이 있는 디렉토리 (설정 디렉토리 기준 상대 경로)
  prompts_dir: "./prompts"

  # 파일에서 기본 프롬프트 로드
  default_file: "system.md"

  # 파일에서 백엔드별 프롬프트
  backends:
    anthropic:
      prompt_file: "anthropic-system.md"
    openai:
      prompt_file: "openai-system.md"

  # 파일에서 모델별 프롬프트
  models:
    gpt-4o:
      prompt_file: "gpt4o-system.md"
    claude-3-opus:
      prompt_file: "claude-opus-system.md"

  merge_strategy: prepend

프롬프트 해석 우선순위

요청에 사용할 프롬프트 결정 시:

  1. 모델별 프롬프트 (최고 우선순위) - global_prompts.models.<model-id>
  2. 백엔드별 프롬프트 - global_prompts.backends.<backend-name>
  3. 기본 프롬프트 - global_prompts.default 또는 global_prompts.default_file

각 레벨에서 prompt (인라인)와 prompt_file이 모두 지정되면 prompt_file이 우선합니다.

병합 전략

전략 동작
prepend 전역 프롬프트가 사용자 시스템 프롬프트 앞에 추가 (기본)
append 전역 프롬프트가 사용자 시스템 프롬프트 뒤에 추가
replace 전역 프롬프트가 사용자 시스템 프롬프트를 완전히 대체

REST API 관리

프롬프트 파일은 Admin API를 통해 런타임에 관리할 수 있습니다:

# 모든 프롬프트 목록
curl http://localhost:8080/admin/config/prompts

# 특정 프롬프트 파일 가져오기
curl http://localhost:8080/admin/config/prompts/prompts/system.md

# 프롬프트 파일 업데이트
curl -X PUT http://localhost:8080/admin/config/prompts/prompts/system.md \
  -H "Content-Type: application/json" \
  -d '{"content": "# 업데이트된 시스템 프롬프트\n\n새 콘텐츠."}'

# 디스크에서 모든 프롬프트 파일 리로드
curl -X POST http://localhost:8080/admin/config/prompts/reload

전체 API 문서는 Admin REST API 참조를 참조하세요.

보안 고려 사항

  • 경로 탐색 보호: 디렉토리 탐색 공격 방지를 위한 모든 파일 경로 검증
  • 파일 크기 제한: 개별 파일 1MB, 전체 캐시 50MB 제한
  • 상대 경로만: 프롬프트 파일은 설정된 prompts_dir 또는 설정 디렉토리 내에 있어야 함
  • 샌드박스 접근: 허용된 디렉토리 외부 파일은 거부

핫 리로드

전역 프롬프트는 즉시 핫 리로드를 지원합니다. 프롬프트 설정 또는 파일 변경 사항은 서버 재시작 없이 다음 요청에 적용됩니다.

모델 메타데이터

Continuum Router는 모델 기능, 가격, 한도에 대한 상세 정보를 제공하는 풍부한 모델 메타데이터를 지원합니다. 이 메타데이터는 /v1/models API 응답에 반환되며 클라이언트가 정보에 입각한 모델 선택 결정을 내리는 데 사용할 수 있습니다.

메타데이터 소스

모델 메타데이터는 세 가지 방법으로 설정할 수 있습니다 (우선순위 순):

  1. 백엔드별 model_configs (최고 우선순위)
  2. 외부 메타데이터 파일 (model-metadata.yaml)
  3. 메타데이터 없음 (모델은 메타데이터 없이도 작동)

외부 메타데이터 파일

model-metadata.yaml 파일을 만드세요:

models:
  - id: "gpt-4"
    aliases:                    # 이 메타데이터를 공유하는 대체 ID
      - "gpt-4-0125-preview"
      - "gpt-4-turbo-preview"
      - "gpt-4-vision-preview"
    metadata:
      display_name: "GPT-4"
      summary: "복잡한 작업을 위한 가장 유능한 GPT-4 모델"
      capabilities: ["text", "image", "function_calling"]
      knowledge_cutoff: "2024-04"
      pricing:
        input_tokens: 0.03   # 1000 토큰당
        output_tokens: 0.06  # 1000 토큰당
      limits:
        context_window: 128000
        max_output: 4096

  - id: "llama-3-70b"
    aliases:                    # 동일 모델의 다른 양자화
      - "llama-3-70b-instruct"
      - "llama-3-70b-chat"
      - "llama-3-70b-q4"
      - "llama-3-70b-q8"
    metadata:
      display_name: "Llama 3 70B"
      summary: "강력한 성능의 오픈 소스 모델"
      capabilities: ["text", "code"]
      knowledge_cutoff: "2023-12"
      pricing:
        input_tokens: 0.001
        output_tokens: 0.002
      limits:
        context_window: 8192
        max_output: 2048

설정에서 참조하세요:

model_metadata_file: "model-metadata.yaml"

Thinking 패턴 설정

일부 모델은 추론/사고 콘텐츠를 비표준 방식으로 출력합니다. 라우터는 스트리밍 응답을 적절히 변환하기 위해 모델별 thinking 패턴 설정을 지원합니다.

패턴 유형:

패턴 설명 예시 모델
none thinking 패턴 없음 (기본값) 대부분의 모델
standard 명시적 시작/종료 태그 (<think>...</think>) 커스텀 추론 모델
unterminated_start 시작 태그 없이 종료 태그만 있음 nemotron-3-nano

설정 예시:

models:
    - id: nemotron-3-nano
      metadata:
        display_name: "Nemotron 3 Nano"
        capabilities: ["chat", "reasoning"]
        # Thinking 패턴 설정
        thinking:
          pattern: unterminated_start
          end_marker: "</think>"
          assume_reasoning_first: true

Thinking 패턴 필드:

필드 타입 설명
pattern string 패턴 유형: none, standard, 또는 unterminated_start
start_marker string standard 패턴용 시작 마커 (예: <think>)
end_marker string 종료 마커 (예: </think>)
assume_reasoning_first boolean true인 경우, 종료 마커까지 첫 토큰들을 추론으로 처리

작동 방식:

모델에 thinking 패턴이 설정되면:

  1. 스트리밍 응답이 가로채져 변환됨
  2. end_marker 이전 콘텐츠는 reasoning_content 필드로 전송
  3. end_marker 이후 콘텐츠는 content 필드로 전송
  4. 출력은 호환성을 위해 OpenAI의 reasoning_content 형식을 따름

출력 예시:

// 추론 콘텐츠 (종료 마커 이전)
{"choices": [{"delta": {"reasoning_content": "분석해 보겠습니다..."}}]}

// 일반 콘텐츠 (종료 마커 이후)
{"choices": [{"delta": {"content": "답은 42입니다."}}]}

Responses-API 전용 모델

OpenAI는 일부 모델을 Responses API(/v1/responses)로만 노출합니다. 이런 모델은 /v1/chat/completions에서 닿을 수 없어, Chat Completions 엔드포인트로 요청하면 업스트림에서 404 not_found가 돌아옵니다.

responses_only capability 플래그가 이런 모델을 표시하면, 라우터가 자동으로 Responses API 쪽으로 dispatch합니다. 기본값은 false이므로 기존 모델 항목은 그대로 두면 됩니다.

설정 예시:

models:
    - id: gpt-5.4-pro
      metadata:
        display_name: "GPT-5.4 Pro"
        capabilities: ["chat", "vision", "code", "reasoning", "tool"]
        # /v1/responses에서만 제공되며, /v1/chat/completions로는 접근 불가.
        responses_only: true
        limits:
          context_window: 1050000
          max_output: 128000

기본 제공되는 Responses-API 전용 모델

아래 목록은 model-metadata.yaml과 내장 OpenAI 레지스트리(src/infrastructure/backends/openai/models/gpt5_family.rs)와 동기화되어 있습니다. 새로운 Responses-API 전용 모델이 업스트림에 추가되면 두 파일을 함께 갱신해야 합니다.

모델 ID 출처 비고
gpt-5.2-pro 내장 OpenAI 메타데이터 + model-metadata.yaml 어려운 질문용 최상위 모델, xhigh reasoning effort
gpt-5.4-pro model-metadata.yaml Frontier 급 심층 추론, medium/high/xhigh 지원
gpt-5.5-pro model-metadata.yaml 고난도 작업용 GPT-5.5 고성능 변형

플래그는 메타데이터 전체와 같은 우선순위 체인(백엔드 model_configs > model-metadata.yaml > 내장 OpenAI 메타데이터)을 따르므로, 운영자가 정의한 항목으로 어떤 모델의 기본값도 덮어쓸 수 있습니다.

새 모델을 Responses-API 전용으로 표시하기

추가 모델을 Responses-API 전용으로 표시하려면, 지원되는 소스 중 한 곳의 모델 항목 metadata 블록에 responses_only: true를 추가합니다. 배포 범위에 맞는 우선순위를 골라 사용합니다:

  • model-metadata.yaml: 모든 백엔드에 적용되는 라우터 전체 기본값. 기존 capability 메타데이터 옆에 플래그를 추가하면 되고 다른 필드는 건드릴 필요가 없습니다. 공급자 전반에서 일관되게 Responses-API 전용으로 출시되는 새 Pro 모델에는 이 위치를 권장합니다.
  • config.yaml의 백엔드 model_configs: 백엔드별 오버라이드 (예: 자체 호스팅한 Pro 모델 클론을 Chat Completions 엔드포인트에 노출했고, /v1/responses로 보내면 안 되는 경우). 백엔드 단의 responses_only: false는 해당 백엔드에서만 메타데이터 파일 기본값을 덮어씁니다.
  • src/infrastructure/backends/openai/models/gpt5_family.rs의 내장 OpenAI 레지스트리: 바이너리에 같이 들어가는 모델용. 여기에 새 항목을 추가하면 외부에서 로드되는 메타데이터와 일관성을 유지하기 위해 model-metadata.yaml에도 같이 반영합니다.

이런 소스를 갱신한 뒤에는 라우터를 재시작하거나 핫 리로드를 트리거해야 새 플래그가 후속 요청에 적용됩니다.

Dispatch 동작

responses_only=true가 설정된 모델은 /v1/chat/completions로 도달했을 모든 공개 surface에서 라우터가 Responses API로 보냅니다:

  • /v1/chat/completions: 요청이 업스트림 /v1/responses 엔드포인트로 투명하게 전달되고, 응답은 strict 모드의 chat.completion(스트리밍은 chat.completion.chunk) 봉투로 다시 변환됩니다.
  • /anthropic/v1/messages: Anthropic 형식 요청이 Responses API 형태로 변환되어 /v1/responses로 dispatch되고, 업스트림 응답이 다시 Anthropic Messages JSON(스트리밍은 Anthropic SSE 이벤트 시퀀스)으로 변환됩니다. 도구 호출 왕복, 웹 검색 에뮬레이션, Unix 소켓 전송 모두 이 플래그에서 분기합니다.

두 경우 모두 dispatch는 클라이언트에 투명합니다. 요청과 응답 모양이 클라이언트가 호출한 surface와 일치하므로, responses_only 모델을 쓰기 위해 클라이언트 쪽을 바꿀 필요가 없습니다.

백엔드 유형 제약

/v1/responses는 OpenAI와 Azure OpenAI 백엔드에서만 제공됩니다. responses_only 모델이 OpenAI/Azure OpenAI가 아닌 백엔드와 짝지어지면, 라우터가 업스트림 dispatch 전에 400 invalid_request_error로 거절합니다(/anthropic/v1/messages에서는 Anthropic 모양, /v1/chat/completions에서는 OpenAI 모양). 메시지에 모델 이름과 설정된 백엔드 유형이 함께 들어가므로, 잘못된 설정이 클라이언트 로그에서 바로 보입니다.

(backend, model) 쌍별 첫 dispatch는 info 레벨로 로깅되어, 디버그 로그를 켜지 않고도 운영자가 Responses-API 라우팅 여부를 확인할 수 있습니다.

네임스페이스 인식 매칭

라우터는 네임스페이스 접두사가 있는 모델 ID를 지능적으로 처리합니다. 예:

  • 백엔드 반환: "custom/gpt-4", "openai/gpt-4", "optimized/gpt-4"
  • 메타데이터 정의: "gpt-4"
  • 결과: 모든 변형이 일치하고 동일한 메타데이터 수신

따라서 다른 백엔드가 공통 메타데이터 정의를 공유하면서 자체 명명 규칙을 사용할 수 있습니다.

메타데이터 우선순위 및 별칭 해석

모델의 메타데이터를 조회할 때, 라우터는 다음 우선순위 체인을 사용합니다:

  1. 정확한 모델 ID 매칭
  2. 정확한 별칭 매칭
  3. 날짜 접미사 정규화 (자동, 설정 불필요)
  4. 와일드카드 패턴 별칭 매칭
  5. 기본 모델 이름 폴백 (네임스페이스 제거)

각 소스 (백엔드 설정, 메타데이터 파일, 내장) 내에서 동일한 우선순위가 적용됩니다:

  1. 백엔드별 model_configs (최고 우선순위)

    backends:
      - name: "my-backend"
        model_configs:
          - id: "gpt-4"
            aliases: ["gpt-4-turbo", "gpt-4-vision"]
            metadata: {...}  # 이것이 우선
    

  2. 외부 메타데이터 파일 (두 번째 우선순위)

    model_metadata_file: "model-metadata.yaml"
    

  3. 내장 메타데이터 (OpenAI 및 Gemini 백엔드용)

자동 날짜 접미사 처리

LLM 프로바이더는 날짜 접미사가 있는 모델 버전을 자주 릴리스합니다. 라우터는 설정 없이 자동으로 날짜 접미사를 감지하고 정규화합니다:

지원되는 날짜 패턴:

  • -YYYYMMDD (예: claude-opus-4-5-20251130)
  • -YYYY-MM-DD (예: gpt-4o-2024-08-06)
  • -YYMM (예: o1-mini-2409)
  • @YYYYMMDD (예: model@20251130)

작동 방식:

요청: claude-opus-4-5-20251215
      ↓ (날짜 접미사 감지됨)
조회: claude-opus-4-5-20251101  (기존 메타데이터 항목)
      ↓ (기본 이름 일치)
결과: claude-opus-4-5-20251101 메타데이터 사용

이는 모델 패밀리당 메타데이터를 한 번만 설정하면 되고, 새로운 날짜 버전이 자동으로 메타데이터를 상속한다는 것을 의미합니다.

자동 양자화 및 형식 접미사 처리

실제 환경에서 /v1/models, 라우팅 로직, 백엔드 메타데이터 보강에 도착하는 모델 ID는 정규 기본 ID 뒤에 하나 이상의 양자화, 형식, 플레이버 토큰이 붙은 형태인 경우가 많습니다. 라우터는 허용 목록에 등록된 이런 토큰을 반복적으로 제거하고, 각 peel 이후 정확한 ID, 정확한 별칭, 날짜 접미사 매칭을 다시 시도하므로, 메타데이터는 정규 기본 ID에 대해서만 설정하면 됩니다.

토큰 카테고리

다음 후행 토큰이 감지되어 제거됩니다 (대소문자 무시):

카테고리 예시
비트 폭 -2bit, -3bit, -4bit, -5bit, -6bit, -8bit, -16bit
GGUF / llama.cpp 양자화 -Q4_K_M, -Q4_K_S, -Q5_K_M, -Q6_K, -Q8_0, -Q2_K, -IQ2_XS, -IQ3_XXS, -IQ4_XS, -F16, -F32, -BF16
FP 형식 -FP4, -FP8, -FP16, -FP32, -NVFP4, -MXFP4
INT 형식 -INT2, -INT4, -INT8
라이브러리 태그 -AWQ, -GPTQ, -BNB, -HQQ, -EXL2, -EXL3, -MLX
Imatrix / 축약형 -i1부터 -i8, -q2부터 -q8
Unsloth 동적 -UD-Q*, -UD-IQ*
컨테이너 형식 -GGUF, -GGML, -SAFETENSORS
플레이버 -it, -instruct, -chat, -base, -thinking, -qat

파라미터 수 접미사는 보존됩니다

파라미터 수처럼 보이는 토큰은 끝이 같은 b로 끝나더라도 절대 제거되지 않습니다:

  • 유지: -32b, -70b, -8b, -4b, -a3b, -a22b, -0.6b, -1.7b, -e4b
  • 제거: -4bit, -8bit, -16bit (리터럴 bit 접미사가 양자화를 나타냄)

이 구분 덕분에 qwen3-32b 같은 파라미터 수 변형은 명시적인 qwen3-32b 메타데이터로만 해석되며, 실수로 제거되어 일반 qwen3 항목으로 연결되는 일이 없습니다.

계층적 peel

토큰은 한 번에 하나씩 제거됩니다. 각 peel 이후 라우터는 다음 peel을 시도하기 전에 정확한 ID, 정확한 별칭, 날짜 접미사 매칭을 다시 실행합니다. 그래서 요청이 gemma-3-4b-it-qat-4bit이더라도 gemma-3-4b-it-qat 같은 별칭 설정이 여전히 승리할 수 있습니다:

요청: gemma-3-4b-it-qat-4bit
      ↓ (-4bit peel)
시도: gemma-3-4b-it-qat
      ↓ (gemma-3-4b-qat의 별칭과 매칭)
결과: gemma-3-4b-qat 메타데이터 사용

우선순위 참고

제거는 정확한 ID 및 정확한 별칭 매칭 이후에 실행됩니다. 정규 기본 ID가 우연히 허용 목록 토큰으로 끝나더라도 (예: gemma-3-12b-qat) peel 단계가 실행되기 전에 승리하므로, 기존 설정은 안정적으로 유지됩니다.

접미사 순서 모호성

실제 모델 ID에는 -qat-4bit 순서와 -4bit-qat 순서가 모두 나타납니다. peel은 오른쪽에서 토큰을 하나씩 제거하므로, 중간 형태는 입력에 토큰이 나타난 순서를 그대로 따릅니다. gemma-3-12b-qat-4bit의 매칭 순서는 gemma-3-12b-qat-4bitgemma-3-12b-qatgemma-3-12b이고, gemma-3-12b-4bit-qatgemma-3-12b-4bit-qatgemma-3-12b-4bitgemma-3-12b로 진행됩니다. 두 접미사 순서가 모두 동일한 QAT 변형 메타데이터로 해석되어야 한다면, 정규 QAT 기본 ID(gemma-3-12b-qat)에 해당 메타데이터를 설정하고 비QAT 형태(gemma-3-12b)에는 자체 항목을 두세요. 각 peel 깊이에서 가장 깊이 성공한 매칭이 승리합니다. QAT 변형과 비QAT 변형에 서로 다른 티어 또는 기능 메타데이터가 필요하다면, peel 순서에만 의존하기보다 순서 조합을 열거하는 별칭을 사용하는 편이 좋습니다.

길이 제한

레이어드 peel 단계는 비정상적인 입력에 대한 심층 방어로 입력 길이를 256자, 반복 횟수를 8회 peel로 제한합니다. 매칭 자체는 계속 실행되지만 (정확한 ID 및 정확한 별칭 단계는 그대로 유효), peel 단계는 긴 허용 목록 토큰 체인을 따라가는 대신 조기에 중단됩니다. 요청 핸들러도 모든 채팅 / 완료 / 임베딩 엔드포인트의 model 필드에 동일한 256자 제한을 적용하므로, 정상적인 트래픽이 내부 제한에 걸리는 일은 없습니다.

대소문자 무시

제거는 대소문자를 구분하지 않으므로 Qwen3.5-4B-4bit, QWEN3.5-4B-4BIT, qwen3.5-4b-4bit가 모두 동일한 qwen3.5-4b 메타데이터 항목으로 해석됩니다. 정확한 ID 및 정확한 별칭 매칭 단계 (1단계와 2단계)는 여전히 대소문자를 구분하므로, BAAI/bge-m3 같은 HuggingFace 스타일 별칭은 기존 동작을 유지합니다.

와일드카드 패턴 매칭

별칭은 * 문자를 사용한 glob 스타일 와일드카드 패턴을 지원합니다:

  • 접두사 매칭: claude-*claude-opus, claude-sonnet 등과 매칭
  • 접미사 매칭: *-previewgpt-4o-preview, o1-preview 등과 매칭
  • 중위 매칭: gpt-*-turbogpt-4-turbo, gpt-3.5-turbo 등과 매칭

와일드카드 패턴이 있는 설정 예:

models:
    - id: "claude-opus-4-5-20251101"
    aliases:
        - "claude-opus-4-5"     # 기본 이름의 정확한 매칭
        - "claude-opus-*"       # 모든 claude-opus 변형에 대한 와일드카드
    metadata:
        display_name: "Claude Opus 4.5"
        # 자동 매칭: claude-opus-4-5-20251130, claude-opus-test 등

    - id: "gpt-4o"
    aliases:
        - "gpt-4o-*-preview"    # 프리뷰 버전 매칭
        - "*-4o-turbo"          # 접미사 매칭
    metadata:
        display_name: "GPT-4o"

우선순위 참고: 정확한 별칭은 항상 와일드카드 패턴보다 먼저 매칭되어 둘 다 매칭될 수 있는 경우에도 예측 가능한 동작을 보장합니다.

모델 변형에 별칭 사용

별칭은 특히 다음에 유용합니다:

  • 다른 양자화: qwen3-32b-i1, qwen3-23b-i4 → 모두 qwen3 메타데이터 사용
  • 버전 변형: gpt-4-0125-preview, gpt-4-turbogpt-4 메타데이터 공유
  • 배포 변형: llama-3-70b-instruct, llama-3-70b-chat → 동일 기본 모델
  • 날짜 버전: claude-3-5-sonnet-20241022, claude-3-5-sonnet-20241201 → 메타데이터 공유 (날짜 접미사 처리로 자동)

별칭이 있는 설정 예:

model_configs:
  - id: "qwen3"
    aliases:
      - "qwen3-32b-i1"     # 1비트 양자화된 32B
      - "qwen3-23b-i4"     # 4비트 양자화된 23B
      - "qwen3-16b-q8"     # 8비트 양자화된 16B
      - "qwen3-*"          # 다른 모든 qwen3 변형에 대한 와일드카드
    metadata:
      display_name: "Qwen 3"
      summary: "Alibaba의 Qwen 모델 계열"
      # ... 나머지 메타데이터

별칭과 접미사 정규화: 선택 기준

비정규 모델 id를 해당 메타데이터 항목에 연결하는 두 가지 레이어가 있습니다. 명시적 YAML 별칭과 src/models/pattern_matching.rs의 레이어드 접미사 peel 허용 목록이 그것입니다. 이 둘은 상호 보완적이며 중복이 아닙니다. 이 섹션에서는 새 항목을 추가할 때 어느 쪽을 선택해야 하는지 설명합니다.

매칭 단계 순서

파이프라인은 다음 순서로 실행되며, 앞 단계에서 매칭이 성공하면 이후 단계는 건너뜁니다.

  1. 정확한 모델 id (대소문자 구분).
  2. 정확한 별칭 (대소문자 구분).
  3. 날짜 접미사 정규화 (-YYYYMMDD, -YYYY-MM-DD, -YYMM, @YYYYMMDD).
  4. 레이어드 양자화 / 형식 / 플레이버 peel (대소문자 무시; 각 peel 이후 정확한 id + 정확한 별칭 + 날짜 접미사 단계를 재실행; 날짜 + 형식 결합도 같은 루프에서 처리).
  5. HuggingFace 리포지토리 접두사 제거 (vendor/repo -> repo). 제거된 잔류 문자열로 1-4단계에 한 번만 재진입하며, 재귀는 구조적으로 1회로 제한.
  6. 와일드카드 별칭 (glob 스타일 * 패턴).

명시적 별칭은 2단계에서 실행되어 peel(4단계)과 접두사 제거(5단계)보다 엄격하게 앞섭니다. 이 순서가 중요한 이유는 보존된 별칭과 peel-or-strip 경로가 서로 다른 메타데이터를 가리킬 때 별칭이 결정론적으로 우선하기 때문입니다. 별칭은 peel-or-strip 커버리지보다 약한 신호가 아니라 더 강한 의도 신호입니다.

세 가지 별칭 클래스

model-metadata.yaml의 모든 별칭은 세 클래스 중 하나에 속합니다.

peel-coverable

별칭 없이도 정규화가 동일한 소유자 id에 도달하고, 대상 메타데이터가 올바른 경우입니다. 삭제 후보입니다. 예시: qwen3.6-35b-a3b의 별칭인 qwen3.6-35b-a3b-instruct. 4단계에서 FLAVOR 토큰 -instruct를 peel하면 바로 기본 id에 도달하므로, 명시적 별칭이 커버리지를 추가하지 않습니다. 이 클래스의 별칭을 삭제할 때는 tests/format_suffix_normalization_test.rs::real_metadata_removed_aliases_still_resolve에 회귀 어서션을 추가해서, 별칭을 대신하는 peel 커버리지를 고정해 두세요.

vendor-prefix

별칭이 접미사 peel로는 제거할 수 없는 벤더 또는 리포지토리 접두사를 포함하는 경우입니다. 5단계(HuggingFace 접두사 제거 + 1-4단계 재진입, 그중 4단계는 대소문자를 구분하지 않음)가 있어서 대소문자가 혼합된 HF 형식도 별칭 없이 해결됩니다. 예시: qwen3.6-35b-a3b의 별칭인 Qwen/Qwen3.6-35B-A3B. 명시적 별칭이 2단계에서 먼저 승리하지만, 5단계도 Qwen/ -> 잔류 Qwen3.6-35B-A3B -> 4단계 대소문자 무시 매칭으로 기본 id에 도달합니다. 이들 별칭은 peel-coverable-adjacent 클래스입니다. 해석에는 5단계만으로 충분해 중복이지만, 결정론적인 의도 표시 역할은 남아 있으니 커버하는 단계를 YAML 주석에 기록한 채 유지하세요.

intentional-override

별칭이 운영자 결정으로 다른 가중치를 가진 모델을 다른 항목의 메타데이터 아래 의도적으로 라우팅하는 경우입니다. 유지해야 합니다. 예시: smoothie-qwen3의 별칭인 smoothie-qwen3-32b-i1. smoothie-qwen3-32b-i1 파인튜닝 모델은 자체 가중치를 가지고 있으며, 운영자가 전용 항목을 만드는 대신 smoothie-qwen3 메타데이터 아래 노출하기로 선택한 것입니다. peel이 이 동등성을 자체적으로 추론해서는 안 됩니다. 이 클래스에 속하는 별칭은 YAML 주석에 소유자 id와 가중치가 다름을 명시해야 합니다.

새 별칭 추가 지침

model-metadata.yaml에 줄을 추가하기 전에, peel이 이미 커버하는지 확인하세요.

  • 새 id가 허용 목록에 있는 후행 양자화, 형식, 플레이버 토큰이 붙은 정규 기본 형태이고 가중치가 기본 메타데이터와 동일하면, 별칭을 추가하지 마세요. peel이 처리하며 별칭은 데드 코드가 됩니다.
  • 새 id가 기본과 동일한 가중치를 공유하지만 peel이 아직 처리하지 않는 토큰 클래스로 끝나는 경우 (예: -abliterated 같은 새로운 파인튜닝 레이블이나 -nf4 같은 새 양자화 형식), src/models/pattern_matching.rs의 peel 허용 목록을 확장하는 것을 우선 고려하세요. 이는 테스트 커버리지가 있는 코드 변경이며, 한 번의 작업으로 해당 클래스의 모든 향후 변형에 적용됩니다.
  • 새 id에 벤더 접두사, 대소문자가 맞지 않는 리포지토리 네임스페이스, peel 체인을 막는 파라미터 수 토큰 (-Nb, -aNb, -eNb), 또는 의도적으로 다른 가중치가 있는 경우, 이유를 명시한 YAML 주석과 함께 별칭을 추가하세요. 가중치가 소유자 id와 다르다면 주석에 그 사실을 명시하세요.

표면 구분: 코드 게이트 vs. YAML 게이트

변경 위치 게이트 릴리스 주기 사용 목적
src/models/pattern_matching.rs의 peel 허용 목록 코드 리뷰 + Rust 릴리스 다음 라우터 릴리스에 포함 모든 모델에 걸쳐 전체 토큰 클래스를 커버하는 전략적 정규화.
model-metadata.yaml의 별칭 YAML 리뷰 + 핫 리로드 관리자 API를 통해 당일 리로드 개별 오버라이드, 벤더 접두사 수정, 가중치 차이 오버라이드, 새 토큰이 peel 허용 목록 항목을 얻기 전의 임시 커버리지.

peel 허용 목록은 전략적 레이어이고, 별칭은 전술적 오버라이드 및 긴급 채널입니다.

peel 허용 목록에 있는 토큰 카테고리

src/models/pattern_matching.rs의 허용 목록은 현재 다음을 커버합니다.

  • BIT_WIDTH: -2bit, -3bit, -4bit, -5bit, -6bit, -8bit, -16bit
  • GGUF_QUANT: -Q4_K_M, -Q4_K_S, -Q5_K_M, -Q6_K, -Q8_0, -Q2_K, -IQ2_XS, -IQ3_XXS, -IQ4_XS, -F16, -F32, -BF16
  • FP_FORMAT: -FP4, -FP8, -FP16, -FP32, -NVFP4, -MXFP4
  • INT_FORMAT: -INT2, -INT4, -INT8
  • LIBRARY: -AWQ, -GPTQ, -BNB, -HQQ, -EXL2, -EXL3, -MLX
  • IMATRIX: -i1부터 -i8, -q2부터 -q8
  • UNSLOTH: -UD-Q<digit>_<KIND>, -UD-IQ<digit>_<KIND>
  • CONTAINER: -GGUF, -GGML, -SAFETENSORS
  • FLAVOR: -it, -instruct, -chat, -base, -thinking, -qat

파라미터 수 접미사(-Nb, -aNb, -eNb, -0.6b, -1.7b)는 peel되지 않습니다. 이는 정규 모델 식별자의 일부이며 peel 체인을 종료시킵니다. 이 때문에 qwen3-32b-i1qwen3의 명시적 별칭으로 유지되어야 합니다. 4단계에서 -i1을 peel한 뒤 -32b에서 멈추기 때문에, 별칭이 없으면 체인이 기본 id에 도달하지 못하고 소진됩니다.

HuggingFace 리포지토리 접두사 제거 (5단계)

5단계는 모델 id의 왼쪽에서 HuggingFace 스타일 vendor/repo 접두사를 정규화해서 오른쪽 접미사 peel을 보완합니다. 사용자가 unsloth/Qwen3.6-35B-A3B-GGUF 같은 id를 제출할 때 매번 별칭을 등록하지 않고도 정규 qwen3.6-35b-a3b 메타데이터로 라우팅되게 하는 단계입니다. 즉, vendor x base x quant 조합마다 별칭을 수동으로 추가할 필요가 없습니다.

5단계 동작 방식
  1. 입력에 / 구분자가 있는지 확인합니다. 없으면 no-op입니다.
  2. 전체 세그먼트 수(/ 개수 + 1)가 MAX_PREFIX_SEGMENTS (3) 이하여야 합니다. org/team/repo는 허용되고, a/b/c/d/model은 즉시 거부됩니다.
  3. 모든 세그먼트는 비어있지 않아야 하며 ASCII 공백 문자를 포함하지 않아야 합니다. /repo, vendor/, vendor//repo, vendor /repo 같은 잘못된 입력은 거부됩니다.
  4. 성공 시 마지막 / 이후의 부분 문자열이 잔류 문자열입니다. 이 잔류 문자열이 재진입 게이트가 닫힌 상태로 1-4단계에 다시 투입됩니다. 5단계는 재귀하지 않고, 내부 호출은 다시 5단계를 트리거할 수 없으므로 재귀 깊이는 구조적으로 정확히 1입니다.
접미사 peel과의 조합

재진입은 4단계를 거치므로 접두사 제거는 단일 조회 내에서 접미사 peel과 조합됩니다. unsloth/Qwen3.6-35B-A3B-GGUFQwen3.6-35B-A3B-GGUF로 제거되고, 4단계가 -GGUF를 peel한 뒤 대소문자를 무시하고 qwen3.6-35b-a3b에 매칭됩니다. 이 단계의 핵심 사용 사례이며, 수동으로 등록된 별칭 없이 HuggingFace GGUF 포크를 커버합니다.

등록된 별칭 우선 순위

운영자가 vendor/repo 형식을 명시적으로 YAML 별칭으로 등록하면 결정론적인 제어권이 유지됩니다. 2단계가 5단계보다 먼저 실행되므로, 정확한 별칭은 접두사 제거 레이어가 입력을 고려하기도 전에 승리합니다. 접두사 형식이 정규 기본 id와는 다른 메타데이터 항목으로 라우팅되어야 할 때 이 기법을 사용하세요.

범위 밖
  • 하이픈으로 구분된 벤더 접두사 (예: smoothie-qwen/smoothie-qwen3-32b-i1). 다른 의미 클래스이고, 탐지 난이도도 다르며, 종종 다른 가중치를 의미하기 때문에 기본 메타데이터로의 조용한 라우팅이 잘못된 결정일 수 있습니다.
  • HuggingFace API를 통한 자동 벤더 발견. 이 레이어는 순수하게 구문적입니다.
  • 접미사 peel 허용 목록 확장. 직교적인 변경입니다. 새 토큰 클래스에 대해서는 peel 확장 경로를 따르세요.
보안 경계

접미사 peel과 동일한 구조입니다.

경계 효과
MAX_PREFIX_SEGMENTS 3 세그먼트가 더 많은 입력은 스캔 전에 거부됩니다.
MAX_MODEL_ID_LEN 256 과도하게 긴 입력은 4단계와 마찬가지로 5단계를 건너뜁니다.
재진입 깊이 1 카운터가 아니라 재귀 게이트로 구조적으로 강제됩니다.

5단계는 적대적 입력에 대해 상수 시간입니다. 세그먼트 수, 빈 세그먼트, 공백, 길이 가드를 통과한 뒤에는 작업이 단일 슬라이스 조회 + 1-4단계 한 번의 추가 순회로 축소됩니다.

감사 절차

YAML을 재감사하려면 다음을 실행하세요.

cargo test --test alias_audit_helper -- --ignored --nocapture audit_metadata_aliases

헬퍼는 각 별칭을 분류(REDUNDANT, LOAD-BEARING-DRIFT, LOAD-BEARING-LOSS, 또는 WILDCARD)와 제거 후 결정 대상과 함께 출력합니다. 현재 스냅샷은 docs/reports/alias-audit-2026-04.md에 기록되어 있습니다.

API 응답

/v1/models 엔드포인트는 풍부한 모델 정보를 반환합니다:

{
  "object": "list",
  "data": [
    {
      "id": "gpt-4",
      "object": "model",
      "created": 1234567890,
      "owned_by": "openai",
      "backends": ["openai-proxy"],
      "metadata": {
        "display_name": "GPT-4",
        "summary": "복잡한 작업을 위한 가장 유능한 GPT-4 모델",
        "capabilities": ["text", "image", "function_calling"],
        "knowledge_cutoff": "2024-04",
        "pricing": {
          "input_tokens": 0.03,
          "output_tokens": 0.06
        },
        "limits": {
          "context_window": 128000,
          "max_output": 4096
        }
      }
    }
  ]
}

핫 리로드

Continuum Router는 서버 재시작 없이 런타임 설정 업데이트를 위한 핫 리로드를 지원합니다. 설정 변경은 자동으로 감지되어 분류에 따라 적용됩니다.

설정 항목 분류

설정 항목은 핫 리로드 기능에 따라 세 가지 범주로 분류됩니다:

즉시 업데이트 (서비스 중단 없음)

이 설정들은 서비스 중단 없이 즉시 업데이트됩니다:

# 로깅 설정
logging:
  level: "info"                  # ✅ 즉시: 로그 레벨 변경이 즉시 적용
  format: "json"                 # ✅ 즉시: 로그 형식 변경이 즉시 적용

# 속도 제한 설정
rate_limiting:
  enabled: true                  # ✅ 즉시: 속도 제한 활성화/비활성화
  limits:
    per_client:
      requests_per_second: 10    # ✅ 즉시: 새 제한이 즉시 적용
      burst_capacity: 20         # ✅ 즉시: 버스트 설정이 즉시 업데이트

# 서킷 브레이커 설정
circuit_breaker:
  enabled: true                  # ✅ 즉시: 서킷 브레이커 활성화/비활성화
  failure_threshold: 5           # ✅ 즉시: 임계값 업데이트가 즉시 적용
  timeout_seconds: 60            # ✅ 즉시: 타임아웃 변경이 즉시

# 재시도 설정
retry:
  max_attempts: 3                # ✅ 즉시: 재시도 정책이 즉시 업데이트
  base_delay: "100ms"            # ✅ 즉시: 백오프 설정이 즉시 적용
  exponential_backoff: true      # ✅ 즉시: 전략 변경이 즉시

# 전역 프롬프트
global_prompts:
  default: "당신은 도움이 됩니다"     # ✅ 즉시: 프롬프트 변경이 새 요청에 적용
  default_file: "prompts/system.md"  # ✅ 즉시: 파일 기반 프롬프트도 핫 리로드

# Admin 통계
admin:
  stats:
    retention_window: "24h"        # ✅ 즉시: 보존 기간 즉시 업데이트
    token_tracking: true           # ✅ 즉시: 토큰 추적 토글 즉시 적용

점진적 업데이트 (기존 연결 유지)

이 설정들은 기존 연결을 유지하면서 새 연결에 적용됩니다:

# 백엔드 설정
backends:
  - name: "ollama"               # ✅ 점진적: 새 요청이 업데이트된 백엔드 풀 사용
    url: "http://localhost:11434"
    weight: 2                    # ✅ 점진적: 새 요청에 로드 밸런싱 업데이트
    models: ["llama3.2"]         # ✅ 점진적: 모델 라우팅이 점진적으로 업데이트

# 헬스 체크 설정
health_checks:
  interval: "30s"                # ✅ 점진적: 다음 헬스 체크 주기가 새 간격 사용
  timeout: "10s"                 # ✅ 점진적: 새 체크가 업데이트된 타임아웃 사용
  unhealthy_threshold: 3         # ✅ 점진적: 임계값이 새 평가에 적용
  healthy_threshold: 2           # ✅ 점진적: 복구 임계값이 점진적으로 업데이트

# 타임아웃 설정
timeouts:
  connection: "10s"              # ✅ 점진적: 새 요청이 업데이트된 타임아웃 사용
  request:
    standard:
      first_byte: "30s"          # ✅ 점진적: 새 요청에 적용
      total: "180s"              # ✅ 점진적: 새 요청이 새 타임아웃 사용
    streaming:
      chunk_interval: "30s"      # ✅ 점진적: 새 스트림이 업데이트된 설정 사용

재시작 필요 (핫 리로드 불가)

이 설정들은 적용을 위해 서버 재시작이 필요합니다. 변경 사항은 경고로 기록됩니다:

server:
  bind_address: "0.0.0.0:8080"   # ❌ 재시작 필요: TCP/Unix 소켓 바인딩
  #   - "0.0.0.0:8080"
  #   - "unix:/var/run/router.sock"
  socket_mode: 0o660              # ❌ 재시작 필요: 소켓 권한
  workers: 4                      # ❌ 재시작 필요: 워커 스레드 풀 크기

이 설정들이 변경되면 라우터는 다음과 같은 경고를 기록합니다:

WARN server.bind_address가 '0.0.0.0:8080'에서 '0.0.0.0:9000'으로 변경됨 - 적용하려면 재시작 필요

핫 리로드 프로세스

  1. 파일 시스템 감시자 - 설정 파일 변경 자동 감지
  2. 설정 로딩 - 새 설정 로드 및 파싱
  3. 유효성 검사 - 스키마에 대해 새 설정 검증
  4. 변경 감지 - ConfigDiff 계산으로 변경 사항 식별
  5. 분류 - 변경 사항 분류 (즉시/점진적/재시작)
  6. 원자적 업데이트 - 유효한 설정이 원자적으로 적용
  7. 컴포넌트 전파 - 영향받는 컴포넌트에 업데이트 전파:
  8. HealthChecker가 체크 간격 및 임계값 업데이트
  9. RateLimitStore가 속도 제한 규칙 업데이트
  10. CircuitBreaker가 실패 임계값 및 타임아웃 업데이트
  11. BackendPool이 백엔드 설정 업데이트
  12. 즉시 헬스 체크 - 백엔드가 추가되면 즉시 헬스 체크가 실행되어 새 백엔드가 다음 주기적 체크를 기다리지 않고 1-2초 내에 사용 가능해짐
  13. 오류 처리 - 잘못된 경우 오류 기록 및 이전 설정 유지

핫 리로드 상태 확인

admin API를 사용하여 핫 리로드 상태 및 기능을 확인하세요:

# 핫 리로드가 활성화되었는지 확인
curl http://localhost:8080/admin/config/hot-reload-status

# 현재 설정 보기
curl http://localhost:8080/admin/config

핫 리로드 동작 예제

예제 1: 로그 레벨 변경 (즉시)

# 이전
logging:
  level: "info"

# 이후
logging:
  level: "debug"
결과: 로그 레벨이 즉시 변경됩니다. 재시작 불필요. 진행 중인 요청은 계속되고 새 로그는 debug 레벨 사용.

예제 2: 백엔드 추가 (즉시 헬스 체크와 함께 점진적)

# 이전
backends:
  - name: "ollama"
    url: "http://localhost:11434"

# 이후
backends:
  - name: "ollama"
    url: "http://localhost:11434"
  - name: "lmstudio"
    url: "http://localhost:1234"
결과: 새 백엔드가 풀에 추가되고 즉시 헬스 체크가 실행됩니다. 새 백엔드는 1-2초 내에 사용 가능해집니다(다음 주기적 헬스 체크까지 최대 30초를 기다리는 대신). 기존 요청은 현재 백엔드로 계속됩니다. 헬스 체크 통과 후 새 요청은 lmstudio로 라우팅 가능.

예제 2b: 백엔드 제거 (우아한 드레이닝)

# 이전
backends:
  - name: "ollama"
    url: "http://localhost:11434"
  - name: "lmstudio"
    url: "http://localhost:1234"

# 이후
backends:
  - name: "ollama"
    url: "http://localhost:11434"
결과: "lmstudio" 백엔드가 드레이닝 상태로 전환됩니다. 새 요청은 해당 백엔드로 라우팅되지 않지만, 기존 진행 중인 요청(스트리밍 포함)은 완료될 때까지 계속됩니다. 모든 참조가 해제되거나 5분 타임아웃 후에 백엔드가 메모리에서 완전히 제거됩니다.

백엔드 상태 생명주기

백엔드가 설정에서 제거되면 우아한 종료 프로세스를 거칩니다:

  1. Active → Draining: 백엔드가 드레이닝으로 표시됩니다. 새 요청은 이 백엔드를 건너뜁니다.
  2. 진행 중인 요청 완료: 기존 요청/스트림은 중단 없이 계속됩니다.
  3. 정리: 모든 참조가 해제되거나 5분 타임아웃 후에 백엔드가 제거됩니다.

설정 변경 중에도 진행 중인 연결에 영향을 주지 않습니다.

예제 3: 바인드 주소 변경 (재시작 필요)

# 이전
server:
  bind_address: "0.0.0.0:8080"

# 이후
server:
  bind_address: "0.0.0.0:9000"
결과: 경고 기록됨. 변경이 적용되지 않음. 새 포트에 바인드하려면 재시작 필요.

분산 추적

Continuum Router는 백엔드 서비스 간 요청 상관관계를 위한 분산 추적을 지원합니다. 이 기능은 여러 서비스를 통과하는 요청의 디버깅 및 모니터링에 도움을 줍니다.

설정

tracing:
  enabled: true                         # 분산 추적 활성화/비활성화 (기본값: true)
  w3c_trace_context: true               # W3C Trace Context 헤더 지원 (기본값: true)
  headers:
    trace_id: "X-Trace-ID"              # 추적 ID 헤더 이름 (기본값)
    request_id: "X-Request-ID"          # 요청 ID 헤더 이름 (기본값)
    correlation_id: "X-Correlation-ID"  # 상관 ID 헤더 이름 (기본값)

동작 방식

  1. 추적 ID 추출: 요청이 도착하면 라우터는 다음 우선순위로 헤더에서 추적 ID를 추출합니다:

    • W3C traceparent 헤더 (W3C 지원 활성화 시)
    • 설정된 trace_id 헤더 (X-Trace-ID)
    • 설정된 request_id 헤더 (X-Request-ID)
    • 설정된 correlation_id 헤더 (X-Correlation-ID)
  2. 추적 ID 생성: 헤더에서 추적 ID가 발견되지 않으면 새 UUID가 생성됩니다.

  3. 헤더 전파: 추적 ID는 여러 헤더를 통해 백엔드 서비스로 전파됩니다:

    • X-Request-ID: 광범위한 호환성을 위함
    • X-Trace-ID: 주요 추적 식별자
    • X-Correlation-ID: 상관관계 추적용
    • traceparent: W3C Trace Context (활성화 시)
    • tracestate: W3C Trace State (원본 요청에 존재 시)
  4. 재시도 시 보존: 동일한 추적 ID가 모든 재시도 시도에서 보존되어, 단일 클라이언트 요청에 대한 여러 백엔드 요청의 상관관계를 쉽게 파악할 수 있습니다.

구조화된 로깅

추적이 활성화되면 모든 로그 메시지에 trace_id 필드가 포함됩니다:

{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "info",
  "trace_id": "0af7651916cd43dd8448eb211c80319c",
  "message": "Processing chat completions request",
  "backend": "openai",
  "model": "gpt-4o"
}

W3C Trace Context

w3c_trace_context가 활성화되면 라우터는 W3C Trace Context 표준을 지원합니다:

  • 수신: traceparent 헤더 파싱 (형식: 00-{trace_id}-{span_id}-{flags})
  • 송신: 보존된 추적 ID와 새 span ID로 traceparent 헤더 생성
  • 상태: 원본 요청에 있는 경우 tracestate 헤더 전달

traceparent 예시: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

추적 비활성화

분산 추적을 비활성화하려면:

tracing:
  enabled: false

로드 밸런싱 전략

load_balancer:
  strategy: "round_robin"         # round_robin, weighted, random
  health_aware: true              # 정상 백엔드만 사용

전략:

  • round_robin: 백엔드 간 균등 분배
  • weighted: 백엔드 가중치에 기반한 분배
  • random: 무작위 선택 (패턴 방지에 좋음)

백엔드별 재시도 설정

backends:
  - name: "slow-backend"
    url: "http://slow.example.com"
    retry_override:               # 전역 재시도 설정 오버라이드
      max_attempts: 5             # 느린 백엔드에 더 많은 시도
      base_delay: "500ms"         # 더 긴 지연
      max_delay: "60s"

모델 폴백

Continuum Router는 기본 모델을 사용할 수 없을 때 자동 모델 폴백을 지원합니다. 이 기능은 계층화된 장애 조치 보호를 위해 서킷 브레이커와 통합됩니다.

프리스트림 vs. 미드스트림 폴백

라우터는 서로 독립적인 두 가지 폴백 메커니즘을 제공합니다:

메커니즘 활성화 시점 설정 섹션 기본값
프리스트림 폴백 응답이 시작되기 전 또는 시작 시점: 연결 오류, 타임아웃, 트리거 오류 코드, 라우팅 시점의 비정상 백엔드 fallback fallback.enabled: true일 때 활성화
미드스트림 폴백 스트리밍이 시작된 뒤 백엔드가 응답 도중 실패할 때 fallback + streaming.mid_stream_fallback fallback.enabled: true이고 폴백 체인이 설정되어 있으면 활성화. 연속 모드가 기본으로 켜져 있음.

fallback.enabled: true이고 요청된 모델에 폴백 체인이 설정되어 있으면, streaming.mid_stream_fallback.enabledfalse이더라도 미드스트림 연결 끊김은 억제되고 라우터가 투명하게 다음 백엔드로 전환합니다.

streaming.mid_stream_fallback.enabled는 연속 동작만 제어합니다: 폴백 백엔드가 (누적된 부분 응답을 사용하는) 연속 프롬프트를 받을지, 아니면 원래 요청을 처음부터 다시 실행할지를 결정합니다. 기본값은 true (연속 모드)이며, 클라이언트에 끊김 없는 출력을 제공합니다. false로 설정하면 재시작 모드가 강제되어, 부분 출력이 이미 클라이언트로 전송된 경우 중복되거나 일관성 없는 내용이 생길 수 있습니다.

프리스트림 폴백은 유지하면서 스트림당 미드스트림 버퍼링을 끄려면 fallback.mid_stream_enabled: false로 설정하세요. 이는 streaming.mid_stream_fallback.enabled와는 별개의 플래그입니다. 아래 미드스트림 버퍼링 비활성화를 참고하세요.

설정

fallback:
  enabled: true

  # 미드스트림 폴백과 스트림당 버퍼링 허용 여부 (기본값: true).
  # false로 두면 버퍼링 없이 프리스트림 재선택만 유지합니다.
  # 아래 "미드스트림 버퍼링 비활성화" 참고.
  mid_stream_enabled: true

  # 각 기본 모델에 대한 폴백 체인 정의
  fallback_chains:
    # 동일 프로바이더 폴백
    "gpt-4o":
      - "gpt-4-turbo"
      - "gpt-3.5-turbo"

    "claude-opus-4-5-20251101":
      - "claude-sonnet-4-5"
      - "claude-haiku-4-5"

    # 크로스 프로바이더 폴백
    "gemini-2.5-pro":
      - "gemini-2.5-flash"
      - "gpt-4o"  # Gemini를 사용할 수 없으면 OpenAI로 폴백

  fallback_policy:
    trigger_conditions:
      error_codes: [429, 500, 502, 503, 504]
      timeout: true
      connection_error: true
      model_not_found: true
      circuit_breaker_open: true

    max_fallback_attempts: 3
    fallback_timeout_multiplier: 1.5
    preserve_parameters: true

  model_settings:
    "gpt-4o":
      fallback_enabled: true
      notify_on_fallback: true

트리거 조건

조건 설명
error_codes 폴백을 트리거하는 HTTP 상태 코드 (예: 429, 500, 502, 503, 504)
timeout 요청 타임아웃
connection_error TCP 연결 실패
model_not_found 백엔드에서 모델을 사용할 수 없음
circuit_breaker_open 백엔드 서킷 브레이커가 열림

응답 헤더

폴백이 사용되면 다음 헤더가 응답에 추가됩니다:

헤더 설명 예제
X-Fallback-Used 폴백이 사용되었음을 나타냄 true
X-Original-Model 원래 요청된 모델 gpt-4o
X-Fallback-Model 요청을 처리한 모델 gpt-4-turbo
X-Fallback-Reason 폴백이 트리거된 이유 error_code_429
X-Fallback-Attempts 폴백 시도 횟수 2

크로스 프로바이더 매개변수 변환

프로바이더 간 폴백 시 (예: OpenAI → Anthropic) 라우터가 요청 매개변수를 자동으로 변환합니다:

OpenAI 매개변수 Anthropic 매개변수 비고
max_tokens max_tokens 누락 시 자동 채움 (Anthropic 필수)
temperature temperature 직접 매핑
top_p top_p 직접 매핑
stop stop_sequences 배열 변환

프로바이더별 매개변수는 크로스 프로바이더 폴백 중에 자동으로 제거되거나 변환됩니다.

서킷 브레이커와의 통합

폴백 시스템은 서킷 브레이커와 함께 작동합니다:

  1. 서킷 브레이커가 실패 감지하고 임계값 초과 시 열림
  2. 폴백 체인 활성화 서킷 브레이커가 열렸을 때
  3. 요청이 폴백 모델로 라우팅 설정된 체인에 따라
  4. 서킷 브레이커가 복구 테스트하고 백엔드 복구 시 닫힘
# 예제: 서킷 브레이커와 폴백 결합 설정
circuit_breaker:
  enabled: true
  failure_threshold: 5
  timeout: 60s

fallback:
  enabled: true
  fallback_policy:
    trigger_conditions:
      circuit_breaker_open: true  # 서킷 브레이커에 연결

미드스트림 폴백

미드스트림 폴백은 기본 백엔드가 응답 도중 실패할 때 라우터가 진행 중인 SSE 스트림을 폴백 백엔드에서 투명하게 이어가도록 합니다. 클라이언트 연결은 열린 채 유지되고, 전환 중 잠깐의 멈춤만 있을 뿐 끊김 없는 응답을 받습니다.

미드스트림 폴백은 fallback.enabled: true이고 요청된 모델에 폴백 체인이 설정되어 있으면 자동으로 활성화됩니다. streaming.mid_stream_fallback 섹션은 폴백이 일어날지 여부가 아니라 폴백 백엔드를 어떻게 호출할지 (연속 vs 재시작 모드)를 제어합니다.

mid_stream_fallback.enabled는 버퍼링이나 메모리 킬 스위치가 아닙니다

streaming.mid_stream_fallback.enabled는 폴백이 트리거된 뒤의 복구 모드 (연속 vs. 재시작)만 선택합니다. 스트림당 버퍼링을 비활성화하거나 메모리 사용량을 줄이지는 않습니다. enabled: false로 설정해도:

  • 스트림 누적기는 여전히 생성되고 스트리밍 응답을 계속 버퍼링합니다 (스트림당 최대 약 100 KB).
  • 백엔드 실패 시 미드스트림 폴백은 여전히 활성화됩니다.
  • 유일한 차이는 폴백 요청이 이어서 계속되는 대신 처음부터 재시작된다는 점입니다.

프리스트림 폴백은 유지하면서 스트림당 버퍼링과 그 메모리 비용을 없애려면 fallback.mid_stream_enabled: false로 설정하세요 (미드스트림 버퍼링 비활성화 참고). 폴백을 완전히 비활성화하려면 fallback.enabled: false로 설정하거나 fallback.fallback_chains에서 해당 모델을 제거하세요.

미드스트림 버퍼링 비활성화

fallback.mid_stream_enabled(기본값 true)는 프리스트림 폴백과 미드스트림 버퍼링을 분리합니다. fallback.enabled: true이고 체인이 설정된 상태에서 false로 두면 다음과 같이 동작합니다.

  • 초기 연결은 연결 오류, 타임아웃, 트리거 오류 코드 발생 시 여전히 폴백 체인을 따라 다시 라우팅됩니다(프리스트림 폴백은 영향받지 않음).
  • SSE 스트림이 시작된 뒤에는 스트림 누적기를 할당하지 않으므로 스트림당 버퍼링과 그에 따른 메모리 비용이 없습니다.
  • 스트림이 시작된 다음에 발생한 실패는 백엔드를 조용히 전환하는 대신 일반 스트림 오류로 클라이언트에 전달됩니다.

메모리가 빠듯한 호스트나, 스트림당 약 100~200 KB 버퍼가 누적되는 긴 컨텍스트 스트리밍 세션이 동시에 많은 환경에서 mid_stream_enabled: false를 사용하면 됩니다.

이 플래그는 streaming.mid_stream_fallback.enabled와 다릅니다:

플래그 섹션 제어 대상
fallback.enabled fallback 모든 폴백의 마스터 스위치
fallback.mid_stream_enabled fallback 미드스트림 버퍼링 자체의 동작 여부(false면 프리스트림만 유지)
streaming.mid_stream_fallback.enabled streaming 미드스트림 폴백 발생 후 복구 모드(연속 vs 재시작), 버퍼링은 제어하지 않음
fallback:
  enabled: true             # 프리스트림 폴백은 그대로 유지
  mid_stream_enabled: false # 스트림당 버퍼링 없음, 미드스트림 오류는 클라이언트로 전달
  fallback_chains:
    "gpt-4o":
      - "gpt-4-turbo"
      - "gpt-3.5-turbo"

설정

fallback:
  enabled: true  # 필수: 미드스트림 폴백 경로를 활성화
  fallback_chains:
    "gpt-4o":
      - "gpt-4-turbo"
      - "gpt-3.5-turbo"

streaming:
  mid_stream_fallback:
    # 연속 모드 활성화 (기본값: true).
    # true이면 누적된 부분 응답으로 연속 프롬프트를 구성하여
    # 클라이언트에 끊김 없는 출력을 제공합니다.
    # false이면 폴백 백엔드가 요청을 처음부터 다시 실행하며, 부분 출력이
    # 이미 전송된 경우 중복되거나 일관성 없는 내용이 생길 수 있습니다.
    enabled: true

    # 연속 모드를 사용하기 전에 누적되어야 하는 최소 추정 토큰 수 (기본값: 50)
    # 이 임계값 미만이면 연속 프롬프트를 덧붙이는 대신
    # 폴백 백엔드에서 요청을 처음부터 다시 실행합니다.
    min_accumulated_tokens: 50

    # 스트리밍 요청당 최대 폴백 시도 횟수 (기본값: 2, 최대: 10)
    max_fallback_attempts: 2

    # 부분 어시스턴트 응답 뒤에 사용자 메시지로 덧붙이는 프롬프트
    continuation_prompt: "Continue from where you left off exactly. Do not repeat any previously generated content."

동작 방식

  1. 클라이언트가 스트리밍 채팅 완료 요청을 보냅니다.
  2. 라우터가 기본 백엔드에서 스트리밍을 시작하면서 응답 내용을 누적합니다.
  3. 백엔드가 스트림 도중 실패하면 (연결 끊김, 타임아웃, 오류 이벤트):

    • 오류는 클라이언트로 전달되지 않습니다.
    • 누적된 부분 응답이 캡처됩니다.
    • 폴백 체인에서 다음 정상 백엔드가 선택됩니다 (비정상 백엔드는 건너뜀).
    • 연속 또는 재시작 요청이 폴백 백엔드로 전송됩니다.
    • 클라이언트 연결을 닫지 않은 채 폴백 백엔드에서 스트리밍이 재개됩니다.
  4. 클라이언트는 전환 중 잠깐의 멈춤만 있을 뿐 끊김 없는 응답을 받습니다.

연속 vs. 재시작 모드

min_accumulated_tokens 임계값이 어떤 복구 모드를 사용할지 결정합니다:

조건 모드 동작
enabled: true (기본값)이고 토큰 수가 min_accumulated_tokens 이상이며 잘리지 않음 연속 원본 메시지 + 부분 어시스턴트 응답 + 연속 프롬프트
enabled: true (기본값)이고 토큰 수가 min_accumulated_tokens 미만 재시작 원본 요청 재실행 (이어가기에는 컨텍스트 부족)
enabled: true (기본값)이고 내용이 잘림 (> 100 KB) 재시작 일관성 없는 컨텍스트를 피하기 위해 강제 재시작
mid_stream_fallback.enabled: false 재시작 원본 요청을 폴백 백엔드에서 처음부터 재실행

연속 모드 (기본값)는 클라이언트에 끊김 없는 출력을 제공합니다. 재시작 모드는 의미 있게 이어가기에는 컨텍스트가 너무 적거나, 누적된 응답이 안전하게 포함하기에는 너무 길 때 자동으로 사용됩니다. enabled: false를 명시적으로 설정하면 무조건 재시작 모드가 강제되며, 클라이언트에 중복되거나 일관성 없는 내용이 보일 수 있습니다.

엣지 케이스 처리

미드스트림 폴백 경로는 여러 엣지 케이스를 자동으로 처리합니다:

  • 전역 타임아웃 예산: 모든 폴백 시도는 원래 요청의 시작 시각을 공유합니다. 각 시도는 전송 전에 남은 예산을 확인하므로, 체인 전체에 걸쳐 타임아웃이 무한정 누적되지 않습니다.
  • 크로스 프로바이더 매개변수 변환: 폴백 모델이 다른 프로바이더에 있으면 (예: OpenAI → Anthropic) 요청 매개변수가 자동으로 변환됩니다: 프로바이더별 필드는 제거되고 매개변수 이름은 매핑됩니다.
  • 동시 요청 폭주: 전역 세마포어 (50 퍼밋)가 동시 폴백 시도 수를 제한합니다. 5초 안에 퍼밋을 얻지 못한 요청은 안전하게 거부됩니다.
  • 누적기 잘림: 누적된 응답 내용이 100 KB를 초과하면, 일관성 없는 컨텍스트가 폴백 백엔드로 전송되지 않도록 연속 모드가 재시작으로 강제됩니다.
  • 헬스 재확인: 체인의 각 폴백 시도 전에 백엔드 헬스를 다시 검증합니다. 비정상 백엔드는 건너뛰고 다음 항목으로 넘어갑니다.
  • [DONE] 마커 누락: [DONE] 없이 끝났지만 finish_reason: "stop"이 있는 스트림은 정상 완료로 처리되어 불필요한 폴백을 방지합니다.

메트릭

세 가지 Prometheus 메트릭이 미드스트림 폴백 활동을 추적합니다. 자세한 내용은 미드스트림 폴백 메트릭을 참고하세요.

장애 조치 지연 최소화

스트리밍 도중 백엔드가 다운되면, 폴백 백엔드가 이어받기까지 걸리는 시간은 여러 하위 시스템에 흩어진 설정 매개변수에 따라 달라집니다. 아래는 이 전환 지연을 최소화하기 위한 튜닝 가이드입니다.

장애 조치 지연의 구성

미드스트림 장애 조치 중 클라이언트가 기다리는 총 시간은 대략 다음과 같습니다:

failover_delay ≈ failure_detection_time + health_recheck_time + fallback_connection_time

각 구성 요소는 특정 설정에 대응합니다:

구성 요소 결정 요인 기본값 튜닝 대상
장애 감지 스트림 비활성 타임아웃 (60초 하드코딩), TCP 읽기 오류 (즉시), 또는 chunk_interval 타임아웃 30~60초 chunk_interval 낮추기
헬스 재확인 폴백 시도 전 헬스 체크 timeout: 5s 낮게 유지
폴백 연결 폴백 백엔드로의 TCP 연결 + TLS 핸드셰이크 connection: 10s connection 낮추기

빠른 장애 조치를 위한 권장 설정

# 1. 타임아웃: 장애 조치 속도에 가장 큰 영향을 주는 설정
timeouts:
  connection: 5s               # 더 빠른 TCP 연결 타임아웃 (기본값: 10s)
  request:
    streaming:
      first_byte: 30s          # 첫 토큰을 기다리는 시간 (기본값: 60s)
      chunk_interval: 10s      # 청크 사이 최대 침묵 시간, 초과 시 실패로 간주 (기본값: 30s)
      total: 600s              # 전체 스트리밍 예산 (넉넉하게 유지)

# 2. 헬스 체크: 백엔드 장애를 선제적으로 감지
health_checks:
  interval: 10s                # 30초 대신 10초마다 확인 (기본값: 30s)
  timeout: 3s                  # 헬스 체크를 더 빨리 실패 처리 (기본값: 5s)
  unhealthy_threshold: 2       # 2회 실패 후 비정상으로 표시 (기본값: 3)
  healthy_threshold: 1         # 1회 성공 후 복구 (기본값: 2)
  warmup_check_interval: 1s   # 백엔드 시작 중에는 빠르게 확인

# 3. 서킷 브레이커: 실패한 백엔드로의 라우팅을 즉시 중단
circuit_breaker:
  enabled: true
  failure_threshold: 3         # 3회 실패 후 서킷 열림 (기본값: 5)
  timeout: 30s                 # 30초 후 복구 시도 (기본값: 60s)
  half_open_max_requests: 2
  half_open_success_threshold: 1
  timeout_as_failure: true     # 타임아웃을 서킷 브레이커 실패로 집계

# 4. 폴백 체인: 미드스트림 폴백이 활성화되려면 반드시 설정해야 함
fallback:
  enabled: true
  fallback_chains:
    "gpt-4o":
        - "gpt-4-turbo"
        - "gpt-3.5-turbo"
  fallback_policy:
    trigger_conditions:
      error_codes: [429, 500, 502, 503, 504]
      timeout: true
      connection_error: true
      circuit_breaker_open: true

# 5. 미드스트림 폴백: 연속 모드 (기본값: 활성화)
streaming:
  mid_stream_fallback:
    enabled: true              # 연속 모드 사용 (기본값)
    max_fallback_attempts: 3   # 복원력을 위해 더 많은 재시도 허용 (기본값: 2)
    min_accumulated_tokens: 30 # 연속 vs 재시작 임계값 낮추기 (기본값: 50)

매개변수 영향 요약

매개변수 장애 조치 속도에 미치는 영향 트레이드오프
timeouts.request.streaming.chunk_interval 높음: 멈춘 스트림을 얼마나 빨리 감지할지 직접 제어 너무 낮으면 느린 모델 (예: thinking 단계가 긴 추론 모델)에서 오탐이 발생할 수 있음
timeouts.connection 중간: 폴백 백엔드로의 TCP 연결 지연을 제한 너무 낮으면 지연이 큰 네트워크에서 실패할 수 있음
health_checks.interval 중간: 더 빨리 감지할수록 서킷 브레이커가 더 일찍 열려 죽은 백엔드로 요청이 가는 것을 방지 확인이 잦을수록 백엔드 부하 증가
health_checks.unhealthy_threshold 중간: 더 적은 실패 횟수로 백엔드를 비정상으로 표시 값이 낮을수록 일시적 오류에 민감해짐
circuit_breaker.failure_threshold 중간: 더 적은 실패 횟수로 서킷 열림 너무 공격적이면 일시적 급증에도 서킷이 열릴 수 있음
circuit_breaker.timeout 낮음: 장애 조치 속도가 아니라 복구 시간에 영향 짧을수록 복구는 빨라지지만 비정상 백엔드를 더 자주 탐침
mid_stream_fallback.max_fallback_attempts 낮음: 시도가 많을수록 복원력은 높아지지만 개별 전환 속도는 빨라지지 않음 시도가 많을수록 전역 타임아웃 예산을 더 많이 소비

장애 감지 시나리오

장애 유형마다 감지 속도가 다릅니다:

장애 유형 감지 시간 메커니즘
TCP 연결 재설정 / 백엔드 크래시 즉시 (< 1초) 스트림 읽기 오류가 즉각 폴백을 트리거
백엔드가 5xx 오류 반환 즉시 (< 1초) 스트리밍 시작 전 HTTP 상태 확인
백엔드 무응답 (멈춤) chunk_interval (기본값 30초) 스트림 비활성 타임아웃
백엔드가 오류 SSE 이벤트 전송 5회 오류 후 스트림 처리의 오류 횟수 임계값
응답 도중 백엔드 프로세스 종료 즉시 (< 1초) TCP FIN/RST가 스트림 읽기 오류로 감지됨

프로덕션에서 가장 흔한 시나리오인 백엔드 무응답은 chunk_interval이 좌우합니다. 지연에 민감한 애플리케이션에서는 이 값을 10~15초로 낮추는 것이 좋고, 느린 모델에는 모델별 오버라이드를 두면 됩니다:

timeouts:
  request:
    streaming:
      chunk_interval: 10s      # 대부분의 모델에 빠른 감지
    model_overrides:
      gemini-2.5-pro:          # 추론 모델에는 더 긴 간격 필요
        streaming:
          chunk_interval: 30s
          first_byte: 120s

속도 제한

설정 가능한 다차원 속도 제한은 속도 제한 문서에서 다룹니다. 그와 별개로 Continuum Router는 모델 목록 엔드포인트에 대해 항상 켜져 있는 내장 보호를 제공하며, 이 제한값은 고정되어 있습니다.

내장 /v1/models 보호

GET /v1/models는 클라이언트별로 속도가 제한됩니다:

제한
지속 분당 100 요청
버스트 5초 윈도우당 20 요청

이 리미터에서 클라이언트는 Authorization: Bearer API 키의 처음 16자로 식별되고, 키가 없으면 클라이언트 IP 주소로 폴백합니다. 클라이언트마다 독립적인 할당량을 가집니다.

POST /v1/models/refresh (강제 새로고침)는 훨씬 빡빡한 예산을 갖습니다. 새로고침 한 번마다 캐시를 비우고 설정된 모든 백엔드로 fan-out하기 때문인데, 분당 12 요청에 5초 윈도우당 버스트 3회까지만 허용됩니다. 검증된 API 키만 키별 버킷을 받고, 익명이거나 토큰이 유효하지 않은 호출자는 전역 버킷 하나를 공유하므로 헤더를 위조해도 새 할당량을 만들어낼 수 없습니다.

제한을 초과하면 엔드포인트가 버스트와 지속 중 어느 제한에 걸렸는지를 알려주는 메시지와 함께 429 Too Many Requests를 반환하고, 거부 내역은 클라이언트 식별자와 함께 로그에 남습니다.

캐시 TTL 최적화

백엔드 장애 중 캐시 오염을 막기 위해, 빈 모델 목록은 처음에 5초만 캐시되고 (빈 응답이 연속되면 최대 60초까지 백오프), 일반 응답은 표준 모델 캐시 TTL을 사용합니다.

스마트 라우팅

스마트 라우팅은 요청의 복잡도와 도메인을 분류한 뒤, 설정 가능한 정책에 따라 가장 적합한 모델 티어로 요청을 보내는 기능입니다. 모델 티어 레지스트리(모델을 티어와 도메인에 매핑)와 규칙 기반 요청 분류기, 그리고 분류 결과를 라우팅 결정으로 변환하는 정책 엔진이 결합되어 동작합니다.

요청에서 model: "auto"를 사용하면 분류 → 정책 평가 → 티어 내 모델 선택 순으로 파이프라인이 실행됩니다. intercept_all: true로 설정하면 모든 요청에 동일한 파이프라인이 적용됩니다.

설정

smart_routing:
  enabled: true

  # 프로필 매칭 실패 및 자동 추론 불가 시 사용할 기본 티어.
  # 1 = Flagship, 2 = Standard, 3 = Lightweight. 기본값 2.
  default_tier: 2

  # 스마트 라우팅을 트리거하는 가상 모델 이름. 기본값 "auto".
  virtual_model: "auto"

  # true로 설정하면 모델 이름과 관계없이 모든 요청에 스마트 라우팅이 적용됩니다.
  intercept_all: false

  model_profiles:
    # 정확한 모델 이름 매칭
    - model: "gpt-4o"
      tier: 1
      domains: [general, code, reasoning, creative]
      cost_per_1k_input_tokens: 0.005
      cost_per_1k_output_tokens: 0.015

    # 또 다른 정확한 매칭
    - model: "gpt-4o-mini"
      tier: 2
      domains: [general, code]
      cost_per_1k_input_tokens: 0.00015
      cost_per_1k_output_tokens: 0.0006

    # 글로브 패턴: Q4_K_M 양자화 모델 전체에 매칭
    - model_pattern: "*-q4_K_M"
      tier: 3
      domains: [general]

  # 라우팅 정책: 위에서 아래로 평가하며 첫 번째 매칭이 적용됩니다.
  routing_policies:
    - name: "trivial_to_lightweight"
      when:
        complexity: [trivial, simple]
        domain: [general]
      route_to:
        tier: 3

    - name: "code_to_flagship"
      when:
        domain: [code]
        complexity: [moderate, complex, expert]
      route_to:
        tier: 1
        prefer_domains: [code]

    - name: "vision_required"
      when:
        requires: [vision]
      route_to:
        tier: 1
        require_capabilities: [vision]

    - name: "complex_to_flagship"
      when:
        complexity: [complex, expert]
      route_to:
        tier: 1

    - name: "default_to_standard"
      when: {}                      # 항상 매칭되는 캐치올
      route_to:
        tier: 2

티어 분류

티어 의미 대표 예시
Flagship 1 최고 성능, 최고 비용 gpt-4o, claude-3.5-sonnet, gemini-1.5-pro
Standard 2 성능과 속도의 균형 gpt-4o-mini, claude-3-haiku
Lightweight 3 속도와 저비용에 최적화 llama-3-8b, phi-3-mini, 양자화 모델

도메인 전문화 태그

태그 설명
general 특정 전문 분야 없음
code 코드 생성, 디버깅, 리뷰
reasoning 복잡한 다단계 추론, 수학
creative 창작 글쓰기, 스토리텔링
multilingual 번역 및 다국어 작업
vision 이미지 이해

자동 추론

명시적 프로필이나 글로브 패턴에 매칭되는 모델이 없으면 라우터가 다음 순서로 티어를 자동 추론합니다.

  1. 가격 (model-metadata.yaml 기준): 입력 토큰 1,000개당 $3 이상이면 Flagship, $0.50 이상이면 Standard, 그 미만이면 Lightweight. 무료 모델은 가격 기반 추론을 건너뜁니다.

  2. 기능 (model-metadata.yaml 기준): vision, reasoning, audio, video, function_calling, tool 중 3개 이상이면 Flagship, 1개 이상이거나 전체 기능이 3개 이상이면 Standard.

  3. 이름 휴리스틱: pro, ultra, opus, sonnet, turbo 키워드가 포함되면 Flagship; mini, small, tiny, nano, lite, flash, haiku 및 양자화 마커(q4_, q5_, q8_, gguf, gptq, awq)가 포함되면 Lightweight.

자동 추론 결과는 모델 ID별로 최대 10,000개까지 캐시됩니다. 핫 리로드 시 또는 /admin/smart-routing/model-profiles PUT 엔드포인트 호출 시 캐시가 초기화됩니다.

글로브 패턴 문법

*만 와일드카드로 사용하며, 여러 개를 중첩할 수 있습니다.

패턴 매칭 미매칭
gpt-* gpt-4o, gpt-4o-mini claude-3
*-q4_K_M llama-3-8b-q4_K_M llama-3-8b-q5_K_M
gpt-*-turbo gpt-4-turbo, gpt-3.5-turbo gpt-4o
* 모든 문자열 해당 없음

요청 분류기

규칙 기반 분류기는 11가지 신호를 분석해 복잡도 수준, 도메인 태그, 필요 기능, 신뢰도 점수로 구성된 ClassificationResult를 생성합니다.

복잡도 수준

수준 설명 예시
trivial 인사말, 예/아니오, 단순 사실 조회 "2+2는?"
simple 짧은 설명, 기본 요약 "이 문단을 요약해줘"
moderate 다단계 추론, 중간 수준의 코드 작업 "이 함수를 리팩토링해줘"
complex 고급 알고리즘, 시스템 설계 "분산 캐시를 설계해줘"
expert 연구 수준 문제, 형식적 증명 "이 정리를 증명해줘"

분류 신호

신호 감지 내용
message_length 모든 메시지의 총 토큰 수
code_blocks 펜스 코드 블록 또는 인라인 코드
math_notation LaTeX, 수식, 수학 기호
system_prompt_complexity 시스템 프롬프트의 길이와 복잡도
conversation_depth 대화 턴 수
image_attachments 메시지의 멀티모달 이미지 콘텐츠
tool_definitions 요청의 도구/함수 정의
complexity_keywords "최적화", "설계", "증명" 같은 키워드
language_detection 비라틴 문자 또는 다국어 콘텐츠
creative_markers "소설", "시", "상상해줘" 같은 표현
analysis_markers "분석해줘", "비교해줘", "평가해줘" 같은 표현

감지된 신호가 많을수록 복잡도와 도메인 태그가 올라갑니다. 상충하는 신호(예: 창작 마커와 코드 마커가 동시에 존재)는 신뢰도 점수를 낮춥니다.

LLM 기반 분류기

규칙 기반 분류기는 빠르지만 일부 요청은 분류가 모호합니다. 정확도를 높여야 할 때, LLM 기반 분류기가 소형 저비용 모델에 분류 요청을 보냅니다. classifier.method로 세 가지 동작 방식을 선택할 수 있습니다.

방식 동작
rule 규칙 기반만 사용 (기본값). LLM 호출 없음.
llm 항상 LLM 분류기를 호출하고, 실패 시 규칙 기반으로 폴백.
hybrid 규칙 기반을 먼저 실행하고, 신뢰도가 confidence_threshold 미만일 때만 LLM으로 에스컬레이션.

하이브리드 모드가 권장 운영 설정입니다. 명확한 요청은 마이크로초 단위로 규칙 기반 분류기가 처리하고, 모호한 요청(보통 전체의 10~20%)에만 LLM 호출 지연이 추가됩니다.

smart_routing:
  enabled: true

  classifier:
    # 분류 방식: "rule" (기본값), "llm", "hybrid"
    method: hybrid

    rule:
      # 하이브리드 모드에서 LLM 에스컬레이션을 트리거하는 신뢰도 임계값.
      # 범위: 0.0 ~ 1.0. 기본값: 0.7.
      confidence_threshold: 0.7

    llm:
      # 분류에 사용할 모델. 빠르고 저렴한 모델이면 충분합니다.
      model: "gpt-4o-mini"

      # 분류 요청을 보낼 백엔드 이름. 설정된 백엔드여야 합니다.
      # 생략하면 백엔드 URL을 직접 사용합니다.
      backend: "openai-fast"

      # 분류 요청의 최대 허용 시간 (밀리초).
      timeout_ms: 2000

      # 분류기에 전송하는 최대 입력 토큰 수 (초과 콘텐츠는 잘림).
      max_input_tokens: 500

      # 파싱 실패 후 재시도 횟수 (0 또는 1). 기본값: 1.
      max_retries: 1

      # 분류 온도. 0.0이면 결정적 출력.
      temperature: 0.0

      # 분류 응답의 최대 출력 토큰 수.
      max_output_tokens: 150

      # 구조화 출력 전략. "auto"는 백엔드 유형에 따라 최적 방식을 선택합니다:
      # json_schema (OpenAI/vLLM), tool_use (Anthropic), json_object (Ollama/Gemini/LM Studio),
      # prompt_only (기타).
      structured_output: auto   # auto | json_schema | tool_use | json_object | prompt_only

      # 시스템 프롬프트에 기본 퓨샷 예제 포함 여부.
      few_shot_examples: true

      # 기본 예제 다음에 추가되는 커스텀 퓨샷 예제.
      custom_examples:
        - user: "이부프로펜의 권장 복용량은 얼마인가요?"
          classification:
            complexity: simple
            domain: medical

      # 분류 결과의 캐시 유효 시간 (초). 기본값: 300.
      cache_ttl_seconds: 300

      # 최대 캐시 항목 수. 기본값: 10000.
      max_cache_entries: 10000

      # 이 부하 상태에 도달하면 LLM 분류기를 비활성화합니다.
      # "critical" (기본값) 또는 "warning". 비워두면 항상 활성.
      disable_under_load_state: critical

    # 기본 복잡도 분류 체계를 커스텀 수준으로 확장합니다.
    custom_complexity_levels:
      - name: specialized
        description: "도메인 특화 파인튜닝 모델이 필요한 요청"
        rank: 6   # 선택적 순서 힌트 (높을수록 어려움)

    # 기본 도메인 분류 체계를 커스텀 카테고리로 확장합니다.
    custom_domains:
      - name: medical
        description: "의료  임상 관련 질문"

구조화 출력 전략

LLM 분류기는 분류 모델에서 구조화된 JSON을 받아야 합니다. auto 전략이 백엔드 유형에 따라 적절한 방식을 자동 선택하지만, 명시적으로 지정할 수도 있습니다.

전략 방식 지원 백엔드
json_schema response_format: { type: "json_schema" } OpenAI, Azure, vLLM
tool_use 도구/함수 호출 Anthropic, Gemini
json_object response_format: { type: "json_object" } OpenAI, Ollama, Gemini, LM Studio, llama.cpp
prompt_only 자유형 텍스트에서 정규식으로 JSON 추출 모든 백엔드

분류기 응답을 파싱할 수 없으면 수정 프롬프트로 한 번 재시도합니다(max_retries로 제어). 재시도도 실패하면 규칙 기반 분류기 결과를 사용합니다.

분류 캐시

동일한 요청에 대한 반복 LLM 호출을 줄이기 위해 분류 결과를 TTL과 함께 인메모리에 캐시합니다. 캐시 키는 잘린 사용자 메시지의 SHA-256 해시이므로, 의미적으로 동일한 요청은 같은 캐시 결과를 공유합니다. 캐시는 프로세스 단위이며 여러 라우터 인스턴스 간에 공유되지 않습니다.

커스텀 분류 체계

복잡도 수준과 도메인 태그 모두 확장 가능합니다. custom_complexity_levelscustom_domains로 추가한 커스텀 값은 분류기의 시스템 프롬프트와 구조화 출력 스키마에 반영됩니다. 라우팅 정책에서도 기본 값처럼 커스텀 값을 참조할 수 있습니다.

routing_policies:
  - name: specialized_to_flagship
    when:
      complexity: [specialized]
    route_to:
      tier: 1

우회 헤더

LLM 분류기는 분류 요청에 X-Smart-Route-Bypass: true 헤더를 추가합니다. 이 헤더가 있는 요청은 스마트 라우팅을 건너뛰어, 분류기 백엔드가 동일한 라우터 인스턴스 뒤에 있을 때 발생할 수 있는 순환 분류 루프를 방지합니다.

라우팅 정책

정책은 위에서 아래로 평가하며 첫 번째로 매칭된 정책이 적용됩니다. 매칭되는 정책이 없고 캐치올도 없으면 default_tier로 폴백됩니다.

정책 조건 논리

  • when 블록 내 필드들은 AND로 평가됩니다. 명시된 모든 필드가 일치해야 합니다.
  • 하나의 필드 안에서 값들은 OR로 평가됩니다. complexity: [trivial, simple]은 둘 중 하나와 일치하면 됩니다.
  • when: {}는 항상 매칭되는 캐치올입니다.

정책 필드

when 조건:

필드 타입 설명
complexity [string] 매칭할 복잡도 수준 목록 (OR 논리)
domain [string] 매칭할 도메인 태그 목록 (OR 논리)
requires [string] 모두 존재해야 하는 기능 목록 (AND 논리)

route_to 액션:

필드 타입 설명
tier int 대상 티어 (1=Flagship, 2=Standard, 3=Lightweight)
prefer_domains [string] 도메인 특화 모델에 대한 소프트 선호도
require_capabilities [string] 하드 필터: 해당 기능이 있는 모델만 선택

티어 내 모델 선택

매칭된 티어에 여러 모델이 있을 경우 다음 기준으로 점수를 매겨 선택합니다.

  1. 도메인 선호도 매칭 (소프트 보너스)
  2. 기능 매칭 (require_capabilities가 설정된 경우 하드 필터)
  3. 비용 점수 (같은 티어 안에서 낮은 비용이 높은 점수)
  4. 동점일 경우 무작위 선택

매칭된 티어에 사용 가능한 모델이 없으면 인접 티어를 순서대로 시도합니다(예: Lightweight가 비어 있으면 Standard, 그다음 Flagship 순).

부하 기반 동적 티어 조정

평상시에는 각 요청에 가장 적합한 티어를 선택하지만, 트래픽이 급증하거나 지연 시간이 높아지면 부하 모니터가 실시간 지표를 추적해 자동으로 더 가벼운 모델 티어로 라우팅을 전환하고, 부하가 회복되면 원래대로 돌아옵니다.

부하 관리는 기본적으로 비활성화되어 있습니다. smart_routing.load_management 아래에서 활성화합니다:

smart_routing:
  enabled: true
  load_management:
    enabled: true

    # 부하 지표 평가 주기(밀리초). 기본값: 1000.
    assessment_interval_ms: 1000

    # Warning/Critical 상태로 전환하는 임계값.
    # 하나의 지표라도 초과되면 해당 상태로 전환됩니다.
    thresholds:
      warning:
        requests_per_second: 100
        avg_latency_ms: 3000
        error_rate: 0.05
        in_flight_requests: 50
      critical:
        requests_per_second: 200
        avg_latency_ms: 5000
        error_rate: 0.15
        in_flight_requests: 100

    # 각 부하 상태에서 적용할 라우팅 제한.
    degradation:
      warning:
        max_tier: 2           # Standard 티어까지만 허용
        prefer_quantized: false
        reject_expert: false
      critical:
        max_tier: 3           # Lightweight 티어까지만 허용
        prefer_quantized: true
        reject_expert: false

    # 복구 동작.
    recovery:
      cooldown_seconds: 30    # 낮은 부하 상태로 전환하기 전 최소 대기 시간(초)
      hysteresis_factor: 0.8  # 임계값의 80% 이하로 떨어져야 복구됨

부하 상태

상태 의미 기본 라우팅 제한
normal 모든 지표가 정상 범위 내 제한 없음
warning Warning 임계값을 초과한 지표 발생 Standard 티어(tier 2) 이하로 제한
critical Critical 임계값을 초과한 지표 발생 Lightweight 티어(tier 3) 이하로 제한, 양자화 모델 우선

본래 Flagship(tier 1)으로 라우팅될 요청이 Warning 상태에서는 Standard로 자동 하향됩니다. 라우팅 결정 로그에는 조정된 정책 이름이 <원래_정책>__load_warning 또는 <원래_정책>__load_critical 형식으로 기록됩니다.

히스테리시스와 쿨다운

부하 상태가 빠르게 오락가락하면 그 자체로 시스템 불안정을 유발할 수 있어, 두 가지 메커니즘으로 이를 방지합니다:

  • 히스테리시스: Warning 상태를 벗어나려면 지표가 임계값 * hysteresis_factor(기본 0.8) 이하로 내려가야 합니다. 100 RPS에서 Warning에 진입했다면 80 RPS 이하로 떨어져야 Normal로 회복됩니다.
  • 쿨다운: 상태 전환 후 cooldown_seconds(기본 30초) 동안은 더 낮은 상태로의 복구가 차단됩니다. 반면 상태 상향(Normal에서 Warning, Warning에서 Critical)은 쿨다운과 무관하게 즉시 적용됩니다.

티어별 임계값 재정의

티어마다 처리 용량이 다를 때는 티어별로 임계값을 별도 지정할 수 있습니다:

smart_routing:
  load_management:
    enabled: true
    thresholds:
      warning:
        requests_per_second: 100
    tier_thresholds:
      "1":                      # Flagship은 RPS 내성이 낮음
        warning:
          requests_per_second: 50
      "3":                      # Lightweight는 더 많은 요청을 처리 가능
        warning:
          requests_per_second: 300

Prometheus 지표

metrics 기능이 활성화된 경우 부하 관리 관련 지표가 노출됩니다:

지표 유형 설명
smart_routing_load_state Gauge 현재 부하 상태: 0=Normal, 1=Warning, 2=Critical
smart_routing_tier_degradation_total Counter 부하로 인해 티어가 하향된 횟수
smart_routing_load_transitions_total Counter 부하 상태 전환 횟수(from, to 레이블 포함)

LLM 분류기는 추가로 6개의 지표를 노출합니다:

지표 유형 설명
smart_routing_llm_classifier_calls_total Counter LLM 분류기 호출 총 횟수
smart_routing_llm_classifier_cache_hits_total Counter 캐시에서 반환된 분류 결과 수
smart_routing_llm_classifier_duration_seconds Histogram LLM 분류 전체 지연 시간
smart_routing_llm_classifier_fallbacks_total Counter LLM 결과를 버리고 규칙 기반 결과를 사용한 횟수
smart_routing_llm_classifier_parse_errors_total Counter 재시도 전 응답 파싱 실패 횟수
smart_routing_llm_classifier_retries_total Counter 초기 파싱 실패 후 재시도 횟수

디버그 응답 헤더

debug_headers: true로 설정하면 HTTP 응답 헤더에 스마트 라우팅 결정 정보가 포함됩니다. 개발 및 스테이징 환경에서 사용하기 위한 기능입니다.

smart_routing:
  enabled: true
  debug_headers: true  # 개발/스테이징 환경에서 활성화

활성화하면 스마트 라우팅된 응답에 다음 헤더가 추가됩니다:

헤더 설명
X-Smart-Route-Source 요청된 원본 모델 (예: auto)
X-Smart-Route-Target 선택된 모델 (예: gpt-4o-mini)
X-Smart-Route-Complexity 분류된 복잡도 수준
X-Smart-Route-Domain 분류된 도메인
X-Smart-Route-Policy 매칭된 정책
X-Smart-Route-Load-State 라우팅 시점의 부하 상태
X-Smart-Route-Classifier 사용된 분류기 (rule_based 또는 llm_based)

Admin API

스마트 라우팅은 관찰 가능성과 관리를 위해 /admin/smart-routing/ 아래에 여러 admin 엔드포인트를 노출합니다. 전체 엔드포인트 참조는 Admin API 문서에서 확인하세요.

주요 엔드포인트:

  • GET /status -- 전체 상태, 부하 상태, 정책 수
  • POST /classify -- 실제 라우팅 없이 요청 분류 (진단용)
  • POST /simulate -- 전체 라우팅 파이프라인 시뮬레이션
  • GET /policiesPUT /policies -- 정책 조회 및 핫 리로드
  • GET /load-state -- 평가 정보를 포함한 현재 부하 상태
  • GET /cache/statsPOST /cache/clear -- LLM 분류기 캐시 관리

구조화 로깅

모든 스마트 라우팅 결정은 DEBUG 수준으로 구조화된 필드와 함께 기록됩니다:

level=DEBUG msg="Smart routing decision"
  source_model="auto"
  target_model="gpt-4o-mini"
  complexity="simple"
  domain="general"
  policy="trivial_to_lightweight"
  load_state="normal"
  classifier="rule_based"
  confidence=0.92
  classification_ms=0.3

부하 상태 전환 및 정책 변경은 INFO 수준으로 기록됩니다.

핫 리로드

smart_routing 섹션은 설정 파일이 변경될 때 즉시 리로드됩니다. 리로드 후 추론 캐시가 초기화되어 다음 요청부터 새 프로필을 기준으로 재평가합니다. routing_policiesload_management 설정도 서버 재시작 없이 즉시 반영됩니다. PUT /admin/smart-routing/policies 엔드포인트를 통해 런타임에 정책을 업데이트할 수도 있습니다.


환경별 설정

개발 설정

# config/development.yaml
server:
  bind_address: "127.0.0.1:8080"

backends:
  - name: "local-ollama"
    url: "http://localhost:11434"

health_checks:
  interval: "10s"                 # 더 빈번한 확인
  timeout: "5s"

logging:
  level: "debug"                  # 상세 로깅
  format: "pretty"                # 사람이 읽기 쉬운

프로덕션 설정

# config/production.yaml
server:
  bind_address: "0.0.0.0:8080"
  workers: 8                      # 프로덕션용 더 많은 워커
  connection_pool_size: 300       # 더 큰 연결 풀

backends:
  - name: "primary-openai"
    url: "https://api.openai.com"
    weight: 3
  - name: "secondary-azure"
    url: "https://azure-openai.example.com"
    weight: 2
  - name: "fallback-local"
    url: "http://internal-llm:11434"
    weight: 1

health_checks:
  interval: "60s"                 # 덜 빈번한 확인
  timeout: "15s"                  # 네트워크 지연을 위한 더 긴 타임아웃
  unhealthy_threshold: 5          # 더 많은 허용
  healthy_threshold: 3

timeouts:
  request:
    standard:
      total: "120s"               # 프로덕션용 제한된 타임아웃

retry:
  max_attempts: 5                 # 더 많은 재시도

logging:
  level: "warn"                   # 덜 상세한 로깅
  format: "json"                  # 구조화된 로깅

컨테이너 설정

# config/container.yaml - 컨테이너에 최적화
server:
  bind_address: "0.0.0.0:8080"
  workers: 0                      # 컨테이너 제한에 따라 자동 감지

backends:
  - name: "backend-1"
    url: "${BACKEND_1_URL}"       # 환경 변수 치환
  - name: "backend-2"
    url: "${BACKEND_2_URL}"

logging:
  level: "${LOG_LEVEL}"           # 환경을 통해 설정 가능
  format: "json"                  # 컨테이너에서 항상 JSON