콘텐츠로 이동

고급 설정

전역 프롬프트

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

model_metadata_file은 기본 계층일 뿐입니다. 라우터는 model-metadata.d/ 드롭인 디렉터리와 model_metadata_dirs 설정 키에서도 메타데이터를 조립하므로, 운영자가 바꾼 내용을 metadata download가 덮어쓰는 파일에 그대로 둘 필요가 없습니다. 탐색 순서, 병합 규칙, metadata show계층형 모델 메타데이터에서 다룹니다.

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-pro 내장 OpenAI 메타데이터 + model-metadata.yaml 원래 GPT-5 Pro, high reasoning effort만 지원
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 모델을 쓰기 위해 클라이언트 쪽을 바꿀 필요가 없습니다.

Chat Completions 브리지는 reasoning_effort, 평면 verbosity, metadata, safety_identifier, prompt_cache_key, prompt_cache_options, prompt_cache_retention 등 호환되는 GPT-5 제어를 보존합니다. 잘못된 값은 변환 과정에서 조용히 누락하지 않고 명시적으로 실패합니다.

백엔드 유형 제약

/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)와 제거 후 결정 대상과 함께 출력합니다.

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
        }
      }
    }
  ]
}

요청 파라미터 정책

선택 사항인 request_params 섹션은 요청 파라미터 정책의 typed 기본값, 운영자 override, 제한 계약을 정의합니다. 라우터는 이 설정 스냅샷을 검증하고 핫 리로드한 뒤 Chat Completions, Completions, Responses, Anthropic Messages 진입점에서 캐시 identity나 프로바이더 라우팅을 정하기 전에 한 번 적용합니다.

request_params:
  defaults:
    max_tokens: 1024
  overrides:
    temperature: 0.0
  limits:
    max_tokens:
      min: 1
      max: 4096
  models:
    gpt-5:
      defaults:
        top_p: 0.9
      limits:
        max_tokens:
          max: 2048

관리되는 예제는 블록 스타일 YAML을 사용합니다. { max_tokens: 1024 } 같은 동등한 플로 매핑도 허용되며 TOML 설정도 같은 의미를 가집니다.

적용 순서

프로토콜 통합은 세 모드를 정해진 순서로 적용합니다.

  1. defaults는 누락되었거나 JSON/YAML null인 필드를 채우고 클라이언트가 제공한 값은 보존합니다.
  2. overrides는 클라이언트 값이나 기본값에서 만들어진 결과를 항상 대체합니다.
  3. limits는 존재하는 값을 포괄적 minmax 경계 안으로 제한합니다. 제한만으로 누락된 필드를 만들지는 않습니다.

최상위 객체는 전역 정책입니다. 각 models.<name> 항목도 같은 세 객체를 정의할 수 있습니다. 모델 필드는 각 모드 안에서 전역 필드를 개별적으로 대체하며, 모델 항목에서 생략한 필드는 해당 전역 값이나 범위를 상속합니다.

런타임 계약은 모델 이름이 로컬 별칭 해석 후 정확히 일치하고, 선택한 범위가 컨트롤 플레인 치환, arbitrage, 백엔드 선택, 재시도 또는 폴백보다 먼저 고정되며 그 과정에서 바뀌지 않아야 함을 요구합니다. control_plane.policy로 전달된 Hub 티어 제한은 limits-only이며 각 경계에서 이 로컬 제한과 교차하므로 어느 쪽도 다른 쪽보다 범위를 넓힐 수 없고 빈 교집합은 요청을 거부합니다. 백엔드 범위, 정규식, 글로브 매칭은 지원하지 않습니다.

지원 파라미터 및 유효성 검사

다음 canonical 파라미터 이름만 허용됩니다.

파라미터 타입 허용 정책 범위
temperature 실수 0 이상 2 이하
top_p 실수 0 이상 1 이하
max_tokens 부호 없는 정수 1 이상 1,000,000 이하
presence_penalty 실수 -2 이상 2 이하
frequency_penalty 실수 -2 이상 2 이하
top_k 부호 없는 정수 1 이상 1,000,000 이하
min_p 실수 0 이상 1 이하

