ในฐานะวิศวกรที่ทำงานกับ Next.js App Router มากว่า 2 ปี ผมเคยเจอปัญหามากมายในการบูรณาการ AI API เข้ากับแอปพลิเคชันจริง ตั้งแต่ปัญหา streaming ที่ทำให้ UI ค้าง ไปจนถึงการจัดการ API key ที่ไม่ปลอดภัย บทความนี้จะแชร์ best practices ที่ผมใช้ในโปรเจกต์ production จริง พร้อมโค้ดที่พร้อม copy-paste ไปใช้งานทันที

ทำไมต้องเลือก HolySheep AI

ก่อนจะเริ่ม ให้ผมอธิบายเหตุผลที่ผมเลือก สมัครที่นี่ HolySheep AI เป็น API provider ราคาถูกที่สุดในตลาด ความหน่วงเฉลี่ยต่ำกว่า 50ms และรองรับโมเดลหลากหลายตั้งแต่ GPT-4.1 ไปจนถึง DeepSeek V3.2 โดยมีอัตราแลกเปลี่ยน ¥1=$1 ทำให้ประหยัดได้ถึง 85% เมื่อเทียบกับผู้ให้บริการอื่น

สถาปัตยกรรมโปรเจกต์และโครงสร้างไฟล์

สำหรับ Next.js App Router ผมแนะนำให้แยก AI logic ออกมาเป็น layer ชัดเจน เพื่อให้งาน maintenance และ testing ทำได้ง่าย

app/
├── lib/
│   ├── ai/
│   │   ├── client.ts          # AI client configuration
│   │   ├── stream.ts          # Streaming utilities
│   │   └── types.ts           # TypeScript definitions
│   └── prompts/
│       └── templates.ts       # Prompt templates
├── actions/
│   └── ai-actions.ts          # Server Actions
└── components/
    └── ai/
        ├── chat.tsx           # Chat component
        └── stream.tsx         # Streaming display

การตั้งค่า AI Client อย่างถูกต้อง

สิ่งสำคัญที่สุดคือการ config client ให้รองรับ streaming อย่างเต็มรูปแบบ ผมเคยเจอปัญหาที่ response ถูกส่งมาทั้งหมดก่อนแล้วค่อย render ทำให้ user experience แย่มาก

import OpenAI from 'openai';

const holySheep = new OpenAI({
  baseURL: 'https://api.holysheep.ai/v1',
  apiKey: process.env.HOLYSHEEP_API_KEY,
  defaultHeaders: {
    'HTTP-Referer': process.env.NEXT_PUBLIC_APP_URL,
    'X-Title': 'My AI App',
  },
  maxRetries: 3,
  timeout: 120_000, // 2 minutes for streaming
});

export async function createAICompletion(
  messages: OpenAI.Chat.ChatCompletionMessageParam[],
  options: {
    model?: string;
    temperature?: number;
    maxTokens?: number;
    stream?: boolean;
  } = {}
) {
  const {
    model = 'gpt-4.1',
    temperature = 0.7,
    maxTokens = 2048,
    stream = false,
  } = options;

  const response = await holySheep.chat.completions.create({
    model,
    messages,
    temperature,
    max_tokens: maxTokens,
    stream,
    stream_options: stream ? { include_usage: true } : undefined,
  });

  return response;
}

Server Actions สำหรับ AI Streaming

การใช้ Server Actions ร่วมกับ streaming คือหัวใจสำคัญของ Next.js App Router เวอร์ชันใหม่ ผมใช้เทคนิคนี้มาหลายโปรเจกต์และประสิทธิภาพดีมาก

'use server';

import { createAICompletion } from '@/lib/ai/client';
import { OpenAIStream } from 'ai';
import { NextResponse } from 'next/server';

