rlbisbe @ dev

Tengo muchas cosas en la cabeza… sobre todo punteros a null

LLM6 – Buscando determinismo

Nota: He cambiado el título de la serie de rlbisbe.AI a LLMx, e iré renombrando el resto de los artículos de la serie.

En el artículo anterior hablábamos de la necesidad de practicar y tomar soltura con los LLMs usando técnicas como TDD o Coding Katas, y en la parte 3 de esta serie hablábamos de ideas como el patrón Plan/Act o el uso de Kanban. Estas ideas nos ayudan a definir momentos en los que podemos usar estas herramientas, pero como dice el tío Ben, un gran poder implica una gran responsabilidad.

Hoy vamos a hablar, recuperando el concepto que vimos en la parte 4 de cómo combinar el mundo determinista con el no determinista, de cómo no todas las acciones necesitan ser ejecutadas por el agente utilizando tres escenarios prácticos que he experimentado a lo largo de esta semana.

El caso de la ejecución interrumpida

Comenzamos con procesos repetitivos, tenía que hacer un cambio en un fichero de configuración y ejecutar una serie de tests. Como el proceso era relativamente simple, recurrí a Cline y fijé un prompt parecido a este:

given the following server list [US-EAST-1, EU-WEST-1...]
for each server, change the configuration on build.gradle to point to the server, execute the command 'gradle tests' and confirm the tests pass

Este código tenía varios problemas: Por una parte comprobaba la ejecución desde la terminal integrada en VS Code y a veces perdía la conexión, con lo cual el proceso se quedaba congelado esperando mi feedback. Por otra parte, los LLMs tienden a intentar cualquier problema que ven, así que en este caso el programa paraba la ejecución e intentaba encontrar una solución, haciendo cambios aleatorios al código.

Los LLMs nos permiten automatizar tareas interactuando en lenguaje natural, pero ejemplos como este me hicieron replantearme si esta automatización era la manera adecuada de hacerlo.

Generar, no ejecutar

La inspiración me llegó a raíz del podcast Agent, take the wheel en el que, entre otras cosas recalcaban que la IA nos permite generar herramientas que a su vez nos permite escribir código. Con esa idea en mente volví a mi prompt original e hice un ligero cambio.

given the following server list [US-EAST-1, EU-WEST-1...]
create a script that, for each server, change the configuration on build.gradle to point to the server, execute the command 'gradle tests' and confirm the tests pass

El resultado de esta ejecución no es la ejecución de los tests, sino un programa (en este caso en shell script) que podemos ejecutar tantas veces como necesitemos, con un resultado determinista.

El resultado para mí ha sido muy exitoso, he tenido que modificar el script un par de veces para asegurarme que iba en la dirección en la que estaba pensando, pero la ejecución es rápida y precisa. Además, como en el mundo de los LLMs tenemos una capacidad limitada (económicamente, ya sea tokens comprados o limitaciones del plan), este tipo de delegación no consume capacidad del modelo.

El caso del json truncado

Seguimos con la necesidad de usar herramientas especializadas. Para una de mis tareas necesitaba modificar un fichero JSON de configuración bastante largo y combinarlo con el contenido de otro fichero JSON.

given the following json file #1
for all the servers in the json file, take the configuration from file #2, add it to file#1 in the same position, change the name of the configation to "new"

Para mi sorpresa, el LLM no fue nada eficiente, no hizo todos los cambios pedidos, generó problemas de sintaxis en el JSON y en una ocasión, truncó el JSON perdiendo un buen rato de trabajo.

Los LLMs que tienen la capacidad de usar herramientas son los que se pueden convertir en agentes, y estas herramientas nos permiten controlar cuanta libertad de ejecución le damos al agente. El problema es que si no somos específicos con las herramientas que usamos, el agente va a usar lo que sabe, en este caso modificar un json «a mano».

Herramientas especializadas

El universo de linux está lleno de herramientas open source que nos permiten tratar json, xml o texto plano. Estas herramientas están optimizadas, tienen una estructura concreta y son deterministas.

Por tanto, en vez de modificar el fichero directamente le puedo pedir al LLM que use jq para manipular el json.

