Admin REST API 레퍼런스¶
이 문서는 Continuum Router의 Admin REST API를 사용하여 설정 제어 애플리케이션을 구축하는 개발자를 위한 포괄적인 가이드를 제공합니다. 설정 관리 API를 사용하면 서버 재시작 없이 런타임에 설정을 보고, 수정하고, 관리할 수 있습니다.
목차¶
- 개요
- 인증
- 기본 URL 및 헤더
- 설정 쿼리 API
- 설정 수정 API
- 설정 저장/복원 API
- 백엔드 관리 API
- 통계 API
- 응답 캐시 Admin API
- KV 캐시 인덱스 Admin API
- 데이터 모델
- 핫 리로드 동작
- 오류 처리
- 클라이언트 SDK 예제
- 모범 사례
- 보안 고려 사항
개요¶
Admin REST API는 Continuum Router의 설정 시스템에 프로그래밍 방식으로 액세스할 수 있게 하며 다음을 지원합니다:
- 실시간 설정 보기: 민감한 데이터가 자동으로 마스킹된 현재 설정 조회
- 동적 설정 업데이트: 서버 재시작 없이 설정 섹션 수정
- 설정 버전 관리: 전체 히스토리 및 롤백 기능으로 변경 사항 추적
- 백엔드 관리: 백엔드를 동적으로 추가, 제거, 수정
- 내보내기/가져오기: 여러 형식 (YAML, JSON, TOML)으로 설정 저장 및 복원
주요 기능¶
| 기능 | 설명 |
|---|---|
| 핫 리로드 | 섹션 타입에 따라 변경 사항이 즉시 또는 점진적으로 적용됨 |
| 민감 정보 마스킹 | API 키, 비밀번호, 토큰이 응답에서 자동으로 마스킹됨 |
| 검증 | dry-run 지원으로 적용 전 모든 변경 사항 검증 |
| 감사 로깅 | 보안 및 규정 준수를 위해 모든 수정 사항 로깅 |
| 히스토리 추적 | 롤백을 위해 최대 100개의 설정 버전 유지 |
인증¶
모든 Admin API 엔드포인트는 Admin Auth 시스템을 통한 인증이 필요합니다.
인증 방법¶
1. Bearer 토큰¶
2. Basic 인증¶
3. API 키 헤더¶
설정¶
config.yaml에서 admin 인증을 설정합니다:
admin:
auth:
method: bearer_token # 옵션: none, bearer_token, basic, api_key
token: "${ADMIN_TOKEN}" # 환경 변수 지원
# Basic auth의 경우:
# username: admin
# password: "${ADMIN_PASSWORD}"
# IP 화이트리스트 (선택 사항)
ip_whitelist:
- "127.0.0.1"
- "10.0.0.0/8"
# 설정 가능한 제한
max_history_entries: 100
max_backend_name_length: 256
기본 URL 및 헤더¶
기본 URL¶
일반 요청 헤더¶
일반 응답 헤더¶
설정 쿼리 API¶
전체 설정 가져오기¶
민감한 정보가 마스킹된 전체 설정을 조회합니다.
응답¶
{
"config": {
"server": {
"bind_address": "0.0.0.0:8080",
"workers": 4
},
"backends": [
{
"name": "openai",
"url": "https://api.openai.com",
"api_key": "sk-***abcd",
"weight": 1
}
],
"logging": {
"level": "info"
},
"rate_limiting": {
"enabled": true,
"requests_per_minute": 100
}
},
"hot_reload_enabled": true,
"last_modified": "2025-12-13T10:30:00Z"
}
예제¶
설정 섹션 목록¶
핫 리로드 기능과 함께 사용 가능한 모든 설정 섹션을 가져옵니다.
응답¶
{
"sections": [
{
"name": "server",
"description": "Server configuration including bind address and workers",
"hot_reload_capability": "requires_restart"
},
{
"name": "backends",
"description": "Backend server configurations",
"hot_reload_capability": "gradual"
},
{
"name": "logging",
"description": "Logging configuration",
"hot_reload_capability": "immediate"
},
{
"name": "rate_limiting",
"description": "Rate limiting configuration",
"hot_reload_capability": "immediate"
},
{
"name": "circuit_breaker",
"description": "Circuit breaker configuration",
"hot_reload_capability": "immediate"
},
{
"name": "retry",
"description": "Retry policy configuration",
"hot_reload_capability": "immediate"
},
{
"name": "timeouts",
"description": "Timeout configuration",
"hot_reload_capability": "gradual"
},
{
"name": "health_checks",
"description": "Health check configuration",
"hot_reload_capability": "gradual"
},
{
"name": "global_prompts",
"description": "Global prompt injection configuration",
"hot_reload_capability": "immediate"
},
{
"name": "fallback",
"description": "Model fallback configuration",
"hot_reload_capability": "gradual"
},
{
"name": "files",
"description": "Files API configuration",
"hot_reload_capability": "gradual"
},
{
"name": "api_keys",
"description": "API keys configuration",
"hot_reload_capability": "immediate"
},
{
"name": "metrics",
"description": "Metrics and monitoring configuration",
"hot_reload_capability": "gradual"
},
{
"name": "admin",
"description": "Admin API configuration",
"hot_reload_capability": "gradual"
},
{
"name": "routing",
"description": "Request routing configuration",
"hot_reload_capability": "gradual"
}
]
}
예제¶
curl -s http://localhost:8080/admin/config/sections \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.sections[].name'
섹션 설정 가져오기¶
특정 섹션의 설정을 조회합니다.
경로 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
section | string | 예 | 섹션 이름 (위 목록 참조) |
응답¶
{
"section": "logging",
"config": {
"level": "info",
"format": "json",
"file": "/var/log/continuum-router.log"
},
"hot_reload_capability": "immediate",
"description": "Logging configuration"
}
예제¶
# 로깅 설정 가져오기
curl -s http://localhost:8080/admin/config/logging \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
# 백엔드 설정 가져오기
curl -s http://localhost:8080/admin/config/backends \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
설정 스키마 가져오기¶
설정 검증을 위한 JSON Schema를 조회합니다.
쿼리 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
section | string | 아니오 | 특정 섹션에 대한 스키마만 가져오기 |
응답¶
{
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"server": {
"type": "object",
"properties": {
"bind_address": {
"type": "string",
"pattern": "^[^:]+:[0-9]+$",
"description": "Server bind address in host:port format"
},
"workers": {
"type": "integer",
"minimum": 1,
"description": "Number of worker threads"
}
}
},
"logging": {
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": ["trace", "debug", "info", "warn", "error"]
}
}
}
}
}
}
예제¶
# 전체 스키마 가져오기
curl -s http://localhost:8080/admin/config/schema \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
# 특정 섹션 스키마 가져오기
curl -s "http://localhost:8080/admin/config/schema?section=logging" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
설정 수정 API¶
섹션 설정 대체¶
전체 섹션 설정을 새 값으로 대체합니다.
요청 본문¶
응답¶
{
"success": true,
"message": "Configuration updated successfully",
"version": 5,
"hot_reload_capability": "immediate",
"applied": true,
"warnings": []
}
예제¶
# 로깅 레벨을 debug로 업데이트
curl -X PUT http://localhost:8080/admin/config/logging \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"config": {
"level": "debug"
}
}'
섹션 부분 업데이트¶
JSON 병합 패치 의미론을 사용하여 부분 업데이트를 적용합니다.
요청 본문¶
지정된 필드만 업데이트되고 다른 필드는 변경되지 않습니다.
응답¶
{
"success": true,
"message": "Configuration partially updated",
"version": 6,
"hot_reload_capability": "immediate",
"applied": true,
"merged_config": {
"level": "warn",
"format": "json",
"file": "/var/log/continuum-router.log"
}
}
예제¶
# 속도 제한 값만 업데이트
curl -X PATCH http://localhost:8080/admin/config/rate_limiting \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"config": {
"requests_per_minute": 200
}
}'
설정 검증¶
변경 사항을 적용하지 않고 설정을 검증합니다.
요청 본문¶
{
"section": "server",
"config": {
"bind_address": "0.0.0.0:9090",
"workers": 8
},
"dry_run": true
}
응답 (유효)¶
{
"valid": true,
"errors": [],
"warnings": [
{
"field": "bind_address",
"message": "Changing bind_address requires server restart"
}
],
"hot_reload_capability": "requires_restart"
}
응답 (유효하지 않음)¶
{
"valid": false,
"errors": [
{
"field": "workers",
"message": "workers must be greater than 0",
"code": "VALIDATION_ERROR"
}
],
"warnings": []
}
예제¶
# 적용 전 검증
curl -X POST http://localhost:8080/admin/config/validate \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"section": "rate_limiting",
"config": {
"enabled": true,
"requests_per_minute": 500
}
}'
설정 적용¶
보류 중인 설정 변경 사항을 즉시 적용합니다 (핫 리로드 트리거).
요청 본문¶
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
sections | array | 아니오 | 적용할 특정 섹션 (기본값: 모든 보류 중) |
force | boolean | 아니오 | 경고가 있어도 강제 적용 (기본값: false) |
응답¶
{
"success": true,
"applied_sections": ["logging", "rate_limiting"],
"version": 7,
"results": {
"logging": {
"status": "applied",
"hot_reload_type": "immediate"
},
"rate_limiting": {
"status": "applied",
"hot_reload_type": "immediate"
}
}
}
예제¶
curl -X POST http://localhost:8080/admin/config/apply \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sections": ["logging"]
}'
설정 저장/복원 API¶
설정 내보내기¶
지정된 형식으로 현재 설정을 내보냅니다.
요청 본문¶
{
"format": "yaml",
"sections": ["server", "backends", "logging"],
"include_sensitive": false,
"include_defaults": true
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
format | string | 예 | 출력 형식: yaml, json, 또는 toml |
sections | array | 아니오 | 내보낼 섹션 (기본값: 전체) |
include_sensitive | boolean | 아니오 | 마스킹 안 된 민감 데이터 포함 (기본값: false) |
include_defaults | boolean | 아니오 | 기본값 포함 (기본값: true) |
응답¶
{
"format": "yaml",
"content": "server:\n bind_address: \"0.0.0.0:8080\"\n workers: 4\n\nbackends:\n - name: openai\n url: https://api.openai.com\n api_key: \"sk-***abcd\"\n",
"exported_at": "2025-12-13T10:30:00Z",
"sections_exported": ["server", "backends", "logging"]
}
예제¶
# YAML로 내보내기
curl -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"format": "yaml"}' | jq -r '.content' > config-backup.yaml
# JSON으로 내보내기
curl -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"format": "json"}' | jq -r '.content' > config-backup.json
# 특정 섹션 내보내기
curl -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "yaml",
"sections": ["backends", "rate_limiting"]
}'
설정 가져오기¶
콘텐츠에서 설정을 가져오고 적용합니다.
요청 본문¶
{
"format": "yaml",
"content": "logging:\n level: info\n format: json\n",
"apply": true,
"dry_run": false,
"merge": true
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
format | string | 예 | 콘텐츠 형식: yaml, json, 또는 toml |
content | string | 예 | 설정 콘텐츠 (최대 1MB) |
apply | boolean | 아니오 | 검증 후 적용 (기본값: true) |
dry_run | boolean | 아니오 | 적용 없이 검증만 (기본값: false) |
merge | boolean | 아니오 | 기존 설정과 병합 (기본값: false) |
응답¶
{
"success": true,
"message": "Configuration imported and applied",
"version": 8,
"validation": {
"valid": true,
"errors": [],
"warnings": []
},
"sections_imported": ["logging"],
"applied": true
}
예제¶
# 파일에서 가져오기
curl -X POST http://localhost:8080/admin/config/import \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"format\": \"yaml\",
\"content\": $(cat config-backup.yaml | jq -Rs .),
\"apply\": true
}"
# 가져오기 미리보기 (dry run)
curl -X POST http://localhost:8080/admin/config/import \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "yaml",
"content": "logging:\n level: debug\n",
"dry_run": true
}'
설정 히스토리 가져오기¶
설정 변경 히스토리를 확인합니다.
쿼리 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
limit | integer | 아니오 | 반환할 항목 수 (기본값: 20, 최대: 100) |
offset | integer | 아니오 | 건너뛸 항목 수 (기본값: 0) |
section | string | 아니오 | 섹션 이름으로 필터링 |
응답¶
{
"history": [
{
"version": 8,
"timestamp": "2025-12-13T10:30:00Z",
"sections_changed": ["logging"],
"source": "api",
"user": "admin",
"description": "Updated logging level to debug",
"rollback_available": true
},
{
"version": 7,
"timestamp": "2025-12-13T10:25:00Z",
"sections_changed": ["rate_limiting"],
"source": "api",
"user": "admin",
"description": "Increased rate limit to 200 rpm",
"rollback_available": true
},
{
"version": 6,
"timestamp": "2025-12-13T09:00:00Z",
"sections_changed": ["backends"],
"source": "file_reload",
"user": "system",
"description": "Configuration file changed",
"rollback_available": true
}
],
"total_entries": 8,
"current_version": 8
}
예제¶
# 최근 히스토리 가져오기
curl -s http://localhost:8080/admin/config/history \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
# 특정 섹션 히스토리 가져오기
curl -s "http://localhost:8080/admin/config/history?section=backends&limit=10" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
설정 롤백¶
이전 설정 버전으로 롤백합니다.
경로 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
version | integer | 예 | 롤백할 버전 번호 |
요청 본문¶
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
sections | array | 아니오 | 롤백할 특정 섹션 (기본값: 변경된 모든 섹션) |
dry_run | boolean | 아니오 | 적용 없이 미리보기 (기본값: false) |
응답¶
{
"success": true,
"message": "Rolled back to version 5",
"previous_version": 8,
"new_version": 9,
"sections_rolled_back": ["logging", "rate_limiting"],
"changes": {
"logging": {
"level": {
"from": "debug",
"to": "info"
}
}
}
}
예제¶
# 버전 5로 롤백
curl -X POST http://localhost:8080/admin/config/rollback/5 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# 롤백 미리보기 (dry run)
curl -X POST http://localhost:8080/admin/config/rollback/5 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"dry_run": true}'
백엔드 관리 API¶
백엔드 추가¶
새 백엔드를 동적으로 추가합니다.
요청 본문¶
{
"name": "new-ollama",
"url": "http://192.168.1.100:11434",
"weight": 1,
"models": ["llama3.2", "mistral"],
"api_key": "optional-key",
"enabled": true,
"health_check": {
"enabled": true,
"path": "/v1/models"
}
}
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
name | string | 예 | 고유한 백엔드 이름 (영숫자, -, _) |
type | string | 아니오 | 백엔드 타입: openai, azure, vllm, ollama, anthropic, gemini, llamacpp, generic. 기본값: generic (자동 감지) |
url | string | 예 | 백엔드 URL (http:// 또는 https://) |
weight | integer | 아니오 | 로드 밸런싱 가중치 (기본값: 1) |
models | array | 아니오 | 이 백엔드가 제공하는 모델 목록 |
api_key | string | 아니오 | 백엔드 인증용 API 키 |
enabled | boolean | 아니오 | 백엔드 활성화 여부 (기본값: true) |
백엔드 타입 자동 감지¶
type이 지정되지 않거나 generic으로 설정된 경우, 라우터는 백엔드의 /v1/models 엔드포인트를 자동으로 탐색하여 백엔드 타입을 감지합니다. 현재 자동 감지가 지원되는 백엔드:
- llama.cpp: 응답의
owned_by: "llamacpp"또는 llama.cpp 전용 메타데이터 필드로 식별
이를 통해 명시적인 타입 설정 없이도 llama.cpp 백엔드를 통합할 수 있습니다:
# llama.cpp 백엔드 - 타입 자동 감지
curl -X POST http://localhost:8080/admin/backends \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "local-llama",
"url": "http://localhost:8080"
}'
응답¶
{
"success": true,
"message": "Backend 'new-ollama' added successfully",
"backend": {
"name": "new-ollama",
"url": "http://192.168.1.100:11434",
"weight": 1,
"models": ["llama3.2", "mistral"],
"enabled": true,
"health_status": "unknown"
}
}
예제¶
curl -X POST http://localhost:8080/admin/backends \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "new-backend",
"url": "http://192.168.1.100:11434",
"weight": 2,
"models": ["llama3.2"]
}'
백엔드 가져오기¶
특정 백엔드의 설정을 가져옵니다.
응답¶
{
"name": "openai",
"url": "https://api.openai.com",
"api_key": "sk-***abcd",
"weight": 1,
"models": ["gpt-4", "gpt-3.5-turbo"],
"enabled": true,
"health_status": "healthy",
"stats": {
"total_requests": 1250,
"failed_requests": 12,
"average_latency_ms": 150,
"last_used": "2025-12-13T10:29:55Z"
}
}
예제¶
백엔드 업데이트¶
백엔드 설정을 업데이트합니다.
요청 본문¶
{
"url": "https://api.openai.com",
"weight": 2,
"models": ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"],
"enabled": true
}
응답¶
{
"success": true,
"message": "Backend 'openai' updated successfully",
"backend": {
"name": "openai",
"url": "https://api.openai.com",
"weight": 2,
"models": ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"],
"enabled": true
}
}
예제¶
curl -X PUT http://localhost:8080/admin/backends/openai \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"weight": 3,
"models": ["gpt-4", "gpt-4-turbo"]
}'
백엔드 삭제¶
라우터에서 백엔드를 제거합니다.
쿼리 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
force | boolean | 아니오 | 활성 연결이 있어도 강제 삭제 |
응답¶
{
"success": true,
"message": "Backend 'old-backend' removed successfully",
"removed_backend": "old-backend"
}
참고사항¶
- 마지막 백엔드 삭제 허용: 라우터는 백엔드가 없는 상태로도 운영할 수 있습니다. 마지막 백엔드가 삭제되면:
/v1/models는 빈 목록을 반환- 라우팅 요청은 503 "No backends available" 반환
POST /admin/backends를 통해 새 백엔드 추가 가능
예제¶
curl -X DELETE http://localhost:8080/admin/backends/old-backend \
-H "Authorization: Bearer $ADMIN_TOKEN"
# 강제 삭제
curl -X DELETE "http://localhost:8080/admin/backends/old-backend?force=true" \
-H "Authorization: Bearer $ADMIN_TOKEN"
백엔드 가중치 업데이트¶
로드 밸런싱을 위한 백엔드 가중치만 업데이트합니다.
요청 본문¶
응답¶
{
"success": true,
"message": "Backend 'openai' weight updated to 5",
"previous_weight": 2,
"new_weight": 5
}
예제¶
curl -X PUT http://localhost:8080/admin/backends/openai/weight \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"weight": 5}'
백엔드 모델 업데이트¶
백엔드의 모델 목록을 업데이트합니다.
요청 본문¶
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
models | array | 예 | 모델 이름 목록 |
append | boolean | 아니오 | 기존 목록에 추가 (기본값: false, 대체) |
응답¶
{
"success": true,
"message": "Backend 'openai' models updated",
"models": ["gpt-4", "gpt-4-turbo", "gpt-4o", "gpt-3.5-turbo"]
}
예제¶
# 모델 대체
curl -X PUT http://localhost:8080/admin/backends/openai/models \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"models": ["gpt-4", "gpt-4o"]}'
# 모델 추가
curl -X PUT http://localhost:8080/admin/backends/openai/models \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"models": ["gpt-4.5-turbo"], "append": true}'
통계 API¶
통계 API는 StatsCollector가 수집한 집계 요청 메트릭을 노출합니다. 네 가지 엔드포인트 모두 /admin/stats 하위에 마운트되며 나머지 Admin API와 동일한 인증을 공유합니다.
통계 수집은 기본적으로 활성화되어 있습니다. config.yaml의 admin.stats 섹션을 통해 설정하거나 비활성화할 수 있습니다:
admin:
stats:
enabled: true # 수집 활성화/비활성화 (기본값: true)
retention_window: 24h # 윈도우 쿼리용 링 버퍼 보존 기간 (기본값: 24h)
token_tracking: true # 토큰 사용량 파싱 (기본값: true)
persistence:
enabled: true # 재시작 시 통계 영속 (기본값: true)
path: ./data/stats.json # 스냅샷 파일 경로 (기본값: ./data/stats.json)
snapshot_interval: 5m # 주기적 스냅샷 간격 (기본값: 5m)
max_age: 7d # 시작 시 이보다 오래된 스냅샷은 폐기 (기본값: 7d)
retention_window와 token_tracking 설정은 핫 리로드를 지원합니다: 재시작 없이 즉시 변경 사항이 적용됩니다.
통계 영속화¶
persistence 하위 섹션이 존재하고 enabled가 true이면, 라우터는 주기적으로 통계 스냅샷을 디스크에 저장하고 시작 시 복원합니다. 따라서 요청 카운터, 모델별 분류, 레이턴시 링 버퍼가 재시작 후에도 유지됩니다.
동작 방식:
- 시작 시 라우터가 스냅샷 파일을 읽고 모든 카운터와 링 버퍼 레코드를 복원합니다. 업타임은 각 재시작마다 초기화됩니다.
- 백그라운드 태스크가
snapshot_interval마다 새 스냅샷을 저장합니다. 손상 방지를 위해 원자적 쓰기(임시 파일 + 이름 변경)를 사용합니다. - 정상 종료(SIGTERM/SIGINT) 시, 프로세스 종료 전에 최종 스냅샷이 저장됩니다.
- 스냅샷 파일이 없거나, 손상되었거나,
max_age보다 오래된 경우 라우터는 새 카운터로 시작하고 경고 또는 정보 메시지를 로깅합니다.
snapshot_interval과 max_age에 지원되는 기간 형식:
| 형식 | 예제 | 의미 |
|---|---|---|
Xs | 30s | 30초 |
Xm | 5m | 5분 |
Xh | 1h | 1시간 |
Xd | 7d | 7일 |
max_age를 "0" 또는 ""으로 설정하면 유효 기간 검사를 비활성화합니다 (기간에 관계없이 항상 복원).
전체 통계 조회¶
전체, 모델별, 백엔드별 통계를 반환합니다.
쿼리 파라미터¶
| 파라미터 | 타입 | 설명 |
|---|---|---|
window | string | 선택적 시간 윈도우 필터. 허용 형식: 30m, 1h, 24h, 7d. 전체 기간 합계는 생략. |
응답¶
{
"uptime_seconds": 3600,
"window": "all",
"overall": {
"total_requests": 1500,
"successful_requests": 1480,
"failed_requests": 20,
"avg_latency_ms": 145.3,
"p50_latency_ms": 120.0,
"p95_latency_ms": 380.0,
"p99_latency_ms": 750.0,
"total_prompt_tokens": 450000,
"total_completion_tokens": 180000,
"total_tokens": 630000,
"tokens_per_sec_avg": 87.4
},
"models": [
{
"model_id": "gpt-4",
"total_requests": 900,
"successful_requests": 895,
"failed_requests": 5,
"total_prompt_tokens": 270000,
"total_completion_tokens": 108000,
"total_tokens": 378000,
"avg_latency_ms": 160.2,
"avg_tokens_per_sec": 92.1,
"last_used": "2026-03-05T10:30:00Z"
}
],
"backends": [
{
"backend_name": "openai",
"total_requests": 900,
"successful_requests": 895,
"failed_requests": 5,
"avg_latency_ms": 160.2,
"health_status": "healthy"
}
]
}
예제¶
# 전체 기간 통계
curl -s http://localhost:8080/admin/stats \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
# 지난 1시간만
curl -s "http://localhost:8080/admin/stats?window=1h" \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
모델별 통계 조회¶
모델별 분류만 반환합니다 (전체 통계 응답의 부분집합).
응답¶
{
"models": [
{
"model_id": "gpt-4",
"total_requests": 900,
"successful_requests": 895,
"failed_requests": 5,
"total_prompt_tokens": 270000,
"total_completion_tokens": 108000,
"total_tokens": 378000,
"avg_latency_ms": 160.2,
"avg_tokens_per_sec": 92.1,
"last_used": "2026-03-05T10:30:00Z"
}
]
}
모델은 total_requests 기준 내림차순으로 정렬됩니다.
예제¶
curl -s http://localhost:8080/admin/stats/models \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq '.models[].model_id'
백엔드별 통계 조회¶
백엔드별 분류만 반환합니다. health_status 필드는 헬스 체커에서 채워집니다 ("healthy", "unhealthy", 또는 헬스 체크가 비활성화된 경우 "unknown").
응답¶
{
"backends": [
{
"backend_name": "openai",
"total_requests": 900,
"successful_requests": 895,
"failed_requests": 5,
"avg_latency_ms": 160.2,
"health_status": "healthy"
}
]
}
백엔드는 total_requests 기준 내림차순으로 정렬됩니다.
예제¶
통계 초기화¶
모든 카운터, 모델별 레코드, 백엔드별 레코드, 레이턴시 링 버퍼를 초기화합니다. 이 작업은 되돌릴 수 없습니다.
응답¶
예제¶
응답 캐시 Admin API¶
응답 캐시 Admin API는 응답 캐시에 대한 통계 및 무효화 작업을 제공합니다. 모든 엔드포인트는 /admin/response-cache 하위에 마운트되며 나머지 Admin API와 동일한 인증이 필요합니다.
응답 캐싱은 YAML 설정의 response_cache 섹션에서 구성합니다. 전체 설정 세부 사항은 응답 캐시 설정 가이드를 참조하세요.
응답 캐시 통계 조회¶
히트/미스 카운트, 메모리 사용량, 설정 요약을 포함한 현재 응답 캐시 통계를 반환합니다.
응답¶
{
"enabled": true,
"backend_type": "memory",
"entries": 42,
"capacity": 1000,
"requests": {
"hit": 120,
"miss": 80,
"skip": 15,
"total": 215
},
"hit_rate": "0.6000",
"evictions": 3,
"size_bytes": 1048576,
"config": {
"backend": "memory",
"ttl": "5m",
"capacity": 1000,
"max_response_size": 1048576,
"max_stream_buffer_size": 10485760
}
}
Redis 백엔드(backend: redis)를 사용하는 경우, 응답에 추가 redis 객체가 포함됩니다:
{
"enabled": true,
"backend_type": "redis",
"entries": 42,
"capacity": 1000,
"requests": { "hit": 120, "miss": 80, "skip": 15, "total": 215 },
"hit_rate": "0.6000",
"evictions": 3,
"size_bytes": 1048576,
"config": { "backend": "redis", "ttl": "5m", "capacity": 1000, "max_response_size": 1048576, "max_stream_buffer_size": 10485760 },
"redis": {
"connections": { "active": 3, "idle": 5 },
"errors": { "connection": 0, "timeout": 0, "other": 0, "total": 0 },
"fallback_active": false
}
}
응답 캐싱이 비활성화된 경우(response_cache.enabled: false 또는 섹션이 없는 경우), enabled는 false, entries와 capacity는 0, config는 null입니다.
응답 필드¶
| 필드 | 타입 | 설명 |
|---|---|---|
enabled | boolean | 응답 캐싱 활성화 여부 |
backend_type | string | 활성 캐시 백엔드: "memory" 또는 "redis" |
entries | integer | 현재 캐시된 항목 수 |
capacity | integer | 최대 캐시 용량 (LRU 제한) |
requests.hit | integer | 캐시에서 제공된 요청 수 |
requests.miss | integer | 캐시 미스 (백엔드 호출, 항목 저장) |
requests.skip | integer | 캐시 불가 요청 (예: temperature > 0) |
requests.total | integer | 총 캐시 가능 조회 (hit + miss + skip) |
hit_rate | string | 롤링 캐시 히트율 (소수 문자열, 예: "0.6000") |
evictions | integer | 시작 이후 총 LRU 제거 횟수 |
size_bytes | integer | 캐시된 항목의 대략적인 메모리 사용량 (바이트) |
config | object 또는 null | 활성 설정 요약; 비활성화 시 null |
redis | object 또는 없음 | Redis 전용 통계 (backend_type이 "redis"인 경우에만 존재) |
redis.connections.active | integer | Redis 풀의 활성 연결 수 |
redis.connections.idle | integer | Redis 풀의 유휴 연결 수 |
redis.errors.connection | integer | 시작 이후 Redis 연결 오류 수 |
redis.errors.timeout | integer | 시작 이후 Redis 명령 타임아웃 오류 수 |
redis.errors.other | integer | 시작 이후 기타 Redis 오류 수 |
redis.errors.total | integer | 시작 이후 총 Redis 오류 수 |
redis.fallback_active | boolean | 인메모리 폴백 활성화 여부 |
예제¶
curl -s http://localhost:8080/admin/response-cache/stats \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
응답 캐시 무효화¶
캐시 항목을 삭제합니다. 현재 clear_all: true를 통한 전체 캐시 무효화를 지원합니다. 모델 또는 테넌트별 대상 무효화는 향후 릴리스에서 지원될 예정입니다.
요청 본문¶
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
clear_all | boolean | 아니오 | true이면 전체 캐시를 삭제합니다. 기본값: false. |
model | string | 아니오 | 향후 대상 무효화를 위해 예약됨. 256자를 초과할 수 없습니다. |
tenant_id | string | 아니오 | 향후 대상 무효화를 위해 예약됨. 256자를 초과할 수 없습니다. |
응답 (clear_all: true)¶
응답 (clear_all: false 또는 생략)¶
{
"success": true,
"action": "noop",
"message": "Targeted invalidation by model/tenant_id is not yet supported. Use clear_all: true to clear the entire cache."
}
응답 (캐시 비활성화)¶
예제¶
# 전체 캐시 삭제
curl -X POST http://localhost:8080/admin/response-cache/invalidate \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"clear_all": true}'
KV 캐시 인덱스 Admin API¶
KV 캐시 인덱스 Admin API는 KV 캐시 인덱스 하위 시스템에 대한 통계, 백엔드별 상태, 삭제 작업을 제공합니다. 모든 엔드포인트는 /admin/kv-index 하위에 마운트되며 나머지 Admin API와 동일한 인증이 필요합니다.
KV 캐시 인덱스는 특정 토큰 접두사에 대해 어떤 백엔드가 캐시된 KV 데이터를 보유하고 있는지 추적하여 KV 인식 라우팅을 가능하게 합니다. YAML 설정의 kv_cache_index 섹션에서 구성합니다.
KV 캐시 인덱스 통계 조회¶
인덱스 크기, 이벤트 소스 연결 상태, 라우팅 결정 카운트를 포함한 전체 KV 캐시 인덱스 통계를 반환합니다.
응답¶
{
"enabled": true,
"config": {
"backend": "memory",
"max_entries": 100000,
"entry_ttl_seconds": 600,
"event_sources_count": 2,
"scoring": {
"overlap_weight": 0.6,
"load_weight": 0.3,
"health_weight": 0.1,
"min_overlap_threshold": 0.3
}
},
"index": {
"prefix_count": 45,
"entry_count": 120,
"total_hits": 3842,
"total_evictions": 12
},
"event_sources": [
{
"backend_name": "vllm-1",
"connected": true,
"events_received": 2100,
"events_dropped": 0,
"last_event_at": "2025-03-12T10:45:00Z",
"reconnect_count": 0
}
],
"routing_decisions": {
"kv_aware": 980,
"fallback": 120,
"total": 1100
},
"query_latency_count": 1100,
"overlap_score_count": 980
}
KV 캐시 인덱스가 비활성화된 경우(kv_cache_index.enabled: false 또는 섹션이 없는 경우), enabled는 false, config는 null, 모든 카운터는 0입니다.
응답 필드¶
| 필드 | 타입 | 설명 |
|---|---|---|
enabled | boolean | KV 캐시 인덱스 활성화 여부 |
config | object 또는 null | 활성 설정 요약; 비활성화 시 null |
config.backend | string | 인덱스 백엔드: "memory" 또는 "redis" |
config.max_entries | integer | 최대 추적 접두사 해시 항목 수 |
config.entry_ttl_seconds | integer | 인덱스 항목 TTL (초) |
config.event_sources_count | integer | 구성된 이벤트 소스 수 |
config.scoring | object | 스코어링 가중치 설정 |
index.prefix_count | integer | 추적 중인 고유 접두사 해시 수 |
index.entry_count | integer | 총 (접두사, 백엔드) 쌍 추적 수 |
index.total_hits | integer | 시작 이후 총 캐시 히트 기록 수 |
index.total_evictions | integer | 시작 이후 총 캐시 제거 기록 수 |
event_sources | array | 각 이벤트 소스 컨슈머의 상태 |
event_sources[].connected | boolean | 컨슈머가 현재 연결되어 있는지 여부 |
event_sources[].events_received | integer | 이 소스에서 수신한 총 이벤트 수 |
event_sources[].events_dropped | integer | 백프레셔로 인해 드롭된 이벤트 수 |
event_sources[].reconnect_count | integer | 시작 이후 재연결 시도 횟수 |
routing_decisions.kv_aware | integer | KV 인식 선택을 사용하여 라우팅된 요청 수 |
routing_decisions.fallback | integer | 기본 전략으로 폴백된 요청 수 |
routing_decisions.total | integer | 총 라우팅 결정 수 |
예제¶
백엔드별 KV 캐시 상태 조회¶
백엔드별 KV 캐시 이벤트 통계를 반환합니다. 수신, 처리, 드롭된 이벤트, 연결 상태, 인덱스 이벤트 카운트를 포함합니다.
응답 (활성화)¶
{
"enabled": true,
"backends": [
{
"backend_name": "vllm-1",
"connection": {
"connected": true,
"reconnect_count": 0,
"last_event_at": "2025-03-12T10:45:00Z"
},
"events": {
"received": 2100,
"dropped": 0,
"index_created": 1950,
"index_evicted": 150
}
},
{
"backend_name": "vllm-2",
"connection": {
"connected": false,
"reconnect_count": 3,
"last_event_at": null
},
"events": {
"received": 0,
"dropped": 0,
"index_created": 0,
"index_evicted": 0
},
"configured_endpoint": "ws://vllm-2:8000/v1/kv_events"
}
]
}
kv_cache_index.event_sources에 나타나지만 아직 활성 컨슈머가 없는 백엔드는 connected: false와 configured_endpoint 필드와 함께 포함됩니다.
응답 (비활성화)¶
응답 필드¶
| 필드 | 타입 | 설명 |
|---|---|---|
enabled | boolean | KV 캐시 인덱스 활성화 여부 |
backends[].backend_name | string | 백엔드 식별자 |
backends[].connection.connected | boolean | 이벤트 스트림 컨슈머 연결 여부 |
backends[].connection.reconnect_count | integer | 시작 이후 재연결 시도 횟수 |
backends[].connection.last_event_at | string 또는 null | 가장 최근 이벤트의 ISO 8601 타임스탬프 |
backends[].events.received | integer | 이 백엔드에서 수신한 총 이벤트 수 |
backends[].events.dropped | integer | 백프레셔로 인해 드롭된 이벤트 수 |
backends[].events.index_created | integer | 이벤트에서 생성된 인덱스 항목 수 |
backends[].events.index_evicted | integer | 이벤트에서 제거된 인덱스 항목 수 |
backends[].configured_endpoint | string | 구성된 엔드포인트 URL (비활성 소스에만 존재) |
예제¶
curl -s http://localhost:8080/admin/kv-index/backends \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
KV 캐시 인덱스 삭제¶
KV 캐시 인덱스의 모든 항목을 삭제합니다. 디버깅 및 테스트용입니다. 프로덕션에서는 수신되는 KV 이벤트로부터 인덱스가 자동으로 재구축됩니다.
응답 (성공)¶
entries_before_clear는 삭제 전 총 (접두사, 백엔드) 쌍 수입니다. cleared_entries는 제거된 접두사 해시 버킷 수입니다. Redis 백엔드의 경우, cleared_entries는 삭제된 Redis 키 수를 카운트합니다; 각 키에 TTL이 있으므로 남은 키는 자동으로 만료됩니다.
응답 (비활성화)¶
예제¶
데이터 모델¶
설정 섹션¶
| 섹션 | 설명 | 핫 리로드 |
|---|---|---|
server | 바인드 주소, 워커, 연결 풀 | 재시작 필요 |
backends | 백엔드 URL, 가중치, 모델 | 점진적 |
health_checks | 간격, 임계값 | 점진적 |
logging | 로그 레벨, 형식, 출력 | 즉시 |
retry | 최대 시도, 지연, 백오프 | 즉시 |
timeouts | 연결, 요청, 유휴 타임아웃 | 점진적 |
rate_limiting | 제한, 스토리지, 화이트리스트 | 즉시 |
circuit_breaker | 임계값, 복구 시간 | 즉시 |
global_prompts | 시스템 프롬프트 주입 | 즉시 |
fallback | 폴백 체인, 정책 | 점진적 |
files | Files API 설정 | 점진적 |
api_keys | API 키 설정 | 즉시 |
metrics | Prometheus, 레이블 | 점진적 |
admin | Admin API 설정 | 점진적 |
admin.stats | 통계 수집 설정 | 즉시 |
routing | 모델 라우팅 규칙 | 점진적 |
백엔드 객체¶
{
"name": "string",
"url": "string (http:// 또는 https://)",
"api_key": "string (선택 사항, 응답에서 마스킹)",
"weight": "integer (1-100)",
"models": ["string"],
"enabled": "boolean",
"health_check": {
"enabled": "boolean",
"path": "string",
"interval": "string (duration)"
}
}
히스토리 항목 객체¶
{
"version": "integer",
"timestamp": "string (ISO 8601)",
"sections_changed": ["string"],
"source": "string (api|file_reload|initial|rollback)",
"user": "string",
"description": "string (선택 사항)",
"rollback_available": "boolean"
}
검증 결과 객체¶
{
"valid": "boolean",
"errors": [
{
"field": "string",
"message": "string",
"code": "string"
}
],
"warnings": [
{
"field": "string",
"message": "string"
}
]
}
핫 리로드 동작¶
업데이트 타입¶
| 타입 | 동작 | 섹션 |
|---|---|---|
| 즉시 | 즉시 적용, 중단 없음 | logging, ratelimiting, circuitbreaker, retry, globalprompts, apikeys |
| 점진적 | 기존 연결 유지, 새 연결이 새 설정 사용 | backends, health_checks, timeouts, fallback, files, metrics, admin, routing |
| 재시작 필요 | 경고로 로깅, 서버 재시작 필요 | server.bind_address, server.workers |
예제 워크플로우¶
# 1. 현재 설정 확인
curl -s http://localhost:8080/admin/config/logging | jq
# 2. 변경 검증
curl -X POST http://localhost:8080/admin/config/validate \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"section": "logging", "config": {"level": "debug"}}'
# 3. 변경 적용 (즉시 효과)
curl -X PATCH http://localhost:8080/admin/config/logging \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"config": {"level": "debug"}}'
# 4. 변경 확인
curl -s http://localhost:8080/admin/config/logging | jq '.config.level'
오류 처리¶
오류 응답 형식¶
오류 코드¶
| 코드 | HTTP 상태 | 설명 |
|---|---|---|
VALIDATION_ERROR | 400 | 설정 검증 실패 |
INVALID_SECTION | 400 | 알 수 없는 설정 섹션 |
PARSE_ERROR | 400 | 설정 콘텐츠 파싱 실패 |
SECTION_NOT_FOUND | 404 | 섹션을 찾을 수 없음 |
VERSION_NOT_FOUND | 404 | 히스토리 버전을 찾을 수 없음 |
BACKEND_NOT_FOUND | 404 | 백엔드를 찾을 수 없음 |
BACKEND_EXISTS | 409 | 해당 이름의 백엔드가 이미 존재함 |
CONTENT_TOO_LARGE | 413 | 설정 콘텐츠가 1MB 제한 초과 |
INTERNAL_ERROR | 500 | 내부 서버 오류 |
오류 예제¶
// 검증 오류
{
"error_code": "VALIDATION_ERROR",
"message": "Configuration validation failed",
"details": {
"errors": [
{"field": "workers", "message": "workers must be greater than 0"}
]
}
}
// 섹션을 찾을 수 없음
{
"error_code": "SECTION_NOT_FOUND",
"message": "Configuration section 'invalid' not found",
"details": {
"available_sections": ["server", "backends", "logging", "..."]
}
}
// 백엔드가 이미 존재함
{
"error_code": "BACKEND_EXISTS",
"message": "Backend 'openai' already exists",
"details": {
"existing_backend": "openai"
}
}
클라이언트 SDK 예제¶
Python¶
import requests
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
@dataclass
class ContinuumAdminClient:
"""Continuum Router Admin API 클라이언트"""
base_url: str
token: str
def __post_init__(self):
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
})
# 설정 쿼리 API
def get_full_config(self) -> Dict[str, Any]:
"""마스킹된 민감 데이터가 포함된 전체 설정 가져오기"""
resp = self.session.get(f"{self.base_url}/admin/config/full")
resp.raise_for_status()
return resp.json()
def get_sections(self) -> List[Dict[str, Any]]:
"""모든 설정 섹션 가져오기"""
resp = self.session.get(f"{self.base_url}/admin/config/sections")
resp.raise_for_status()
return resp.json()["sections"]
def get_section(self, section: str) -> Dict[str, Any]:
"""특정 섹션의 설정 가져오기"""
resp = self.session.get(f"{self.base_url}/admin/config/{section}")
resp.raise_for_status()
return resp.json()
def get_schema(self, section: Optional[str] = None) -> Dict[str, Any]:
"""검증을 위한 JSON 스키마 가져오기"""
params = {"section": section} if section else {}
resp = self.session.get(
f"{self.base_url}/admin/config/schema",
params=params
)
resp.raise_for_status()
return resp.json()
# 설정 수정 API
def update_section(self, section: str, config: Dict[str, Any]) -> Dict[str, Any]:
"""섹션 설정 대체"""
resp = self.session.put(
f"{self.base_url}/admin/config/{section}",
json={"config": config}
)
resp.raise_for_status()
return resp.json()
def patch_section(self, section: str, config: Dict[str, Any]) -> Dict[str, Any]:
"""섹션 설정 부분 업데이트"""
resp = self.session.patch(
f"{self.base_url}/admin/config/{section}",
json={"config": config}
)
resp.raise_for_status()
return resp.json()
def validate_config(
self,
section: str,
config: Dict[str, Any],
dry_run: bool = True
) -> Dict[str, Any]:
"""적용 없이 설정 검증"""
resp = self.session.post(
f"{self.base_url}/admin/config/validate",
json={"section": section, "config": config, "dry_run": dry_run}
)
resp.raise_for_status()
return resp.json()
def apply_config(
self,
sections: Optional[List[str]] = None,
force: bool = False
) -> Dict[str, Any]:
"""보류 중인 설정 변경 적용"""
body = {"force": force}
if sections:
body["sections"] = sections
resp = self.session.post(
f"{self.base_url}/admin/config/apply",
json=body
)
resp.raise_for_status()
return resp.json()
# 설정 저장/복원 API
def export_config(
self,
format: str = "yaml",
sections: Optional[List[str]] = None,
include_sensitive: bool = False
) -> str:
"""지정된 형식으로 설정 내보내기"""
body = {"format": format, "include_sensitive": include_sensitive}
if sections:
body["sections"] = sections
resp = self.session.post(
f"{self.base_url}/admin/config/export",
json=body
)
resp.raise_for_status()
return resp.json()["content"]
def import_config(
self,
content: str,
format: str = "yaml",
apply: bool = True,
dry_run: bool = False
) -> Dict[str, Any]:
"""콘텐츠에서 설정 가져오기"""
resp = self.session.post(
f"{self.base_url}/admin/config/import",
json={
"format": format,
"content": content,
"apply": apply,
"dry_run": dry_run
}
)
resp.raise_for_status()
return resp.json()
def get_history(
self,
limit: int = 20,
offset: int = 0,
section: Optional[str] = None
) -> Dict[str, Any]:
"""설정 변경 히스토리 가져오기"""
params = {"limit": limit, "offset": offset}
if section:
params["section"] = section
resp = self.session.get(
f"{self.base_url}/admin/config/history",
params=params
)
resp.raise_for_status()
return resp.json()
def rollback(
self,
version: int,
sections: Optional[List[str]] = None,
dry_run: bool = False
) -> Dict[str, Any]:
"""이전 버전으로 롤백"""
body = {"dry_run": dry_run}
if sections:
body["sections"] = sections
resp = self.session.post(
f"{self.base_url}/admin/config/rollback/{version}",
json=body
)
resp.raise_for_status()
return resp.json()
# 백엔드 관리 API
def list_backends(self) -> List[Dict[str, Any]]:
"""모든 백엔드 목록"""
resp = self.session.get(f"{self.base_url}/admin/backends")
resp.raise_for_status()
return resp.json()["backends"]
def get_backend(self, name: str) -> Dict[str, Any]:
"""백엔드 설정 가져오기"""
resp = self.session.get(f"{self.base_url}/admin/backends/{name}")
resp.raise_for_status()
return resp.json()
def add_backend(
self,
name: str,
url: str,
weight: int = 1,
models: Optional[List[str]] = None
) -> Dict[str, Any]:
"""새 백엔드 추가"""
body = {"name": name, "url": url, "weight": weight}
if models:
body["models"] = models
resp = self.session.post(
f"{self.base_url}/admin/backends",
json=body
)
resp.raise_for_status()
return resp.json()
def update_backend(self, name: str, **kwargs) -> Dict[str, Any]:
"""백엔드 설정 업데이트"""
resp = self.session.put(
f"{self.base_url}/admin/backends/{name}",
json=kwargs
)
resp.raise_for_status()
return resp.json()
def delete_backend(self, name: str, force: bool = False) -> Dict[str, Any]:
"""백엔드 삭제"""
params = {"force": str(force).lower()} if force else {}
resp = self.session.delete(
f"{self.base_url}/admin/backends/{name}",
params=params
)
resp.raise_for_status()
return resp.json()
def update_backend_weight(self, name: str, weight: int) -> Dict[str, Any]:
"""백엔드 가중치 업데이트"""
resp = self.session.put(
f"{self.base_url}/admin/backends/{name}/weight",
json={"weight": weight}
)
resp.raise_for_status()
return resp.json()
def update_backend_models(
self,
name: str,
models: List[str],
append: bool = False
) -> Dict[str, Any]:
"""백엔드 모델 업데이트"""
resp = self.session.put(
f"{self.base_url}/admin/backends/{name}/models",
json={"models": models, "append": append}
)
resp.raise_for_status()
return resp.json()
# 사용 예제
if __name__ == "__main__":
client = ContinuumAdminClient(
base_url="http://localhost:8080",
token="your-admin-token"
)
# 현재 로깅 설정 가져오기
logging_config = client.get_section("logging")
print(f"현재 로그 레벨: {logging_config['config']['level']}")
# 로깅 레벨 업데이트
result = client.patch_section("logging", {"level": "debug"})
print(f"업데이트됨: {result['success']}")
# 새 백엔드 추가
client.add_backend(
name="new-ollama",
url="http://192.168.1.100:11434",
weight=2,
models=["llama3.2", "mistral"]
)
# 설정 백업 내보내기
backup = client.export_config(format="yaml")
with open("config-backup.yaml", "w") as f:
f.write(backup)
JavaScript/TypeScript¶
interface ConfigSection {
name: string;
config: Record<string, any>;
hot_reload_capability: 'immediate' | 'gradual' | 'requires_restart';
}
interface HistoryEntry {
version: number;
timestamp: string;
sections_changed: string[];
source: string;
user: string;
}
interface Backend {
name: string;
url: string;
weight: number;
models: string[];
enabled: boolean;
health_status: string;
}
class ContinuumAdminClient {
private baseUrl: string;
private token: string;
constructor(baseUrl: string, token: string) {
this.baseUrl = baseUrl;
this.token = token;
}
private async request<T>(
method: string,
path: string,
body?: any,
params?: Record<string, string>
): Promise<T> {
const url = new URL(`${this.baseUrl}${path}`);
if (params) {
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
}
const response = await fetch(url.toString(), {
method,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
// 설정 쿼리 API
async getFullConfig(): Promise<any> {
return this.request('GET', '/admin/config/full');
}
async getSections(): Promise<ConfigSection[]> {
const result = await this.request<{ sections: ConfigSection[] }>(
'GET', '/admin/config/sections'
);
return result.sections;
}
async getSection(section: string): Promise<ConfigSection> {
return this.request('GET', `/admin/config/${section}`);
}
async getSchema(section?: string): Promise<any> {
const params = section ? { section } : undefined;
return this.request('GET', '/admin/config/schema', undefined, params);
}
// 설정 수정 API
async updateSection(section: string, config: Record<string, any>): Promise<any> {
return this.request('PUT', `/admin/config/${section}`, { config });
}
async patchSection(section: string, config: Record<string, any>): Promise<any> {
return this.request('PATCH', `/admin/config/${section}`, { config });
}
async validateConfig(
section: string,
config: Record<string, any>,
dryRun: boolean = true
): Promise<any> {
return this.request('POST', '/admin/config/validate', {
section,
config,
dry_run: dryRun,
});
}
async applyConfig(sections?: string[], force: boolean = false): Promise<any> {
return this.request('POST', '/admin/config/apply', { sections, force });
}
// 설정 저장/복원 API
async exportConfig(
format: 'yaml' | 'json' | 'toml' = 'yaml',
sections?: string[],
includeSensitive: boolean = false
): Promise<string> {
const result = await this.request<{ content: string }>(
'POST', '/admin/config/export',
{ format, sections, include_sensitive: includeSensitive }
);
return result.content;
}
async importConfig(
content: string,
format: 'yaml' | 'json' | 'toml' = 'yaml',
apply: boolean = true,
dryRun: boolean = false
): Promise<any> {
return this.request('POST', '/admin/config/import', {
format,
content,
apply,
dry_run: dryRun,
});
}
async getHistory(
limit: number = 20,
offset: number = 0,
section?: string
): Promise<{ history: HistoryEntry[]; total_entries: number }> {
const params: Record<string, string> = {
limit: limit.toString(),
offset: offset.toString(),
};
if (section) params.section = section;
return this.request('GET', '/admin/config/history', undefined, params);
}
async rollback(
version: number,
sections?: string[],
dryRun: boolean = false
): Promise<any> {
return this.request('POST', `/admin/config/rollback/${version}`, {
sections,
dry_run: dryRun,
});
}
// 백엔드 관리 API
async listBackends(): Promise<Backend[]> {
const result = await this.request<{ backends: Backend[] }>(
'GET', '/admin/backends'
);
return result.backends;
}
async getBackend(name: string): Promise<Backend> {
return this.request('GET', `/admin/backends/${name}`);
}
async addBackend(
name: string,
url: string,
weight: number = 1,
models?: string[]
): Promise<any> {
return this.request('POST', '/admin/backends', {
name,
url,
weight,
models,
});
}
async updateBackend(name: string, updates: Partial<Backend>): Promise<any> {
return this.request('PUT', `/admin/backends/${name}`, updates);
}
async deleteBackend(name: string, force: boolean = false): Promise<any> {
const params = force ? { force: 'true' } : undefined;
return this.request('DELETE', `/admin/backends/${name}`, undefined, params);
}
async updateBackendWeight(name: string, weight: number): Promise<any> {
return this.request('PUT', `/admin/backends/${name}/weight`, { weight });
}
async updateBackendModels(
name: string,
models: string[],
append: boolean = false
): Promise<any> {
return this.request('PUT', `/admin/backends/${name}/models`, {
models,
append,
});
}
}
// 사용 예제
async function main() {
const client = new ContinuumAdminClient(
'http://localhost:8080',
'your-admin-token'
);
// 현재 로깅 설정 가져오기
const loggingConfig = await client.getSection('logging');
console.log(`현재 로그 레벨: ${loggingConfig.config.level}`);
// 로깅 레벨 업데이트
const result = await client.patchSection('logging', { level: 'debug' });
console.log(`업데이트됨: ${result.success}`);
// 새 백엔드 추가
await client.addBackend('new-ollama', 'http://192.168.1.100:11434', 2, [
'llama3.2',
'mistral',
]);
// 설정 백업 내보내기
const backup = await client.exportConfig('yaml');
console.log('설정 내보내기 완료');
}
main().catch(console.error);
Go¶
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
type ContinuumAdminClient struct {
BaseURL string
Token string
client *http.Client
}
func NewClient(baseURL, token string) *ContinuumAdminClient {
return &ContinuumAdminClient{
BaseURL: baseURL,
Token: token,
client: &http.Client{},
}
}
func (c *ContinuumAdminClient) request(method, path string, body interface{}) (map[string]interface{}, error) {
var reqBody io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
reqBody = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequest(method, c.BaseURL+path, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("HTTP %d: %v", resp.StatusCode, result)
}
return result, nil
}
// GetFullConfig 전체 설정을 조회합니다
func (c *ContinuumAdminClient) GetFullConfig() (map[string]interface{}, error) {
return c.request("GET", "/admin/config/full", nil)
}
// GetSection 특정 설정 섹션을 조회합니다
func (c *ContinuumAdminClient) GetSection(section string) (map[string]interface{}, error) {
return c.request("GET", "/admin/config/"+section, nil)
}
// PatchSection 설정 섹션을 부분 업데이트합니다
func (c *ContinuumAdminClient) PatchSection(section string, config map[string]interface{}) (map[string]interface{}, error) {
return c.request("PATCH", "/admin/config/"+section, map[string]interface{}{
"config": config,
})
}
// AddBackend 새 백엔드를 추가합니다
func (c *ContinuumAdminClient) AddBackend(name, backendURL string, weight int, models []string) (map[string]interface{}, error) {
return c.request("POST", "/admin/backends", map[string]interface{}{
"name": name,
"url": backendURL,
"weight": weight,
"models": models,
})
}
// ExportConfig 지정된 형식으로 설정을 내보냅니다
func (c *ContinuumAdminClient) ExportConfig(format string) (string, error) {
result, err := c.request("POST", "/admin/config/export", map[string]interface{}{
"format": format,
})
if err != nil {
return "", err
}
return result["content"].(string), nil
}
// GetHistory 설정 변경 히스토리를 조회합니다
func (c *ContinuumAdminClient) GetHistory(limit int) (map[string]interface{}, error) {
u, _ := url.Parse(c.BaseURL + "/admin/config/history")
q := u.Query()
q.Set("limit", fmt.Sprintf("%d", limit))
u.RawQuery = q.Encode()
return c.request("GET", u.Path+"?"+u.RawQuery, nil)
}
func main() {
client := NewClient("http://localhost:8080", "your-admin-token")
// 현재 로깅 설정 가져오기
config, _ := client.GetSection("logging")
fmt.Printf("현재 설정: %v\n", config)
// 로깅 레벨 업데이트
result, _ := client.PatchSection("logging", map[string]interface{}{
"level": "debug",
})
fmt.Printf("업데이트 결과: %v\n", result)
// 새 백엔드 추가
client.AddBackend("new-ollama", "http://192.168.1.100:11434", 2, []string{"llama3.2"})
// 설정 내보내기
backup, _ := client.ExportConfig("yaml")
fmt.Println("설정 내보내기 완료")
fmt.Println(backup)
}
모범 사례¶
1. 적용 전 항상 검증¶
# 1단계: 검증
curl -X POST http://localhost:8080/admin/config/validate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"section": "logging", "config": {"level": "debug"}}'
# 2단계: 유효한 경우에만 적용
curl -X PATCH http://localhost:8080/admin/config/logging \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"config": {"level": "debug"}}'
2. 가져오기에 Dry Run 사용¶
# 가져오기 변경 미리보기
curl -X POST http://localhost:8080/admin/config/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"format": "yaml",
"content": "...",
"dry_run": true
}'
3. 정기적인 설정 백업¶
# 일일 백업 스크립트
#!/bin/bash
DATE=$(date +%Y%m%d)
curl -s -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"format": "yaml"}' | jq -r '.content' > "config-backup-$DATE.yaml"
4. 설정 히스토리 모니터링¶
# 최근 변경 확인
curl -s http://localhost:8080/admin/config/history?limit=5 \
-H "Authorization: Bearer $TOKEN" | jq '.history[] | {version, timestamp, sections_changed}'
5. 최소 변경에 부분 업데이트 (PATCH) 사용¶
# 필요한 것만 업데이트
curl -X PATCH http://localhost:8080/admin/config/rate_limiting \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"config": {"requests_per_minute": 200}}'
6. 프로덕션 전 스테이징에서 설정 변경 테스트¶
# 예제: 프로덕션 전 스테이징에서 설정 테스트
staging_client = ContinuumAdminClient("http://staging:8080", staging_token)
production_client = ContinuumAdminClient("http://production:8080", prod_token)
# 먼저 스테이징에 적용
staging_client.patch_section("rate_limiting", {"requests_per_minute": 500})
# 스테이징에서 확인
staging_config = staging_client.get_section("rate_limiting")
assert staging_config["config"]["requests_per_minute"] == 500
# 그 다음 프로덕션에 적용
production_client.patch_section("rate_limiting", {"requests_per_minute": 500})
보안 고려 사항¶
1. 민감 데이터 처리¶
- 모든 API 응답은 민감한 필드 (API 키, 비밀번호, 토큰)를 자동으로 마스킹합니다
include_sensitive: true는 절대적으로 필요한 경우에만 export에서 사용하세요- 감사 로그는 민감 데이터 액세스 시 기록합니다
2. 인증 모범 사례¶
admin:
auth:
method: bearer_token
token: "${ADMIN_TOKEN}" # 환경 변수 사용
# IP로 액세스 제한
ip_whitelist:
- "10.0.0.0/8" # 내부 네트워크만
- "192.168.1.0/24" # 사무실 네트워크
3. 감사 로깅¶
모든 설정 변경은 다음과 함께 로깅됩니다:
- 타임스탬프
- 사용자/소스
- 변경된 섹션
- 이전 및 새 값 (민감 데이터 마스킹)
4. Admin 엔드포인트 속도 제한¶
남용 방지를 위해 admin 엔드포인트 속도 제한을 고려하세요:
5. 주요 변경 전 백업¶
# 주요 변경 전 항상 백업
backup=$(curl -s -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $TOKEN" \
-d '{"format": "yaml"}' | jq -r '.content')
# 변경 수행...
# 필요시 복원
curl -X POST http://localhost:8080/admin/config/import \
-H "Authorization: Bearer $TOKEN" \
-d "{\"format\": \"yaml\", \"content\": $(echo "$backup" | jq -Rs .)}"
프롬프트 파일 관리 API¶
프롬프트 파일 관리 API를 사용하면 외부 Markdown 파일에 저장된 시스템 프롬프트를 관리할 수 있습니다. 메인 설정 파일을 수정하지 않고 시스템 프롬프트를 중앙에서 관리할 수 있습니다.
모든 프롬프트 목록¶
소스 및 콘텐츠와 함께 설정된 모든 프롬프트 목록을 가져옵니다.
응답¶
{
"prompts": [
{
"id": "default",
"prompt_type": "default",
"source": "file",
"file_path": "prompts/system.md",
"content": "# System Prompt\n\nYou are a helpful assistant...",
"loaded": true,
"size_bytes": 1024
},
{
"id": "anthropic",
"prompt_type": "backend",
"source": "file",
"file_path": "prompts/anthropic.md",
"content": "# Anthropic-specific prompt...",
"loaded": true,
"size_bytes": 512
},
{
"id": "gpt-4",
"prompt_type": "model",
"source": "inline",
"content": "You are GPT-4...",
"size_bytes": 256
}
],
"total": 3,
"prompts_directory": "./prompts"
}
예제¶
프롬프트 파일 가져오기¶
특정 프롬프트 파일의 콘텐츠를 가져옵니다.
경로 파라미터¶
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
path | string | 예 | 프롬프트 파일의 상대 경로 |
응답¶
{
"path": "prompts/system.md",
"content": "# System Prompt\n\nYou are a helpful assistant that follows company policies...",
"size_bytes": 1024,
"modified_at": 1702468200
}
예제¶
curl -s http://localhost:8080/admin/config/prompts/prompts/system.md \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
프롬프트 파일 업데이트¶
새 콘텐츠로 프롬프트 파일을 생성하거나 업데이트합니다.
요청 본문¶
{
"content": "# Updated System Prompt\n\nYou are a helpful assistant that follows all company policies.\n\n## Security Guidelines\n\n- Never reveal internal system details\n- Follow data privacy regulations"
}
응답¶
{
"success": true,
"path": "prompts/system.md",
"size_bytes": 245,
"message": "Prompt file updated successfully"
}
예제¶
curl -X PUT http://localhost:8080/admin/config/prompts/prompts/system.md \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"content": "# System Prompt\n\nYou are a helpful assistant."
}'
프롬프트 파일 리로드¶
디스크에서 모든 프롬프트 파일을 리로드합니다. 수동 파일 편집 후 유용합니다.
응답¶
{
"success": true,
"reloaded_count": 3,
"reloaded": [
"prompts/system.md",
"prompts/anthropic.md",
"prompts/gpt4.md"
],
"errors": [],
"message": "Successfully reloaded 3 prompt file(s)"
}
예제¶
curl -X POST http://localhost:8080/admin/config/prompts/reload \
-H "Authorization: Bearer $ADMIN_TOKEN" | jq
설정 예제¶
외부 프롬프트 파일을 사용하려면 설정 파일에서 global_prompts를 설정합니다:
global_prompts:
# 프롬프트 파일이 포함된 디렉토리 (설정 디렉토리 기준 상대 경로)
prompts_dir: "./prompts"
# 외부 파일에서 기본 프롬프트
default_file: "system.md"
# 또는 인라인 프롬프트 (둘 다 지정되면 default_file이 우선)
# default: "You are a helpful assistant."
# 백엔드별 프롬프트
backends:
anthropic:
prompt_file: "anthropic-system.md"
openai:
prompt: "OpenAI-specific inline prompt"
# 모델별 프롬프트
models:
gpt-4:
prompt_file: "gpt4-system.md"
claude-3-opus:
prompt_file: "claude-opus-system.md"
merge_strategy: prepend
보안 고려 사항¶
- 경로 탐색 방지: 디렉토리 탐색 공격을 방지하기 위해 모든 경로가 검증됩니다 (예:
../../../etc/passwd) - 파일 크기 제한: 프롬프트 파일은 최대 1MB로 제한됩니다
- 상대 경로만: 프롬프트 파일은 설정된
prompts_dir또는 설정 디렉토리 내에 있어야 합니다 - 인증 필요: 모든 프롬프트 관리 엔드포인트는 admin 인증이 필요합니다
부록: 빠른 참조¶
설정 섹션¶
| 섹션 | 핫 리로드 | 설명 |
|---|---|---|
server | 재시작 | 바인드 주소, 워커 |
backends | 점진적 | 백엔드 URL, 가중치 |
health_checks | 점진적 | 헬스 모니터링 |
logging | 즉시 | 로그 레벨, 형식 |
retry | 즉시 | 재시도 정책 |
timeouts | 점진적 | 요청 타임아웃 |
rate_limiting | 즉시 | 속도 제한 |
circuit_breaker | 즉시 | 서킷 브레이커 |
global_prompts | 즉시 | 시스템 프롬프트 |
fallback | 점진적 | 모델 폴백 |
files | 점진적 | Files API |
api_keys | 즉시 | API 키 |
metrics | 점진적 | Prometheus 메트릭 |
admin | 점진적 | Admin 설정 |
admin.stats | 즉시 | 통계 수집 설정 |
routing | 점진적 | 라우팅 규칙 |
prefix_routing | 즉시 | 접두사 인식 KV 캐시 라우팅 |
response_cache | 즉시 | 응답 캐시 설정 |
kv_cache_index | 재시작 필요 | KV 캐시 인덱스 백엔드 및 이벤트 소스 |
HTTP 상태 코드¶
| 코드 | 의미 |
|---|---|
| 200 | 성공 |
| 400 | Bad Request (검증 오류) |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 409 | Conflict |
| 413 | Payload Too Large |
| 500 | Internal Server Error |
일반적인 curl 명령어¶
# 전체 설정 가져오기
curl -s http://localhost:8080/admin/config/full -H "Authorization: Bearer $TOKEN"
# 로깅 레벨 업데이트
curl -X PATCH http://localhost:8080/admin/config/logging \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"config": {"level": "debug"}}'
# 백엔드 추가
curl -X POST http://localhost:8080/admin/backends \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name": "new", "url": "http://host:port", "weight": 1}'
# 설정 내보내기
curl -X POST http://localhost:8080/admin/config/export \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"format": "yaml"}'
# 히스토리 보기
curl -s http://localhost:8080/admin/config/history -H "Authorization: Bearer $TOKEN"
# 롤백
curl -X POST http://localhost:8080/admin/config/rollback/5 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'