114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
"""Utilidades para el cálculo de costos de tokens.
|
|
|
|
Define precios por modelo y funciones para estimar costos a partir del uso de tokens
|
|
(prompt, completion, cached).
|
|
"""
|
|
from typing import Dict, Any, Optional
|
|
|
|
# Precios actualizados por 1M tokens (USD) - Enero 2026
|
|
MODEL_PRICING = {
|
|
"gpt-4.1": {
|
|
"input": 2.00,
|
|
"cached_input": 0.50,
|
|
"output": 8.00,
|
|
},
|
|
"gpt-4.1-mini": {
|
|
"input": 0.40,
|
|
"cached_input": 0.10,
|
|
"output": 1.60,
|
|
},
|
|
"gpt-4.1-nano": {
|
|
"input": 0.10,
|
|
"cached_input": 0.03,
|
|
"output": 0.40,
|
|
},
|
|
"gpt-5.1": {
|
|
"input": 1.25,
|
|
"cached_input": 0.13,
|
|
"output": 10.00,
|
|
},
|
|
"gpt-5.1-codex-mini": {
|
|
"input": 0.25,
|
|
"cached_input": 0.025,
|
|
"output": 2.00,
|
|
},
|
|
}
|
|
|
|
# Legacy mapping o fase por defecto (si no se especifica modelo)
|
|
PRICING = {
|
|
"extract": MODEL_PRICING["gpt-4.1"],
|
|
"normalize": MODEL_PRICING["gpt-4.1-mini"],
|
|
}
|
|
|
|
|
|
def compute_cost_from_usage(
|
|
usage: Dict[str, Any] | None,
|
|
phase: str = "extract",
|
|
model: Optional[str] = None
|
|
) -> Dict[str, Any] | None:
|
|
"""Calcula el costo estimado basado en el uso de tokens, la fase o el modelo específico."""
|
|
if not isinstance(usage, dict):
|
|
return None
|
|
|
|
prompt_tokens = int(usage.get("prompt_tokens") or 0)
|
|
completion_tokens = int(usage.get("completion_tokens") or 0)
|
|
|
|
cached_tokens = 0
|
|
try:
|
|
details = usage.get("prompt_tokens_details") or {}
|
|
cached_tokens = int(details.get("cached_tokens") or 0)
|
|
except Exception:
|
|
cached_tokens = 0
|
|
|
|
non_cached_prompt = max(prompt_tokens - cached_tokens, 0)
|
|
|
|
# Determinar tarifas
|
|
rates = None
|
|
if model and model in MODEL_PRICING:
|
|
rates = MODEL_PRICING[model]
|
|
else:
|
|
# Fallback a fase (backward compatibility)
|
|
rates = PRICING.get(phase, PRICING["extract"])
|
|
|
|
rate_in = rates["input"]
|
|
rate_cached = rates["cached_input"]
|
|
rate_out = rates["output"]
|
|
|
|
cost_input = (non_cached_prompt / 1_000_000.0) * rate_in
|
|
cost_cached = (cached_tokens / 1_000_000.0) * rate_cached
|
|
cost_output = (completion_tokens / 1_000_000.0) * rate_out
|
|
|
|
return {
|
|
"input_usd": round(cost_input, 6),
|
|
"cached_input_usd": round(cost_cached, 6),
|
|
"output_usd": round(cost_output, 6),
|
|
"total_usd": round(cost_input + cost_cached + cost_output, 6),
|
|
}
|
|
|
|
|
|
def accumulate_usage(agg: Dict[str, Any], new_usage: Dict[str, Any] | None) -> None:
|
|
"""Acumula tokens de `new_usage` en el dict agregado `agg` (in-place)."""
|
|
if not isinstance(new_usage, dict):
|
|
return
|
|
agg["prompt_tokens"] += int(new_usage.get("prompt_tokens") or 0)
|
|
agg["completion_tokens"] += int(new_usage.get("completion_tokens") or 0)
|
|
details = new_usage.get("prompt_tokens_details") or {}
|
|
agg["prompt_tokens_details"]["cached_tokens"] += int(details.get("cached_tokens") or 0)
|
|
|
|
|
|
def sum_costs(a: Dict[str, Any] | None, b: Dict[str, Any] | None) -> Dict[str, Any]:
|
|
"""Suma dos objetos de costos."""
|
|
def safe(x): return float(x) if x is not None else 0.0
|
|
|
|
input_usd = safe((a or {}).get("input_usd")) + safe((b or {}).get("input_usd"))
|
|
cached_usd = safe((a or {}).get("cached_input_usd")) + safe((b or {}).get("cached_input_usd"))
|
|
output_usd = safe((a or {}).get("output_usd")) + safe((b or {}).get("output_usd"))
|
|
|
|
total = {
|
|
"input_usd": round(input_usd, 6),
|
|
"cached_input_usd": round(cached_usd, 6),
|
|
"output_usd": round(output_usd, 6),
|
|
}
|
|
total["total_usd"] = round(total["input_usd"] + total["cached_input_usd"] + total["output_usd"], 6)
|
|
return total
|