การสร้าง AI API Gateway Middleware ที่ดีไม่ใช่แค่การส่งต่อ request ไปยัง LLM provider แต่ต้องรวมระบบความปลอดภัย การจัดการโควต้า และการบันทึกล็อกอย่างมีประสิทธิภาพ ในบทความนี้เราจะออกแบบ middleware ที่รองรับทุกความต้องการในตัวเดียว พร้อมตัวอย่างโค้ดที่นำไปใช้งานได้จริง โดยใช้ HolySheep AI เป็น API provider หลักที่มีราคาประหยัดกว่า 85% เมื่อเทียบกับ OpenAI โดยตรง

ทำไมต้องมี AI API Gateway Middleware?

ในยุคที่ AI API กลายเป็นหัวใจหลักของแอปพลิเคชัน การจัดการ request ที่เข้ามาต้องมีระบบที่ครบถ้วน

กรณีศึกษาที่ 1: ระบบ AI ลูกค้าสัมพันธ์อีคอมเมิร์ซ

ร้านค้าออนไลน์ที่มีลูกค้าหลายหมื่นรายต้องการระบบตอบคำถามอัตโนมัติ รองรับ peak season ที่ traffic พุ่งสูงผิดปกติ 5-10 เท่า Middleware นี้ต้องรองรับ:

กรณีศึกษาที่ 2: ระบบ RAG องค์กรขนาดใหญ่

บริษัทที่ต้องการเปิดตัวระบบ RAG (Retrieval-Augmented Generation) เพื่อค้นหาข้อมูลภายในองค์กร รองรับพนักงาน 1,000+ คน Middleware ต้องจัดการ:

  • Multi-tenant isolation ป้องกันข้อมูลรั่วไหลระหว่างแผนก
  • Token budget ต่อแผนก
  • Audit log สำหรับ compliance

กรณีศึกษาที่ 3: แพลตฟอร์มนักพัฒนาอิสระ

นักพัฒนาที่สร้าง SaaS บน AI API ต้องการระบบที่รองรับ:

การออกแบบ Middleware Architecture

เราจะสร้าง middleware แบบ modular ที่ประกอบด้วย 3 ส่วนหลัก:

โครงสร้างโปรเจกต์

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();
}

5. Holy