ในโลกของ Large Language Model (LLM) API นั้น การเข้าใจโมเดลการกำหนดราคาและการจัดการต้นทุนเป็นทักษะที่จำเป็นอย่างยิ่งสำหรับวิศวกรที่ต้องการสร้างระบบ production ที่มีประสิทธิภาพ บทความนี้จะพาคุณไปสำรวจสถาปัตยกรรมการ pricing ของ LLM API หลักๆ, เทคนิคการ optimize cost, และโค้ด production-ready ที่พร้อมใช้งานจริง โดยเนื้อหาทั้งหมดมาจากประสบการณ์ตรงในการ deploy ระบบที่รองรับ request หลายหมื่นต่อวัน

ทำความเข้าใจ Token-Based Pricing Model

LLM API ทุกตัวใช้โมเดลการคิดเงินแบบ token โดยพื้นฐาน token คือหน่วยย่อยที่สุดของข้อความ โดยทั่วไป 1 token เทียบเท่ากับประมาณ 4 ตัวอักษรในภาษาอังกฤษ หรือประมาณ 1-2 คำ ในภาษาไทยอาจแตกต่างกันมากขึ้นอยู่กับความซับซ้อนของข้อความ

ตารางเปรียบเทียบราคา API ปี 2026

จะเห็นได้ว่าราคาของแต่ละโมเดลแตกต่างกันมากถึง 35 เท่า เมื่อเปรียบเทียบระหว่าง DeepSeek V3.2 กับ Claude Sonnet 4.5 การเลือกโมเดลที่เหมาะสมกับ use case จึงเป็นสิ่งสำคัญอย่างยิ่งในการควบคุมต้นทุน

สถาปัตยกรรม Smart Routing สำหรับ Cost Optimization

ในการพัฒนาระบบ production ที่รองรับ workload สูง การใช้โมเดลเดียวตลอดเวลาไม่ใช่ทางเลือกที่ดีที่สุด สถาปัตยกรรม Smart Router ช่วยให้คุณสามารถกำหนดเส้นทาง request ไปยังโมเดลที่เหมาะสมที่สุดตามความซับซ้อนของงาน ลดต้นทุนโดยรวมได้อย่างมีนัยสำคัญ

"""
Smart LLM Router - Production Grade
Cost-aware request routing with automatic model selection
"""

import asyncio
import tiktoken
from dataclasses import dataclass, field
from typing import Optional, Dict, List
from enum import Enum
import time
from collections import defaultdict

class ModelTier(Enum):
    CHEAP = "cheap"       # DeepSeek V3.2
    BALANCED = "balanced" # Gemini 2.5 Flash
    PREMIUM = "premium"   # GPT-4.1, Claude Sonnet 4.5

@dataclass
class ModelConfig:
    name: str
    provider: str
    tier: ModelTier
    cost_per_mtok_input: float
    cost_per_mtok_output: float
    avg_latency_ms: float
    max_tokens: int
    supports_streaming: bool = True

@dataclass
class RequestContext:
    task_type: str
    complexity_score: float  # 0.0 - 1.0
    input_tokens: int
    preferred_tier: Optional[ModelTier] = None
    priority: int = 1  # 1=low, 5=high

@dataclass
class RoutingDecision:
    model: ModelConfig
    estimated_cost: float
    estimated_latency_ms: float
    reasoning: str

