ในการพัฒนาแอปพลิเคชัน 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 ทำงานได้อย่างมีประสิทธิภาพและแม่นยำ
- ใช้ tiktoken สำหรับการนับ Token ที่แม่นยำ — แทนที่จะประมาณจากจำนวนตัวอักษร ควรใช้ Library ที่รองรับ Encoding ของแต่ละโมเดล
- Cache Token Calculation — สำหรับข้อความที่ใช้บ่อย เช่น System Prompt ควร Cache ค่า Token เพื่อไม่ต้องคำนวณซ้ำ
- Batch Processing — สำหรับงานที่ต้องส่งหลาย Request ควรจัดกลุ่มเพื่อลดค่าใช้จ่ายรวม
- เลือกโมเดลที่เหมาะสม — DeepSeek V3.2 มีราคาถูกที่สุดในกลุ่มที่ $0.42/MTok ซึ่งประหยัดกว่า GPT-4.1 ถึง 95%
- Monitor Real-time — ใช้ WebSocket หรือ Server-Sent Events เพื่ออัปเดต UI แบบ Real-time โดยไม่ต้อง Reload
// 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">