← Todos los artículos

N.º 10X by Nexow

Una preautorización para cada prompt

En X by Nexow, un widget cuesta créditos, y nadie sabe la factura hasta que el modelo termina de escribir. Así que tomamos prestado de los pagos con tarjeta: autorizar antes de la llamada, cobrar después, nunca más de lo autorizado.

Vincent Porret

Cofundador y CEO · · 4 min de lectura

Estampa 10, Piedra. Gotas y peines sobre un baño, sacada al construir el sitio.Pasa el puntero por encima para dejar caer tinta.

En riesgos y tesorería, un pago cuyo importe no se conoce de antemano es un problema antiguo. Un hotel o una gasolinera no sabe la cuenta final cuando le das la tarjeta, así que primero retiene un importe y cobra el real más tarde. X by Nexow tiene el mismo problema cada vez que describes un widget: el modelo escribe hasta que termina, y solo entonces el proveedor dice cuántos tokens ha usado. Así que X hace lo que hace una red de tarjetas.

Autorizar

Antes de que una petición vaya al modelo, el servidor pide a la base de datos que retenga créditos para ella: una estimación de la entrada que va a enviar, más el máximo de salida que la llamada puede escribir. La base de datos concede esa estimación o lo que queda en tu saldo, lo que sea menor. Si lo que queda está por debajo del mínimo que necesita una llamada para empezar, la petición se para ahí, con un HTTP 402, y nunca se llama al modelo. Si lo concedido es menor que la estimación, el límite de salida de la llamada se baja a lo que la retención puede pagar.

Cobrar

Cuando la llamada termina, el informe de uso del proveedor fija el cargo real, con precio por modelo y por tipo de token: entrada, salida, lecturas y escrituras de caché. La liquidación toma lo menor entre ese cargo y la retención, y devuelve el resto. Esta es la línea de la función de la base de datos que lo hace:

v_charge := least(coalesce(v_hold.pending_actual, 0), v_hold.amount);   -- clamp to reserved

Ese tope es a propósito. Si la estimación se quedó corta, la diferencia la asumimos nosotros, y un comentario del código lo dice: una estimación baja solo pone en riesgo al operador, que se come el exceso, nunca al usuario, que no paga de más. Una llamada que no consumió nada libera toda su retención. Un flujo cortado antes de que llegara el informe de uso se cobra con un mínimo prudente, calculado a partir de lo que se envió y de lo que ya había vuelto, para que tampoco se pierdan los tokens que el proveedor ya sirvió.

Nunca se te cobra más de lo retenido. Si nuestra estimación se quedó corta, la diferencia la pagamos nosotros.

La base de datos dice que no

Los saldos viven en Postgres, y las reglas también, como restricciones y no como código de la aplicación:

balance  bigint not null default 0 check (balance >= 0),
held     bigint not null default 0 check (held >= 0),
…
constraint held_le_balance check (held <= balance)

Un saldo no puede bajar de cero, y los créditos retenidos no pueden superarlo, intente lo que intente un error más arriba. Cada cambio pasa por una función de la base de datos que primero bloquea la fila de la cuenta, así que dos construcciones lanzadas en el mismo momento se esperan en lugar de gastar las dos los mismos créditos. Solo el servidor puede llamar a esas funciones, y un usuario solo puede leer sus propias filas.

Los créditos llegan por lotes, cada uno con su origen y, algunos, con caducidad. Un cargo los consume en un orden fijo: por prioridad, los de suscripción antes que los comprados, luego los que caducan antes, luego los más antiguos. Cada movimiento se escribe en un registro, con el saldo resultante.

Un evento, un abono

Los créditos se compran a través de Stripe, y Stripe puede entregar el mismo evento más de una vez. El identificador de cada evento entra en una tabla donde es la clave primaria, antes de abonar nada, así que una repetición se confirma y se ignora. Si el procesamiento falla, la fila se borra y se le pide a Stripe que lo vuelva a intentar. Los créditos abonados llevan su propia clave única, como el identificador de la factura, así que incluso un duplicado que se colara por el primer control no podría abonar dos veces.

Las retenciones no sobreviven a su llamada. Un barrido pasa cada cinco minutos: una retención que ha superado sus cinco minutos de vida se liquida si tiene un cargo registrado, y se libera si no. Una generación se corta a los dos minutos, así que el barrido nunca toca una llamada que siga en curso. Y si traes tu propia clave del modelo, nada de esto se aplica: tus llamadas nunca gastan créditos.

Vincent