안녕하세요, 저는 HolySheep AI의 기술 문서 엔지니어입니다. 이번 튜토리얼에서는 AI API 게이트웨이 사용 시 반드시 마주하게 되는 Rate Limit(비율 제한)과 Timeout(시간 초과) 문제를 효과적으로 모니터링하고, 자동으로 장애를 전환하는 시스템을 구축하는 방법을 단계별로 알려드리겠습니다.
AI API를 운영하면서 가장 흔하게遭遇하는 문제들이 있습니다. 요청이 갑자기 실패하거나, 응답이 너무 느려지거나, 순간적으로 한도가 초과되는 상황이죠. HolySheep AI를 사용하면 이런 문제들을 실시간으로 감시하고 자동으로 대처할 수 있는 시스템을 쉽게 만들 수 있습니다.
이 튜토리얼에서 만들 것
이번 가이드를 마치면 다음과 같은 모니터링 대시보드를 직접 구축하게 됩니다:
- 실시간 Rate Limit 카운터 (1분/5분/15분 윈도우)
- Timeout 발생 빈도 추이 그래프
- 자동 장애 전환 상태 패널
- Slack/Discord 알림 연동
- HolySheep API 상태 요약
1. HolySheep AI란?
먼저 HolySheep AI가 무엇인지 간략히 설명드리겠습니다. HolySheep AI는 글로벌 AI API 게이트웨이 서비스로, 지금 가입하면 단일 API 키로 다양한 AI 모델을 통합적으로 사용할 수 있습니다.
| 모델 | 가격 ($/1M 토큰) | 주요 특징 |
|---|---|---|
| GPT-4.1 | $8.00 | 최고 품질, 복잡한 작업 |
| Claude Sonnet 4.5 | $15.00 | 긴 컨텍스트, 분석력 |
| Gemini 2.5 Flash | $2.50 | 빠른 응답, 비용 효율 |
| DeepSeek V3.2 | $0.42 | 경제적, 다국어 지원 |
HolySheep AI의 가장 큰 장점은 단일 엔드포인트로 여러 모델을 전환할 수 있다는 점입니다. 이 특징을 이용하면 모니터링과 장애 조치를 훨씬 쉽게 구현할 수 있습니다.
2. 기본 개념 이해
Rate Limit(비율 제한)이란?
Rate Limit은 일정 시간 내에 보낼 수 있는 요청 수를 제한하는机制입니다. 예를 들어 HolySheep API는 분당 100회 요청을 허용한다고 가정해보겠습니다. 이 한도를 넘으면 429 에러(HTTP 429: Too Many Requests)가 반환됩니다.
Timeout(시간 초과)이란?
요청을 보낸 후 일정 시간 안에 응답이 오지 않으면 타임아웃으로 판단합니다. HolySheep AI의 평균 응답 시간은 200~800ms 정도ですが、네트워크 문제나 서버 부하 시 더 길어질 수 있습니다.
push.log란?
push.log는 HolySheep AI가 제공하는 로그 스트리밍 서비스입니다. 모든 API 요청/응답의 상세 정보를 실시간으로 전달받아 모니터링 시스템에 활용할 수 있습니다. 이 로그를 기반으로 Rate Limit와 Timeout을 감지하고 알림을 보내는 시스템을 구축하겠습니다.
3. 준비물
- HolySheep AI 계정 (무료 크레딧 포함 가입)
- Node.js 18 이상 설치된 서버
- 基础的 JavaScript 지식
- Webhook 수신 가능한 URL (ngrok 또는 자체 서버)
4. HolySheep AI API 키 발급받기
먼저 HolySheep AI에서 API 키를 발급받아야 합니다. 다음 단계를 진행하세요:
- HolySheep AI 가입 페이지 방문
- 이메일과 비밀번호로 계정 생성
- 로그인 후 Dashboard → API Keys 메뉴 접속
- "Create New Key" 버튼 클릭
- 키 이름 입력 후 생성 (화면에 키가 한 번만 표시됩니다)
⚠️ 중요: API 키는 다시 확인할 수 없으므로 안전한 곳에 보관하세요.
5. push.log 스트리밍 설정
HolySheep AI의 push.log를 사용하려면 먼저 Webhook URL을 등록해야 합니다. HolySheep Dashboard에서 Settings → Webhooks로 이동하여 다음 정보를 입력하세요:
- Webhook URL: https://your-server.com/webhook/holysheep
- Events: request.completed, request.failed, rate_limit.exceeded
6. 프로젝트 구조 생성
터미널에서 다음 명령어를 실행하여 프로젝트 폴더를 생성합니다:
mkdir holysheep-monitor
cd holysheep-monitor
npm init -y
npm install express cors dotenv ws
7. 기본 서버 코드 작성
이제 HolySheep API를 호출하고 push.log를 수신하는 기본 서버를 만들겠습니다:
// server.js
const express = require('express');
const cors = require('cors');
const { WebSocketServer } = require('ws');
const app = express();
app.use(cors());
app.use(express.json());
// HolySheep API 설정
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
const API_KEY = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';
// 모니터링 데이터 저장소
const monitoringData = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
rateLimitHits: 0,
timeouts: 0,
avgLatency: 0,
latencyHistory: [],
lastMinuteRequests: [],
last5MinRequests: [],
last15MinRequests: []
};
// HolySheep AI API 호출 예제
async function callHolySheepAPI(prompt) {
const startTime = Date.now();
try {
const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: prompt }],
max_tokens: 500
})
});
const latency = Date.now() - startTime;
if (response.status === 429) {
monitoringData.rateLimitHits++;
monitoringData.failedRequests++;
return { success: false, error: 'rate_limit', latency };
}
if (!response.ok) {
monitoringData.failedRequests++;
return { success: false, error: 'api_error', status: response.status, latency };
}
const data = await response.json();
monitoringData.successfulRequests++;
monitoringData.totalRequests++;
monitoringData.latencyHistory.push(latency);
// 평균 지연 시간 계산
const recent = monitoringData.latencyHistory.slice(-100);
monitoringData.avgLatency = recent.reduce((a, b) => a + b, 0) / recent.length;
return { success: true, data, latency };
} catch (error) {
monitoringData.failedRequests++;
if (error.name === 'AbortError' || error.message.includes('timeout')) {
monitoringData.timeouts++;
return { success: false, error: 'timeout', latency: 30000 };
}
return { success: false, error: error.message };
}
}
// HolySheep push.log 웹훅 수신 엔드포인트
app.post('/webhook/holysheep', (req, res) => {
const event = req.body;
console.log('收到 HolySheep 事件:', event.type);
switch (event.type) {
case 'request.completed':
monitoringData.successfulRequests++;
monitoringData.totalRequests++;
monitoringData.latencyHistory.push(event.latency_ms);
updateRequestWindows(Date.now());
break;
case 'request.failed':
monitoringData.failedRequests++;
monitoringData.totalRequests++;
break;
case 'rate_limit.exceeded':
monitoringData.rateLimitHits++;
console.log('⚠️ Rate Limit 감지!', event.details);
// 여기서 알림 발송 가능
break;
}
res.status(200).send('OK');
});
// 시간별 요청 윈도우 업데이트
function updateRequestWindows(timestamp) {
const window1min = 60 * 1000;
const window5min = 5 * 60 * 1000;
const window15min = 15 * 60 * 1000;
monitoringData.lastMinuteRequests.push(timestamp);
monitoringData.last5MinRequests.push(timestamp);
monitoringData.last15MinRequests.push(timestamp);
// 1분 윈도우 정리
monitoringData.lastMinuteRequests = monitoringData.lastMinuteRequests
.filter(t => timestamp - t < window1min);
// 5분 윈도우 정리
monitoringData.last5MinRequests = monitoringData.last5MinRequests
.filter(t => timestamp - t < window5min);
// 15분 윈도우 정리
monitoringData.last15MinRequests = monitoringData.last15MinRequests
.filter(t => timestamp - t < window15min);
}
// 모니터링 데이터 API
app.get('/api/monitoring', (req, res) => {
const now = Date.now();
res.json({
totalRequests: monitoringData.totalRequests,
successfulRequests: monitoringData.successfulRequests,
failedRequests: monitoringData.failedRequests,
rateLimitHits: monitoringData.rateLimitHits,
timeouts: monitoringData.timeouts,
avgLatency: Math.round(monitoringData.avgLatency),
requestsPerMinute: monitoringData.lastMinuteRequests.length,
requestsPer5Min: monitoringData.last5MinRequests.length,
requestsPer15Min: monitoringData.last15MinRequests.length,
successRate: monitoringData.totalRequests > 0
? ((monitoringData.successfulRequests / monitoringData.totalRequests) * 100).toFixed(2)
: 100
});
});
// 장애 전환 엔드포인트
app.post('/api/failover', async (req, res) => {
const { primaryModel, fallbackModel, prompt } = req.body;
// 기본 모델로 시도
let result = await callWithModel(primaryModel, prompt);
if (!result.success) {
console.log(${primaryModel} 실패, ${fallbackModel}로 전환...);
result = await callWithModel(fallbackModel, prompt);
result.failover = true;
}
res.json(result);
});
async function callWithModel(model, prompt) {
try {
const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 500
})
});
if (response.status === 429) {
return { success: false, error: 'rate_limit' };
}
if (!response.ok) {
return { success: false, error: 'api_error' };
}
const data = await response.json();
return { success: true, data, model };
} catch (error) {
return { success: false, error: error.message };
}
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(HolySheep 모니터링 서버 실행 중: http://localhost:${PORT});
console.log(API 키: ${API_KEY.substring(0, 10)}...);
});
8. Rate Limit 관찰 패널 구현
이제 실시간으로 Rate Limit 현황을 보여주는 웹 패널을 만들겠습니다:
<!-- monitor.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HolySheep AI 모니터링 대시보드</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f0f23;
color: #fff;
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.header h1 { color: #00d4ff; font-size: 24px; }
.status-badge {
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
}
.status-healthy { background: #00c853; }
.status-warning { background: #ffc107; color: #000; }
.status-critical { background: #ff1744; }
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.card {
background: linear-gradient(135deg, #1a1a3e 0%, #2d2d5a 100%);
border-radius: 16px;
padding: 24px;
border: 1px solid #3d3d6d;
}
.card-title {
font-size: 14px;
color: #888;
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 1px;
}
.card-value {
font-size: 42px;
font-weight: bold;
margin-bottom: 8px;
}
.card-subtitle {
font-size: 13px;
color: #aaa;
}
.rate-limit-section {
background: linear-gradient(135deg, #1a1a3e 0%, #2d2d5a 100%);
border-radius: 16px;
padding: 24px;
margin-bottom: 30px;
border: 1px solid #3d3d6d;
}
.rate-bar {
height: 12px;
background: #2d2d5a;
border-radius: 6px;
overflow: hidden;
margin: 12px 0;
}
.rate-fill {
height: 100%;
background: linear-gradient(90deg, #00c853, #69f0ae);
border-radius: 6px;
transition: width 0.3s ease;
}
.rate-fill.warning { background: linear-gradient(90deg, #ffc107, #ffeb3b); }
.rate-fill.critical { background: linear-gradient(90deg, #ff1744, #ff5252); }
.alert-panel {
background: linear-gradient(135deg, #3d1a1a 0%, #5a2d2d 100%);
border-radius: 16px;
padding: 24px;
border: 1px solid #6d3d3d;
}
.alert-item {
display: flex;
align-items: center;
padding: 12px;
margin: 8px 0;
background: rgba(255, 23, 68, 0.1);
border-radius: 8px;
border-left: 4px solid #ff1744;
}
.alert-time { color: #888; font-size: 12px; margin-right: 12px; }
.alert-message { flex: 1; }
.controls {
display: flex;
gap: 12px;
margin-bottom: 30px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: transform 0.1s;
}
.btn:hover { transform: scale(1.02); }
.btn:active { transform: scale(0.98); }
.btn-primary { background: #00d4ff; color: #000; }
.btn-success { background: #00c853; color: #fff; }
.btn-danger { background: #ff1744; color: #fff; }
.toast {
position: fixed;
bottom: 20px;
right: 20px;
padding: 16px 24px;
background: #00c853;
color: #fff;
border-radius: 8px;
font-weight: bold;
transform: translateY(100px);
transition: transform 0.3s ease;
z-index: 1000;
}
.toast.show { transform: translateY(0); }
.toast.error { background: #ff1744; }
</style>
</head>
<body>
<div class="header">
<h1>🦄 HolySheep AI 모니터링</h1>
<span id="statusBadge" class="status-badge status-healthy">정상</span>
</div>
<div class="dashboard">
<div class="card">
<div class="card-title">총 요청 수</div>
<div class="card-value" id="totalRequests">0</div>
<div class="card-subtitle">오늘의 API 호출</div>
</div>
<div class="card">
<div class="card-title">성공률</div>
<div class="card-value" id="successRate" style="color: #00c853">100%</div>
<div class="card-subtitle">전체 요청 대비</div>
</div>
<div class="card">
<div class="card-title">평균 지연 시간</div>
<div class="card-value" id="avgLatency">0ms</div>
<div class="card-subtitle">최근 100회 평균</div>
</div>
<div class="card">
<div class="card-title">Timeout 발생</div>
<div class="card-value" id="timeouts" style="color: #ff1744">0</div>
<div class="card-subtitle">연결 시간 초과</div>
</div>
</div>
<div class="rate-limit-section">
<h2 style="margin-bottom: 20px;">📊 Rate Limit 현황</h2>
<div style="margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>1분 윈도우</span>
<span id="req1min">0 / 100</span>
</div>
<div class="rate-bar">
<div class="rate-fill" id="bar1min" style="width: 0%"></div>
</div>
</div>
<div style="margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>5분 윈도우</span>
<span id="req5min">0 / 500</span>
</div>
<div class="rate-bar">
<div class="rate-fill" id="bar5min" style="width: 0%"></div>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span>15분 윈도우</span>
<span id="req15min">0 / 1500</span>
</div>
<div class="rate-bar">
<div class="rate-fill" id="bar15min" style="width: 0%"></div>
</div>
</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="testAPI()">🧪 API 테스트 요청</button>
<button class="btn btn-success" onclick="testFailover()">🔄 장애 전환 테스트</button>
<button class="btn btn-danger" onclick="simulateLoad()">⚡ 부하 테스트</button>
</div>
<div class="alert-panel">
<h2 style="margin-bottom: 16px;">🚨 실시간 알림</h2>
<div id="alerts">
<div class="alert-item">
<span class="alert-time">--:--:--</span>
<span class="alert-message">알림을 기다리는 중...</span>
</div>
</div>
</div>
<div id="toast" class="toast">메시지</div>
<script>
const API_BASE = 'http://localhost:3000';
const MAX_1MIN = 100;
const MAX_5MIN = 500;
const MAX_15MIN = 1500;
// 모니터링 데이터 폴링
async function fetchMonitoringData() {
try {
const response = await fetch(\\${API_BASE}/api/monitoring\);\n
if (!response.ok) throw new Error('API 연결 실패');
const data = await response.json();
updateDashboard(data);
} catch (error) {
console.error('모니터링 데이터 fetch 오류:', error);
}
}
// 대시보드 업데이트
function updateDashboard(data) {
document.getElementById('totalRequests').textContent = data.totalRequests.toLocaleString();
document.getElementById('successRate').textContent = data.successRate + '%';
document.getElementById('avgLatency').textContent = data.avgLatency + 'ms';
document.getElementById('timeouts').textContent = data.timeouts;
// 1분 윈도우
const pct1min = (data.requestsPerMinute / MAX_1MIN) * 100;
document.getElementById('req1min').textContent = \\${data.requestsPerMinute} / \${MAX_1MIN}\;
updateRateBar('bar1min', pct1min);
// 5분 윈도우
const pct5min = (data.requestsPer5Min / MAX_5MIN) * 100;
document.getElementById('req5min').textContent = \\${data.requestsPer5Min} / \${MAX_5MIN}\;
updateRateBar('bar5min', pct5min);
// 15분 윈도우
const pct15min = (data.requestsPer15Min / MAX_15MIN) * 100;
document.getElementById('req15min').textContent = \\${data.requestsPer15Min} / \${MAX_15MIN}\;
updateRateBar('bar15min', pct15min);
// 상태 배지 업데이트
const badge = document.getElementById('statusBadge');
if (data.rateLimitHits > 5 || data.timeouts > 3) {
badge.className = 'status-badge status-critical';
badge.textContent = '위험';
} else if (data.rateLimitHits > 0 || data.timeouts > 0) {
badge.className = 'status-badge status-warning';
badge.textContent = '주의';
} else {
badge.className = 'status-badge status-healthy';
badge.textContent = '정상';
}
}
function updateRateBar(id, percentage) {
const bar = document.getElementById(id);
bar.style.width = Math.min(percentage, 100) + '%';
bar.className = 'rate-fill';
if (percentage >= 90) bar.classList.add('critical');
else if (percentage >= 70) bar.classList.add('warning');
}
// 알림 추가
function addAlert(message, isError = false) {
const alerts = document.getElementById('alerts');
const now = new Date();
const time = now.toLocaleTimeString('ko-KR');
const alertItem = document.createElement('div');
alertItem.className = 'alert-item';
alertItem.innerHTML = \`
<span class="alert-time">\${time}</span>
<span class="alert-message">\${message}</span>
\`;
alerts.insertBefore(alertItem, alerts.firstChild);
// 최대 10개 알림 유지
if (alerts.children.length > 10) {
alerts.removeChild(alerts.lastChild);
}
}
// 토스트 메시지
function showToast(message, isError = false) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = 'toast' + (isError ? ' error' : '');
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// API 테스트 요청
async function testAPI() {
showToast('API 요청 전송 중...');
addAlert('API 테스트 요청을 보냈습니다');
try {
const response = await fetch(\\${API_BASE}/api/failover\, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
primaryModel: 'gpt-4.1',
fallbackModel: 'gemini-2.5-flash',
prompt: '안녕하세요, 테스트 메시지입니다.'
})
});
const result = await response.json();
if (result.success) {
showToast(\✅ 성공! (\${result.latency}ms)\);
addAlert(\✅ 요청 성공 - 모델: \${result.data.model}, 지연: \${result.latency}ms\);
} else {
showToast(\❌ 실패: \${result.error}\, true);
addAlert(\❌ 요청 실패: \${result.error}\, true);
}
} catch (error) {
showToast('❌ 연결 오류', true);
addAlert(\❌ 연결 오류: \${error.message}\, true);
}
}
// 장애 전환 테스트
async function testFailover() {
showToast('장애 전환 시뮬레이션...');
addAlert('🔄 장애 전환 테스트 시작');
// 실제로는 primary를 강제로 실패시키거나, 타임아웃을 유도
for (let i = 0; i < 3; i++) {
await testAPI();
await new Promise(r => setTimeout(r, 1000));
}
}
// 부하 테스트
async function simulateLoad() {
showToast('⚡ 부하 테스트 시작...');
addAlert('⚡ 대량 요청 시뮬레이션 시작 (10회)');
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(fetch(\\${API_BASE}/api/monitoring\));
}
Promise.all(promises)
.then(() => {
showToast('✅ 부하 테스트 완료');
addAlert('⚡ 부하 테스트 완료 - 10회 동시 요청 완료');
})
.catch(() => {
showToast('❌ 부하 테스트 중 오류', true);
});
}
// 3초마다 데이터 갱신
setInterval(fetchMonitoringData, 3000);
// 초기 로드
fetchMonitoringData();
</script>
</body>
</html>
9. 자동 장애 전환 시스템 구현
Rate Limit이나 Timeout이 발생하면 자동으로 다른 모델로 전환하는 시스템을 만들겠습니다:
// failover.js
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
const API_KEY = process.env.HOLYSHEEP_API_KEY;
// 모델 우선순위 설정
const MODEL_POOLS = {
'high-quality': ['gpt-4.1', 'claude-sonnet-4.5', 'gemini-2.5-flash'],
'balanced': ['gemini-2.5-flash', 'gpt-4.1', 'claude-sonnet-4.5'],
'cost-effective': ['deepseek-v3.2', 'gemini-2.5-flash', 'gpt-4.1']
};
// 모델별 비용 ($/1M 토큰)
const MODEL_COSTS = {
'gpt-4.1': 8.00,
'claude-sonnet-4.5': 15.00,
'gemini-2.5-flash': 2.50,
'deepseek-v3.2': 0.42
};
// 장애 전환 상태 추적
const failoverState = {
consecutiveFailures: 0,
currentModelIndex: 0,
totalFailovers: 0,
lastFailoverTime: null,
modelHealth: {}
};
// 자동 장애 전환 함수
async function callWithAutoFailover(prompt, poolName = 'balanced', maxRetries = 3) {
const models = MODEL_POOLS[poolName];
let lastError = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const model = models[attempt % models.length];
console.log(🤖 [시도 ${attempt + 1}] 모델: ${model});
try {
const result = await callWithTimeout(model, prompt, 30000);
if (result.success) {
failoverState.consecutiveFailures = 0;
updateModelHealth(model, true);
console.log(✅ ${model} 성공! 지연: ${result.latency}ms);
return {
success: true,
data: result.data,
model: model,
latency: result.latency,
failoverCount: attempt
};
}
// Rate Limit 또는 실패
console.log(⚠️ ${model} 실패: ${result.error});
failoverState.consecutiveFailures++;
updateModelHealth(model, false);
lastError = result.error;
} catch (error) {
console.error(❌ ${model} 예외: ${error.message});
failoverState.consecutiveFailures++;
lastError = error.message;
}
}
// 모든 모델 실패
failoverState.totalFailovers++;
console.error('🚨 모든 모델 실패!');
return {
success: false,
error: lastError || 'all_models_failed',
failoverCount: maxRetries
};
}
async function callWithTimeout(model, prompt, timeoutMs) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
const startTime = Date.now();
try {
const response = await fetch(${HOLYSHEEP_BASE_URL}/chat/completions, {
method: 'POST',
headers: {
'Authorization': Bearer ${API_KEY},
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 500
}),
signal: controller.signal
});
const latency = Date.now() - startTime;
clearTimeout(timeoutId);
// HTTP 상태 코드 체크
if (response.status === 429) {
return { success: false, error: 'rate_limit_exceeded', status: 429 };
}
if (response.status === 408) {
return { success: false, error: 'request_timeout', status: 408 };
}
if (!response.ok) {
return { success: false, error: `http_${