class SmartLLMRouter:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        
        # Model registry with pricing (2026 rates)
        self.models: Dict[str, ModelConfig] = {
            "deepseek-v3.2": ModelConfig(
                name="deepseek-v3.2",
                provider="holysheep",
                tier=ModelTier.CHEAP,
                cost_per_mtok_input=0.42,
                cost_per_mtok_output=1.68,
                avg_latency_ms=1200,
                max_tokens=64000
            ),
            "gemini-2.5-flash": ModelConfig(
                name="gemini-2.5-flash",
                provider="holysheep",
                tier=ModelTier.BALANCED,
                cost_per_mtok_input=2.50,
                cost_per_mtok_output=10.00,
                avg_latency_ms=800,
                max_tokens=128000
            ),
            "gpt-4.1": ModelConfig(
                name="gpt-4.1",
                provider="holysheep",
                tier=ModelTier.PREMIUM,
                cost_per_mtok_input=8.00,
                cost_per_mtok_output=24.00,
                avg_latency_ms=2500,
                max_tokens=128000
            ),
            "claude-sonnet-4.5": ModelConfig(
                name="claude-sonnet-4.5",
                provider="holysheep",
                tier=ModelTier.PREMIUM,
                cost_per_mtok_input=15.00,
                cost_per_mtok_output=75.00,
                avg_latency_ms=3000,
                max_tokens=200000
            ),
        }
        
        self.usage_stats = defaultdict(lambda: {"requests": 0, "cost": 0.0})
        self._tokenizer = tiktoken.get_encoding("cl100k_base")
    
    def estimate_cost(
        self,
        model: ModelConfig,
        input_tokens: int,
        output_tokens: int = 500
    ) -> float:
        """Calculate estimated cost for a request"""
        input_cost = (input_tokens / 1_000_000) * model.cost_per_mtok_input
        output_cost = (output_tokens / 1_000_000) * model.cost_per_mtok_output
        return input_cost + output_cost
    
    def calculate_complexity(self, text: str) -> float:
        """Estimate task complexity based on input characteristics"""
        tokens = self._tokenizer.encode(text)
        
        # Factors affecting complexity
        length_factor = min(len(tokens) / 2000, 1.0)  # Max at 2000 tokens
        
        # Code detection (higher complexity)
        code_indicators = ['```', 'def ', 'class ', 'function', 'const ', 'import ']
        code_factor = 0.3 if any(ind in text for ind in code_indicators) else 0.0
        
        # Multi-turn conversation detection
        turn_indicators = text.count('\n\n') + text.count('---')
        turn_factor = min(turn_indicators * 0.1, 0.3)
        
        # Keyword-based task type detection
        complex_keywords = ['analyze', 'compare', 'design', 'architect', 'evaluate']
        simple_keywords = ['translate', 'summarize', 'list', 'define', 'find']
        
        keyword_score = 0.0
        if any(kw in text.lower() for kw in complex_keywords):
            keyword_score = 0.3
        if any(kw in text.lower() for kw in simple_keywords):
            keyword_score = -0.2
        
        complexity = min(1.0, max(0.0, 
            length_factor * 0.3 + 
            code_factor + 
            turn_factor + 
            keyword_score + 
            0.1
        ))
        
        return complexity
    
    def route_request(self, context: RequestContext) -> RoutingDecision:
        """Determine optimal model for given request context"""
        
        # Override with preferred tier if specified
        target_tier = context.preferred_tier or self._determine_tier(context)
        
        # Filter models by tier and token limit
        candidates = [
            m for m in self.models.values()
            if m.tier == target_tier and m.max_tokens >= context.input_tokens
        ]
        
        if not candidates:
            # Fallback to more expensive tier
            tier_order = [ModelTier.CHEAP, ModelTier.BALANCED, ModelTier.PREMIUM]
            current_idx = tier_order.index(target_tier)
            for next_tier in tier_order[current_idx + 1:]:
                candidates = [
                    m for m in self.models.values()
                    if m.tier == next_tier and m.max_tokens >= context.input_tokens
                ]
                if candidates:
                    break
        
        # Score candidates by cost-efficiency
        best_model = min(candidates, key=lambda m: self.estimate_cost(
            m, context.input_tokens
        ))
        
        estimated_cost = self.estimate_cost(best_model, context.input_tokens)
        estimated_latency = best_model.avg_latency_ms
        
        return RoutingDecision(
            model=best_model,
            estimated_cost=estimated_cost,
            estimated_latency_ms=estimated_latency,
            reasoning=f"Selected {best_model.name} for {target_tier.value} task"
        )
    
    def _determine_tier(self, context: RequestContext) -> ModelTier:
        """Determine appropriate model tier based on task complexity"""
        complexity = context.complexity_score
        
        if complexity < 0.25:
            return ModelTier.CHEAP
        elif complexity < 0.6:
            return ModelTier.BALANCED
        else:
            return ModelTier.PREMIUM
    
    def record_usage(self, model_name: str, tokens: int, cost: float):
        """Track usage for cost analysis"""
        self.usage_stats[model_name]["requests"] += 1
        self.usage_stats[model_name]["cost"] += cost
    
    def get_cost_report(self) -> Dict:
        """Generate cost analysis report"""
        total_cost = sum(s["cost"] for s in self.usage_stats.values())
        total_requests = sum(s["requests"] for s in self.usage_stats.values())
        
        return {
            "total_cost_usd": round(total_cost, 4),
            "total_requests": total_requests,
            "avg_cost_per_request": round(total_cost / total_requests, 4) if total_requests else 0,
            "by_model": {
                model: {
                    "requests": stats["requests"],
                    "cost": round(stats["cost"], 4),
                    "avg_cost": round(stats["cost"] / stats["requests"], 4) if stats["requests"] else 0
                }
                for model, stats in self.usage_stats.items()
            }
        }

