ในการพัฒนาแอปพลิเคชัน AI ที่ต้องคำนึงถึงต้นทุนการใช้งาน การแสดงผล Token Usage และค่าใช้จ่ายแบบ Real-time เป็นฟีเจอร์ที่จำเป็นอย่างยิ่ง ในบทความนี้ผมจะอธิบายวิธีการสร้างระบบ Token Tracking ที่แม่นยำ พร้อมโค้ด Production-Ready ที่สามารถนำไปใช้งานได้จริง

หลักการทำงานของ Token Counting

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

// Token Calculation Utility Class
class TokenCalculator {
    // Base pricing per million tokens (2026 rates)
    static const PRICING = {
        'gpt-4.1': { input: 8.0, output: 8.0 },        // $8/MTok
        'claude-sonnet-4.5': { input: 15.0, output: 15.0 }, // $15/MTok
        'gemini-2.5-flash': { input: 2.50, output: 2.50 },   // $2.50/MTok
        'deepseek-v3.2': { input: 0.42, output: 0.42 }      // $0.42/MTok
    };

    // Calculate tokens using tiktoken approximation
    static estimateTokens(text) {
        // Rough estimation: ~4 chars per token for mixed text
        const charCount = text.length;
        return Math.ceil(charCount / 4);
    }

    // Calculate cost in USD
    static calculateCost(model, promptTokens, completionTokens, currency = 'USD') {
        const pricing = PRICING[model];
        if (!pricing) throw new Error(Unknown model: ${model});
        
        const inputCost = (promptTokens / 1_000_000) * pricing.input;
        const outputCost = (completionTokens / 1_000_000) * pricing.output;
        const totalUSD = inputCost + outputCost;
        
        // Convert to THB if needed (rate: 1 USD = 35 THB)
        const rate = currency === 'THB' ? 35 : 1;
        
        return {
            inputTokens: promptTokens,
            outputTokens: completionTokens,
            totalTokens: promptTokens + completionTokens,
            inputCostUSD: inputCost.toFixed(6),
            outputCostUSD: outputCost.toFixed(6),
            totalCostUSD: totalUSD.toFixed(6),
            totalCostCurrency: (totalUSD * rate).toFixed(2),
            currency: currency
        };
    }
}

// Example usage
const cost = TokenCalculator.calculateCost(
    'deepseek-v3.2',  // Cheapest option at $0.42/MTok
    1500,             // Prompt tokens
    800,              // Completion tokens
    'THB'
);

console.log(Total Cost: ${cost.totalCostCurrency} ${cost.currency});
console.log(Total Tokens: ${cost.totalTokens});

การสร้าง Real-time Token Tracker

การสร้างระบบ Tracking แบบ Real-time ต้องอาศัยการติดตามทั้ง Prompt และ Completion Token จาก Response ของ API รวมถึงการคำนวณต้นทุนแบบ Streaming เพื่อแสดงผลระหว่างที่คำตอบกำลังถูกสร้าง

// Real-time Token Tracker with WebSocket Support
class RealtimeTokenTracker {
    constructor(apiKey, model = 'deepseek-v3.2') {
        this.apiKey = apiKey;
        this.model = model;
        this.sessionStats = {
            totalPromptTokens: 0,
            totalCompletionTokens: 0,
            totalCostUSD: 0,
            requestCount: 0,
            startTime: Date.now()
        };
        this.callbacks = [];
    }

    // Subscribe to token updates
    onUpdate(callback) {
        this.callbacks.push(callback);
    }

    // Notify all subscribers
    notify(stats) {
        this.callbacks.forEach(cb => cb(stats));
    }

