Ir al contenido principal
Laravel, thinking fast.

Capitulo 9

Los resultados de las herramientas no son respuestas

Julian Beaujardin

Una herramienta devuelve JSON. El modelo lee ese JSON y escribe una frase sobre él.

Si dejas que la frase sea tu interfaz, has construido un chat que describe un producto en lugar de un producto. El modelo escribirá diligentemente «He encontrado cuatro paletas de color: Ocean usa #1B4965 y #62B6CB, Sunset usa…» —y le habrás pedido a un ser humano que lea códigos hexadecimales en una burbuja de chat y escriba uno de vuelta. Eso no es una elección. Es un examen.

El resultado estructurado es la parte interesante. Este capítulo trata de representarlo.

Ramifica por claves, no por nombres de herramienta

Cada resultado trae una carga útil, y el manejador decide qué representar mirando lo que hay dentro:

$resultText = $result['content'][0]['text'] ?? '';
$data = json_decode($resultText, true);

if (! $data) {
    return $state;
}

if (isset($data['palettes']) && is_array($data['palettes'])) { /**/ }
if (isset($data['options_prompt']) && is_array($data['options_prompt'])) { /**/ }
if (isset($data['available'])) { /**/ }
if (isset($data['site_id'])) { /**/ }

Ramificar por la forma de la carga y no por el nombre de la herramienta significa que un renombrado aguas arriba no rompe la interfaz, y que dos herramientas que devuelven el mismo tipo de cosa comparten un renderizador gratis. Es también por lo que el capítulo 7 insistía en nombrar las claves de resultado por su herramienta —options_prompt, no options. Este despacho es exactamente lo que una clave genérica habría roto.

Fíjate en el retorno temprano. Un resultado que no se decodifica se salta en silencio, no lanza. El modelo ha producido texto para este turno igualmente; una carga que no puedes analizar debería costarle al usuario una tarjeta, no la respuesta entera.

Las ramas son if independientes y no elseif, porque un resultado puede legítimamente traer varias cosas representables a la vez.

Los mensajes de estado no son mensajes de chat

Los resultados representados van a la misma transcripción que la conversación, pero no son habla:

$state['messages'][] = [
    'role' => 'status',
    'type' => 'palette',
    'card' => 'pal-'.substr(md5((string) json_encode($data['palettes'])), 0, 8),
    'palettes' => $data['palettes'],
    'prompt' => $data['prompt'] ?? '',
];

Un tercer rol junto a user y bot, con un type sobre el que conmuta el frontend. Todo lo que la tarjeta necesita está en el mensaje; la vista no hace búsquedas ni derivaciones.

Esto mantiene la transcripción como una única lista ordenada —que es lo que la hace trivialmente reproducible, porque una tarjeta y la frase anterior conservan el orden en que ocurrieron.