การสร้าง AI API Gateway Middleware ที่ดีไม่ใช่แค่การส่งต่อ request ไปยัง LLM provider แต่ต้องรวมระบบความปลอดภัย การจัดการโควต้า และการบันทึกล็อกอย่างมีประสิทธิภาพ ในบทความนี้เราจะออกแบบ middleware ที่รองรับทุกความต้องการในตัวเดียว พร้อมตัวอย่างโค้ดที่นำไปใช้งานได้จริง โดยใช้ HolySheep AI เป็น API provider หลักที่มีราคาประหยัดกว่า 85% เมื่อเทียบกับ OpenAI โดยตรง
ทำไมต้องมี AI API Gateway Middleware?
ในยุคที่ AI API กลายเป็นหัวใจหลักของแอปพลิเคชัน การจัดการ request ที่เข้ามาต้องมีระบบที่ครบถ้วน
- ความปลอดภัย: ป้องกัน API key รั่วไหล และจำกัดสิทธิ์การเข้าถึง
- การจัดการโควต้า: ป้องกันการใช้งานเกินขีดจำกัด และจัดลำดับความสำคัญของ request
- การบันทึกล็อก: ติดตามการใช้งาน วิเคราะห์ปัญหา และ optimize ต้นทุน
- การแคช: ลดการเรียก API ซ้ำๆ ประหยัดค่าใช้จ่าย
กรณีศึกษาที่ 1: ระบบ AI ลูกค้าสัมพันธ์อีคอมเมิร์ซ
ร้านค้าออนไลน์ที่มีลูกค้าหลายหมื่นรายต้องการระบบตอบคำถามอัตโนมัติ รองรับ peak season ที่ traffic พุ่งสูงผิดปกติ 5-10 เท่า Middleware นี้ต้องรองรับ:
- Rate limiting ต่อ user session ไม่ให้เกิน 60 request/นาที
- Priority queue สำหรับลูกค้า VIP
- Response caching สำหรับคำถามที่ถามบ่อย
กรณีศึกษาที่ 2: ระบบ RAG องค์กรขนาดใหญ่
บริษัทที่ต้องการเปิดตัวระบบ RAG (Retrieval-Augmented Generation) เพื่อค้นหาข้อมูลภายในองค์กร รองรับพนักงาน 1,000+ คน Middleware ต้องจัดการ:
- Multi-tenant isolation ป้องกันข้อมูลรั่วไหลระหว่างแผนก
- Token budget ต่อแผนก
- Audit log สำหรับ compliance
กรณีศึกษาที่ 3: แพลตฟอร์มนักพัฒนาอิสระ
นักพัฒนาที่สร้าง SaaS บน AI API ต้องการระบบที่รองรับ:
- API key สำหรับลูกค้าของตัวเอง
- Usage-based billing
- Webhook notification
การออกแบบ Middleware Architecture
เราจะสร้าง middleware แบบ modular ที่ประกอบด้วย 3 ส่วนหลัก:
- Authentication Layer: ตรวจสอบ API key และจัดการสิทธิ์
- Rate Limiter: จำกัดจำนวน request ตาม plan
- Logging Service: บันทึก request/response อย่างครบถ้วน
โครงสร้างโปรเจกต์
ai-gateway-middleware/
├── src/
│ ├── index.ts
│ ├── middleware/
│ │ ├── auth.ts
│ │ ├── rateLimiter.ts
│ │ └── logger.ts
│ ├── services/
│ │ ├── holySheepClient.ts
│ │ └── cache.ts
│ └── types/
│ └── index.ts
├── package.json
└── tsconfig.json
1. สร้าง Type Definitions
// src/types/index.ts
export interface AIRequest {
model: string;
messages: Array<{
role: 'system' | 'user' | 'assistant';
content: string;
}>;
temperature?: number;
max_tokens?: number;
}
export interface AIResponse {
id: string;
model: string;
choices: Array<{
message: {
role: string;
content: string;
};
finish_reason: string;
}>;
usage: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}
export interface RateLimitConfig {
maxRequests: number;
windowMs: number;
}
export interface UserQuota {
userId: string;
dailyLimit: number;
usedToday: number;
resetAt: Date;
}
export interface LogEntry {
timestamp: Date;
userId: string;
request: AIRequest;
response?: AIResponse;
duration: number;
status: 'success' | 'error' | 'rate_limited';
errorMessage?: string;
}
export interface MiddlewareContext {
apiKey: string;
userId: string;
plan: 'free' | 'pro' | 'enterprise';
rateLimit: RateLimitConfig;
}
2. Authentication Middleware
// src/middleware/auth.ts
import { MiddlewareContext } from '../types';
const API_KEY_PREFIX = 'hs_';
const VALID_API_KEYS = new Map([
['hs_prod_user123', { userId: 'user_123', plan: 'pro', dailyLimit: 100000 }],
['hs_prod_user456', { userId: 'user_456', plan: 'enterprise', dailyLimit: 1000000 }],
['hs_test_key', { userId: 'test_user', plan: 'free', dailyLimit: 10000 }],
]);
export function authenticate(
apiKey: string | undefined
): { success: boolean; context?: MiddlewareContext; error?: string } {
if (!apiKey) {
return { success: false, error: 'API key is required' };
}
if (!apiKey.startsWith(API_KEY_PREFIX)) {
return { success: false, error: 'Invalid API key format' };
}
const keyData = VALID_API_KEYS.get(apiKey);
if (!keyData) {
return { success: false, error: 'Invalid API key' };
}
const rateLimitMap: Record = {
free: { maxRequests: 10, windowMs: 60000 },
pro: { maxRequests: 60, windowMs: 60000 },
enterprise: { maxRequests: 300, windowMs: 60000 },
};
return {
success: true,
context: {
apiKey,
userId: keyData.userId,
plan: keyData.plan as 'free' | 'pro' | 'enterprise',
rateLimit: rateLimitMap[keyData.plan],
},
};
}
3. Rate Limiter Middleware
// src/middleware/rateLimiter.ts
import { MiddlewareContext, RateLimitConfig } from '../types';
interface RateLimitEntry {
count: number;
resetAt: number;
}
const rateLimitStore = new Map();
export function checkRateLimit(
userId: string,
config: RateLimitConfig
): { allowed: boolean; remaining: number; resetIn: number } {
const now = Date.now();
const key = rate_${userId};
const entry = rateLimitStore.get(key);
if (!entry || now > entry.resetAt) {
rateLimitStore.set(key, {
count: 1,
resetAt: now + config.windowMs,
});
return {
allowed: true,
remaining: config.maxRequests - 1,
resetIn: config.windowMs,
};
}
if (entry.count >= config.maxRequests) {
return {
allowed: false,
remaining: 0,
resetIn: entry.resetAt - now,
};
}
entry.count++;
return {
allowed: true,
remaining: config.maxRequests - entry.count,
resetIn: entry.resetAt - now,
};
}
export function getRateLimitHeaders(
context: MiddlewareContext,
checkResult: { allowed: boolean; remaining: number; resetIn: number }
): Record {
return {
'X-RateLimit-Limit': context.rateLimit.maxRequests.toString(),
'X-RateLimit-Remaining': checkResult.remaining.toString(),
'X-RateLimit-Reset': Math.ceil(checkResult.resetIn / 1000).toString(),
'X-RateLimit-Policy': context.plan,
};
}
4. Logger Middleware
// src/middleware/logger.ts
import { AIRequest, AIResponse, LogEntry } from '../types';
interface LogStore {
logs: LogEntry[];
stats: {
totalRequests: number;
successCount: number;
errorCount: number;
totalTokens: number;
avgResponseTime: number;
};
}
const logStore: LogStore = {
logs: [],
stats: {
totalRequests: 0,
successCount: 0,
errorCount: 0,
totalTokens: 0,
avgResponseTime: 0,
},
};
export function logRequest(
userId: string,
request: AIRequest
): string {
const logId = log_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
const entry: LogEntry = {
timestamp: new Date(),
userId,
request,
duration: 0,
status: 'success',
};
logStore.logs.push(entry);
logStore.stats.totalRequests++;
return logId;
}
export function logResponse(
logId: string,
response: AIResponse,
duration: number
): void {
const entry = logStore.logs.find((l) => {
const testLogId = log_${Date.now()}_${Math.random().toString(36).substr(2, 9)};
return l.timestamp.getTime() > Date.now() - 60000;
});
if (entry) {
entry.response = response;
entry.duration = duration;
entry.status = 'success';
const totalTokens = response.usage?.total_tokens || 0;
logStore.stats.totalTokens += totalTokens;
logStore.stats.successCount++;
const prevAvg = logStore.stats.avgResponseTime;
const count = logStore.stats.successCount;
logStore.stats.avgResponseTime = prevAvg + (duration - prevAvg) / count;
}
}
export function logError(
logId: string,
error: string,
duration: number
): void {
const recentLogs = logStore.logs.filter(
(l) => l.timestamp.getTime() > Date.now() - 60000
);
if (recentLogs.length > 0) {
const entry = recentLogs[recentLogs.length - 1];
entry.status = 'error';
entry.errorMessage = error;
entry.duration = duration;
}
logStore.stats.errorCount++;
}
export function getStats() {
return {
...logStore.stats,
storageUsed: logStore.logs.length,
};
}
export function getUserLogs(userId: string, limit = 100) {
return logStore.logs
.filter((l) => l.userId === userId)
.slice(-limit)
.reverse();
}