    // Send chat completion request with token tracking
    async sendMessage(messages, onChunk) {
        const startTime = Date.now();
        let totalPromptTokens = 0;
        let totalCompletionTokens = 0;

        try {
            // Calculate prompt tokens before request
            const promptText = messages.map(m => m.content).join('\n');
            totalPromptTokens = this.estimateTokens(promptText);

            const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': Bearer ${this.apiKey}
                },
                body: JSON.stringify({
                    model: this.model,
                    messages: messages,
                    stream: true
                })
            });

            if (!response.ok) {
                throw new Error(API Error: ${response.status});
            }

            const reader = response.body.getReader();
            const decoder = new TextDecoder();
            let fullResponse = '';
            let chunkCount = 0;

            while (true) {
                const { done, value } = await reader.read();
                if (done) break;

                const chunk = decoder.decode(value);
                const lines = chunk.split('\n');

                for (const line of lines) {
                    if (line.startsWith('data: ')) {
                        const data = line.slice(6);
                        if (data === '[DONE]') continue;

                        try {
                            const parsed = JSON.parse(data);
                            const content = parsed.choices?.[0]?.delta?.content;
                            if (content) {
                                fullResponse += content;
                                chunkCount++;

                                // Real-time callback for streaming display
                                if (onChunk) {
                                    const currentTokens = this.estimateTokens(fullResponse);
                                    const currentCost = this.calculatePartialCost(
                                        totalPromptTokens,
                                        currentTokens
                                    );
                                    onChunk({
                                        content: content,
                                        accumulatedText: fullResponse,
                                        currentTokens: currentTokens,
                                        currentCostUSD: currentCost
                                    });
                                }
                            }
                        } catch (e) {
                            // Skip malformed JSON
                        }
                    }
                }
            }

            // Final token count from usage object
            totalCompletionTokens = this.estimateTokens(fullResponse);
            const finalCost = this.calculateFullCost(totalPromptTokens, totalCompletionTokens);

            // Update session stats
            this.sessionStats.totalPromptTokens += totalPromptTokens;
            this.sessionStats.totalCompletionTokens += totalCompletionTokens;
            this.sessionStats.totalCostUSD += finalCost;
            this.sessionStats.requestCount++;
            this.sessionStats.lastRequestTime = Date.now();

            // Final notification
            this.notify({
                ...this.sessionStats,
                lastRequestLatency: Date.now() - startTime,
                currentRequestTokens: totalPromptTokens + totalCompletionTokens,
                currentRequestCost: finalCost
            });

            return {
                content: fullResponse,
                usage: {
                    prompt_tokens: totalPromptTokens,
                    completion_tokens: totalCompletionTokens,
                    total_tokens: totalPromptTokens + totalCompletionTokens
                },
                cost: finalCost,
                latency: Date.now() - startTime
            };

        } catch (error) {
            console.error('Request failed:', error);
            throw error;
        }
    }

    estimateTokens(text) {
        return Math.ceil(text.length / 4);
    }

    calculatePartialCost(promptTokens, completionTokens) {
        const pricing = TokenCalculator.PRICING[this.model] || { input: 0.42, output: 0.42 };
        return (promptTokens / 1_000_000) * pricing.input +
               (completionTokens / 1_000_000) * pricing.output;
    }

    calculateFullCost(promptTokens, completionTokens) {
        return this.calculatePartialCost(promptTokens, completionTokens);
    }

    // Get formatted session summary
    getSessionSummary(currency = 'THB') {
        const rate = currency === 'THB' ? 35 : 1;
        const duration = (Date.now() - this.sessionStats.startTime) / 1000;
        
        return {
            ...this.sessionStats,
            totalCostCurrency: (this.sessionStats.totalCostUSD * rate).toFixed(2),
            currency: currency,
            averageCostPerRequest: (this.sessionStats.totalCostUSD / this.sessionStats.requestCount).toFixed(6),
            averageLatencyMs: 0, // Would need to track this
            duration: ${Math.floor(duration / 60)}m ${Math.floor(duration % 60)}s
        };
    }
}

// Initialize tracker
const tracker = new RealtimeTokenTracker(
    'YOUR_HOLYSHEEP_API_KEY',  // Get from https://www.holysheep.ai/register
    'deepseek-v3.2'
);