export async function streamAIResponse(formData: FormData) {
  const prompt = formData.get('prompt') as string;
  const model = (formData.get('model') as string) || 'deepseek-v3.2';
  
  if (!prompt || prompt.trim().length === 0) {
    return NextResponse.json(
      { error: 'Prompt is required' },
      { status: 400 }
    );
  }

  const messages = [
    { role: 'system', content: 'You are a helpful AI assistant.' },
    { role: 'user', content: prompt },
  ];

  const response = await createAICompletion(messages, {
    model,
    temperature: 0.7,
    maxTokens: 2048,
    stream: true,
  });

  // Convert to Vercel AI SDK stream format
  const stream = OpenAIStream(response as any);
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

Client Component สำหรับ Real-time Display

ส่วน client component ต้องรองรับการรับ stream แบบ real-time ผมใช้ Vercel AI SDK ที่ support การ streaming อย่างเป็นธรรมชาติ

'use client';

import { useChat } from 'ai/react';
import { useState } from 'react';

export default function AIChat() {
  const [selectedModel, setSelectedModel] = useState('deepseek-v3.2');
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
    api: '/api/ai/stream',
    body: { model: selectedModel },
  });

  return (
    <div className="flex flex-col h-[600px] max-w-2xl mx-auto p-4">
      <div className="mb-4 flex gap-2">
        <select
          value={selectedModel}
          onChange={(e) => setSelectedModel(e.target.value)}
          className="px-4 py-2 border rounded-lg"
        >
          <option value="deepseek-v3.2">DeepSeek V3.2 ($0.42/MTok)</option>
          <option value="gemini-2.5-flash">Gemini 2.5 Flash ($2.50/MTok)</option>
          <option value="gpt-4.1">GPT-4.1 ($8/MTok)</option>
          <option value="claude-sonnet-4.5">Claude Sonnet 4.5 ($15/MTok)</option>
        </select>
      </div>

      <div className="flex-1 overflow-y-auto space-y-4 mb-4">
        {messages.map((m) => (
          <div key={m.id} className={`p-4 rounded-lg ${
            m.role === 'user' ? 'bg-blue-100 ml-20' : 'bg-gray-100 mr-20'
          }`}>
            <div className="text-xs text-gray-500 mb-1">{m.role}</div>
            {m.content}
          </div>
        ))}
        {isLoading && (
          <div className="bg-gray-100 p-4 rounded-lg">
            <span className="animate-pulse">กำลังประมวลผล...</span>
          </div>
        )}
      </div>

      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="พิมพ์ข้อความของคุณ..."
          className="flex-1 px-4 py-2 border rounded-lg"
          disabled={isLoading}
        />
        <button
          type="submit"
          disabled={isLoading}
          className="px-6 py-2 bg-blue-600 text-white rounded-lg disabled:opacity-50"
        >
          {isLoading ? 'กำลังส่ง...' : 'ส่ง'}
        </button>
      </form>
    </div>
  );
}

การจัดการ Error และ Retry Logic

ใน production environment การจัดการ error ที่ดีจะช่วยลด downtime ได้มาก ผมแนะนำให้ใช้ exponential backoff สำหรับ retry logic

async function withRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  } = {}
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;
  
  let lastError: Error | undefined;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;
      
      if (attempt === maxRetries) break;
      
      // Don't retry on client errors (4xx)
      if (error instanceof APIError && error.status < 500) {
        throw error;
      }
      
      // Exponential backoff with jitter
      const delay = Math.min(
        baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
        maxDelay
      );
      
      console.warn(Attempt ${attempt + 1} failed, retrying in ${delay}ms);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

// Usage example
const result = await withRetry(
  () => createAICompletion(messages, { stream: false }),
  { maxRetries: 3 }
);

ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข

1. Error 401 Unauthorized - API Key ไม่ถูกต้อง

สาเหตุ: API key หมดอายุ หรือไม่ได้ตั้งค่าใน environment variable อย่างถูกต้อง

// ❌ วิธีผิด - hardcode key ในโค้ด
const holySheep = new OpenAI({
  apiKey: 'sk-xxxxxxx', // ไม่ควรทำแบบนี้
});

// ✅ วิธีถูก - ใช้ environment variable
const holySheep = new OpenAI({
  baseURL: 'https://api.holysheep.ai/v1',
  apiKey: process.env.HOLYSHEEP_API_KEY,
});

// และสร้างไฟล์ .env.local
// HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY

2. Streaming หยุดกลางคันไม่ทำงาน

สาเหตุ: ไม่ได้ตั้งค่า headers อย่างถูกต้อง หรือไม่รองรับ CORS

// ✅ วิธีแก้ไข - ตั้งค่า headers ที่ถูกต้อง
export async function GET(req: Request) {
  const response = await createAICompletion(messages, { stream: true });
  const stream = OpenAIStream(response as any);
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
      'X-Accel-Buffering': 'no', // สำหรับ Nginx proxy
    },
  });
}

3. Rate Limit Error 429

สาเหตุ: เรียก API บ่อยเกินไปเกิน rate limit ที่กำหนด

// ✅ วิธีแก้ไข - ใช้ rate limiter
import { RateLimiter } from 'rate-limiter-flexible';

const rateLimiter = new RateLimiterMemory({
  points: 60, // จำนวน requests
  duration: 60, // ต่อ 60 วินาที
});

export async function withRateLimit(fn: () => Promise<Response>) {
  try {
    await rateLimiter.consume(req.ip || 'anonymous');
    return await fn();
  } catch (error) {
    return NextResponse.json(
      { error: 'Rate limit exceeded. กรุณารอสักครู่' },
      { status: 429 }
    );
  }
}

4. Context Window Exceeded

สาเหตุ: ส่ง messages รวมกันเกินขนาด context window ของโมเดล

// ✅ วิธีแก้ไข - truncate messages ให้พอดี
function truncateMessages(
  messages: OpenAI.Chat.ChatCompletionMessage[],
  maxTokens: number = 3000
): OpenAI.Chat.ChatCompletionMessage[] {
  const tokenizer = new TokenCounter();
  let totalTokens = 0;
  
  // ประมวลผลจากข้อความล่าสุดก่อน
  const truncated: OpenAI.Chat.ChatCompletionMessage[] = [];
  
  for (let i = messages.length - 1; i >= 0; i--) {
    const msg = messages[i];
    const tokens = tokenizer.count(msg.content);
    
    if (totalTokens + tokens <= maxTokens) {
      truncated.unshift(msg);
      totalTokens += tokens;
    } else {
      break; // ถ้าเกินให้หยุดเพิ่ม
    }
  }
  
  return truncated