Seguimiento de costos
ZeroClaw registra cada llamada al modelo que consume tokens en un libro mayor de solo anexado, incluidas las llamadas cuyo precio solo está disponible parcialmente. Atribuye el gasto al agente que la originó, aplica presupuestos diarios y mensuales sobre la parte con precio conocido, y muestra tanto el gasto como la exposición por precios faltantes a los operadores. Las reglas de precios se encuentran en la configuración, de modo que los operadores puedan editarlas sin recompilar.
Esta página describe el esquema, el flujo de búsqueda y las superficies del operador. El código se encuentra en crates/zeroclaw-config/src/cost/ y crates/zeroclaw-runtime/src/agent/cost.rs.
Esquema de configuración
Dos secciones relacionadas controlan la superficie. cost cubre la aplicación del presupuesto y el comportamiento de registro. cost.rates.* es la tabla de tarifas gestionada por el operador; la ruta con puntos de cada subsección refleja la ruta providers.* correspondiente, con el segmento final <alias> reemplazado por el recurso ascendente al que se le asigna precio.
Por qué la clave es un id de recurso, no un alias
Una entrada [providers.models.anthropic.<alias>] está identificada por un alias elegido por el operador (glados, production) que cumple con el validador de alias: ASCII en minúsculas, guiones bajos simples, sin guiones. Una entrada [cost.rates.providers.models.anthropic.<resource>] está identificada por el id del modelo upstream tal como aparece en la telemetría de uso (claude-opus-4-7, gpt-4o-mini, whisper-1): esas cadenas de id provienen del espacio de nombres del proveedor y casi siempre contienen guiones.
El esquema marca cada HashMap de hoja de tarifas con #[resource_key] (en crates/zeroclaw-macros/src/lib.rs). Ese atributo excluye el campo de validate_alias_key en create_map_key / rename_map_key, de modo que el POST /api/config/map-key de la puerta de enlace acepta ids con guiones. Sin él, create_map_key rechaza todo id de modelo realista y la interfaz de usuario de la hoja de tarifas se viene abajo. Los alias y los ids de recursos comparten la estructura en disco (HashMap<String, T>), pero son sistemas de nomenclatura diferentes con validadores diferentes.
Las listas de slots son la única fuente de verdad
Los espacios por tipo de proveedor bajo [cost.rates.providers.models.<type>], [cost.rates.providers.tts.<type>] y [cost.rates.providers.transcription.<type>] se expanden a partir de las mismas macros que controlan los contenedores de espacios [providers.*]:
#![allow(unused)]
fn main() {
// crates/zeroclaw-config/src/providers.rs
for_each_model_provider_slot!(emit_model_cost_rates_struct);
for_each_tts_provider_slot!(emit_tts_cost_rates_struct, super::schema::TtsCostRates);
for_each_transcription_provider_slot!(emit_transcription_cost_rates_struct, super::schema::TranscriptionCostRates);
}
Agregar un nuevo tipo de proveedor de modelos es una fila en for_each_model_provider_slot!; el slot de la hoja de tarifas, el slot de configuración del proveedor y los menús desplegables del dashboard se expanden todos a partir de ella. Sin tablas de dispatch escritas a mano, sin listas de cadenas paralelas en el frontend.
Precios al momento de la solicitud
El flujo desde [cost.rates.*] hasta un valor cost_usd registrado es:
-
El arranque del orquestador construye el mapa de precios. Cuando el supervisor de canales instancia un contexto de tiempo de ejecución para un agente, recorre
config.cost.rates.providers.models.iter_entries()y fusiona las tarifas en unHashMap<provider_type, HashMap<key, f64>>dondekeyes"<model_id>.input","<model_id>.output"o"<model_id>.cached_input". La tabla heredada por alias[providers.models.<type>.<alias>].pricingtambién se fusiona;[cost.rates.*]prevalece en caso de conflicto porque es la superficie orientada al futuro. (Véasecrates/zeroclaw-channels/src/orchestrator/mod.rs, la clausura bajocost_tracking: CostTracker::get_or_init_global(...).map(|tracker| ...).) -
Registro dentro del bucle del agente. Cada respuesta exitosa del LLM llega a
record_tool_loop_cost_usage(provider_name, model, usage)encrates/zeroclaw-runtime/src/agent/cost.rs. La función obtiene la ranura del mapa de precios paraprovider_name, llama aresolve_rates(map, model), multiplica por los recuentos de tokens y almacena unCostRecordmediante elCostTrackerglobal. -
Resolución y validación por dimensión.
resolve_rates_optintenta primero el id del modelo y, después, la forma con sufijo de ruta para cadenasprovider/model(de modo queanthropic/claude-opus-4-7se degrada aclaude-opus-4-7si el operador solo guardó la forma corta). Conserva unaOptionpor dimensión, de modo que los precios en vivo y el catálogo global solo rellenan los huecos. Una tarifa de entrada o de salida de0.0deliberada permanece configurada y es gratuita. Una tarifa de entrada en caché de0.0también está configurada, pero no es gratuita: el constructor de coste existente la trata como «sin descuento de caché» y factura los tokens en caché a la tarifa estándar de entrada. Las tarifas negativas, no finitas o implausiblemente altas (por encima del límite de seguridad compartido de$1,000,000por unidad configurada) se tratan como no disponibles en el límite de registro, incluso si una ruta de carga de configuración más antigua o permisiva las dejó pasar.Las dimensiones de entrada faltante, entrada en caché y salida solo se evalúan cuando esa dimensión contiene tokens. La advertencia de ejecución de una sola vez identifica las dimensiones incompletas. Por lo tanto, una llamada con precio parcial registra su costo conocido y registra por separado únicamente el subconjunto de tokens que no pudo valorarse.
-
CostTracker es un singleton de proceso global (
OnceLockencrates/zeroclaw-config/src/cost/tracker.rs). La recarga aplica laCostConfigmás reciente al tracker existente, y si el seguimiento de costos estaba deshabilitado al arrancar, una recarga posterior concost.enabled = trueconstruye el tracker bajo demanda. El mapa de precios del orquestador también se reconstruye en cada recarga del daemon a partir de la configuración activa, por lo que las ediciones de tarifas surten efecto en la siguiente solicitud después de la recarga.
Precios en vivo desde pasarelas
Los operadores no tienen que mantener manualmente una tarifa para cada modelo. Un proveedor puede optar por obtener los precios de los tokens directamente desde su propia pasarela configurando live_pricing = true en ese bloque del proveedor (junto con su api_key y la configuración de modelos existentes); los precios provienen del listado /models de la propia pasarela.
Comportamiento:
- Gateway es la fuente principal. El endpoint existente
/modelsdel proveedor (el mismo que usa la incorporación para listar modelos) se analiza para obtener el precio por modelo. Los gateways que publican precios allí los informan como cadenas decimales por token (laspricing{prompt,completion,...}de OpenRouter y Kilo), que se escalan a USD por 1M de tokens. Un gateway cuyo listado de/modelsno incluye ningún precio en absoluto (como opencode zen, que solo enumera ids de modelo) queda cubierto por la alternativa de models.dev que se indica abajo. No hay una segunda copia de la URL del endpoint ni de las credenciales: se leen de la configuración existente del proveedor. - models.dev fallback. Un modelo que la pasarela no tarifica (o un proveedor sin listado HTTP
/modelsen absoluto, como una pasarela de subproceso comokilocli) recurre al catálogo público de models.dev (api.json), indexado por el nombre de models.dev de la familia (véasecatalog_source_forencrates/zeroclaw-providers/src/catalog.rs). El catálogo de respaldo se obtiene de nuevo en cada ciclo de actualización, por lo que ambas fuentes siguen los cambios de precios upstream con la misma cadencia horaria. - La configuración siempre prevalece. Los precios en vivo rellenan solo las dimensiones para las que un modelo no tiene una entrada de
[cost.rates]/pricing. Una tarifa configurada (incluido un0.0deliberado) nunca se sobrescribe. Esto es un relleno de huecos, no un reemplazo. - Una llamada por gateway, solo modelos marcados. Los alias que comparten un gateway se deduplican a una sola recuperación de
/models; de esa respuesta, solo se completa elmodelconfigurado de cada alias que se haya inscrito, no todos los modelos que enumera el gateway. - Actualización en segundo plano, nunca bloquea. Una sola tarea actualiza una instantánea de precios a nivel de proceso cada hora. La ruta de registro de costos lee la instantánea en caché de forma sincrónica y nunca realiza una llamada de red en línea, por lo que una pasarela lenta no puede bloquear la contabilidad de solicitudes.
- Desactivado de forma predeterminada. Sin ninguna configuración de proveedor con
live_pricing = trueno hay tarea de actualización ni tráfico de red; el comportamiento es idéntico al de una compilación sin la funcionalidad. Al desactivar en tiempo de ejecución el último proveedor marcado (recarga de configuración), se limpia la instantánea en el siguiente ciclo de actualización, por lo que los precios en vivo dejan de rellenarse sin reiniciar. La instantánea vive solo enzeroclaw_providers::pricing(véasecrates/zeroclaw-providers/src/pricing.rs); la leerecord_tool_loop_cost_usagey se inicia una vez desde el supervisor de canales y el arranque del gateway.
Como [cost.rates], un precio en vivo solo afecta a las solicitudes realizadas después de que se haya completado la instantánea; no hay recálculo retroactivo de registros anteriores.
Persistencia
CostTracker::record_usage_with_agent añade un CostRecord por cada respuesta que contenga tokens a <workspace>/state/costs.jsonl, un objeto JSON por línea. El registro se lee al iniciar para que el desglose por agente del mes en curso del panel sobreviva a los reinicios.
Cada registro de TokenUsage contiene dos campos de procedencia de precios:
unpriced_tokens: el subconjunto de tokens de entrada, de entrada en caché o de salida cuya tarifa efectiva no estaba disponible. Es0para las llamadas con precio completo, incluidas las llamadas facturadas a una tarifa de entrada o salida deliberadamente gratuita de0.0y las llamadas cuya tarifa de entrada en caché de0.0recurrió a la tarifa de entrada estándar.pricing_available: resumen de compatibilidad de esa procedencia. Los registros nuevos lo establecen enfalsecuandounpriced_tokens > 0.
Las filas del registro escritas antes de que existieran estos campos se deserializan con pricing_available = true y unpriced_tokens = 0. Un gasto cero en una fila heredada, por tanto, no demuestra que el precio histórico estuviera disponible; la procedencia faltante no puede reconstruirse de forma segura a posteriori.
cost_usd se calcula en el momento del registro a partir de la tabla de tarifas vigente en ese momento. Los registros son inmutables: si el operador agrega tarifas después de que algunas solicitudes ya hayan sido registradas, esos registros existentes conservan cost_usd = 0. Solo las solicitudes realizadas después de que se configure la tarifa (y se recargue el daemon para que se reconstruya el mapa de precios del orquestador) llevan un costo distinto de cero.
Esta es la sorpresa más común después de habilitar la hoja de tarifas por primera vez. La solución es esperar nuevas solicitudes; no hay retarificación retroactiva.
Aplicación de presupuesto
CostConfig::enforcement.mode decide qué sucede cuando un costo proyectado superaría daily_total o monthly_total por encima del límite configurado:
warn: el valor predeterminado; registra el evento con un log de nivel warn y deja pasar la solicitud.block: rechaza la solicitud con un errorBudgetExceeded.route_down: sustituyeroute_down_model(una alternativa más económica) por el modelo original. La sustitución ocurre antes de que se envíe la solicitud.
allow_override = true permite que una solicitud omita block pasando un token de anulación en la CLI (zeroclaw --override). El valor predeterminado es false. warn_at_percent controla cuándo la puerta de enlace muestra un banner de advertencia antes del límite estricto; el valor predeterminado es 80%.
Las comparaciones de presupuesto usan el cost_usd registrado, por lo que no pueden tener en cuenta el subconjunto unpriced_tokens. Un total diario o mensual por debajo de su límite no es garantía de seguridad mientras el mes en curso contenga unpriced_tokens. Configure las dimensiones de tarifa que faltan y genere una nueva solicitud antes de confiar en la aplicación de los límites; las filas existentes del registro nunca se vuelven a tarificar retroactivamente.
Atribución por agente
Cuando cost.track_per_agent es true (valor predeterminado), cada CostRecord registrado incluye el alias del agente de origen. El panel Spend by agent del dashboard y GET /api/cost?agent=<alias> consumen este campo. Establecer track_per_agent = false es una optimización para instalaciones de alto volumen donde la agregación adicional de HashMap aparece en los perfiles; la contrapartida es perder la dimensión por agente en todas partes.
Superficies de operador
Estado de la CLI
zeroclaw status muestra el gasto de hoy y el gasto del mes en curso según el registro. Cuando algún modelo registró unpriced_tokens > 0 en cualquier momento del mes UTC en curso, también muestra una advertencia de precio no disponible que enumera los modelos afectados y el total de tokens sin coste en todos ellos. La advertencia lee un resumen por modelo con alcance mensual (CostTracker::get_current_month_model_stats), no el desglose diario by_model que usa el panel, de modo que el uso sin precio de un día anterior del mes sigue visible tras el cambio de día UTC y tras un reinicio. La advertencia es deliberadamente independiente del total monetario agregado: las filas mixtas con y sin precio siguen visibles, mientras que un modelo gratuito totalmente configurado no genera aviso.
Esta advertencia significa que el gasto mostrado es un límite inferior y que el margen restante del tope es un límite superior, no un cálculo completo. Las filas heredadas no pueden activarla porque se desconoce la procedencia histórica de su precio.
Interfaz de configuración
/config/cost→ pestaña Limits: todos los campos planos[cost].*(enabled, limits, enforcement, track_per_agent). Las filas de la tabla de tarifas no se editan aquí, están vinculadas al proveedor propietario del modelo, por lo que se encuentran un nivel más abajo./config/providers.<category>/<type>→ pestaña Costs: editor de tarifas para ese tipo de proveedor. La entrada+ Addsugiere ids de recursos upstream extraídos deproviders.<category>.<type>.*.modelentre los alias configurados, de modo que el operador puede agregar con un clic una fila de tarifa para cada modelo que haya vinculado realmente. Este es el único punto de entrada para editar[cost.rates.providers.<category>.<type>.*].
Panel de control
La pestaña Cost del panel muestra tres paneles más un selector de Ventana (hoy / últimos 7 días / últimos 30 días / este mes / todo el tiempo):
- Totales de gasto: totales diarios y mensuales de
costs.jsonl. - Gasto por agente ·
<window>: resumen acumulado por agente durante la ventana seleccionada. Visible cuandotrack_per_agentes true. - Gasto por modelo ·
<window>: resumen acumulado por modelo. El id de modelo de cada fila es clicable; el clic resuelve el tipo de proveedor propietario a partir de los alias configurados y navega a la pestaña Costs de ese proveedor. Cuando el id de modelo no está vinculado a ningún proveedor configurado, el clic no tiene efecto (no existe una ruta de tarifario calificada para un modelo huérfano).
Gateway
GET /api/cost:CostSummaryactual (coincide con la estructura de la vista general de costos del panel de control). Agregue?agent=<alias>para una vista de un solo agente.GET /api/config/templates: cada sección con claves de tipo map que registra el esquema, utilizada por los menús desplegables de categoría × tipo de proveedor de la pestaña Rates.POST /api/config/map-key?path=cost.rates.providers.<category>.<type>&key=<resource>crea una nueva fila de tarifa. La ruta se rechaza si no existe tal sección de mapa; la clave del recurso pasa por#[resource_key]en lugar devalidate_alias_key.
Solución de problemas
El panel muestra $0.0000 para todos los agentes después de configurar las tarifas. Los registros antiguos son inmutables, se registraron con cost_usd = 0 porque no había ninguna tarifa configurada cuando ocurrieron. Realiza una nueva solicitud de chat después de recargar el daemon y verifica Cost overview > Session junto con Spend by model; ambos deberían completarse para la nueva solicitud.
zeroclaw status indica que los precios no están disponibles aunque hay algunas tarifas configuradas. El precio se resuelve por cada dimensión que lleva tokens. Por ejemplo, una tarifa de entrada no fija el precio de los tokens de salida, y una tarifa de entrada en caché no fija el precio de la entrada sin caché. Añada las dimensiones que indica la advertencia del runtime. Una tarifa de entrada o de salida configurada en 0.0 es una tarifa gratuita válida y no activa la advertencia de estado; una tarifa de entrada en caché configurada en 0.0 factura los tokens en caché a la tarifa estándar de entrada y tampoco la activa.
Se detectó deriva en las rutas cost.rates.* después de guardar. Un daemon anterior a v0.8.0 corrompía las claves con guiones de HashMap en la ruta de guardado de cambios pendientes (dirty-save), descartando silenciosamente cada escritura en la hoja de tarifas. Si ve esto en v0.8.0 o posterior, es un bug real: la resolución de rutas con cambios pendientes se encuentra en crates/zeroclaw-config/src/schema.rs::apply_dirty_path; abra un issue con la versión del daemon y la ruta que presentó la deriva.
missing_pricing avisos llenan el registro. El runtime emite este aviso una vez por cada par (provider_type, model) cuando una dimensión con tokens de entrada, de entrada en caché o de salida sigue sin precio después de que resolve_rates_opt combine las tarifas configuradas, en vivo y del catálogo global. El aviso indica las dimensiones incompletas; por tanto, un modelo parcialmente configurado puede emitir el aviso aunque otra dimensión ya tenga precio. Añada únicamente las dimensiones que faltan para el id exacto del modelo indicado en el aviso. Algunos proveedores devuelven ids con versión, como claude-3-5-sonnet-20241022, aunque el id configurado sea claude-3-5-sonnet; el resolvedor también prueba su candidato documentado de sufijo de ruta antes de informar del vacío.