// Subscribe to real-time updates
tracker.onUpdate((stats) => {
    console.log([Real-time] Tokens: ${stats.totalTokens}, Cost: $${stats.totalCostUSD});
});

// Usage example
async function chat() {
    const result = await tracker.sendMessage(
        [
            { role: 'system', content: 'คุณเป็นผู้ช่วยภาษาไทย' },
            { role: 'user', content: 'อธิบายเรื่อง Token Counting' }
        ],
        (chunk) => {
            // Update UI in real-time
            updateDisplay(chunk.accumulatedText, chunk.currentCostUSD);
        }
    );
    
    console.log('Final Result:', result);
}

UI Component สำหรับแสดงผล Real-time

การแสดงผล Token และ Cost แบบ Real-time ต้องออกแบบ UI ให้ไม่รบกวนการใช้งาน แต่ยังคงเห็นข้อมูลได้ชัดเจน ด้านล่างคือ React Component ที่พัฒนาจากประสบการณ์การใช้งานจริง

// React Component for Real-time Token Display
import React, { useState, useEffect, useRef } from 'react';

const TokenDisplay = ({ tracker, darkMode = false }) => {
    const [stats, setStats] = useState({
        currentTokens: 0,
        currentCostUSD: 0,
        totalSessionTokens: 0,
        totalSessionCost: 0
    });
    const [isStreaming, setIsStreaming] = useState(false);
    const costRef = useRef(null);

    useEffect(() => {
        if (!tracker) return;

        const updateHandler = (newStats) => {
            setStats({
                currentTokens: newStats.currentRequestTokens || 0,
                currentCostUSD: newStats.currentRequestCost || 0,
                totalSessionTokens: newStats.totalTokens || 0,
                totalSessionCost: newStats.totalCostUSD || 0
            });
            setIsStreaming(newStats.currentRequestTokens > 0);
        };

        tracker.onUpdate(updateHandler);
        
        // Animation for cost changes
        if (costRef.current) {
            costRef.current.classList.add('pulse');
            setTimeout(() => costRef.current?.classList.remove('pulse'), 300);
        }
    }, [tracker]);

    const formatCost = (usd) => {
        const thb = usd * 35; // 1 USD = 35 THB
        return ฿${thb.toFixed(2)};
    };

    const formatTokens = (num) => {
        if (num >= 1000) return ${(num / 1000).toFixed(1)}K;
        return num.toString();
    };

    const styles = {
        container: {
            background: darkMode ? '#1a1a2e' : '#f5f5f5',
            color: darkMode ? '#fff' : '#333',
            padding: '12px 16px',
            borderRadius: '8px',
            fontFamily: 'monospace',
            fontSize: '13px',
            display: 'flex',
            gap: '20px',
            alignItems: 'center',
            boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
        },
        stat: {
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center'
        },
        label: {
            fontSize: '10px',
            opacity: 0.7,
            marginBottom: '2px'
        },
        value: {
            fontWeight: 'bold',
            fontSize: '16px'
        },
        streaming: {
            width: '8px',
            height: '8px',
            borderRadius: '50%',
            background: '#00ff00',
            animation: 'blink 1s infinite'
        },
        pulse: {
            animation: 'pulse 0.3s ease-out'
        }
    };

    return (
        <div style={styles.container}>
            {/* Streaming Indicator */}
            <div style={styles.stat}>
                <div style={styles.label}>STATUS</div>
                {isStreaming ? (
                    <div style={styles.streaming}></div>
                ) : (
                    <span style={{color: '#888'}}>Ready</span>
                )}
            </div>

            {/* Current Request Tokens */}
            <div style={styles.stat}>
                <div style={styles.label}>CURRENT TOKENS</div>
                <div style={styles.value}>{formatTokens(stats.currentTokens)}</div>
            </div>

            {/* Current Cost */}
            <div style={styles.stat} ref={costRef}>
                <div style={styles.label}>CURRENT COST</div>
                <div style={{...styles.value, color: '#00cc00'}}>
                    {formatCost(stats.currentCostUSD)}
                </div>
            </div>

            {/* Session Total */}
            <div style={styles.stat}>
                <div style={styles.label}>SESSION TOTAL</div>
                <div style={{...styles.value, color: '#ff6600'}}>
                    {formatCost(stats.totalSessionCost)}
                </div>
            </div>

            {/* Model Info */}
            <div style={styles.stat}>
                <div style={styles.label}>MODEL</div>
                <div style={styles.value, {fontSize: '12px'}}>
                    DeepSeek V3.2
                </div>
            </div>

            <style>{`
                @keyframes blink {
                    0%, 100% { opacity: 1; }
                    50% { opacity: 0.3; }
                }
                @keyframes pulse {
                    0% { transform: scale(1); }
                    50% { transform: scale(1.1); }
                    100% { transform: scale(1); }
                }
            `}</style>
        </div>
    );
};