알 수 없는 모드나 파라미터 이름, 잘못된 스칼라 타입, 유한하지 않은 실수, 범위를 벗어난 값, min > max는 경로가 포함된 오류로 거부됩니다. models에는 정확히 일치하는 범위를 최대 64개까지 둘 수 있으며, 각 모델 키는 공백 제거 후 비어 있지 않고 256바이트 이하여야 하며 제어 문자를 포함할 수 없습니다. 적용 순서가 결정적이므로 같은 파라미터를 여러 모드에 지정해도 됩니다. 정책의 null 값은 생략으로 취급하며 JSON null을 강제로 설정하라는 의미가 아닙니다.

런타임 프로바이더 호환성

호환성 검사는 정책에 설정되어 실제로 변경되거나 제한된 필드에만 적용됩니다. 정책이 관리하지 않는 클라이언트 필드는 기존 프로바이더별 passthrough 동작을 유지합니다. 적격 primary 또는 설정된 fallback 경로가 정책 적용 필드를 보존할 수 없으면, 라우터는 그 값을 조용히 버리는 대신 캐시 조회나 업스트림 I/O 전에 unsupported_request_parameter로 요청을 거부합니다.

진입점과 선택 경로 보존 가능한 정책 적용 필드 거부되는 정책 적용 필드 또는 조건
Chat Completions → 로컬 OpenAI 호환 백엔드 canonical 필드 7개 전부 이 정책 표면에서는 없음
Chat Completions → cloud OpenAI 또는 Gemini temperature, top_p, max_tokens, presence_penalty, frequency_penalty top_k, min_p
Chat Completions → Anthropic, Mantle 또는 Anthropic 호환 runtime 변환 temperature, top_p, max_tokens, top_k penalty 두 개와 min_p; 모델이나 reasoning 모드가 sampling 필드를 추가로 제한할 수 있음
Chat Completions → Bedrock Converse temperature, top_p, max_tokens top_k, min_p, penalty 두 개
Legacy Completions OpenAI 호환 raw 경로는 canonical 필드를 보존 이 진입점에는 손실 없는 native 변환이 없으므로 native Anthropic, Gemini, Bedrock 경로는 정책 적용 필드를 거부
Responses passthrough 또는 Chat bridge temperature, top_p, max_tokens, penalty 두 개 top_k, min_p; 프로바이더별 모델 또는 reasoning 제한은 계속 적용
Responses → Anthropic 또는 Gemini 변환 temperature, top_p, max_tokens top_k, min_p, penalty 두 개
Native Anthropic Messages 선택 전에 모든 적격 백엔드의 capability를 해석 그 경로 중 하나라도 지원하지 않는 필드는 선택 전에 거부

스트리밍 폴백에도 같은 표가 적용됩니다. 손실 없는 native, Bedrock, Gemini 또는 Unix 소켓 변환이 없는 cross-provider 스트리밍 hop은 명시적으로 실패하며, 일부만 변환된 정책 요청을 전달하지 않습니다. 각 hop은 canonical effective request에서 다시 만들어지므로 여러 단계의 폴백에서 변환이 누적되거나 복원된 필드가 사라지지 않습니다.

응답 캐시 조회와 저장은 라우팅 결과가 정책 호환 백엔드 하나로 확정되고 폴백 체인이 목적지를 바꿀 수 없을 때만 활성화됩니다. 백엔드 identity도 캐시 namespace에 포함됩니다. 적격 백엔드가 여러 개이거나 폴백 체인이 활성화된 요청은 exact, prefix, streaming 캐시를 우회하므로 한 프로바이더에서 만들어진 항목이 다른 경로의 요청을 충족하지 않습니다.

관측 및 강제 적용

정책이 요청 필드를 하나 이상 변경하고 선택된 프로바이더 경로가 유효 요청을 보존할 수 있으면 라우터는 정보 제공용 x-continuum-request-params 응답 헤더를 추가합니다. 쉼표로 구분된 토큰에는 default:max_tokens, limit:temperature처럼 상한이 정해진 동작과 canonical 파라미터 이름만 들어갑니다. 요청 값, 모델 이름, 키, 티어 식별자는 절대 포함하지 않습니다.

