จากประสบการณ์การสร้าง production AI pipeline มากกว่า 3 ปี ผมพบว่าการจัดการ timeout ที่ไม่เหมาะสมเป็นสาเหตุหลักของปัญหา reliability และต้นทุนที่สูงเกินไป บทความนี้จะอธิบายวิธีการตั้งค่า dynamic timeout ที่เหมาะสมกับ model ที่ใช้ พร้อมโค้ด production-ready ที่พร้อมนำไปใช้งานจริง
ทำไม Timeout ถึงสำคัญต่อ AI API
เมื่อเรียกใช้ AI API เช่น HolySheep AI ซึ่งมี latency เฉลี่ยต่ำกว่า 50 มิลลิวินาที การตั้ง timeout ที่ถูกต้องจะช่วยป้องกันปัญหา:
- Resource Blocking — Thread หรือ connection ถูกครองโดย request ที่ค้าง
- Cascade Failure — ระบบล่มเป็นลูกโซ่เมื่อ timeout วงจรเดียวกัน
- Cost Overrun — Request ที่ล้มเหลวแต่ยังคง consume tokens
สถาปัตยกรรม Dynamic Timeout ตาม Model Complexity
แต่ละ model มี response time ที่แตกต่างกันตามความซับซ้อน จากการทดสอบของผมบน HolySheep AI:
- DeepSeek V3.2 ($0.42/MTok) — ใช้เวลาเฉลี่ย 800-2000ms สำหรับ complex reasoning
- Gemini 2.5 Flash ($2.50/MTok) — ใช้เวลาเฉลี่ย 400-1200ms สำหรับ long context
- Claude Sonnet 4.5 ($15/MTok) — ใช้เวลาเฉลี่ย 2000-8000ms สำหรับ deep analysis
- GPT-4.1 ($8/MTok) — ใช้เวลาเฉลี่ย 1500-6000ms สำหรับ creative tasks
การตั้งค่า Configuration พื้นฐาน
// config/ai_timeouts.ts
export interface ModelTimeoutConfig {
model: string;
baseTimeout: number; // milliseconds
maxTimeout: number; // milliseconds
contextLength: number; // tokens
complexity: 'low' | 'medium' | 'high' | 'extreme';
}
export const MODEL_TIMEOUTS: Record<string, ModelTimeoutConfig> = {
'deepseek-v3.2': {
model: 'deepseek-v3.2',
baseTimeout: 15000, // 15 วินาที
maxTimeout: 30000, // 30 วินาที
contextLength: 64000,
complexity: 'medium'
},
'gemini-2.5-flash': {
model: 'gemini-2.5-flash',
baseTimeout: 12000, // 12 วินาที
maxTimeout: 25000, // 25 วินาที
contextLength: 1000000,
complexity: 'low'
},
'claude-sonnet-4.5': {
model: 'claude-sonnet-4.5',
baseTimeout: 25000, // 25 วินาที
maxTimeout: 60000, // 60 วินาที
contextLength: 200000,
complexity: 'extreme'
},
'gpt-4.1': {
model: 'gpt-4.1',
baseTimeout: 20000, // 20 วินาที
maxTimeout: 45000, // 45 วินาที
contextLength: 128000,
complexity: 'high'
}
};
export function calculateTimeout(
modelConfig: ModelTimeoutConfig,
estimatedTokens: number
): number {
// เพิ่ม timeout ตามจำนวน tokens ที่ประมาณการ
const tokenFactor = Math.min(
estimatedTokens / modelConfig.contextLength,
1.5 // สูงสุด 1.5 เท่าของ base
);
const calculated = modelConfig.baseTimeout * (1 + tokenFactor * 0.5);
return Math.min(calculated, modelConfig.maxTimeout);
}
Client Implementation พร้อม Retry และ Circuit Breaker
// lib/ai-client.ts
import https from 'https';
import { EventEmitter } from 'events';
import { MODEL_TIMEOUTS, calculateTimeout, type ModelTimeoutConfig } from '../config/ai_timeouts';
interface RetryConfig {
maxRetries: number;
baseDelay: number;
maxDelay: number;
backoffFactor: number;
}
interface CircuitBreakerState {
failures: number;
lastFailure: number;
state: 'closed' | 'open' | 'half-open';
successCount: number;
}
class AIClient extends EventEmitter {
private apiKey: string;
private baseUrl = 'https://api.holysheep.ai/v1';
private retryConfig: RetryConfig = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2
};
private circuitBreaker: Record<string, CircuitBreakerState> = {};
private readonly CIRCUIT_THRESHOLD = 5;
private readonly CIRCUIT_TIMEOUT = 30000; // 30 วินาที
constructor(apiKey: string) {
super();
this.apiKey = apiKey;
}
private getCircuitBreaker(model: string): CircuitBreakerState {
if (!this.circuitBreaker[model]) {
this.circuitBreaker[model] = {
failures: 0,
lastFailure: 0,
state: 'closed',
successCount: 0
};
}
return this.circuitBreaker[model];
}
private updateCircuitBreaker(model: string, success: boolean): void {
const cb = this.getCircuitBreaker(model);
const now = Date.now();
if (success) {
cb.successCount++;
if (cb.state === 'half-open') {
cb.state = 'closed';
cb.failures = 0;
cb.successCount = 0;
}
} else {
cb.failures++;
cb.lastFailure = now;
if (cb.failures >= this.CIRCUIT_THRESHOLD) {
cb.state = 'open';
// ปิด circuit ชั่วคราว 30 วินาที
setTimeout(() => {
cb.state = 'half-open';
cb.successCount = 0;
}, this.CIRCUIT_TIMEOUT);
}
}
}
private async executeWithRetry<T>(
model: string,
payload: any,
timeout: number
): Promise<T> {
const cb = this.getCircuitBreaker(model);
// ตรวจสอบ circuit breaker
if (cb.state === 'open') {
throw new Error(Circuit breaker is open for model ${model}. Try again later.);
}
let lastError: Error | null = null;
for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
try {
const result = await this.executeRequest<T>(model, payload, timeout);
this.updateCircuitBreaker(model, true);
return result;
} catch (error: any) {
lastError = error;
this.updateCircuitBreaker(model, false);
// ถ้าเป็น timeout error และยังมี retry หลงเหลือ
if (error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET') {
if (attempt < this.retryConfig.maxRetries) {
// Exponential backoff with jitter
const delay = Math.min(
this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffFactor, attempt),
this.retryConfig.maxDelay
);
const jitter = delay * 0.2 * Math.random();
await this.sleep(delay + jitter);
// เพิ่ม timeout สำหรับ retry ครั้งต่อไป
timeout = Math.min(timeout * 1.2, MODEL_TIMEOUTS[model]?.maxTimeout ?? 60000);
continue;
}
}
// 401, 403, 429 ไม่ควร retry
if (error.statusCode >= 400 && error.statusCode < 500) {
throw error;
}
}
}
throw lastError;
}
async chat(model: string, messages: any[], options?: { temperature?: number; maxTokens?: number }): Promise<any> {
const modelConfig = MODEL_TIMEOUTS[model];
if (!modelConfig) {
throw new Error(Unknown model: ${model});
}
// ประมาณการ tokens จากขนาด messages
const estimatedTokens = this.estimateTokens(messages);
const timeout = calculateTimeout(modelConfig, estimatedTokens);
const payload = {
model: modelConfig.model,
messages,
temperature: options?.temperature ?? 0.7,
max_tokens: options?.maxTokens ?? 4096
};
return this.executeWithRetry(model, payload, timeout);
}
private estimateTokens(messages: any[]): number {
// ประมาณการอย่างง่าย: 1 token ≈ 4 characters
return messages.reduce((total, msg) => {
return total + Math.ceil((msg.content?.length ?? 0) / 4);
}, 0);
}
private executeRequest<T>(model: string, payload: any, timeout: number): Promise<T> {
return new Promise((resolve, reject) => {
const data = JSON.stringify(payload);
const options = {
hostname: 'api.holysheep.ai',
port: 443,
path: '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${this.apiKey},
'Content-Length': Buffer.byteLength(data)
},
timeout: timeout
};
const req = https.request(options, (res) => {
let body = '';
res.on('data', (chunk) => body += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
resolve(JSON.parse(body));
} catch {
reject(new Error('Invalid JSON response'));
}
} else {
const error: any = new Error(API Error: ${res.statusCode});
error.statusCode = res.statusCode;
reject(error);
}
});
});
req.on('timeout', () => {
req.destroy();
const error: any = new Error('Request timeout');
error.code = 'ETIMEDOUT';
reject(error);
});
req.on('error', (error) => {
const err: any = new Error(error.message);
err.code = error.code;
reject(err);
});
req.write(data);
req.end();
});
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
getCircuitStatus(model: string): CircuitBreakerState['state'] {
return this.getCircuitBreaker(model).state;
}
}
export const aiClient = new AIClient(process.env.YOUR_HOLYSHEEP_API_KEY ?? '');
export default aiClient;
Middleware สำหรับ Express.js
// middleware/ai-timeout.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { MODEL_TIMEOUTS, calculateTimeout } from '../config/ai_timeouts';
import aiClient from '../lib/ai-client';
interface AIRequest extends Request {
aiTimeout?: number;
modelConfig?: typeof MODEL_TIMEOUTS[string];
}
export const aiTimeoutMiddleware = (req: AIRequest, res: Response, next: NextFunction) => {
const model = req.body?.model ?? 'deepseek-v3.2';
const modelConfig = MODEL_TIMEOUTS[model];
if (!modelConfig) {
return res.status(400).json({
error: 'Invalid model',
availableModels: Object.keys(MODEL_TIMEOUTS)
});
}
// คำนวณ timeout จาก prompt size
const estimatedTokens = Math.ceil((JSON.stringify(req.body.messages ?? []).length) / 4);
req.aiTimeout = calculateTimeout(modelConfig, estimatedTokens);
req.modelConfig = modelConfig;
// เพิ่ม headers สำหรับ debugging
res.setHeader('X-Timeout-Configured', ${req.aiTimeout}ms);
res.setHeader('X-Model-Complexity', modelConfig.complexity);
// ตรวจสอบ circuit breaker status
const circuitStatus = aiClient.getCircuitStatus(model);
if (circuitStatus === 'open') {
return res.status(503).json({
error: 'Service temporarily unavailable',
reason: 'Circuit breaker open',
retryAfter: '30 seconds'
});
}
next();
};
// Error handling middleware
export const aiErrorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
if (err.message.includes('timeout') || (err as any).code === 'ETIMEDOUT') {
return res.status(504).json({
error: 'Gateway Timeout',
message: 'AI request timed out',
suggestion: 'Try with a simpler prompt or smaller model',
timeout: req.aiTimeout
});
}
if ((err as any).statusCode) {
return res.status((err as any).statusCode).json({
error: 'API Error',
message: err.message
});
}
next(err);
};
การ Benchmark และ Monitor Performance
// scripts/benchmark-timeouts.ts
import aiClient from '../lib/ai-client';
import { MODEL_TIMEOUTS } from '../config/ai_timeouts';
interface BenchmarkResult {
model: string;
avgLatency: number;
p95Latency: number;
p99Latency: number;
timeoutRate: number;
errorRate: number;
totalRequests: number;
}
async function benchmarkModel(model: string, testCases: any[]): Promise<BenchmarkResult> {
const latencies: number[] = [];
let timeouts = 0;
let errors = 0;
const config = MODEL_TIMEOUTS[model];
for (const testCase of testCases) {
const start = Date.now();
try {
await aiClient.chat(model, testCase.messages, testCase.options);
latencies.push(Date.now() - start);
} catch (error: any) {
if (error.code === 'ETIMEDOUT') {
timeouts++;
latencies.push(config.maxTimeout);
} else {
errors++;
}
}
}
latencies.sort((a, b) => a - b);
const total = latencies.length;
return {
model,
avgLatency: latencies.reduce((a, b) => a + b, 0) / total,
p95Latency: latencies[Math.floor(total * 0.95)],
p99Latency: latencies[Math.floor(total * 0.99)],
timeoutRate: (timeouts / testCases.length) * 100,
errorRate: (errors / testCases.length) * 100,
totalRequests: testCases.length
};
}
// สร้าง test cases ตามความซับซ้อน
const testCases = {
simple: [
{ messages: [{ role: 'user', content: 'What is 2+2?' }] },
{ messages: [{ role: 'user', content: 'Hello' }] },
],
medium: [
{ messages: [{ role: 'user', content: 'Explain quantum computing in 100 words' }] },
{ messages: [{ role: 'user', content: 'Write a function to sort an array' }] },
],
complex: [
{ messages: [{ role: 'user', content: 'Analyze this code and suggest improvements: ' + 'x=1; '.repeat(500) }] },
{ messages: [{ role: 'user', content: 'Write a comprehensive technical document about microservices architecture' }] },
],
extreme: [
{ messages: [{ role: 'user', content: 'Review and refactor this entire codebase... ' + 'code '.repeat(2000) }] },
]
};
async function runBenchmarks() {
console.log('🚀 Starting AI Timeout Benchmark...\n');
for (const [model] of Object.entries(MODEL_TIMEOUTS)) {
const complexity = MODEL_TIMEOUTS[model].complexity;
const cases = testCases[complexity === 'low' ? 'simple' : complexity === 'medium' ? 'medium' : complexity === 'high' ? 'complex' : 'extreme'];
console.log(\n📊 Testing ${model} (${complexity} complexity)...);
const result = await benchmarkModel(model, cases);
console.log( Avg Latency: ${result.avgLatency.toFixed(2)}ms);
console.log( P95 Latency: ${result.p95Latency}ms);
console.log( P99 Latency: ${result.p99Latency}ms);
console.log( Timeout Rate: ${result.timeoutRate.toFixed(2)}%);
console.log( Error Rate: ${result.errorRate.toFixed(2)}%);
// เช็คว่า timeout rate สูงเกินไปหรือไม่
if (result.timeoutRate > 5) {
console.log( ⚠️ WARNING: High timeout rate! Consider increasing baseTimeout.);
}
}
}
runBenchmarks().catch(console.error);
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
1. Timeout เกิดขึ้นแม้ Model ตอบปกติ
// ❌ วิธีผิด: ตั้ง timeout แบบ fixed สำหรับทุก model
const fixedTimeout = 10000; // 10 วินาทีเสมอ
// ✅ วิธีถูกต้อง: ใช้ dynamic timeout ตาม model และ context size
const dynamicTimeout = calculateTimeout(
MODEL_TIMEOUTS[selectedModel],
estimatedTokens
);
// เพิ่ม buffer 20% สำหรับ network jitter
const finalTimeout = dynamicTimeout * 1.2;
สาเหตุ: Model ที่ซับซ้อนเช่น Claude Sonnet 4.5 อาจใช้เวลาถึง 8 วินาทีสำหรับ response เดียว แต่ timeout ตั้งไว้แค่ 10 วินาที ไม่เผื่อให้เพียงพอ
2. Circuit Breaker ไม่ทำงานหลังระบบกลับมา
// ❌ วิธีผิด: ไม่มี half-open state
if (cb.failures >= threshold) {
cb.state = 'open';
// ไม่มีการกู้คืน
}
// ✅ วิธีถูกต้อง: Half-open state สำหรับการทดสอบ
if (cb.failures >= this.CIRCUIT_THRESHOLD) {
cb.state = 'open';
// หลังจากผ่านไป timeout ให้เปลี่ยนเป็น half-open
setTimeout(() => {
cb.state = 'half-open';
cb.successCount = 0;
}, this.CIRCUIT_TIMEOUT);
}
// เมื่อใน half-open state และ request สำเร็จ
if (cb.state === 'half-open' && success) {
cb.successCount++;
// ต้องสำเร็จ 2 ครั้งจึงจะปิด circuit
if (cb.successCount >= 2) {
cb.state = 'closed';
cb.failures = 0;
}
}
สาเหตุ: Circuit breaker เปิดอยู่ตลอดเวลาหลังจากล้มเหลวถึง threshold โดยไม่มีกลไกการกู้คืน ทำให้ระบบไม่มีทางกลับมาใช้งานได้
3. Retry ไม่เพิ่ม Timeout ทำให้ล้มเหลวซ้ำ
// ❌ วิธีผิด: Retry ด้วย timeout เท่าเดิม
for (let i = 0; i < maxRetries; i++) {
try {
return await this.executeRequest(payload, initialTimeout);
} catch (e) {
// timeout เท่าเดิม = ล้มเหลวแน่นอนถ้าครั้งแรก timeout
await this.sleep(delay);
}
}
// ✅ วิธีถูกต้อง: เพิ่ม timeout สำหรับแต่ละ retry
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
// เพิ่ม timeout 20% ทุกครั้งที่ retry
const adjustedTimeout = initialTimeout * Math.pow(1.2, attempt);
return await this.executeRequest(payload, Math.min(adjustedTimeout, maxTimeout));
} catch (e) {
if (isRetryableError(e)) {
const delay = baseDelay * Math.pow(backoffFactor, attempt);
const jitter = delay * 0.1 * Math.random();
await this.sleep(delay + jitter);
}
}
}
สาเหตุ: เมื่อ request timeout ในครั้งแรก การ retry ด้วย timeout เท่าเดิมจะทำให้ timeout ซ้ำอีก เนื่องจากอาจเป็นเพราะ server กำลังประมวลผล model ที่ซับซ้อน
สรุป
การตั้งค่า timeout ที่เหมาะสมต้องพิจารณาหลายปัจจัย: ความซับซ้อนของ model, ขนาดของ context, network latency, และ business requirements การใช้ HolySheep AI ที่มี latency ต่ำกว่า 50ms ช่วยลดความซับซ้อนในการ tuning ได้มาก ร่วมกับ circuit breaker และ exponential backoff จะทำให้ระบบมีความ resilient ต่อการใช้งานจริง
อย่าลืม benchmark เป็นประจำและปรับ timeout values ตาม actual traffic patterns ของคุณ
👉 สมัคร HolySheep AI — รับเครดิตฟรีเมื่อลงทะเบียน