Ir al contenido principal
Laravel, thinking fast.

Capitulo 4

Lo que te cuesta un token

Julian Beaujardin

Todos los demás fallos de este libro se anuncian. Una respuesta malformada lanza una excepción, un tiempo de espera se registra, una respuesta truncada se ve mal en pantalla.

El coste no. El coste se acumula en silencio, correctamente, una petición perfectamente exitosa cada vez, y te lo cuenta a fin de mes. Para entonces la única pregunta que queda es cuán grande es el número.

Esa asimetría es la razón de tratar aquí el gasto como una propiedad de corrección y no como un asunto de operaciones. Una función de IA sin topes no es una función barata que podría encarecerse. Es una función sin terminar.

La factura tiene más líneas de las que crees

Antes de poner tope a nada, conoce lo que te cobran. Vuelven cuatro números en cada llamada, y se comportan distinto:

  • Tokens de entrada — todo lo que enviaste. Prompt, historial de conversación, definiciones de herramientas, resultados de herramientas. Crece con la longitud de la conversación, no con la de la respuesta.
  • Tokens de salida — lo que volvió. Normalmente el número más pequeño y, en muchos proveedores, el más caro por token.
  • Lecturas de caché — tokens de entrada servidos desde un prefijo cacheado, facturados a una fracción de la tarifa normal.
  • Escrituras de caché — el coste de meter un prefijo en la caché, que suele ser algo mayor que enviarlo sin cachear. El cacheo se amortiza en la segunda llamada, no en la primera.

Y luego el multiplicador que nadie pone en la estimación: un turno no es una llamada. En un bucle de agente se reenvía toda la conversación, incluidos todos los resultados de herramientas hasta ese momento, en cada iteración. Un turno que usa cuatro herramientas no cuesta cuatro llamadas: cuesta cuatro peticiones cada vez más grandes. El capítulo 8 trata de controlar ese bucle. Este trata de asegurarse de que no se desboque mientras tanto.

El tope es una decisión del servidor, tomada por propósito

Primera regla del gasto: el cliente no elige nada que cueste dinero. Ni el modelo, ni el límite de tokens, ni el proveedor.

Es fácil equivocarse aquí, porque la forma natural de un endpoint es aceptar parámetros. La forma natural está mal en este caso. Un parámetro model es una petición del modelo más caro que soportes, enviada por quien haya leído tu JavaScript.

El patrón que lo resuelve limpiamente es un FormRequest que sobrescribe la petición antes de validarla:

protected function prepareForValidation()
{
    $isTranslate = $this->input('purpose') === 'translate';

    $this->merge([
        'api' => Config::string('services.ai.default'),
        'model' => $isTranslate
            ? Config::string('services.ai.drivers.openai.translate_model')
            : Config::string('services.ai.drivers.openai.prompt_model'),
        'max_tokens' => $isTranslate
            ? Config::integer('services.ai.drivers.openai.translate_max_tokens')
            : Config::integer('services.ai.drivers.openai.prompt_max_tokens'),
    ]);
}

prepareForValidation() se ejecuta antes que las reglas, así que lo que el llamante enviara en api, model o max_tokens se descarta y se reemplaza por un valor elegido por el servidor. Las reglas pueden seguir listando esos campos: nunca verán la versión del cliente.

Lo único que el llamante controla es purpose, y es un enum:

'purpose' => ['sometimes', 'string', 'in:prompt,translate'],

Esa única palabra se mapea a un carril: una pareja fija de modelo y tope de salida elegida para un tipo de trabajo. Reescribir un campo de una página es un modelo barato con un tope pequeño. Traducir un sitio entero es un modelo más capaz con un tope mayor, porque una traducción truncada es una página rota.

Dos cosas hacen que esto merezca la pena copiarse.

Los carriles son la granularidad correcta. Ni un tope global, que o se queda corto para tu trabajo más grande o es demasiado generoso para el más pequeño. Ni un parámetro libre, que no es un tope en absoluto. Una lista corta de propósitos con nombre, cada uno con sus números fijados en el servidor.

Añadir un carril es un acto deliberado. Un tipo de trabajo nuevo no hereda un tope por accidente; alguien elige su modelo y su techo, en un commit.