이 정책은 운영자가 강제로 적용하는 설정입니다. 클라이언트는 요청 헤더, 쿼리 파라미터, 요청 본문 옵션으로 정책을 끄거나 우회할 수 없습니다. 정보 제공용 응답 헤더는 옵트아웃 수단이 아니며 인증 자격 증명도 아닙니다.

유효한 변경이 일어난 뒤 만들어진 성공 응답, 호환되는 캐시 적중, 업스트림 오류 응답에는 이 헤더가 포함됩니다. 정책이 아무것도 변경하지 않았거나 정책 또는 프로바이더 호환성 검사에서 트랜잭션을 거부하면 헤더를 생략합니다. 거부된 트랜잭션은 롤백되며 적용되었다고 오해하게 하는 헤더를 받지 않습니다. 로그와 Prometheus 메트릭도 같은 상한이 정해진 applied/rejected 결과를 사용합니다.

핫 리로드 및 개인정보 보호

request_params는 즉시 핫 리로드되는 섹션입니다. 파싱과 유효성 검사를 통과한 새 리비전은 게시된 설정 스냅샷을 원자적으로 교체합니다. 거부된 리비전은 마지막으로 검증된 스냅샷을 교체하지 않습니다. 각 요청은 변경 전에 불변 config 스냅샷 하나와 인증된 Hub 티어 정책 하나를 캡처하므로 진행 중인 요청·재시도·폴백에서 정책이 중간에 바뀌지 않습니다.

적용 정책 텔레메트리는 제한된 파라미터 이름, 동작, 조합 출처, 결과만 노출하고 요청 값은 포함하지 않습니다. 설정 및 MCP 화면에는 운영자가 작성한 정책이 표시될 수 있지만 프롬프트 내용이나 임의의 요청 데이터를 직렬화하지 않으며 비밀 타입 설정 필드는 계속 마스킹합니다.

핫 리로드

hot-reload 기능과 파일 감시자가 활성화되면 유효한 파일 개정이 새 불변 설정 스냅샷으로 게시됩니다. 유효하지 않은 개정은 이전 스냅샷을 유지합니다. 파싱되는 모든 필드에 실행 중인 런타임 소비자가 있는 것은 아니므로 아래의 보수적인 표를 따르십시오.

재시작 없이 적용

  • 백엔드 추가/삭제/수정(풀이 조정되고 새 백엔드는 즉시 상태 확인)
  • 상태 확인 주기와 임계값
  • 해당 서비스가 활성화된 경우 속도 제한 및 서킷 브레이커 정책
  • 새 요청이 읽는 전역 프롬프트, 요청 매개변수, 스트리밍 정책
  • 폴백 서비스가 이미 존재하는 경우 폴백 체인/정책 수정
  • 스마트 라우팅 정책 스냅샷
  • selection_strategy(실행 중인 풀에 원자적으로 교체되며 멤버십, 통계, 처리 중 요청 집계가 보존됨)
  • 재시도 정책(새 요청은 갱신된 전체 정책을 캡처하고 진행 중 루프는 진입 시 스냅샷을 유지함)
  • 요청/스트리밍/이미지/모델 오버라이드/상태 확인 타임아웃 예산
  • 모든 prefix_routing.* 필드: enabledmax_prefix_length는 요청 시 접두사 추출에 사용되고, load_factor_epsilonvirtual_nodes는 실행 중인 풀에 교체되며(virtual_nodes 변경 시 해시 링도 다시 만듦), anthropic_cache_control_injection은 요청마다 읽힘

재시작 필요 또는 현재 실시간 미적용

  • server.bind_address, server.socket_mode, server.workers
  • server.connection_pool_size와 CORS 라우터 계층
  • 로깅 subscriber 형식/필터와 추적 미들웨어 구성
  • 시작 시 존재하지 않았던 선택 서비스 토글(예: fallback.enabled)
  • 응답 캐시 백엔드/객체 구성
  • timeouts.connectiontimeouts.request.streaming.total(공유 HTTP 클라이언트의 연결/전체 상한이며, 요청별 예산 자체는 실시간 반영)

