¿Qué es un AI Agent? Explicación detallada con ejemplos en Java
1. Descripción general
“AI Agent” es una palabra clave que aparece por todas partes pero cuya definición suele ser vaga: algunos llaman agent a cualquier chatbot, otros solo consideran agent verdadero a los sistemas complejos de múltiples pasos. Este artículo te explicará qué es un AI Agent, cuáles son sus componentes obligatorios y el mecanismo del bucle que hay detrás. Después de leerlo, serás capaz de construir por ti mismo un agente mínimo en Java puro para entender cómo funciona en su “esencia”, y luego escalarlo a una versión que use un LLM real con Spring AI.
2. ¿Qué es un AI Agent?
Definición concisa: Un AI Agent es un sistema que usa un LLM como cerebro para decidir automáticamente qué acciones tomar con el objetivo de alcanzar una meta, en lugar de simplemente responder una pregunta y detenerse.
Diferencia con un chatbot convencional:
| Característica | Chatbot común | AI Agent |
|---|---|---|
| Número de iteraciones | 1 iteración: pregunta → respuesta | Múltiples pasos: observa → razona → actúa → repite |
| ¿Llama a tools/APIs? | Generalmente no | Sí, decide automáticamente qué tool usar |
| ¿Tiene objetivo a largo plazo? | No | Sí, el agente se divide el trabajo en pasos para alcanzar el objetivo |
| Ejemplo | “¿Qué tiempo hace en Hanói?” → devuelve descripción de texto | “Reserva el vuelo más barato Hanói - Ciudad Ho Chi Minh para mañana” → busca vuelos, compara precios, reserva, confirma |
En otras palabras, un LLM es solo un “cerebro de lenguaje” que sabe generar texto. Un agente transforma ese cerebro en una entidad capaz de actuar en el mundo real (llamar APIs, leer archivos, consultar bases de datos) y repetir automáticamente el proceso de razonamiento hasta completar la tarea.
3. Componentes esenciales de un AI Agent
Un agente mínimo necesita 4 componentes. Sin uno de ellos, el sistema es solo un chatbot “disfrazado” de agente.
3.1. LLM – el cerebro que razona
El LLM recibe un prompt (que incluye el objetivo, el historial y la lista de tools disponibles) y genera la siguiente decisión: responder directamente o llamar a algún tool, con qué parámetros.
3.2. Tool (function calling) – las manos del agente
Un tool es una función específica que el agente puede invocar: consultar un pedido, llamar a una API del clima, consultar una base de datos. Esta es la parte que convierte al agente de “saber hablar” a “saber actuar”.
public interface Tool {
String name();
boolean canHandle(String input);
String execute(String input);
}
canHandle verifica si este tool puede procesar la entrada actual; execute realiza la acción y devuelve el resultado (observation).
3.3. Memory – la memoria
El agente necesita recordar lo que ha hecho en pasos anteriores (historial de conversación, resultados de tools invocados) para evitar repetir acciones inútiles. La memoria puede ser tan simple como una List<String> que almacene un log de pasos, o más compleja como un vector store para recuperar información relevante.
3.4. Control loop – el bucle de control
Esta es la parte “motor” que ejecuta todo el proceso: itera sobre los pasos Thought → Action → Observation hasta alcanzar el objetivo o agotar el número de pasos permitidos. La siguiente sección profundizará en este mecanismo.
4. El bucle ReAct: cómo el agente “piensa y luego actúa”
ReAct (Reasoning + Acting) es el modelo de bucle más común para agentes:
- Thought: El LLM razona “¿qué debo hacer a continuación?”
- Action: selecciona un tool e lo invoca con los parámetros adecuados.
- Observation: recibe el resultado del tool y lo vuelve a integrar en el contexto.
- Repite desde el paso 1 hasta que el LLM decide que tiene suficiente información para la respuesta final, o se agota el
maxSteps.
Para entender este mecanismo sin necesidad de llamar a un LLM real (que gasta claves API), construiremos nosotros mismos un agente mini en Java puro, usando if-else en lugar de un LLM real.
public class SimpleAgent {
private final List<Tool> tools;
private final int maxSteps;
public SimpleAgent(List<Tool> tools, int maxSteps) {
this.tools = tools;
this.maxSteps = maxSteps;
}
public String run(String goal) {
String observation = goal;
for (int step = 1; step <= maxSteps; step++) {
Tool chosen = tools.stream()
.filter(t -> t.canHandle(observation))
.findFirst()
.orElse(null);
if (chosen == null) {
return "No hay ningún tool que pueda procesar: " + observation;
}
System.out.printf("Paso %d - Thought: necesito usar el tool '%s'%n", step, chosen.name());
String result = chosen.execute(observation);
System.out.printf("Paso %d - Observation: %s%n", step, result);
return result; // ejemplo simple: terminado después de 1 paso
}
return "Se agotaron los pasos permitidos (" + maxSteps + ") sin completar la tarea.";
}
}
maxSteps es el “presupuesto” de pasos de iteración — es obligatorio tener uno para que el agente no corra infinitamente si nunca alcanza el objetivo. El bucle for simula Thought (seleccionar tool), Action (execute), Observation (el resultado impreso en consola).
Tres tools de ejemplo:
public class WeatherTool implements Tool {
@Override
public String name() {
return "WeatherTool";
}
@Override
public boolean canHandle(String input) {
return input.toLowerCase().contains("clima") || input.toLowerCase().contains("tiempo");
}
@Override
public String execute(String input) {
return "Hanói hoy 28°C, cielo nublado.";
}
}
public class OrderStatusTool implements Tool {
@Override
public String name() {
return "OrderStatusTool";
}
@Override
public boolean canHandle(String input) {
return input.toLowerCase().contains("pedido");
}
@Override
public String execute(String input) {
return "Pedido #1023 está siendo enviado, se espera entregar en 2 días.";
}
}
public class CalculatorTool implements Tool {
@Override
public String name() {
return "CalculatorTool";
}
@Override
public boolean canHandle(String input) {
return input.matches(".*\\d+\\s*\\+\\s*\\d+.*");
}
@Override
public String execute(String input) {
String digits = input.replaceAll("[^0-9+]", "");
String[] parts = digits.split("\\+");
int sum = Integer.parseInt(parts[0]) + Integer.parseInt(parts[1]);
return "Resultado: " + sum;
}
}
Prueba de invocación:
SimpleAgent agent = new SimpleAgent(
List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
3
);
String result = agent.run("Dime el clima en Hanói hoy");
System.out.println(result);
Resultado:
Paso 1 - Thought: necesito usar el tool 'WeatherTool'
Paso 1 - Observation: Hanói hoy 28°C, cielo nublado.
Hanói hoy 28°C, cielo nublado.
Escribimos pruebas para confirmar el comportamiento de seleccionar el tool correcto y manejar casos en que ningún tool es adecuado:
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class SimpleAgentTest {
@Test
void shouldPickWeatherToolWhenGoalMentionsWeather() {
SimpleAgent agent = new SimpleAgent(
List.of(new CalculatorTool(), new WeatherTool(), new OrderStatusTool()),
3
);
String result = agent.run("Dime el clima en Hanói hoy");
assertThat(result).contains("Hanói");
}
@Test
void shouldReturnErrorWhenNoToolMatches() {
SimpleAgent agent = new SimpleAgent(List.of(new CalculatorTool()), 3);
String result = agent.run("Cántame una canción");
assertThat(result).startsWith("No hay ningún tool que pueda procesar");
}
}
Este ejemplo usa if-else en lugar de un LLM, por lo que el “razonamiento” es muy rígido. El agente real difiere en que: en lugar de un canHandle rígido, el LLM lee por sí mismo la descripción del tool y decide qué tool usar, extrayendo automáticamente los parámetros de la pregunta natural.
5. Construir un agente real con Spring AI y function calling
Para ver cómo decide un LLM real, usamos Spring AI — un framework que integra modelos AI en Spring Boot con soporte integrado para function calling.
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>1.0.0</version>
</dependency>
Configura la clave API y el modelo:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini
Declara un tool usando la anotación @Tool, similar al rol de OrderStatusTool en el ejemplo anterior, pero esta vez el LLM real lo invocará:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class OrderTools {
@Tool(description = "Consulta el estado de un pedido según su código de pedido")
public String getOrderStatus(String orderId) {
// en la práctica, llamaría a OrderService / base de datos aquí
return "Pedido " + orderId + " está siendo enviado, se espera entregar en 2 días.";
}
}
description es extremadamente importante: el LLM se basa en esta descripción para decidir si debe invocar este tool y qué parámetros pasar.
El servicio usa ChatClient para adjuntar el tool al agente:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class OrderAssistantService {
private final ChatClient chatClient;
public OrderAssistantService(ChatClient.Builder builder, OrderTools orderTools) {
this.chatClient = builder
.defaultTools(orderTools)
.build();
}
public String ask(String question) {
return chatClient.prompt(question).call().content();
}
}
Cuando invocas ask("¿Dónde está mi pedido ORD-1023?"), Spring AI internamente:
- Envía la pregunta junto con la descripción del tool
getOrderStatusal modelo. - El modelo devuelve la decisión “invocar
getOrderStatusconorderId="ORD-1023"” en lugar de responder inmediatamente. - Spring AI invoca automáticamente el método Java correspondiente y obtiene el resultado.
- Envía ese resultado de vuelta al modelo para que escriba la respuesta final en lenguaje natural.
Tu pedido ORD-1023 está siendo enviado actualmente y se espera entregarlo en 2 días más.
Esos 4 pasos anteriores constituyen un bucle ReAct completo, con la diferencia de que Spring AI oculta el bucle for — no necesitas escribir el control loop tú mismo como en el ejemplo Java puro de la sección 4.
Nota: La versión de
spring-ai-starter-model-openaiy el nombre del artefacto pueden cambiar con el tiempo; siempre verifica el BOM correspondiente con la versión de Spring Boot que uses antes de agregar la dependencia.
6. Tipos comunes de agentes
- Single-agent ReAct: un agente único que itera Thought-Action-Observation. Adecuado para tareas de complejidad media, como los dos ejemplos anteriores.
- Plan-and-Execute: el agente crea un plan detallado (lista de pasos) primero, luego ejecuta cada paso sin razonar constantemente como en ReAct. Adecuado para tareas multi-paso con estructura clara (por ejemplo: “crear un informe de 5 secciones”).
- Multi-agent: múltiples agentes especializados colaboran, típicamente con un agente “Supervisor” que coordina los agentes “Worker” (por ejemplo: agente de búsqueda, agente de escritura de texto, agente de verificación de datos), y los resultados se sintetizan.
Elegir qué tipo depende de la complejidad de la tarea: usa single-agent para tareas simples, y multi-agent para tareas con múltiples dominios diferentes (búsqueda + escritura + verificación) para facilitar el control y el debugging de cada parte.
7. Errores comunes y cuándo NO usar un agente
- Bucles infinitos, quema de tokens: si no estableces
maxStepso un presupuesto de tokens, el agente puede iterar indefinidamente porque el LLM “cree” que no ha terminado. Siempre establece un límite claro de pasos como enSimpleAgenten la sección 4. - Descripción de tool vaga: si la
descriptiondel tool no es clara, el LLM fácilmente puede invocar el tool incorrecto o pasar parámetros de tipo incorrecto. Escribe descripciones específicas, indicando claramente el formato esperado de los parámetros. - Falta de guardrails para acciones peligrosas: si el agente puede invocar un tool que elimina datos, transfiere dinero, etc. sin un paso de confirmación, es un riesgo grande. Siempre añade una capa de revisión (human-in-the-loop o verificación basada en reglas) antes de ejecutar acciones que no se pueden deshacer.
- Exceso de ingeniería para tareas simples: si la pregunta solo necesita 1 llamada al LLM para obtener una respuesta (por ejemplo, traducir una frase), no construyas un bucle agente completo con function calling. Solo añade latencia extra y cuesta más llamar al modelo múltiples veces innecesariamente.
Nota: si tu sistema no necesita invocar acciones externas (APIs, bases de datos) y solo necesita generar texto una vez, ese es un problema para un LLM normal — no para un agente. No agregues complejidad innecesaria.
8. Conclusión
Los AI Agents difieren de los chatbots convencionales en su capacidad de decidir automáticamente qué acciones tomar e iterar el proceso de razonamiento hasta alcanzar el objetivo, basándose en cuatro componentes: LLM, tools, memoria y control loop. El bucle ReAct (Thought-Action-Observation) es el mecanismo común detrás de la mayoría de los agentes actuales, y puedes simularlo tú mismo en Java puro para entender su naturaleza antes de pasar a frameworks como Spring AI para producción. Lo más importante: siempre establece un límite de pasos, escribe descripciones claras de tools y añade guardrails para acciones que no se pueden deshacer — los agentes son poderosos pero también pueden “excederse” si carecen de control.