AI API를 운영 환경에서 사용하려면 단순히 요청을 전달하는 것 이상의 것이 필요합니다. 인증, 요청 빈도 제한, 사용량 로깅, 에러 추적, 비용 관리—이 모든 것을 하나의 미들웨어에서 통합 처리해야 안정적인 프로덕션 환경 구축이 가능합니다. 이번 튜토리얼에서는 HolySheep AI 게이트웨이 기반의 미들웨어 아키텍처를 설계하고, Python과 Node.js 환경에서 바로 적용 가능한 실전 코드를 제공합니다.
HolySheep vs 공식 API vs 기타 릴레이 서비스 비교
| 기능 | HolySheep AI | 공식 API 직접 | 기타 릴레이 서비스 |
|---|---|---|---|
| 지원 모델 | GPT-4.1, Claude 4.5, Gemini 2.5, DeepSeek V3.2 등 20개+ | 단일 벤더 모델만 | 제한적 모델 지원 |
| 단일 API 키 | ✅ 모든 모델 통합 | ❌ 벤더별 별도 키 필요 | ⚠️ 제한적 |
| 로컬 결제 | ✅ 해외 신용카드 불필요 | ✅ (국제 카드) | ⚠️ 제한적 |
| 가격 (GPT-4.1) | $8.00/MTok | $2.00/1MTok (입력) | $2.50-10.00/MTok |
| 가격 (Claude 4.5) | $15.00/MTok | $15.00/MTok | $18.00-25.00/MTok |
| 가격 (DeepSeek V3.2) | $0.42/MTok | $0.27/MTok | $0.50-2.00/MTok |
| 평균 지연 시간 | ~180ms | ~150ms | ~300-800ms |
| 기본_rate limit | RPM 1000, TPM 1M | 벤더별 상이 | 제한적 |
| 빌트인 로깅 | ✅ 상세 요청/응답 추적 | ❌ 별도 구현 필요 | ⚠️ 기본 수준 |
| 개발자 문서 | ✅ 한국어 완전 지원 | 영문 중심 | ⚠️ 제한적 |
왜 미들웨어 통합이 필요한가
저는 이전에 여러 AI 프로젝트에서 API 관리의 복잡성에 직면한 경험이 있습니다. 초기에는 각 모델마다 별도의 SDK를 사용했지만, 유지보수가 어려워지고 비용 추적이 불가능해졌습니다. HolySheep AI의 게이트웨이 미들웨어를 도입한 후, 단일 엔드포인트로 모든 모델을 관리하면서 인증·편향제어·로깅을 중앙화할 수 있었습니다.
통합 미들웨어의 핵심 이점은 다음과 같습니다:
- 일관된 인증 체계: 모든 모델 호출에 단일 API 키 사용
- 세밀한 빈도 제어: 사용자별, 팀별, 엔드포인트별 차등 제한
- 중앙 집중식 로깅: 요청 본문, 응답, 토큰 사용량, 지연 시간 추적
- 비용 최적화: 모델별 비용 자동 계산 및 예산 알림
- 캐싱 전략: 동일 요청 중복 호출 방지
아키텍처 설계
┌─────────────────────────────────────────────────────────────────┐
│ 클라이언트 요청 │
│ (Authorization: Bearer {key}) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 미들웨어 스택 │
├─────────────────────────────────────────────────────────────────┤
│ 1. 인증 미들웨어 ──► 2. Rate Limit 미들웨어 ──► 3. 로깅 미들웨어 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ API 키 검증 RPM/TPM 체크 요청/응답 기록 │
│ 사용자 식별 사용자별 제한 토큰 사용량 │
│ 권한 확인 버스트 제어 에러 추적 │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ HolySheep AI Gateway │
│ https://api.holysheep.ai/v1 │
├─────────────────────────────────────────────────────────────────┤
│ ├── OpenAI 호환 레이어 (GPT-4.1, GPT-4o) │
│ ├── Anthropic 레이어 (Claude 4.5 Sonnet) │
│ ├── Google 레이어 (Gemini 2.5 Flash) │
│ └── DeepSeek 레이어 (DeepSeek V3.2) │
└────────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 응답 반환 │
│ (로깅된 메타데이터 포함) │
└─────────────────────────────────────────────────────────────────┘
Python 기반 미들웨어 구현
# middleware/ai_gateway.py
"""
AI API Gateway Middleware - 인증, Rate Limit, 로깅 통합
Author: HolySheep AI Technical Team
"""
import time
import json
import hashlib
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple, Any
from dataclasses import dataclass, field
from enum import Enum
import asyncio
from collections import defaultdict
import httpx
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
============================================================
설정 및 상수
============================================================
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" # HolySheep에서 발급받은 키
모델별 가격 (USD per 1M tokens)
MODEL_PRICING = {
"gpt-4.1": {"input": 8.00, "output": 32.00},
"gpt-4o": {"input": 2.50, "output": 10.00},
"claude-sonnet-4-5": {"input": 15.00, "output": 75.00},
"claude-opus-4": {"input": 75.00, "output": 150.00},
"gemini-2.5-flash": {"input": 2.50, "output": 10.00},
"gemini-2.5-pro": {"input": 7.00, "output": 21.00},
"deepseek-v3.2": {"input": 0.42, "output": 1.68},
}
============================================================
데이터 클래스 정의
============================================================
@dataclass
class RateLimitConfig:
"""Rate Limit 설정"""
requests_per_minute: int = 60
tokens_per_minute: int = 100000
requests_per_day: int = 10000
burst_limit: int = 10 # 짧은 시간内的突发请求限制
@dataclass
class RequestLog:
"""요청 로그"""
request_id: str
timestamp: str
user_id: str
model: str
endpoint: str
input_tokens: int
output_tokens: int
latency_ms: float
status_code: int
cost_usd: float
cached: bool = False
error: Optional[str] = None
@dataclass
class UserQuota:
"""사용자 별 quota 관리"""
user_id: str
daily_used: int = 0
daily_reset: datetime = field(default_factory=datetime.now)
minute_requests: list = field(default_factory=list)
minute_tokens: list = field(default_factory=list)
============================================================
Rate Limit 스토어 (실제 환경에서는 Redis 권장)
============================================================
class RateLimitStore:
"""Rate Limit 관리 메모리 스토어"""
def __init__(self):
self.user_quotas: Dict[str, UserQuota] = defaultdict(
lambda: UserQuota(user_id="")
)
self.request_counts: Dict[str, list] = defaultdict(list)
def check_rate_limit(
self,
user_id: str,
tokens: int,
config: RateLimitConfig
) -> Tuple[bool, Optional[str]]:
"""Rate Limit 체크 - 초과 시 False 반환"""
now = datetime.now()
quota = self.user_quotas[user_id]
# 일일 리셋 체크
if now > quota.daily_reset:
quota.daily_used = 0
quota.daily_reset = now + timedelta(days=1)
# 일일 제한 체크
if quota.daily_used >= config.requests_per_day:
return False, "Daily request limit exceeded"
# RPM 체크 (최근 1분内的 요청)
recent_requests = [
t for t in quota.minute_requests
if now - t < timedelta(minutes=1)
]
if len(recent_requests) >= config.requests_per_minute:
return False, "Rate limit exceeded (RPM)"
# TPM 체크 (최근 1분内的 토큰)
recent_tokens = sum(
t for _, t in quota.minute_tokens
if now - _ < timedelta(minutes=1)
)
if recent_tokens + tokens >= config.tokens_per_minute:
return False, "Rate limit exceeded (TPM)"
# 버스트 제한 체크
burst_count = len([
t for t in recent_requests
if now - t < timedelta(seconds=10)
])
if burst_count >= config.burst_limit:
return False, "Burst limit exceeded"
return True, None
def record_request(self, user_id: str, tokens: int):
"""요청 기록"""
now = datetime.now()
quota = self.user_quotas[user_id]
quota.daily_used += 1
quota.minute_requests.append(now)
quota.minute_tokens.append((now, tokens))
============================================================
요청 로거
============================================================
class RequestLogger:
"""AI API 요청 로거"""
def __init__(self, log_file: str = "ai_requests.log"):
self.log_file = log_file
self.logs: list[RequestLog] = []
def log(
self,
request_id: str,
user_id: str,
model: str,
endpoint: str,
input_tokens: int,
output_tokens: int,
latency_ms: float,
status_code: int,
cached: bool = False,
error: Optional[str] = None
):
"""요청 로그 기록"""
# 비용 계산
pricing = MODEL_PRICING.get(model, {"input": 0, "output": 0})
cost = (input_tokens / 1_000_000 * pricing["input"] +
output_tokens / 1_000_000 * pricing["output"])
log_entry = RequestLog(
request_id=request_id,
timestamp=datetime.now().isoformat(),
user_id=user_id,
model=model,
endpoint=endpoint,
input_tokens=input_tokens,
output_tokens=output_tokens,
latency_ms=lat latency_ms,
status_code=status_code,
cost_usd=round(cost, 6),
cached=cached,
error=error
)
self.logs.append(log_entry)
# 파일에 기록
with open(self.log_file, "a") as f:
f.write(json.dumps(log_entry.__dict__, ensure_ascii=False) + "\n")
return log_entry
def get_stats(self, user_id: Optional[str] = None) -> Dict[str, Any]:
"""통계 조회"""
logs = self.logs
if user_id:
logs = [l for l in logs if l.user_id == user_id]
if not logs:
return {"total_requests": 0, "total_cost": 0}
return {
"total_requests": len(logs),
"total_input_tokens": sum(l.input_tokens for l in logs),
"total_output_tokens": sum(l.output_tokens for l in logs),
"total_cost": sum(l.cost_usd for l in logs),
"avg_latency_ms": sum(l.latency_ms for l in logs) / len(logs),
"success_rate": len([l for l in logs if l.status_code < 400]) / len(logs),
"cache_hit_rate": len([l for l in logs if l.cached]) / len(logs),
}
============================================================
인증 미들웨어
============================================================
class AuthMiddleware:
"""API 키 인증 미들웨어"""
# 실제 환경에서는 데이터베이스에서 관리
VALID_API_KEYS = {
"YOUR_HOLYSHEEP_API_KEY": {
"user_id": "user_001",
"tier": "pro",
"rate_limit": RateLimitConfig(requests_per_minute=1000, tokens_per_minute=1000000)
},
}
@classmethod
async def verify(cls, api_key: str) -> Dict[str, Any]:
"""API 키 검증"""
if not api_key:
raise HTTPException(status_code=401, detail="API key required")
if not api_key.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization format")
token = api_key.replace("Bearer ", "")
if token not in cls.VALID_API_KEYS:
raise HTTPException(status_code=401, detail="Invalid API key")
return cls.VALID_API_KEYS[token]
============================================================
HolySheep AI 게이트웨이 클라이언트
============================================================
class HolySheepGateway:
"""HolySheep AI Gateway 클라이언트"""
def __init__(
self,
api_key: str,
base_url: str = HOLYSHEEP_BASE_URL,
timeout: float = 120.0
):
self.api_key = api_key
self.base_url = base_url
self.timeout = timeout
self.client = httpx.AsyncClient(timeout=timeout)
async def chat_completions(
self,
model: str,
messages: list,
temperature: float = 0.7,
max_tokens: Optional[int] = None,
**kwargs
) -> Dict[str, Any]:
"""Chat Completions API 호출"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
}
if max_tokens:
payload["max_tokens"] = max_tokens
payload.update(kwargs)
url = f"{self.base_url}/chat/completions"
async with self.client.stream("POST", url, json=payload, headers=headers) as response:
if response.status_code != 200:
error_text = await response.text()
raise HTTPException(
status_code=response.status_code,
detail=f"HolySheep API Error: {error_text}"
)
# SSE 스트리밍 처리
chunks = []
async for line in response.aiter_lines():
if line.startswith("data: "):
data = line[6:]
if data == "[DONE]":
break
chunks.append(json.loads(data))
# 완료 응답 조합
return self._process_response(chunks)
def _process_response(self, chunks: list) -> Dict[str, Any]:
"""응답 처리"""
if not chunks:
return {"error": "Empty response"}
first = chunks[0]
if "choices" in first: # 비스트리밍
return first
# 스트리밍 응답 조합
content = "".join(
choice.get("delta", {}).get("content", "")
for chunk in chunks
for choice in chunk.get("choices", [])
)
return {
"choices": [{
"message": {"role": "assistant", "content": content}
}]
}
async def close(self):
await self.client.aclose()
============================================================
메인 미들웨어 스택
============================================================
class AIMiddlewareStack(BaseHTTPMiddleware):
"""AI API 미들웨어 스택"""
def __init__(self, app, api_key: str):
super().__init__(app)
self.gateway = HolySheepGateway(api_key)
self.rate_limiter = RateLimitStore()
self.logger = RequestLogger()
async def dispatch(self, request: Request, call_next):
"""미들웨어 실행"""
request_id = hashlib.md5(
f"{time.time()}{request.client}".encode()
).hexdigest()[:16]
# 1. 인증
auth_header = request.headers.get("Authorization", "")
try:
user_info = await AuthMiddleware.verify(auth_header)
except HTTPException as e:
return JSONResponse(
status_code=e.status_code,
content={"error": e.detail}
)
user_id = user_info["user_id"]
rate_config = user_info["rate_limit"]
# 2. Rate Limit 체크
# 실제 토큰 수는 요청 본문에서 추정
estimated_tokens = 1000 # 기본값
can_proceed, error_msg = self.rate_limiter.check_rate_limit(
user_id, estimated_tokens, rate_config
)
if not can_proceed:
return JSONResponse(
status_code=429,
content={
"error": "Rate limit exceeded",
"detail": error_msg,
"retry_after": 60
},
headers={"Retry-After": "60"}
)
# 3. 요청 처리
start_time = time.time()
try:
body = await request.body()
request_json = json.loads(body) if body else {}
model = request_json.get("model", "")
messages = request_json.get("messages", [])
# HolySheep API 호출
response = await self.gateway.chat_completions(
model=model,
messages=messages,
**request_json
)
latency_ms = (time.time() - start_time) * 1000
# 4. 로깅
input_tokens = self._estimate_tokens(messages)
output_tokens = self._estimate_tokens(
response.get("choices", [{}])[0].get("message", {}).get("content", "")
)
self.logger.log(
request_id=request_id,
user_id=user_id,
model=model,
endpoint=str(request.url),
input_tokens=input_tokens,
output_tokens=output_tokens,
latency_ms=latency_ms,
status_code=200
)
# 5. Rate Limit 기록
self.rate_limiter.record_request(user_id, input_tokens + output_tokens)
return JSONResponse(content=response)
except Exception as e:
latency_ms = (time.time() - start_time) * 1000
self.logger.log(
request_id=request_id,
user_id=user_id,
model=request_json.get("model", ""),
endpoint=str(request.url),
input_tokens=0,
output_tokens=0,
latency_ms=latency_ms,
status_code=500,
error=str(e)
)
return JSONResponse(
status_code=500,
content={"error": "Internal server error", "detail": str(e)}
)
def _estimate_tokens(self, text: Any) -> int:
"""토큰 수 추정 (실제 환경에서는 tiktoken 권장)"""
if isinstance(text, str):
return len(text) // 4 # 대략적估算
elif isinstance(text, list):
return sum(self._estimate_tokens(item) for item in text)
return 0
============================================================
FastAPI 앱 설정
============================================================
app = FastAPI(title="AI Gateway Middleware")
미들웨어 추가
app.add_middleware(AIMiddlewareStack, api_key=HOLYSHEEP_API_KEY)
@app.get("/health")
async def health():
return {"status": "healthy", "gateway": "HolySheep AI"}
@app.get("/stats")
async def stats(user_id: Optional[str] = None):
"""사용량 통계 조회"""
logger = RequestLogger()
return logger.get_stats(user_id)
Node.js 기반 미들웨어 구현
// middleware/aiGateway.js
/**
* AI API Gateway Middleware - 인증, Rate Limit, 로깅 통합
* Author: HolySheep AI Technical Team
*/
const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
// ============================================================
// 설정 및 상수
// ============================================================
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
const HOLYSHEEP_API_KEY = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';
// 모델별 가격 (USD per 1M tokens)
const MODEL_PRICING = {
'gpt-4.1': { input: 8.00, output: 32.00 },
'gpt-4o': { input: 2.50, output: 10.00 },
'claude-sonnet-4-5': { input: 15.00, output: 75.00 },
'claude-opus-4': { input: 75.00, output: 150.00 },
'gemini-2.5-flash': { input: 2.50, output: 10.00 },
'deepseek-v3.2': { input: 0.42, output: 1.68 },
};
// ============================================================
// Rate Limit 스토어
// ============================================================
class RateLimitStore {
constructor() {
this.userQuotas = new Map();
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
}
getUserQuota(userId) {
if (!this.userQuotas.has(userId)) {
this.userQuotas.set(userId, {
dailyUsed: 0,
dailyReset: new Date(Date.now() + 24 * 60 * 60 * 1000),
minuteRequests: [],
minuteTokens: [],
});
}
return this.userQuotas.get(userId);
}
checkRateLimit(userId, tokens, config) {
const now = new Date();
const quota = this.getUserQuota(userId);
// 일일 리셋
if (now > quota.dailyReset) {
quota.dailyUsed = 0;
quota.dailyReset = new Date(now.getTime() + 24 * 60 * 60 * 1000);
}
// 일일 제한
if (quota.dailyUsed >= config.requestsPerDay) {
return { allowed: false, reason: 'Daily request limit exceeded' };
}
// RPM 체크 (최근 1분)
const oneMinuteAgo = new Date(now.getTime() - 60000);
quota.minuteRequests = quota.minuteRequests.filter(t => t > oneMinuteAgo);
if (quota.minuteRequests.length >= config.requestsPerMinute) {
return { allowed: false, reason: 'Rate limit exceeded (RPM)' };
}
// TPM 체크
const recentTokens = quota.minuteTokens
.filter(([timestamp]) => timestamp > oneMinuteAgo)
.reduce((sum, [, t]) => sum + t, 0);
if (recentTokens + tokens >= config.tokensPerMinute) {
return { allowed: false, reason: 'Rate limit exceeded (TPM)' };
}
// 버스트 제한
const tenSecAgo = new Date(now.getTime() - 10000);
const burstCount = quota.minuteRequests.filter(t => t > tenSecAgo).length;
if (burstCount >= config.burstLimit) {
return { allowed: false, reason: 'Burst limit exceeded' };
}
return { allowed: true };
}
recordRequest(userId, tokens) {
const quota = this.getUserQuota(userId);
const now = new Date();
quota.dailyUsed++;
quota.minuteRequests.push(now);
quota.minuteTokens.push([now, tokens]);
}
cleanup() {
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);
for (const [userId, quota] of this.userQuotas) {
quota.minuteRequests = quota.minuteRequests.filter(t => t > oneHourAgo);
quota.minuteTokens = quota.minuteTokens.filter(([t]) => t > oneHourAgo);
}
}
}
// ============================================================
// 요청 로거
// ============================================================
class RequestLogger {
constructor(logFile = 'ai_requests.log') {
this.logFile = logFile;
this.logs = [];
}
log(logEntry) {
const { requestId, userId, model, endpoint, inputTokens, outputTokens, latencyMs, statusCode, cached = false, error = null } = logEntry;
// 비용 계산
const pricing = MODEL_PRICING[model] || { input: 0, output: 0 };
const cost = (inputTokens / 1000000 * pricing.input) +
(outputTokens / 1000000 * pricing.output);
const entry = {
request_id: requestId,
timestamp: new Date().toISOString(),
user_id: userId,
model,
endpoint,
input_tokens: inputTokens,
output_tokens: outputTokens,
latency_ms: latencyMs,
status_code: statusCode,
cost_usd: parseFloat(cost.toFixed(6)),
cached,
error,
};
this.logs.push(entry);
// 파일에 기록
fs.appendFileSync(this.logFile, JSON.stringify(entry) + '\n');
return entry;
}
getStats(userId = null) {
let logs = this.logs;
if (userId) {
logs = logs.filter(l => l.user_id === userId);
}
if (logs.length === 0) {
return { total_requests: 0, total_cost: 0 };
}
return {
total_requests: logs.length,
total_input_tokens: logs.reduce((sum, l) => sum + l.input_tokens, 0),
total_output_tokens: logs.reduce((sum, l) => sum + l.output_tokens, 0),
total_cost: parseFloat(logs.reduce((sum, l) => sum + l.cost_usd, 0).toFixed(6)),
avg_latency_ms: parseFloat((logs.reduce((sum, l) => sum + l.latency_ms, 0) / logs.length).toFixed(2)),
success_rate: parseFloat((logs.filter(l => l.status_code < 400).length / logs.length * 100).toFixed(2)),
cache_hit_rate: parseFloat((logs.filter(l => l.cached).length / logs.length * 100).toFixed(2)),
};
}
}
// ============================================================
// 인증 미들웨어
// ============================================================
const VALID_API_KEYS = {
'YOUR_HOLYSHEEP_API_KEY': {
userId: 'user_001',
tier: 'pro',
rateLimit: {
requestsPerMinute: 1000,
tokensPerMinute: 1000000,
requestsPerDay: 100000,
burstLimit: 50,
},
},
};
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization || '';
if (!authHeader) {
return res.status(401).json({ error: 'API key required' });
}
if (!authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Invalid authorization format' });
}
const token = authHeader.slice(7);
if (!VALID_API_KEYS[token]) {
return res.status(401).json({ error: 'Invalid API key' });
}
req.user = VALID_API_KEYS[token];
next();
}
// ============================================================
// HolySheep AI 게이트웨이 클라이언트
// ============================================================
class HolySheepGateway {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = HOLYSHEEP_BASE_URL;
}
async chatCompletions(model, messages, options = {}) {
const response = await fetch(${this.baseUrl}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages,
temperature: options.temperature || 0.7,
max_tokens: options.maxTokens,
...options,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(HolySheep API Error: ${response.status} - ${errorText});
}
return response.json();
}
async embeddings(text, model = 'text-embedding-3-large') {
const response = await fetch(${this.baseUrl}/embeddings, {
method: 'POST',
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json',
},
body: JSON.stringify({ input: text, model }),
});
if (!response.ok) {
throw new Error(Embedding API Error: ${response.status});
}
return response.json();
}
}
// ============================================================
// 토큰 추정 유틸리티
// ============================================================
function estimateTokens(text) {
if (typeof text === 'string') {
// 대략적估算: 영어는 4자당 1토큰, 한국어는 2자당 1토큰
const koreanChars = (text.match(/[가-힣]/g) || []).length;
const otherChars = text.length - koreanChars;
return Math.ceil(koreanChars / 2 + otherChars / 4);
}
if (Array.isArray(text)) {
return text.reduce((sum, item) => sum + estimateTokens(item), 0);
}
return 0;
}
// ============================================================
// 메인 미들웨어
// ============================================================
function createAIMiddleware(gateway, rateLimiter, logger) {
return async (req, res, next) => {
// 인증 미들웨어 먼저 실행
authMiddleware(req, res, () => {
if (res.headersSent) return;
const startTime = Date.now();
const requestId = crypto.randomBytes(8).toString('hex');
const userId = req.user.userId;
const rateConfig = req.user.rateLimit;
// Rate Limit 체크
const estimatedTokens = estimateTokens(req.body?.messages || []);
const limitCheck = rateLimiter.checkRateLimit(userId, estimatedTokens, rateConfig);
if (!limitCheck.allowed) {
return res.status(429).json({
error: 'Rate limit exceeded',
detail: limitCheck.reason,
retry_after: 60,
});
}
// 요청 처리
gateway.chatCompletions(
req.body.model,
req.body.messages,
req.body
)
.then(response => {
const latencyMs = Date.now() - startTime;
const inputTokens = estimateTokens(req.body.messages);
const outputTokens = estimateTokens(
response.choices?.[0]?.message?.content || ''
);
// 로깅
logger.log({
requestId,
userId,
model: req.body.model,
endpoint: req.originalUrl || req.url,
inputTokens,
outputTokens,
latencyMs,
statusCode: 200,
});
// Rate Limit 기록
rateLimiter.recordRequest(userId, inputTokens + outputTokens);
// 응답 헤더에 메타데이터 추가
res.set({
'X-Request-ID': requestId,
'X-RateLimit-Remaining': rateConfig.requestsPerMinute -
rateLimiter.getUserQuota(userId).minuteRequests.length,
'X-Usage-Input-Tokens': inputTokens,
'X-Usage-Output-Tokens': outputTokens,
'X-Latency-Ms': latencyMs,
});
res.json(response);
})
.catch(error => {
const latencyMs = Date.now() - startTime;
logger.log({
requestId,
userId,
model: req.body.model || 'unknown',
endpoint: req.originalUrl || req.url,
inputTokens: 0,
outputTokens: 0,
latencyMs,
statusCode: 500,
error: error.message,
});
res.status(500).json({
error: 'Internal server error',
detail: error.message,
});
});
});
};
}
// ============================================================
// Express 앱 설정
// ============================================================
const app = express();
app.use(express.json());
// 의존성 주입
const gateway = new HolySheepGateway(HOLYSHEEP_API_KEY);
const rateLimiter = new RateLimitStore();
const logger = new RequestLogger();
// AI API 라우트
const aiRouter = express.Router();
aiRouter.use(createAIMiddleware(gateway, rateLimiter, logger));
aiRouter.post('/chat/completions', (req, res) => {
// 미들웨어에서 처리되므로 여기서는 도달하지 않음
});
aiRouter.post('/embeddings', async (req, res) => {
try {
const { input, model } = req.body;
const response = await gateway.embeddings(input, model