Costos, Rate Limiting y Optimización
El costo es uno de los factores más críticos en aplicaciones AI-First. A diferencia de servidores tradicionales donde pagas por infraestructura, con LLMs pagas por cada token procesado. Dominar la optimización de costos y latencia es esencial para la viabilidad del negocio.
Cálculo de costos
Modelos y precios (2025-2026)
| Modelo | Input (1M tokens) | Output (1M tokens) | Velocidad |
|---|---|---|---|
| GPT-4o | $2.50 | $10.00 | ~80 tok/s |
| GPT-4o mini | $0.15 | $0.60 | ~120 tok/s |
| Claude 3.5 Sonnet | $3.00 | $15.00 | ~70 tok/s |
| Claude Haiku | $0.25 | $1.25 | ~150 tok/s |
| Gemini 2.0 Flash | $0.10 | $0.40 | ~150 tok/s |
Estimador de costos
interface CostEstimate {
inputCost: number;
outputCost: number;
totalCost: number;
monthlyEstimate: number;
}
const MODEL_PRICING: Record<string, { input: number; output: number }> = {
'gpt-4o': { input: 2.50, output: 10.00 },
'gpt-4o-mini': { input: 0.15, output: 0.60 },
'claude-sonnet': { input: 3.00, output: 15.00 },
'claude-haiku': { input: 0.25, output: 1.25 },
'gemini-flash': { input: 0.10, output: 0.40 },
};
function estimateCost(
model: string,
inputTokens: number,
outputTokens: number,
requestsPerDay: number
): CostEstimate {
const pricing = MODEL_PRICING[model];
const inputCost = (inputTokens / 1_000_000) * pricing.input;
const outputCost = (outputTokens / 1_000_000) * pricing.output;
const totalCost = inputCost + outputCost;
return {
inputCost,
outputCost,
totalCost,
monthlyEstimate: totalCost * requestsPerDay * 30,
};
}
// Ejemplo: 1000 requests/día, 500 input tokens, 300 output tokens
const cost = estimateCost('gpt-4o-mini', 500, 300, 1000);
console.log(`Costo mensual: $${cost.monthlyEstimate.toFixed(2)}`);
// ~$7.20/mes con GPT-4o-mini
// ~$127.50/mes con GPT-4o
Caché semántico
Evita llamadas repetidas al LLM cacheando respuestas para preguntas similares:
class SemanticCache {
private ttl: number;
constructor(ttlSeconds = 3600) {
this.ttl = ttlSeconds;
}
async get(query: string): Promise<string | null> {
const queryEmbedding = await getEmbedding(query);
const cached = await db.query(`
SELECT response, 1 - (query_embedding <=> $1::vector) AS similarity
FROM ai_cache
WHERE 1 - (query_embedding <=> $1::vector) > 0.95
AND created_at > NOW() - INTERVAL '${this.ttl} seconds'
ORDER BY similarity DESC
LIMIT 1
`, [JSON.stringify(queryEmbedding)]);
if (cached.rows.length > 0) {
console.log(`Cache hit! Similarity: ${cached.rows[0].similarity}`);
return cached.rows[0].response;
}
return null;
}
async set(query: string, response: string): Promise<void> {
const queryEmbedding = await getEmbedding(query);
await db.query(`
INSERT INTO ai_cache (query, query_embedding, response, created_at)
VALUES ($1, $2::vector, $3, NOW())
`, [query, JSON.stringify(queryEmbedding), response]);
}
}
// Uso en el servicio de chat
class CachedChatService {
private cache = new SemanticCache(3600);
async chat(message: string): Promise<string> {
// 1. Intentar caché
const cached = await this.cache.get(message);
if (cached) return cached;
// 2. Llamar al LLM
const response = await llm.chat(message);
// 3. Guardar en caché
await this.cache.set(message, response);
return response;
}
}
Fallback de modelos
interface ModelConfig {
id: string;
provider: 'openai' | 'anthropic' | 'google';
costTier: 'cheap' | 'standard' | 'premium';
}
const MODEL_TIERS: ModelConfig[] = [
{ id: 'gpt-4o-mini', provider: 'openai', costTier: 'cheap' },
{ id: 'gemini-flash', provider: 'google', costTier: 'cheap' },
{ id: 'gpt-4o', provider: 'openai', costTier: 'standard' },
{ id: 'claude-sonnet', provider: 'anthropic', costTier: 'premium' },
];
class SmartModelRouter {
async chat(message: string, options: {
requiredQuality: 'low' | 'medium' | 'high';
maxCostPerRequest: number;
maxLatencyMs: number;
}): Promise<string> {
// Seleccionar modelo según requerimientos
const qualityMap = { low: 'cheap', medium: 'standard', high: 'premium' };
const targetTier = qualityMap[options.requiredQuality];
const candidates = MODEL_TIERS.filter(m =>
this.getTierOrder(m.costTier) <= this.getTierOrder(targetTier)
);
// Intentar de más barato a más caro
for (const model of candidates) {
try {
const response = await this.callModel(model, message, options.maxLatencyMs);
return response;
} catch (error) {
console.warn(`Model ${model.id} failed, trying next...`);
}
}
throw new Error('All models failed');
}
private getTierOrder(tier: string): number {
return { cheap: 0, standard: 1, premium: 2 }[tier] ?? 1;
}
}
Rate Limiting avanzado
Token bucket con Redis
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
class TokenBucketRateLimiter {
async checkLimit(userId: string, plan: Plan): Promise<{
allowed: boolean;
remaining: number;
resetAt: number;
}> {
const limits = PLAN_LIMITS[plan];
const key = `ratelimit:${userId}:${this.getWindowKey()}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60); // 1 minuto de ventana
}
return {
allowed: current <= limits.messagesPerMinute,
remaining: Math.max(0, limits.messagesPerMinute - current),
resetAt: Date.now() + (await redis.ttl(key)) * 1000,
};
}
private getWindowKey(): string {
return Math.floor(Date.now() / 60000).toString(); // Ventana de 1 minuto
}
}
Optimización de prompts
1. Reducir tokens en system prompt
// ❌ Sistema verbose (150+ tokens)
const verboseSystem = `
Eres un asistente de inteligencia artificial diseñado para ayudar
a los usuarios con sus preguntas técnicas. Debes ser amable,
profesional y proporcionar respuestas detalladas. Si no conoces
la respuesta a algo, debes decirlo claramente en lugar de inventar
información. Responde siempre en español.
`;
// ✅ Sistema conciso (40 tokens)
const conciseSystem = `Asistente técnico experto. Responde en español.
Sé preciso y conciso. Si no sabes, dilo.`;
2. Limitar el historial de conversación
function trimConversationHistory(
messages: Message[],
maxTokens: number = 4000
): Message[] {
let totalTokens = 0;
const trimmed: Message[] = [];
// Siempre incluir el system prompt
const system = messages.find(m => m.role === 'system');
if (system) {
trimmed.push(system);
totalTokens += estimateTokens(system.content);
}
// Incluir mensajes recientes primero
const nonSystem = messages.filter(m => m.role !== 'system').reverse();
for (const msg of nonSystem) {
const tokens = estimateTokens(msg.content);
if (totalTokens + tokens > maxTokens) break;
trimmed.unshift(msg);
totalTokens += tokens;
}
return trimmed;
}
function estimateTokens(text: string): number {
return Math.ceil(text.length / 4); // Approximación: 4 chars ≈ 1 token
}
3. Usar el modelo correcto para cada tarea
async function smartRoute(task: string, content: string): Promise<string> {
// Tareas simples → modelo barato
const simpleTasks = ['classify', 'extract', 'format', 'translate'];
if (simpleTasks.some(t => task.includes(t))) {
return llm.chat(content, { model: 'gpt-4o-mini' });
}
// Razonamiento complejo → modelo capaz
const complexTasks = ['analyze', 'debug', 'architect', 'review'];
if (complexTasks.some(t => task.includes(t))) {
return llm.chat(content, { model: 'gpt-4o' });
}
// Default
return llm.chat(content, { model: 'gpt-4o-mini' });
}
Optimización de latencia
| Técnica | Reducción | Complejidad |
|---|---|---|
| Streaming | Percepción instantánea | Baja |
| Modelo más pequeño | 2-3x más rápido | Baja |
| Caché semántico | 100x (cache hit) | Media |
| Prompt más corto | 10-30% | Baja |
| Parallel tool calls | 2-5x en multi-tool | Media |
| Speculative decoding | 2-3x | Alta |
| Edge deployment | -50ms latencia | Alta |