export default TokenDisplay;

// Usage in App
const App = () => {
    const trackerRef = useRef(null);

    useEffect(() => {
        trackerRef.current = new RealtimeTokenTracker(
            'YOUR_HOLYSHEEP_API_KEY',
            'deepseek-v3.2'
        );
    }, []);

    const sendMessage = async (userMessage) => {
        await trackerRef.current.sendMessage(
            [{ role: 'user', content: userMessage }],
            (chunk) => {
                // Update streaming display
                setStreamingText(chunk.accumulatedText);
            }
        );
    };

    return (
        <div>
            <TokenDisplay tracker={trackerRef.current} />
            <ChatInterface onSend={sendMessage} />
        </div>
    );
};

การเพิ่มประสิทธิภาพและ Best Practices

จากการใช้งานจริงใน Production มีหลายประเด็นที่ต้องพิจารณาเพื่อให้ระบบ Token Tracking ทำงานได้อย่างมีประสิทธิภาพและแม่นยำ

// Optimized Token Manager with Caching
class OptimizedTokenManager {
    constructor() {
        this.cache = new Map();
        this.maxCacheSize = 1000;
        this.requestQueue = [];
        this.isProcessing = false;
    }

    // Get or calculate tokens with caching
    async getTokenCount(text, model = 'deepseek-v3.2') {
        const cacheKey = ${model}:${text};
        
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }

        // Calculate using tiktoken or approximation
        const tokens = await this.calculateTokens(text, model);
        
        // Manage cache size
        if (this.cache.size >= this.maxCacheSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        
        this.cache.set(cacheKey, tokens);
        return tokens;
    }

    async calculateTokens(text, model) {
        // For production, use tiktoken
        // npm install tiktoken
        const encoding = {
            'gpt-4.1': 'cl100k_base',
            'deepseek-v3.2': 'cl100k_base',
            'claude-sonnet-4.5': 'cl100k_base'
        }[model] || 'cl100k_base';

        // Fallback approximation
        return Math.ceil(text.length / 4);
    }

    // Batch process multiple requests
    async batchProcess(requests, onProgress) {
        const results = [];
        const total = requests.length;
        
        for (let i = 0; i < requests.length; i++) {
            const result = await this.processRequest(requests[i]);
            results.push(result);
            
            if (onProgress) {
                onProgress({
                    completed: i + 1,
                    total: total,
                    progress: ((i + 1) / total) * 100,
                    results: results
                });
            }
        }
        
        return results;
    }

    async processRequest(request) {
        // Simulate API call
        const promptTokens = await this.getTokenCount(request.prompt);
        const maxTokens = request.maxTokens || 1000;
        
        return {
            promptTokens,
            estimatedCompletionTokens: maxTokens,
            estimatedTotalTokens: promptTokens + maxTokens,
            estimatedCostUSD: this.calculateCost('deepseek-v3.2', promptTokens, maxTokens)
        };
    }