Usage Example

router = SmartLLMRouter(api_key="YOUR_HOLYSHEEP_API_KEY") context = RequestContext( task_type="code_generation", complexity_score=router.calculate_complexity( "Write a Python function to calculate Fibonacci numbers with memoization" ), input_tokens=25 ) decision = router.route_request(context) print(f"Route to: {decision.model.name}") print(f"Estimated cost: ${decision.estimated_cost:.4f}") print(f"Estimated latency: {decision.estimated_latency_ms}ms")

Concurrent Request Management และ Rate Limiting

การจัดการ request พร้อมกันหลายตัวเป็นส่วนสำคัญของ production system การใช้งาน LLM API อย่างมีประสิทธิภาพต้องอาศัย concurrency control ที่ดีเพื่อหลีกเลี่ยง rate limit errors และ maximize throughput

"""
Production LLM Client with Concurrency Control
Semaphore-based rate limiting and batch processing
"""

import asyncio
import aiohttp
import json
import time
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from collections import deque
import logging

logger = logging.getLogger(__name__)

@dataclass
class RateLimitConfig:
    requests_per_minute: int = 60
    tokens_per_minute: int = 100_000
    concurrent_requests: int = 5
    backoff_base_seconds: float = 1.0
    max_retries: int = 3

@dataclass
class TokenBucket:
    """Token bucket algorithm for rate limiting"""
    capacity: float
    refill_rate: float
    tokens: float
    last_refill: float
    
    def __init__(self, capacity: float, refill_rate: float):
        self.capacity = capacity
        self.refill_rate = refill_rate
        self.tokens = capacity
        self.last_refill = time.time()
    
    def consume(self, tokens: float) -> bool:
        self._refill()
        if self.tokens >= tokens:
            self.tokens -= tokens
            return True
        return False
    
    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now
    
    def wait_time(self, tokens: float) -> float:
        self._refill()
        if self.tokens >= tokens:
            return 0.0
        return (tokens - self.tokens) / self.refill_rate