재시작 필드를 수정한 뒤 검증하고 재시작합니다.

continuum-router config validate config.yaml

인증된 Admin API에서 감시자 가용성과 라우터 기능 요약을 확인합니다.

curl -H "Authorization: Bearer $ADMIN_TOKEN" \
  http://localhost:8080/admin/config/hot-reload-status

재시작 필요 값이 바뀐 경우 새로 파싱된 파일만이 아니라 활성 서브시스템 상태를 기준으로 판단하십시오.

분산 추적

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

로드 밸런싱 전략

최상위 selection_strategy 필드를 사용합니다. 구현된 여섯 값은 RoundRobin, WeightedRoundRobin, LeastLatency, Random, ConsistentHash, PrefixAwareHash입니다. 백엔드 상태 필터링은 선택 전에 수행됩니다.

selection_strategy: WeightedRoundRobin

backends:
  - name: large
    url: http://large.example.com
    weight: 3
  - name: small
    url: http://small.example.com
    weight: 1

정확한 의미와 현재 시작/핫 리로드 제한은 로드 밸런싱을 참고하십시오.

백엔드별 재시도 설정

backends:
  - name: "slow-backend"
    url: "http://slow.example.com"
    retry_override:               # 완전한 백엔드별 재시도 정책
      max_attempts: 5
      initial_delay: "500ms"
      max_delay: "60s"
      backoff_multiplier: 2.0
      jitter: true
      retryable_status_codes: [429, 502, 503, 504]
      retryable_errors: [ConnectionError, TimeoutError]
      timeout: "120s"

모델 폴백

최상위 fallback 섹션에서 순서가 있는 대체 모델을 설정합니다.

fallback:
  enabled: true
  mid_stream_enabled: true
  fallback_chains:
    gpt-5.4:
      - gpt-5.4-mini
      - claude-sonnet-4-6
  fallback_policy:
    trigger_conditions:
      error_codes: [429, 500, 502, 503, 504]
      timeout: true
      connection_error: true
      model_not_found: true
      backend_unhealthy: true
    max_fallback_attempts: 3
    fallback_timeout_multiplier: 1.5
    preserve_parameters: true
  model_settings:
    gpt-5.4:
      fallback_enabled: true
      notify_on_fallback: true

일반 프록시 경로는 circuit_breaker_open을 만들지 않습니다. 추론 트래픽에는 능동 상태 확인과 backend_unhealthy를 사용하세요. 검증, 헤더, 메트릭, 공급자 간 변환, 스트림 시작 전/중간 동작의 차이는 모델 폴백을 참고하세요.

미드스트림 폴백

미드스트림 폴백은 기본 백엔드가 응답 도중 실패할 때 라우터가 진행 중인 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. 폴백 체인: 미드스트림 폴백이 활성화되려면 반드시 설정해야 함
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
      backend_unhealthy: true

# 4. 미드스트림 폴백: 연속 모드 (기본값: 활성화)
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 중간: 더 적은 실패 횟수로 백엔드를 비정상으로 표시 값이 낮을수록 일시적 오류에 민감해짐
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"
  unhealthy_threshold: 3
  healthy_threshold: 2
  endpoint: "/health"

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
  endpoint: "/health"

timeouts:
  connection: "10s"
  request:
    standard:
      first_byte: "30s"
      total: "120s"               # 프로덕션용 제한된 타임아웃
    streaming:
      first_byte: "60s"
      chunk_interval: "30s"
      total: "600s"
  health_check:
    timeout: "15s"
    interval: "60s"

retry:
  max_attempts: 3                 # 제한된 재시도
  initial_delay: "100ms"
  max_delay: "10s"
  backoff_multiplier: 2.0
  jitter: true
  retryable_status_codes: [429, 502, 503, 504]
  retryable_errors: [ConnectionError, TimeoutError]
  timeout: "30s"

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}"

health_checks:
  interval: "30s"
  timeout: "5s"
  unhealthy_threshold: 3
  healthy_threshold: 2
  endpoint: "/health"

logging:
  level: "info"                   # 필요하면 검증 전에 다른 값으로 렌더링
  format: "json"                  # 컨테이너에서 항상 JSON