given the following json file #1
using jq, for all the servers in the json file, take the configuration from file #2, add it to file#1 in the same position, change the name of the configation to "new"

El resultado fue significativamente más rápido, ya que el razonamiento del llm se limitaba a buscar cómo pasar los datos a jq, algo que incluso se podría reducir con documentación inicial.

El caso de 1970

He de reconocer que este caso ha sido particularmente divertido, porque tras más de 15 años haciendo software mis mayores dolores de cabeza siempre vienen asociados con fechas y horas. Nos llegó un aviso que uno de nuestros sistemas tenía un problema y estábamos mostrando fechas de 1970.

En UNIX es bastante común almacenar y transferir el tiempo usando los segundos (o los milisegundos…) desde el 1 de enero de 1970. Es una larga historia pero en la práctica significa que si estamos trabajando con números de 1970 es que hay algo mal en cómo estamos gestionando ese dato.

En nuestro caso el dato venía de otro servicio interno. Mi primera opción fue pedirle a Cline que hiciera un barrido desde que obtenemos la fecha hasta que la mostramos por pantalla, para ver si estábamos haciendo una conversión errónea:

given the dateField from the serviceResponse, find all usages all the way to the controllers and find possible issues where date becomes similar to 1970

La respuesta fue rápida pero desconcertante, no estábamos haciendo nada particularmente importante con las fechas, con lo cual la respuesta tenía que venir de los datos del servidor.

Los tests son (casi) gratis

Los test unitarios nos permiten aislar un bloque de código, controlar la entrada y comprobar que la salida corresponde a la entrada que esperábamos. En mi caso, el primer paso fue crear un test para comprobar que no estábamos modificando las fechas en el código que interactuaba con el servidor.

create a unit test on the ServiceClass that confirms that if the service returns a 2025 date the output is NOT a 1970 date

La combinación de Claude Sonnet 4 y Cline hace que un prompt muy parecido a ese sea todo lo que necesitamos para generar un test que hace exactamente lo que le hemos pedido. El test, por supuesto, fue positivo, lo que confirmaba que el dato venía mal del servidor. Este test podemos tratarlo como algo puntual, o hacer que pase a formar parte de nuestra suite de pruebas.

Código aburrido

Mirando las fechas con detalle, no eran exactamente 1/1/1970, sino que tenían pequeñas variaciones, lo que me hizo pensar que estábamos recibiendo datos, pero no eran los adecuados, y que el resultado se estaba procesando en segundos en vez de en milisegundos.

Otra ventaja que nos proporcionan los LLMs es que son muy buenos escribiendo código «aburrido», y aunque a veces se equivocan [como vimos en la parte 3 también con problemas con horas] nos permiten crear pequeñas utilidades o scripts que de otra manera no habríamos ni siquiera considerado.

Con la lista de fechas en la mano, me dispuse a crear un pequeño script que confirmara mi teoría:

create a script that, given the following dates in 1970, converts them to epoch, multiply by 1000 and convert them back, to confirm they generate dates in 2025

El script generado no solamente convierte las fechas sino además me proporcionó una salida visual que confirmaba mi teoría. Las fechas del servicio venían en segundos, no en milisegundos.

Conclusiones: Mantener el balance

Con estos tres escenarios podemos ver que nuestra relación con los LLMs puede ser más eficiente que hacer preguntas genéricas o pedirles que «nos hagan el trabajo», y que la capacidad de generar código no solamente se puede aplicar al código de producción, sino que podemos generar toda una serie de tests, utilidades y scripts que pueden ser efímeros o formar parte de nuestra caja de herramientas.

Lo mismo aplica a herramientas existentes, si tenemos una serie de scripts o utilidades que hemos desarrollado con el tiempo, o conocemos utilidades como jq, podemos guiar a los LLMs para que las usen, y puede ser tan sencillo como decirles el comando para ejecutar la utilidad o el comando para mostrar la documentación (usando parámetros como «–help» o «man command»).

Estoy convencido de que alguien que programe mejor que yo habría sido capaz de encontrar respuestas a estas soluciones más rápido y sin ayuda de los LLMs, y también estoy cada vez más convencido de que esa persona encontraría maneras más imaginativas de usar los LLMs en su flujo de trabajo. Escribamos código aburrido.

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.