    calculateCost(model, promptTokens, completionTokens) {
        const pricing = {
            'gpt-4.1': 8.0,
            'deepseek-v3.2': 0.42,
            'claude-sonnet-4.5': 15.0,
            'gemini-2.5-flash': 2.50
        };
        
        const rate = pricing[model] || 0.42;
        return ((promptTokens + completionTokens) / 1_000_000) * rate;
    }

    // Get cache statistics
    getCacheStats() {
        return {
            size: this.cache.size,
            maxSize: this.maxCacheSize,
            hitRate: this.hitRate || 0
        };
    }
}

// Usage
const manager = new OptimizedTokenManager();

// Cache system prompt
const systemPrompt = 'คุณเป็นผู้ช่วย AI ที่ตอบคำถามอย่างกระชับ';
const systemTokens = await manager.getTokenCount(systemPrompt);
console.log(System Prompt Tokens: ${systemTokens});

// Batch process requests
const requests = [
    { prompt: '什么是Token?', maxTokens: 500 },
    { prompt: 'Explain AI models', maxTokens: 500 },
    { prompt: 'Token计数原理', maxTokens: 500 }
];

const batchResults = await manager.batchProcess(requests, (progress) => {
    console.log(Progress: ${progress.progress.toFixed(1)}%);
});

const totalCost = batchResults.reduce((sum, r) => sum + r.estimatedCostUSD, 0);
console.log(Total Batch Cost: $${totalCost.toFixed(6)} (${(totalCost * 35).toFixed(2)} THB));

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

1. Token Count ไม่ตรงกับ API Response

ปัญหา: ค่า Token ที่คำนวณเองไม่ตรงกับ usage.prompt_tokens จาก API Response

// ❌ วิธีที่ผิด - ใช้การประมาณจากความยาวตัวอักษร
const wrongTokenCount = (text) => Math.ceil(text.length / 4);

// ✅ วิธีที่ถูกต้อง - ใช้ tiktoken library
import tiktoken from 'tiktoken';

function accurateTokenCount(text, model = 'deepseek-v3.2') {
    // cl100k_base works for most models including DeepSeek
    const encoding = tiktoken.for_model('gpt-4'); // Compatible with DeepSeek
    const tokens = encoding.encode(text);
    encoding.free();
    return tokens.length;
}

// Alternative: Parse from API response directly
async function getAccurateTokenCount(apiKey, text) {
    const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
        method: 'POST',
        headers: {
            'Authorization': Bearer ${apiKey},
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            model: 'deepseek-v3.2',
            messages: [{ role: 'user', content: text }],
            max_tokens: 1
        })
    });
    
    const data = await response.json();
    return data.usage.prompt_tokens; // Accurate count from API
}

2. Cost คำนวณผิดเนื่องจาก Model ผิด

ปัญหา: ใช้ราคาของโมเดลผิด ทำให้ต้นทุนไม่ตรง

// ❌ วิธีที่ผิด - Hardcode ราคาผิด
const WRONG_PRICING = {
    'gpt-4': { input: 30, output: 60 } // เก่าแล้ว!
};

// ✅ วิธีที่ถูกต้อง - ใช้ราคา 2026 ที่อัปเดต
const CORRECT_PRICING_2026 = {
    // Price per Million Tokens (USD)
    'gpt-4.1': { input: 8.0, output: 8.0 },           // $8/MTok
    'claude-sonnet-4.5': { input: 15.0, output: 15.0 }, // $15/MTok
    'gemini-2.5-flash': { input: 2.50, output: 2.50 },   // $2.50/MTok
    'deepseek-v3.2': { input: 0.42, output: 0.42 }      // $0.42/MTok - ถูกที่สุด!
};