class ProductionLLMClient:
    def __init__(
        self,
        api_key: str,
        rate_limit: Optional[RateLimitConfig] = None
    ):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.rate_limit = rate_limit or RateLimitConfig()
        
        # Semaphore for concurrent request control
        self._semaphore = asyncio.Semaphore(self.rate_limit.concurrent_requests)
        
        # Token buckets for different rate limits
        self._request_bucket = TokenBucket(
            capacity=self.rate_limit.requests_per_minute,
            refill_rate=self.rate_limit.requests_per_minute / 60.0
        )
        
        # Track tokens per minute
        self._token_bucket = TokenBucket(
            capacity=self.rate_limit.tokens_per_minute,
            refill_rate=self.rate_limit.tokens_per_minute / 60.0
        )
        
        self._session: Optional[aiohttp.ClientSession] = None
        self._stats = {"success": 0, "failed": 0, "retried": 0}
    
    async def _get_session(self) -> aiohttp.ClientSession:
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession(
                headers={
                    "Authorization": f"Bearer {self.api_key}",
                    "Content-Type": "application/json"
                }
            )
        return self._session
    
    async def _wait_for_rate_limit(self, estimated_tokens: int):
        """Block until rate limits allow the request"""
        # Check request rate limit
        request_wait = self._request_bucket.wait_time(1)
        # Check token rate limit
        token_wait = self._token_bucket.wait_time(estimated_tokens)
        
        wait_time = max(request_wait, token_wait)
        if wait_time > 0:
            logger.debug(f"Rate limit wait: {wait_time:.2f}s")
            await asyncio.sleep(wait_time)
    
    async def _execute_request(
        self,
        model: str,
        messages: List[Dict],
        temperature: float = 0.7,
        max_tokens: int = 2000,
        retry_count: int = 0
    ) -> Dict[str, Any]:
        """Execute a single API request with retry logic"""
        
        payload = {
            "model": model,
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens
        }
        
        async with self._semaphore:
            session = await self._get_session()
            
            try:
                async with session.post(
                    f"{self.base_url}/chat/completions",
                    json=payload,
                    timeout=aiohttp.ClientTimeout(total=60)
                ) as response:
                    
                    if response.status == 200:
                        data = await response.json()
                        usage = data.get("usage", {})
                        self._stats["success"] += 1
                        
                        # Update token bucket
                        total_tokens = usage.get("total_tokens", 0)
                        self._request_bucket.consume(1)
                        self._token_bucket.consume(total_tokens)
                        
                        return {
                            "success": True,
                            "data": data,
                            "tokens_used": total_tokens,
                            "latency_ms": response.headers.get("x-response-time", 0)
                        }
                    
                    elif response.status == 429:
                        # Rate limited
                        retry_after = int(response.headers.get("Retry-After", 60))
                        logger.warning(f"Rate limited, waiting {retry_after}s")
                        await asyncio.sleep(retry_after)
                        
                        if retry_count < self.rate_limit.max_retries:
                            return await self._execute_request(
                                model, messages, temperature, max_tokens, retry_count + 1
                            )
                    
                    elif response.status == 500:
                        # Server error, retry with backoff
                        if retry_count < self.rate_limit.max_retries:
                            backoff = self.rate_limit.backoff_base_seconds * (2 ** retry_count)
                            logger.info(f"Server error, retrying in {backoff}s")
                            self._stats["retried"] += 1
                            await asyncio.sleep(backoff)
                            return await self._execute_request(
                                model, messages, temperature, max_tokens, retry_count + 1
                            )
                    
                    error_data = await response.text()
                    logger.error(f"API error {response.status}: {error_data}")
                    return {
                        "success": False,
                        "error": f"HTTP {response.status}",
                        "details": error_data
                    }
                    
            except asyncio.TimeoutError:
                logger.error("Request timeout")
                self._stats["failed"] += 1
                return {"success": False, "error": "Timeout"}
            
            except Exception as e:
                logger.error(f"Request failed: {e}")
                self._stats["failed"] += 1
                return {"success": False, "error": str(e)}
    
    async def chat_completion(
        self,
        model: str,
        messages: List[Dict],
        temperature: float = 0.7,
        max_tokens: int = 2000
    ) -> Dict[str, Any]:
        """Main interface for chat completions"""
        # Rough estimate of tokens (actual count comes from API)
        estimated_tokens = sum(len(str(m)) // 4 for m in messages) + max_tokens
        
        await self._wait_for_rate_limit(estimated_tokens)
        return await self._execute_request(model, messages, temperature, max_tokens)
    
    async def batch_completion(
        self,
        requests: List[Dict[str, Any]],
        model: str = "deepseek-v3.2"
    ) -> List[Dict[str, Any]]:
        """Process multiple requests concurrently with batching"""
        
        async def process_single(req: Dict) -> Dict[str, Any]:
            result = await self.chat_completion(
                model=model,
                messages=req["messages"],
                temperature=req.get("temperature", 0.7),
                max_tokens=req.get("max_tokens", 2000)
            )
            return {"request_id": req.get("id"), "result": result}
        
        # Process in batches to control memory usage
        batch_size = self.rate_limit.concurrent_requests
        results = []
        
        for i in range(0, len(requests), batch_size):
            batch = requests[i:i + batch_size]
            batch_results = await asyncio.gather(
                *[process_single(req) for req in batch],
                return_exceptions=True
            )
            results.extend(batch_results)
            
            # Small delay between batches
            if i + batch_size < len(requests):
                await asyncio.sleep(0.1)
        
        return results
    
    def get_stats(self) -> Dict[str, Any]:
        """Return usage statistics"""
        total = self._stats["success"] + self._stats["failed"]
        return {
            **self._stats,
            "success_rate": self._stats["success"] / total if total else 0,
            "retry_rate": self._stats["retried"] / total if total else 0
        }
    
    async def close(self):
        """Cleanup resources"""
        if self._session and not self._session.closed:
            await self._session.close()

Production usage example

async def main(): client = ProductionLLMClient( api_key="YOUR_HOLYSHEEP_API_KEY", rate_limit=RateLimitConfig( requests_per_minute=120, tokens_per_minute=200_000, concurrent_requests=10 ) ) # Single request result = await client.chat_completion( model="deepseek-v3.2", messages=[{"role": "user", "content": "Explain quantum computing"}] ) # Batch processing batch_requests = [ {"id": f"req_{i}", "messages": [{"role": "user", "content": f"Task {i}"}]} for i in range(50) ] results = await client.batch_completion(batch_requests) print(f"Stats: {client.get_stats()}") await client.close() if __name__ == "__main__": asyncio.run(main())

Advanced Caching Strategy สำหรับลดต้นทุน

การ caching เป็นเทคนิคที่มีประสิทธิภาพมากในการลดต้นทุน API โดยเฉพาะสำหรับ request ที่ซ้ำกัน การใช้ semantic cache ที่เข้าใจความหมายของ query สามารถลดค่าใช้จ่ายได้ถึง 40-60% ในหลายๆ use case

"""
Semantic Cache with Embedding-based Similarity
Reduces API costs by caching semantically similar queries
"""

import hashlib
import json
import time
import asyncio
from typing import Optional, Dict, List, Tuple
from dataclasses import dataclass, field
from collections import OrderedDict
import numpy as np

@dataclass
class CacheEntry:
    request_hash: str
    embedding: np.ndarray
    response: Dict
    model: str
    created_at: float
    last_accessed: float
    access_count: int = 1
    ttl_seconds: int = 3600

class SemanticCache:
    """
    LLM response cache with semantic similarity matching.
    Uses cosine similarity to match queries with cached responses.
    """
    
    def __init__(
        self,
        similarity_threshold: float = 0.92,
        max_entries: int = 10000,
        default_ttl: int = 3600,
        embedding_dim: int = 1536
    ):
        self.similarity_threshold = similarity_threshold
        self.max_entries = max_entries
        self.default_ttl = default_ttl
        self.embedding_dim = embedding_dim
        
        # In production, use Redis for distributed caching
        self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
        self._embedding_index: Dict[str, np.ndarray] = {}
        
        # Stats
        self.stats = {
            "hits": 0,
            "misses": 0,
            "evictions": 0,
            "savings_tokens": 0,
            "savings_cost": 0.0
        }
        
        # Cost per token (using DeepSeek V3.2 as reference)
        self.cost_per_1k_input = 0.00042
        self.cost_per_1k_output = 0.00168
    
    def _compute_hash(self, content: str, model: str) -> str:
        """Generate deterministic hash for request"""
        data = f"{model}:{content}"
        return hashlib.sha256(data.encode()).hexdigest()[:32]
    
    async def _get_embedding(self, text: str) -> np.ndarray:
        """Get embedding vector for text (simplified - use actual embedding API)"""
        # In production, call embedding API
        # For demo, use a deterministic hash-based pseudo-embedding
        text_hash = hashlib.sha256(text.encode()).digest()
        embedding = np.frombuffer(text_hash * (self.embedding_dim // 32 + 1), 
                                   dtype=np.float32)[:self.embedding_dim]
        return embedding / np.linalg.norm(embedding)
    
    def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
        """Calculate cosine similarity between two vectors"""
        return float(np.dot(a, b))
    
    def _find_similar_entry(
        self,
        embedding: np.ndarray,
        model: str
    ) -> Optional[Tuple[str, float]]:
        """Find most similar cached entry above threshold"""
        best_match = None
        best_similarity = 0.0
        
        for hash_key, cached_emb in self._embedding_index.items():
            if hash_key not in self._cache:
                continue
            
            entry = self._cache[hash_key]
            if entry.model != model:
                continue
            
            # Check TTL
            if time.time() - entry.created_at > entry.ttl_seconds:
                continue
            
            similarity = self._cosine_similarity(embedding, cached_emb)
            
            if similarity > best_similarity and similarity >= self.similarity_threshold:
                best_match = hash_key
                best_similarity = similarity
        
        return (best_match, best_similarity) if best_match else None
    
    async def get_or_compute(
        self,
        request_content: str,
        model: str,
        compute_fn,
        ttl: Optional[int] = None
    ) -> Dict:
        """
        Get cached response or compute new one.
        Returns cached response if similarity matches.
        """
        
        # Generate request hash
        request_hash = self._compute_hash(request_content, model)
        
        # Check exact match first
        if request_hash in self._cache:
            entry = self._cache[request_hash]
            if time.time() - entry.created_at <= entry.ttl_seconds:
                entry.last_accessed = time.time()
                entry.access_count += 1
                self.stats["hits"] += 1
                
                # Move to end (most recently used)
                self._cache.move_to_end(request_hash)
                
                # Calculate savings
                response_tokens = entry.response.get("usage", {}).get("total_tokens", 0)
                savings = (response_tokens / 1000) * self.cost_per_1k_output
                self.stats["savings_tokens"] += response_tokens
                self.stats["savings_cost"] += savings
                
                return {
                    "cached": True,
                    "response": entry.response,
                    "similarity": 1.0,
                    "savings_usd": savings
                }
        
        # Check semantic similarity
        embedding = await self._get_embedding(request_content)
        similar = self._find_similar_entry(embedding, model)
        
        if similar:
            cache_key, similarity = similar
            entry = self._cache[cache_key]
            entry.last_accessed = time.time()
            entry.access_count += 1
            self.stats["hits"] += 1
            
            # Calculate savings
            response_tokens = entry.response.get("usage", {}).get("total_tokens", 0)
            savings = (response_tokens / 1000) * self.cost_per_1k_output
            self.stats["s