Módulo 2: The Bedrock Cost Model
7. Manos a la obra: la calculadora de costo por token
Descripción
La lección 6 dejó una conclusión precisa: el dato que falta para costear extract-shipment-manifest-fields —tokens por invocación, volumen mensual— es una decisión de negocio que un humano tiene que declarar explícitamente, no algo que ninguna herramienta de análisis de infraestructura pueda inferir. Esta lección construye la pieza que resuelve exactamente eso: bedrock_cost_estimate.py, un script de Python 100% determinista, sin random, sin datetime.now(), que corrió de verdad para escribir esta lección, con pytest verificando doce casos fijos.
Conexión con el módulo
Las lecciones 2 y 6 dieron, respectivamente, el precio citado y la razón estructural de por qué ninguna herramienta existente resuelve esto sola. Esta lección construye el código que sí lo resuelve. La lección 8, el proyecto que cierra el módulo, usa esta misma calculadora para producir la proyección de costo real de GENAI-COST-PROFILE.md.
Analogía: la báscula de la verdulería
Una báscula de verdulería no te dice el precio de "una compra" — te dice el precio por kilo, y espera que tú pongas la fruta encima antes de decirte cuánto vas a pagar. No hay atajo: sin poner el producto en la báscula, la única respuesta posible es la tarifa por kilo, nunca un total. bedrock_cost_estimate.py es exactamente esa báscula, aplicada a tokens en vez de a fruta: conoce el precio por millón de tokens de un modelo (la lección 2 se lo dio, citado de AWS), pero necesita que tú declares, explícitamente, cuánto "peso" —cuántos tokens, cuántas veces al mes— vas a poner encima antes de poder decirte un total.
Paso 1 — El script completo
scripts/bedrock_cost_estimate.py, en la raíz de andes-cargo-infra/:
#!/usr/bin/env python3
"""bedrock_cost_estimate.py -- deterministic monthly cost calculator for a
Bedrock on-demand text workload, for Andes Cargo's extract-shipment-manifest-fields.
Three explicit usage inputs, never assumed:
--input-tokens average input tokens per request
--output-tokens average output tokens per request
--monthly-requests the declared monthly volume assumption
Plus a price per model, either resolved from MODEL_PRICING_USD_PER_MILLION_TOKENS
(a small table of prices cited from AWS, see Module 2, lesson 2) or overridden
explicitly with --price-in/--price-out.
Never uses random or datetime.now(). Given the same inputs, this script always
produces the same output -- that determinism is the entire point: see Module 2,
lesson 7 of genai-on-aws-production-guide.
"""
from __future__ import annotations
import argparse
import sys
from dataclasses import dataclass
# Public on-demand prices, US East (N. Virginia), USD per 1,000,000 tokens.
# Verified against the AWS Price List API (pricing.us-east-1.amazonaws.com,
# offer AmazonBedrock, publicationDate 2026-08-13T21:07:07Z).
# These numbers change. Re-verify at aws.amazon.com/bedrock/pricing/ before
# trusting them for a real budget decision -- this table is a citation, not
# a promise. See Module 2, lesson 2.
MODEL_PRICING_USD_PER_MILLION_TOKENS = {
"amazon.nova-micro-v1:0": {"input": 0.035, "output": 0.14},
"amazon.nova-lite-v1:0": {"input": 0.06, "output": 0.24},
"amazon.nova-pro-v1:0": {"input": 0.80, "output": 3.20},
"amazon.nova-premier-v1:0": {"input": 2.50, "output": 12.50},
}
@dataclass(frozen=True)
class CostEstimate:
model_id: str
input_tokens_per_request: int
output_tokens_per_request: int
monthly_requests: int
price_per_million_input: float
price_per_million_output: float
@property
def monthly_input_tokens(self) -> int:
return self.input_tokens_per_request * self.monthly_requests
@property
def monthly_output_tokens(self) -> int:
return self.output_tokens_per_request * self.monthly_requests
@property
def input_cost(self) -> float:
return (self.monthly_input_tokens / 1_000_000) * self.price_per_million_input
@property
def output_cost(self) -> float:
return (self.monthly_output_tokens / 1_000_000) * self.price_per_million_output
@property
def total_monthly_cost(self) -> float:
return round(self.input_cost + self.output_cost, 2)
def estimate(
model_id: str,
input_tokens_per_request: int,
output_tokens_per_request: int,
monthly_requests: int,
price_per_million_input: float | None = None,
price_per_million_output: float | None = None,
) -> CostEstimate:
if input_tokens_per_request < 0 or output_tokens_per_request < 0 or monthly_requests < 0:
raise ValueError("token counts and monthly_requests must be >= 0")
if price_per_million_input is None or price_per_million_output is None:
if model_id not in MODEL_PRICING_USD_PER_MILLION_TOKENS:
raise ValueError(
f"unknown model_id {model_id!r}; pass --price-in/--price-out explicitly, "
f"or use one of: {', '.join(MODEL_PRICING_USD_PER_MILLION_TOKENS)}"
)
cited = MODEL_PRICING_USD_PER_MILLION_TOKENS[model_id]
if price_per_million_input is None:
price_per_million_input = cited["input"]
if price_per_million_output is None:
price_per_million_output = cited["output"]
return CostEstimate(
model_id=model_id,
input_tokens_per_request=input_tokens_per_request,
output_tokens_per_request=output_tokens_per_request,
monthly_requests=monthly_requests,
price_per_million_input=price_per_million_input,
price_per_million_output=price_per_million_output,
)
def format_report(e: CostEstimate) -> str:
lines = [
f"Model {e.model_id}",
f"Input tokens / request {e.input_tokens_per_request:,}",
f"Output tokens / request {e.output_tokens_per_request:,}",
f"Monthly requests (declared) {e.monthly_requests:,}",
"",
f"Monthly input tokens {e.monthly_input_tokens:,}",
f"Monthly output tokens {e.monthly_output_tokens:,}",
"",
f"Input cost (${e.price_per_million_input:.4f}/1M tok) ${e.input_cost:,.2f}",
f"Output cost (${e.price_per_million_output:.4f}/1M tok) ${e.output_cost:,.2f}",
"-" * 52,
f"TOTAL MONTHLY COST ${e.total_monthly_cost:,.2f}",
]
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Deterministic monthly cost estimate for a Bedrock on-demand text workload."
)
parser.add_argument(
"--model", required=True, dest="model_id", help="Bedrock model id, e.g. amazon.nova-lite-v1:0"
)
parser.add_argument("--input-tokens", required=True, type=int, dest="input_tokens_per_request")
parser.add_argument("--output-tokens", required=True, type=int, dest="output_tokens_per_request")
parser.add_argument("--monthly-requests", required=True, type=int)
parser.add_argument(
"--price-in", type=float, default=None, dest="price_per_million_input",
help="override: USD per 1M input tokens",
)
parser.add_argument(
"--price-out", type=float, default=None, dest="price_per_million_output",
help="override: USD per 1M output tokens",
)
args = parser.parse_args(argv)
try:
e = estimate(
args.model_id,
args.input_tokens_per_request,
args.output_tokens_per_request,
args.monthly_requests,
args.price_per_million_input,
args.price_per_million_output,
)
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
print(format_report(e))
return 0
if __name__ == "__main__":
raise SystemExit(main())
Fíjate en tres decisiones de diseño, cada una directamente trazable a una lección anterior de este módulo. Primero, las tres entradas de uso (--input-tokens, --output-tokens, --monthly-requests) son required=True — el script se niega a adivinar un volumen, exactamente la disciplina que la lección 6 exigió. Segundo, MODEL_PRICING_USD_PER_MILLION_TOKENS es una tabla pequeña, con un comentario que cita su fuente exacta y su fecha de verificación — nunca un número "mágico" sin origen. Tercero, --price-in/--price-out permiten sobreescribir el precio citado — para el día en que el precio de AWS cambie, o para modelar un modelo que todavía no está en la tabla, sin tener que editar el código fuente del script.
Paso 2 — Corriendo la calculadora, de verdad
python3 bedrock_cost_estimate.py \
--model amazon.nova-lite-v1:0 \
--input-tokens 800 \
--output-tokens 150 \
--monthly-requests 5000
Qué esperar (literal — corrido de verdad, mismo entorno de esta guía):
Model amazon.nova-lite-v1:0
Input tokens / request 800
Output tokens / request 150
Monthly requests (declared) 5,000
Monthly input tokens 4,000,000
Monthly output tokens 750,000
Input cost ($0.0600/1M tok) $0.24
Output cost ($0.2400/1M tok) $0.18
----------------------------------------------------
TOTAL MONTHLY COST $0.42
Los tres inputs de este comando (800 tokens de entrada, 150 de salida, 5.000 invocaciones al mes) son el supuesto de volumen inicial que la lección 8 formaliza en GENAI-COST-PROFILE.md — un manifiesto de texto libre típico (algo así como el cuerpo de un correo describiendo un envío) ronda ese tamaño de entrada, y una respuesta estructurada de cinco campos (shipmentId, origen, destino, peso, y un campo de confianza) ronda ese tamaño de salida. Compáralo ahora con Nova Micro, el modelo más barato del catálogo, sobre el mismo volumen exacto:
python3 bedrock_cost_estimate.py \
--model amazon.nova-micro-v1:0 \
--input-tokens 800 \
--output-tokens 150 \
--monthly-requests 5000
Qué esperar (literal):
Model amazon.nova-micro-v1:0
Input tokens / request 800
Output tokens / request 150
Monthly requests (declared) 5,000
Monthly input tokens 4,000,000
Monthly output tokens 750,000
Input cost ($0.0350/1M tok) $0.14
Output cost ($0.1400/1M tok) $0.11
----------------------------------------------------
TOTAL MONTHLY COST $0.25
Con el mismo volumen exacto, Nova Micro cuesta $0.25/mes frente a $0.42/mes de Nova Lite — una diferencia real, pero, a este volumen, ninguna de las dos cifras es alta. Este es un hallazgo que vale la pena señalar en voz alta: al volumen bajo que ADR-001 prescribe para un camino de escalamiento, el costo por token de Bedrock, incluso con el modelo más caro de esta comparación, sigue siendo una fracción mínima de cualquier presupuesto real — la preocupación central de este módulo nunca fue "esto es caro hoy", fue "nadie había hecho la cuenta todavía".
Y, para cerrar el círculo con la lección 6, un intento deliberado de romper el script — un modelo que no está en la tabla, sin precio explícito:
python3 bedrock_cost_estimate.py \
--model not-a-real-model \
--input-tokens 800 \
--output-tokens 150 \
--monthly-requests 5000
Qué esperar (literal):
error: unknown model_id 'not-a-real-model'; pass --price-in/--price-out explicitly, or use one of: amazon.nova-micro-v1:0, amazon.nova-lite-v1:0, amazon.nova-pro-v1:0, amazon.nova-premier-v1:0
El script termina con código de salida 1, sin inventar un precio ni asumir uno por defecto — el mismo antipatrón, evitado aquí en código, que la lección 6 nombró en abstracto: nunca adivinar un dato que solo un humano puede declarar.
Paso 3 — El suite de pytest, casos fijos, cero aleatoriedad
scripts/test_bedrock_cost_estimate.py:
"""pytest suite for bedrock_cost_estimate.py -- fixed, deterministic cases only.
No random, no datetime.now(): the same inputs must always produce the same
$ output, every time this file runs, on any machine. Run with:
pytest scripts/test_bedrock_cost_estimate.py -v
"""
import pytest
from bedrock_cost_estimate import (
MODEL_PRICING_USD_PER_MILLION_TOKENS,
estimate,
format_report,
main,
)
def test_nova_lite_andes_cargo_baseline():
"""Andes Cargo's declared assumption for extract-shipment-manifest-fields:
800 input tokens / 150 output tokens per escalated manifest, 5,000
escalated manifests/month, Amazon Nova Lite on-demand."""
e = estimate("amazon.nova-lite-v1:0", 800, 150, 5000)
assert e.monthly_input_tokens == 4_000_000
assert e.monthly_output_tokens == 750_000
assert e.input_cost == pytest.approx(0.24)
assert e.output_cost == pytest.approx(0.18)
assert e.total_monthly_cost == pytest.approx(0.42)
def test_nova_micro_is_cheaper_than_nova_lite_at_same_volume():
lite = estimate("amazon.nova-lite-v1:0", 800, 150, 5000)
micro = estimate("amazon.nova-micro-v1:0", 800, 150, 5000)
assert micro.total_monthly_cost < lite.total_monthly_cost
assert micro.total_monthly_cost == pytest.approx(0.25)
def test_cost_scales_linearly_with_declared_volume():
base = estimate("amazon.nova-lite-v1:0", 800, 150, 5000)
doubled = estimate("amazon.nova-lite-v1:0", 800, 150, 10000)
assert doubled.total_monthly_cost == pytest.approx(base.total_monthly_cost * 2)
def test_zero_declared_volume_is_zero_cost():
e = estimate("amazon.nova-lite-v1:0", 800, 150, 0)
assert e.total_monthly_cost == 0.0
def test_unknown_model_without_explicit_price_raises():
with pytest.raises(ValueError):
estimate("some.unlisted-model", 800, 150, 5000)
def test_unknown_model_with_explicit_price_override_works():
e = estimate(
"some.unlisted-model", 1000, 200, 1000,
price_per_million_input=1.0, price_per_million_output=2.0,
)
assert e.input_cost == pytest.approx(1.0)
assert e.output_cost == pytest.approx(0.4)
assert e.total_monthly_cost == pytest.approx(1.40)
def test_negative_token_count_is_rejected():
with pytest.raises(ValueError):
estimate("amazon.nova-lite-v1:0", -1, 150, 5000)
def test_negative_monthly_requests_is_rejected():
with pytest.raises(ValueError):
estimate("amazon.nova-lite-v1:0", 800, 150, -5000)
def test_nova_premier_reference_price_matches_the_cited_figure():
"""Cross-check against the $2.50/$12.50 per 1M token figure cited in
Module 2, lesson 2, verified directly against the AWS Price List API."""
pricing = MODEL_PRICING_USD_PER_MILLION_TOKENS["amazon.nova-premier-v1:0"]
assert pricing["input"] == 2.50
assert pricing["output"] == 12.50
def test_report_contains_the_total_line():
e = estimate("amazon.nova-lite-v1:0", 800, 150, 5000)
report = format_report(e)
assert "TOTAL MONTHLY COST" in report
assert "$0.42" in report
def test_cli_end_to_end(capsys):
exit_code = main(
[
"--model", "amazon.nova-lite-v1:0",
"--input-tokens", "800",
"--output-tokens", "150",
"--monthly-requests", "5000",
]
)
captured = capsys.readouterr()
assert exit_code == 0
assert "$0.42" in captured.out
def test_cli_rejects_unknown_model_with_nonzero_exit(capsys):
exit_code = main(
[
"--model", "not-a-real-model",
"--input-tokens", "800",
"--output-tokens", "150",
"--monthly-requests", "5000",
]
)
captured = capsys.readouterr()
assert exit_code == 1
assert "error:" in captured.err
Doce casos, cada uno probando un comportamiento específico: la línea base de Andes Cargo, la comparación entre modelos, el escalamiento lineal con el volumen, el caso borde de volumen cero, el rechazo de un modelo desconocido (con y sin precio explícito de reemplazo), el rechazo de entradas negativas, la verificación cruzada del precio de Nova Premier citado en la lección 2, el formato del reporte, y el flujo completo de la interfaz de línea de comandos, incluido su caso de error.
pytest test_bedrock_cost_estimate.py -v
Qué esperar (literal — corrido de verdad):
============================= test session starts ==============================
platform darwin -- Python 3.13.7, pytest-7.4.4, pluggy-1.6.0
collected 12 items
test_bedrock_cost_estimate.py::test_nova_lite_andes_cargo_baseline PASSED [ 8%]
test_bedrock_cost_estimate.py::test_nova_micro_is_cheaper_than_nova_lite_at_same_volume PASSED [ 16%]
test_bedrock_cost_estimate.py::test_cost_scales_linearly_with_declared_volume PASSED [ 25%]
test_bedrock_cost_estimate.py::test_zero_declared_volume_is_zero_cost PASSED [ 33%]
test_bedrock_cost_estimate.py::test_unknown_model_without_explicit_price_raises PASSED [ 41%]
test_bedrock_cost_estimate.py::test_unknown_model_with_explicit_price_override_works PASSED [ 50%]
test_bedrock_cost_estimate.py::test_negative_token_count_is_rejected PASSED [ 58%]
test_bedrock_cost_estimate.py::test_negative_monthly_requests_is_rejected PASSED [ 66%]
test_bedrock_cost_estimate.py::test_nova_premier_reference_price_matches_the_cited_figure PASSED [ 75%]
test_bedrock_cost_estimate.py::test_report_contains_the_total_line PASSED [ 83%]
test_bedrock_cost_estimate.py::test_cli_end_to_end PASSED [ 91%]
test_bedrock_cost_estimate.py::test_cli_rejects_unknown_model_with_nonzero_exit PASSED [100%]
============================== 12 passed in 0.03s ==============================
Doce de doce, en menos de un décimo de segundo — porque no hay red, no hay disco más allá de leer el propio código fuente, y no hay ninguna dependencia externa. Corre este mismo comando en tu propia máquina, con el mismo código: el resultado debería ser idéntico, siempre, sin excepción. Esa es, literalmente, la definición de determinismo que esta lección prometió desde su primera línea.
Por qué round() importa más de lo que parece
Fíjate en un detalle real que apareció al construir estos casos de prueba, no anticipado de antemano: el caso de Nova Micro ($0.14 de entrada + $0.105 de salida) suma matemáticamente $0.245 — pero el reporte muestra $0.25, no $0.245, porque total_monthly_cost redondea a dos decimales con round() de Python. Esto no es un error del script — es aritmética de punto flotante real: 0.14 + 0.105 no se representa exactamente en binario, y round(..., 2) en Python usa redondeo "banker's rounding" (al par más cercano) sobre esa representación imperfecta. El test test_nova_micro_is_cheaper_than_nova_lite_at_same_volume verifica el número real que el script produce (0.25), no el número que una calculadora de bolsillo con precisión decimal exacta habría dado (0.245) — una lección pequeña, pero real, sobre por qué "correr el código y ver qué produce" es distinto de "asumir qué debería producir".
Errores comunes
Escribir un test que verifica un número "bonito" en vez del número real que el código produce (de expectativa sobre aritmética de punto flotante). Qué pasa: alguien, escribiendo un test nuevo para un caso propio, calcula el total esperado a mano con una calculadora normal y lo hardcodea, sin correr el script primero. Cómo detectarlo: si tu test falla con una diferencia de centavos que no esperabas. Cómo corregirlo: como mostró esta lección con el caso de Nova Micro, la aritmética de punto flotante puede redondear de forma distinta a una cuenta hecha a mano. Corre bedrock_cost_estimate.py primero, con --model/--input-tokens/--output-tokens/--monthly-requests reales, y usa el número que realmente produce como el valor esperado de tu test — nunca al revés.
Hardcodear un precio directamente en una llamada a estimate(), en vez de usar la tabla citada (de mantenimiento). Qué pasa: alguien, necesitando un cálculo rápido para un modelo nuevo, escribe estimate("amazon.nova-lite-v1:0", 800, 150, 5000, price_per_million_input=0.06, price_per_million_output=0.24) en vez de dejar que el script resuelva el precio desde MODEL_PRICING_USD_PER_MILLION_TOKENS. Cómo detectarlo: si tu código tiene un número de precio escrito directamente, en vez de una referencia al modelo. Cómo corregirlo: usa --price-in/--price-out (o los parámetros equivalentes en Python) únicamente para modelos que no están en la tabla, o para probar un precio hipotético a propósito. Para cualquier modelo ya listado, dejar que el script resuelva el precio desde la tabla citada garantiza que, si el precio de AWS cambia y actualizas la tabla una sola vez, todos los cálculos futuros usan el número correcto automáticamente.
Confundir "determinista" con "el precio nunca cambia" (de alcance de la palabra). Qué pasa: alguien lee "esta calculadora es determinista" y concluye que el resultado en dólares de este script es una verdad permanente. Cómo detectarlo: si citas el resultado de esta lección ($0.42/mes) meses después sin volver a verificar el precio de Nova Lite. Cómo corregirlo: determinismo, en esta lección, significa que el mismo input siempre produce el mismo output — no que el input (el precio citado en MODEL_PRICING_USD_PER_MILLION_TOKENS) sea permanente. El precio está marcado VARIABLE, exactamente como la lección 2 ya advirtió; el comentario del script mismo te dice que lo reverifiques contra aws.amazon.com/bedrock/pricing/ antes de una decisión real.
Ejercicios
Ejercicio 1 — Corre la calculadora con tu propio supuesto de volumen para extract-shipment-manifest-fields, distinto al de Andes Cargo. Elige un volumen mensual hipotético (por ejemplo, 20.000 invocaciones/mes, un escenario de mucho mayor escala) y corre el script con Nova Lite. ¿El resultado escala linealmente frente al caso de 5.000 del Paso 2, tal como predijo el test test_cost_scales_linearly_with_declared_volume?
Ver solución
Sí — 20.000 invocaciones es 4 veces el volumen de 5.000 del Paso 2, así que el costo total debería ser, también, 4 veces $0.42, es decir, $1.68/mes. Esto confirma en la práctica lo que el test de esta lección ya verificó en código: dado el mismo tamaño promedio de invocación (tokens de entrada y de salida fijos), el costo total escala linealmente con el volumen declarado — no hay ningún descuento por volumen ni ningún costo fijo adicional en el modelo On-Demand que rompa esa proporción.
Ejercicio 2 — Explica por qué el script rechaza un monthly_requests negativo, en vez de simplemente calcular un costo negativo. ¿Qué problema real, más allá de "los números negativos no tienen sentido para un volumen", evita esta validación?
Ver solución
Un volumen negativo no representa ningún escenario real de negocio — nadie declara "-500 invocaciones al mes" con intención—, así que lo más probable, si ese valor apareciera, es que sea el resultado de un error de tipeo, una resta mal hecha en otro sistema que alimenta este script, o una unidad confundida (por ejemplo, restar el volumen de un mes al de otro por error). Rechazar el valor con un ValueError explícito, en vez de simplemente calcular un total negativo sin sentido, obliga a que ese error se note y se corrija en el momento en que ocurre, en vez de propagarse silenciosamente hacia un documento como GENAI-COST-PROFILE.md con un número que nadie cuestionaría a simple vista.
Ejercicio 3 — Predice qué pasaría si corrieras pytest dos veces seguidas, sin cambiar ni una línea de código. Basándote en la ausencia de random y datetime.now() en bedrock_cost_estimate.py, ¿esperarías alguna diferencia entre las dos corridas?
Ver solución
No, ninguna diferencia — los doce casos deberían pasar exactamente igual, con los mismos valores exactos verificados en cada assert, en cualquier corrida, en cualquier máquina, en cualquier momento. Esa es, literalmente, la propiedad que hace determinista a este script: como ningún cálculo depende de una fuente externa de aleatoriedad ni del reloj del sistema, el resultado de cada assert depende únicamente de los valores fijos que cada test declara — la misma garantía, aplicada aquí a un script de costo, que ya viste con terraform plan sobre un recurso nuevo en el Módulo 1 y el Módulo 3 de esta guía.
Resumen y siguiente paso
Esta lección construyó bedrock_cost_estimate.py, la calculadora que resuelve exactamente lo que la lección 6 dejó definido como faltante: tres entradas explícitas de volumen y tamaño, nunca inferidas, combinadas con el precio público citado de la lección 2, para producir un total mensual determinista. Corriste el script de verdad, sobre el supuesto inicial de Andes Cargo ($0.42/mes con Nova Lite, $0.25/mes con Nova Micro, a 5.000 invocaciones mensuales) y confirmaste, con doce casos de pytest, que el comportamiento es correcto, incluidos sus casos límite y de error.
Antes de avanzar deberías poder: explicar por qué las tres entradas de uso del script son obligatorias, sin valor por defecto; correr la calculadora con un supuesto de volumen propio y predecir si el resultado escala linealmente; y explicar la diferencia entre "determinista" (mismo input, mismo output, siempre) y "permanente" (el precio citado nunca cambia — sí cambia).
La lección 8 toma esta calculadora y la usa para producir el entregable final del módulo: GENAI-COST-PROFILE.md, con el modelo elegido, el supuesto de volumen declarado, la proyección mensual real, y el resultado honesto del intento de Infracost de la lección 5 — todo en un solo documento.
Recursos
- Python Docs —
argparse— referencia de la interfaz de línea de comandos que este script usa. - Python Docs —
round()— referencia del comportamiento de redondeo que explica la diferencia entre $0.245 y $0.25 en esta lección. - pytest —
approx— la función usada en cada test de esta lección para comparar números de punto flotante sin fallos por imprecisión binaria. - Este módulo, lecciones 2 y 6 — la fuente de los precios citados en
MODEL_PRICING_USD_PER_MILLION_TOKENSy de la razón exacta por la que este script, y no Infracost, resuelve este problema.