El agente esquizofrénico
El otro día estaba trabajando con un agente que venía funcionando bien, haciendo lo suyo sin molestar a nadie. Me pareció buena idea agregarle un segundo modo de operación: misma base, bastante lógica compartida, un flag al principio para elegir por dónde ir. Lo clásico, lo que harías con una función que ya tenés andando y querés que haga “una cosita más”.
El output empezó a salir raro. No roto, porque seguía devolviendo algo con forma de respuesta, pero raaaarooooo. Como cuando le pedís a alguien que te explique algo y notás que está respondiendo en automático sin estar del todo ahí. El agente no fallaba, simplemente no era bueno en ninguna de las dos cosas.
Me llevó un rato entender que el problema no era el flag ni la lógica que había agregado. El problema era más básico: le estaba pidiendo que sostenga dos formas de pensar al mismo tiempo. Y eso, en agentes, se paga igual que en código.
El viejo conocido que nos olvidamos de invitar
Single Responsibility es uno de esos principios que aprendemos temprano en la carrera y que, después de un tiempo, aplicamos casi sin pensar. Una función hace una cosa. Una clase tiene una razón para cambiar. Un módulo tiene un scope claro. Lo internalizamos tanto que nos parece obvio, hasta que nos sentamos a armar agentes y lo tiramos por la ventana como si no aplicara.
La justificación siempre se parece: “comparten el mismo contexto”, “es más fácil mantener uno solo”, “total es un prompt nomás, no es como si fuera código de verdad”. Te tengo malas noticias, es exactamente como código de verdad, con el agravante de que cuando falla, falla de manera no determinista, así que encima te cuesta más encontrar dónde está el problema.
Allen Chan, que es Distinguished Engineer en IBM trabajando con watsonx, publicó hace poco una serie documentando quince anti-patterns de agentes de IA. El primero de la lista se llama “Monolithic Mega-Prompt” y es básicamente lo que en objetos llamamos god object: un agente con 500 líneas de instrucciones procedurales que intenta cubrir todos los casos posibles. Después viene “Tool Soup”, que es lo que pasa cuando le conectás más de 50 herramientas sin criterio de segregación (Chan agarró la calculadora: las definiciones solas de esas tools se comen unos 72K tokens, que es el 60% de una ventana de 128K antes de que el agente procese siquiera tu pedido). Si alguna vez trabajaste con SOLID, estos nombres te van a sonar familiares.
Dónde se empieza a notar
Pensalo con un ejemplo concreto: un agente que hace code review y además genera tests. Suena razonable porque los dos trabajan sobre el mismo código y necesitan entender la codebase, así que alguien decide unificarlos con un parámetro de modo.
El tema es que son formas de pensar opuestas. Cuando estás haciendo code review, tu cabeza está en modo crítico: buscás lo que puede salir mal, cuestionás decisiones, levantás riesgos que no son evidentes a primera vista. Cuando generás tests, necesitás entender la intención del código y construir casos que la validen, es un modo constructivo. Meterlos juntos hace que uno contamine al otro de maneras sutiles: te salen reviews más blandos porque el modo constructivo suaviza las observaciones, o te salen tests que parecen más un ataque que una validación porque el modo crítico se filtró donde no debía.
Otro caso que se ve seguido: un agente que le explica la arquitectura del sistema a devs nuevos y también responde preguntas de soporte a clientes. Son dos audiencias que necesitan cosas completamente distintas. Al dev nuevo le tenés que hablar con la jerga del equipo, mostrar las decisiones de diseño, explicar por qué algo se hizo así y no de otra manera. Al cliente le tenés que hablar en términos de su problema, darle pasos concretos y evitar el detalle interno que no le aporta nada. Con un flag mode: 'dev' | 'customer' parece que estás cubierto, hasta que el agente empieza a explicarle al cliente detalles de implementación que no pidió, o a simplificarle la arquitectura al dev nuevo como si estuviera leyendo un FAQ de soporte.
Por qué pasa esto (la parte técnica, cortita)
Hay un paper de Liu et al. (y otros, si tu latín es tan malo como el mío) del 2023 que se llama “Lost in the Middle” que lo explica bastante bien: cuanta más información metés en el contexto de un modelo, peor recupera lo que está en el medio. Con 20 documentos, la accuracy en la posición 1 anda por el 80%, pero en la posición 10 baja a 55%. Cada instrucción adicional que le metés a un agente compite por atención con las demás.
Cuando tenés dos frameworks operativos en el mismo prompt, no es que el modelo “elige mal”. Lo que pasa es que la información de un framework interfiere con la del otro, y el modelo queda resolviendo ambigüedades que vos le metiste por diseño. El resultado es ese output tibio que no termina de convencer en ninguno de los dos modos.
La regla, que resulta ser la de siempre
La conclusión que saqué es la misma de siempre, seguro te suena: si dos tareas requieren modos mentales distintos, van en agentes distintos. No un flag. No un parámetro de modo. No un switch prende y apaga. Agentes distintos, aunque compartan el 60% del contexto, aunque parezca más trabajo en el momento de armarlos.
“Más fácil” es siempre en el momento de armarlo. Después cada ajuste que hagas para mejorar un caso va a degradar el otro, que es exactamente lo que pasa con esa función con el if que alguien metió hace tres años y hoy nadie quiere tocar.
Lo que me parece más interesante de todo esto es que nadie está hablando de SRP como principio de diseño de agentes. Hablamos de orquestación, de sistemas multi-agente, de mil conceptos nuevos. Pero el principio más básico, el de que cada agente tenga una sola razón para cambiar, queda afuera de la conversación como si fuera algo del siglo pasado que ya no aplica.
Al final es lo mismo de siempre. Una función con dos responsabilidades te da bugs. Un agente con dos frameworks te da un output tibio que no le sirve bien a nadie, y que encima cuesta el doble rastrear porque el modelo no te dice “fallé”, simplemente no te convence.
Deja un comentario