function calculateAccurateCost(model, promptTokens, completionTokens) {
    const pricing = CORRECT_PRICING_2026[model];
    if (!pricing) {
        throw new Error(Unknown model: ${model}. Available: ${Object.keys(CORRECT_PRICING_2026).join(', ')});
    }
    
    const inputCost = (promptTokens / 1_000_000) * pricing.input;
    const outputCost = (completionTokens / 1_000_000) * pricing.output;
    const totalUSD = inputCost + outputCost;
    
    return {
        inputCostUSD: inputUSD.toFixed(6),
        outputCostUSD: outputCost.toFixed(6),
        totalCostUSD: totalUSD.toFixed(6),
        totalCostTHB: (totalUSD * 35).toFixed(2) // Convert to Thai Baht
    };
}

// Example: Calculate for 10,000 token conversation
const cost = calculateAccurateCost('deepseek-v3.2', 5000, 5000);
console.log(DeepSeek V3.2 Cost: $${cost.totalCostUSD} (${cost.totalCostTHB} THB));
// Output: $0.004200 (0.15 THB) - ประหยัดมาก!

const costGPT = calculateAccurateCost('gpt-4.1', 5000, 5000);
console.log(GPT-4.1 Cost: $${costGPT.totalCostUSD} (${costGPT.totalCostTHB} THB));
// Output: $0.080000 (2.80 THB) - แพงกว่า 19 เท่า!

3. Streaming Response แสดงผลไม่ทันท่วงที

ปัญหา: UI อัปเดตช้าหรือกระพริบเมื่อแสดง Streaming Content

// ❌ วิธีที่ผิด - Update state บ่อยเกินไป
async function streamWithIssues(messages) {
    let fullText = '';
    
    // ❌ เรียก setState ทุกครั้งที่ได้ chunk - ทำให้ UI กระพริบ
    for await (const chunk of streamGenerator) {
        fullText += chunk;
        setStreamingText(fullText); // Too frequent updates!
    }
}

// ✅ วิธีที่ถูกต้อง - Debounce UI updates
class StreamingTextManager {
    constructor(onDisplay, debounceMs = 50) {
        this.buffer = '';
        this.onDisplay = onDisplay;
        this.debounceMs = debounceMs;
        this.lastUpdate = 0;
        this.updateScheduled = false;
    }

    addChunk(chunk, tokenCount, costUSD) {
        this.buffer += chunk;
        this.currentTokens = tokenCount;
        this.currentCost = costUSD;
        
        // Only update display every debounceMs milliseconds
        const now = Date.now();
        if (now - this.lastUpdate >= this.debounceMs) {
            this.flush();
        } else if (!this.updateScheduled) {
            this.updateScheduled = true;
            setTimeout(() => {
                this.flush();
                this.updateScheduled = false;
            }, this.debounceMs - (now - this.lastUpdate));
        }
    }

    flush() {
        this.lastUpdate = Date.now();
        this.onDisplay({
            text: this.buffer,
            tokens: this.currentTokens,
            costUSD: this.currentCost
        });
    }

    // Return final buffer and cleanup
    getFinal() {
        this.flush();
        const result = { text: this.buffer, finalTokens: this.currentTokens };
        this.buffer = '';
        return result;
    }
}

// Usage with React
function StreamingChat() {
    const [displayText, setDisplayText] = useState('');
    const [tokenCount, setTokenCount] = useState(0);
    const [costUSD, setCostUSD] = useState(0);
    const managerRef = useRef(null);

    const handleChunk = ({ text, tokens, costUSD }) => {
        setDisplayText(text);
        setTokenCount(tokens);
        setCostUSD(costUSD);
    };

    useEffect(() => {
        managerRef.current = new StreamingTextManager(handleChunk, 50);
    }, []);

    const sendMessage = async (messages) => {
        const result = await tracker.sendMessage(messages, (chunk) => {
            managerRef.current.addChunk(
                chunk.content,
                chunk.currentTokens,
                chunk.currentCostUSD
            );
        });
        
        // Final flush
        managerRef.current.getFinal();
        return result;
    };

    return (
        <div>
            <div className="chat-display">