- ¿Qué hace que algo sea un agente de IA?
- Paso 1: Planificación — La capa de razonamiento
- Paso 2: Uso de herramientas — Conectando el modelo con el mundo
- Paso 3: Ejecución de código — Ejecutando código de forma segura
- Paso 4: Evaluación — Saber cuándo el agente tiene razón
- Elegir un modelo para el razonamiento del agente
- Uniendo todo — Un agente funcional mínimo
- FAQ
- Artículos recomendados
Construir un agente de IA empieza con una idea simple: darle al modelo la capacidad de tomar acciones, observar los resultados y repetir el ciclo. El modelo razona sobre un objetivo, decide qué hacer a continuación, llama a una herramienta, obtiene un resultado y continúa hasta que la tarea está completa. Ese bucle de leer-evaluar-actuar es el núcleo de cualquier agente que construyas, ya sea un llamador de herramientas de un solo paso o un sistema de varios pasos que escribe código, lo ejecuta y corrige sus propios errores.
Este tutorial recorre los cuatro componentes que hacen funcionar a un agente: planificación, uso de herramientas, ejecución de código y evaluación. Al final tendrás un modelo mental funcional y una implementación mínima que puedes ejecutar hoy mismo.
¿Qué hace que algo sea un agente de IA?
Un agente de IA se diferencia de una llamada básica a un LLM en un aspecto crítico: toma acciones y observa sus resultados.
Una conversación simple de chat produce texto. Un agente usa ese texto para llamar a una función, consultar una API, ejecutar un comando o leer un archivo — y retroalimenta el resultado como entrada para el siguiente paso. Este bucle de retroalimentación es lo que hace que los agentes sean útiles para tareas que no pueden responderse en una sola respuesta.
Tres capacidades definen a un agente:
- Llamada a herramientas — invocar funciones definidas por su nombre con argumentos estructurados
- Acumulación de contexto — llevar los resultados de las herramientas hacia adelante a medida que la conversación crece
- Bucle orientado a objetivos — continuar hasta que se cumpla una condición de parada, no solo después de una respuesta
Si un sistema tiene las tres, estás construyendo un agente. Si falta la acumulación de contexto o el bucle, es un chatbot aumentado con herramientas.
La misma arquitectura maneja una amplia gama de tareas: agentes de investigación que buscan y sintetizan, agentes de codificación que escriben y ejecutan código, agentes de datos que consultan bases de datos y generan informes. El patrón de implementación es casi idéntico en cada caso.
Paso 1: Planificación — La capa de razonamiento
La capa de planificación es donde el modelo decide qué hacer a continuación. No necesitas un modelo planificador separado — el mismo modelo que genera texto puede razonar sobre un objetivo y producir una acción estructurada (una llamada a herramienta) en cada paso.
El prompt del sistema hace la mayor parte del trabajo. Un buen prompt de planificación le dice al modelo:
- Hacia qué objetivo está trabajando
- Qué herramientas están disponibles y qué hace cada una
- Qué formato usar al llamar a una herramienta
- Cuándo detenerse (la condición de finalización)
Con la API LLM de Novita — que es compatible con OpenAI — esto se ve así:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.novita.ai/v3/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
tools = [
{
"type": "function",
"function": {
"name": "run_code",
"description": "Ejecuta código Python y devuelve stdout/stderr.",
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Código Python a ejecutar"
}
},
"required": ["code"]
}
}
}
]
messages = [
{
"role": "system",
"content": "Eres un asistente de codificación. Usa la herramienta run_code para ejecutar Python y resolver tareas. Detente cuando tengas una respuesta final."
},
{
"role": "user",
"content": "Encuentra todos los números primos menores de 50."
}
]
response = client.chat.completions.create(
model="meta-llama/llama-3.1-8b-instruct",
messages=messages,
tools=tools,
tool_choice="auto",
)
El modelo devuelve una llamada a herramienta — un objeto JSON con el nombre de la función y los argumentos — en lugar de una respuesta en texto plano. Tu código extrae esto, ejecuta la función y vuelve al bucle con el resultado añadido a messages.
Paso 2: Uso de herramientas — Conectando el modelo con el mundo
Las herramientas son lo que los agentes realmente hacen. Sin ellas, el modelo solo puede producir texto. Con ellas, puede leer archivos, llamar a APIs, ejecutar código, buscar en bases de datos o disparar cualquier función que definas.
Definiendo herramientas
Cada herramienta es un esquema JSON que describe una función. El modelo lee este esquema para formatear su llamada correctamente. Algunas reglas prácticas:
- Mantén las descripciones cortas y precisas. El modelo las lee en cada paso. Las descripciones verbosas desperdician tokens.
- Haz que los nombres de los parámetros coincidan con lo que la función realmente acepta. Las discrepancias entre el esquema y tu implementación causan fallos silenciosos.
- Devuelve resultados estructurados. Si tu herramienta devuelve HTML crudo o prosa sin formato, el modelo desperdicia tokens analizándolo. Devuelve cadenas limpias o JSON con etiquetas claras.
El bucle de llamada a herramientas
Después de que el modelo hace una llamada a herramienta, tu código:
- Analiza el nombre de la herramienta y los argumentos de la respuesta
- Busca la función Python coincidente
- La llama con los argumentos analizados
- Añade el resultado a
messagesconrole: "tool" - Realiza otra solicitud de completación
El bucle continúa hasta que el modelo devuelve un mensaje simple sin llamada a herramienta (la condición de finalización).
import json
def run_agent_loop(client, model, messages, tools, tool_fns, max_steps=10):
for _ in range(max_steps):
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
tool_choice="auto",
)
choice = response.choices[0]
# Hecho — sin llamada a herramienta
if choice.finish_reason == "stop":
return choice.message.content
# Ejecuta la llamada a herramienta
tool_call = choice.message.tool_calls[0]
fn_name = tool_call.function.name
fn_args = json.loads(tool_call.function.arguments)
result = tool_fns[fn_name](**fn_args)
# Añade tanto la llamada a herramienta del modelo como el resultado
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result),
})
return "Se alcanzó el máximo de pasos sin una respuesta final."
Este es el bucle completo del agente. La guarda max_steps evita bucles descontrolados — si el modelo se queda atascado llamando a la misma herramienta repetidamente, el bucle termina en lugar de ejecutarse indefinidamente. Todo lo demás — orquestación de múltiples herramientas, memoria, streaming — se construye sobre este patrón.
Patrones comunes de herramientas
Acceso a archivos: Lee y escribe archivos dentro del directorio de trabajo del agente. Devuelve el contenido de los archivos como cadenas.
Búsqueda web: Devuelve los resultados de búsqueda como una lista de objetos {title, url, snippet}. Mantén los fragmentos cortos.
Ejecución de código: Ejecuta código en un entorno aislado y devuelve stdout/stderr (cubierto en la siguiente sección).
Consulta de datos estructurados: Consulta una base de datos o API y devuelve registros como JSON. El modelo se encarga de la síntesis.
Cada herramienta debe hacer una cosa y devolver una salida determinista dados los mismos insumos. Los agentes que intentan empaquetar demasiada lógica en una sola herramienta se vuelven más difíciles de depurar y evaluar.
Paso 3: Ejecución de código — Ejecutando código de forma segura
Si tu agente genera y ejecuta código, necesitas un entorno aislado. Ejecutar código generado por el modelo directamente en tu máquina es un riesgo de seguridad: el modelo podría producir comandos destructivos de shell, bucles infinitos o código que lea archivos sensibles a los que no debería acceder.
El enfoque estándar es un sandbox — un entorno Linux aislado y de corta duración donde el agente puede ejecutar código libremente sin afectar al sistema anfitrión. El Agent Sandbox de Novita está diseñado exactamente para este caso de uso: un entorno de ejecución en la nube que se inicia en menos de 200ms, ejecuta cualquier código que el agente produzca y descarta el entorno cuando la sesión termina.
Así es como se implementaría la herramienta run_code de la sección de planificación usando el sandbox:
from novita_sandbox.code_interpreter import Sandbox
sandbox = Sandbox.create()
def run_code(code: str) -> str:
result = sandbox.run_code(code)
output = ""
if result.logs.stdout:
output += "\n".join(result.logs.stdout)
if result.logs.stderr:
output += "\nSTDERR: " + "\n".join(result.logs.stderr)
return output or "(sin salida)"
Cuando el agente llama a run_code, esta función ejecuta el código en el entorno aislado y devuelve stdout/stderr como una cadena. El agente ve el resultado y decide qué hacer a continuación — ya sea corregir un error, continuar con la salida o declarar la tarea como terminada.
Esta arquitectura separa limpiamente la capa de razonamiento de la capa de ejecución. El LLM planifica y llama a herramientas; el sandbox ejecuta código sin exponer nada fuera del contenedor.
Qué maneja el Sandbox
- Comandos de shell (
bash,git,python,node) - Operaciones del sistema de archivos dentro del sandbox
- Solicitudes de red desde dentro del contenedor
- Tareas en segundo plano de larga duración con recuperación asíncrona de resultados
- Pausa y reanudación para flujos de trabajo de múltiples sesiones
Lo que no permite: acceso al sistema anfitrión, a otros sandboxes o a credenciales que no hayas pasado explícitamente. Este aislamiento es lo que hace seguro dejar que el modelo escriba y ejecute código arbitrario.
Paso 4: Evaluación — Saber cuándo el agente tiene razón
La evaluación es el paso que más se salta en el desarrollo de agentes. Un agente que produce una salida no es lo mismo que un agente que produce una salida correcta.
Tres niveles de evaluación
1. Comprobaciones deterministas
Para tareas con respuestas correctas conocidas — analizar una fecha, calcular un valor, extraer un campo — escribe pruebas unitarias contra la salida del agente. Son baratas de ejecutar y detectan regresiones rápidamente.
2. Comprobaciones basadas en ejecución
Para agentes de codificación, la señal más fiable es: ¿el código realmente se ejecuta y produce el resultado correcto? Ejecuta la salida del agente en el sandbox y compara stdout con los valores esperados. Esto es más sólido que comparar el texto del código porque prueba el comportamiento, no la sintaxis.
def evaluate_code_task(agent_code: str, expected_stdout: str) -> bool:
result = run_code(agent_code)
return result.strip() == expected_stdout.strip()
3. LLM como juez
Para tareas abiertas — escribir un resumen, responder una pregunta de investigación — usa una segunda llamada al LLM para calificar la salida según una rúbrica. Mantén la rúbrica específica: “¿La respuesta incluyó un ejemplo de código funcional?” es mejor que “¿La respuesta es buena?” Las rúbricas vagas producen puntuaciones inconsistentes.
Rastreo de modos de fallo
Los agentes fallan en un conjunto recurrente de patrones:
| Fallo | Causa | Solución |
|---|---|---|
| Bucle de llamada a herramienta | El modelo sigue llamando a la misma herramienta | Establece max_steps; registra los pasos intermedios |
| Desajuste de argumentos | El esquema no coincide con la implementación | Valida los argumentos de la herramienta antes de llamar |
| Desbordamiento de contexto | La sesión crece más allá del límite de contexto del modelo | Resume o trunca resultados de herramientas antiguos |
| Nombre de herramienta alucinado | El modelo inventa una herramienta que no existe | Usa una lista estricta de herramientas con tool_choice: "auto" |
La evaluación no es una fase separada al final — es parte del desarrollo. Cada vez que extiendes las capacidades de un agente, escribe al menos una prueba que detecte una regresión en el nuevo comportamiento.
Elegir un modelo para el razonamiento del agente
La mayoría de los tutoriales usan por defecto modelos de código cerrado para el razonamiento del agente. Eso funciona bien para prototipado, pero en producción — donde un agente puede hacer 20 llamadas al LLM por tarea — el costo del modelo y los límites de tasa se convierten en restricciones reales.
Los modelos de código abierto alojados a través de la API LLM de Novita igualan cada vez más el rendimiento de los de código cerrado en las tareas que los agentes realmente necesitan: llamada a funciones, salida estructurada y razonamiento de múltiples pasos. Modelos como Llama 3.3 70B y DeepSeek-V3 se comparan con los niveles de GPT-4 en codificación y seguimiento de instrucciones, mientras que se ejecutan a una fracción del costo. Para cargas de trabajo agentivas de alto rendimiento, esta diferencia se acumula rápidamente.
La API de Novita es completamente compatible con OpenAI, por lo que cambiar de un modelo de código cerrado a uno de código abierto es un cambio de una línea:
# Antes
model = "gpt-4o"
# Después — mismo contrato de API, menor costo
model = "meta-llama/llama-3.3-70b-instruct"
Para bucles de agente sensibles a la latencia, modelos más pequeños como meta-llama/llama-3.1-8b-instruct responden más rápido y cuestan menos por paso, lo que importa cuando cada tarea de usuario implica muchas llamadas secuenciales. Un patrón práctico: usa un modelo más rápido y pequeño para los pasos de enrutamiento y clasificación, y un modelo más grande para pasos de razonamiento complejo que necesitan más capacidad.
La biblioteca de modelos de Novita también incluye opciones sólidas para subtareas agentivas específicas — modelos de visión para resultados de herramientas basados en imágenes, modelos de embedding para recuperación y modelos especializados en código para agentes de codificación — todos accesibles desde el mismo endpoint de API.
Uniendo todo — Un agente funcional mínimo
Aquí tienes un agente completo que toma un problema matemático, genera código Python para resolverlo, lo ejecuta en un sandbox y devuelve el resultado verificado:
import os
import json
from openai import OpenAI
from novita_sandbox.code_interpreter import Sandbox
client = OpenAI(
base_url="https://api.novita.ai/v3/openai",
api_key=os.environ["NOVITA_API_KEY"],
)
sandbox = Sandbox.create()
def run_code(code: str) -> str:
result = sandbox.run_code(code)
stdout = "\n".join(result.logs.stdout)
stderr = "\n".join(result.logs.stderr)
return stdout or stderr or "(sin salida)"
tool_fns = {"run_code": run_code}
tools = [
{
"type": "function",
"function": {
"name": "run_code",
"description": "Ejecuta código Python. Devuelve stdout o stderr.",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"}
},
"required": ["code"]
}
}
}
]
messages = [
{
"role": "system",
"content": "Resuelve la tarea escribiendo y ejecutando código Python. Usa run_code para ejecutarlo. Detente cuando tengas una respuesta verificada."
},
{
"role": "user",
"content": "¿Cuál es la suma de todos los números de Fibonacci pares menores de 1.000.000?"
}
]
for _ in range(10):
response = client.chat.completions.create(
model="meta-llama/llama-3.1-8b-instruct",
messages=messages,
tools=tools,
tool_choice="auto",
)
choice = response.choices[0]
if choice.finish_reason == "stop":
print(choice.message.content)
break
tool_call = choice.message.tool_calls[0]
fn_args = json.loads(tool_call.function.arguments)
result = tool_fns[tool_call.function.name](**fn_args)
messages.append(choice.message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
sandbox.kill()
Esto cubre el bucle completo del agente en aproximadamente 50 líneas: planificación, llamada a herramientas, ejecución, acumulación de contexto, guarda de máximo de pasos y limpieza del sandbox. El agente escribe código, lo ejecuta, ve la salida y continúa o da una respuesta final.
Para extenderlo: añade más herramientas a tool_fns y tools, cambia a un modelo más grande para tareas más difíciles, o reemplaza el run_code del sandbox por cualquier otra función — una búsqueda web, una consulta a base de datos, una llamada a API. El bucle sigue siendo el mismo.
FAQ
¿Cuál es la diferencia entre un agente de IA y un chatbot?
Un chatbot genera respuestas de texto. Un agente toma acciones — llamar a herramientas, ejecutar código, consultar APIs — y usa los resultados para seguir razonando hacia un objetivo. La presencia de llamadas a herramientas y un bucle de retroalimentación es lo que hace que algo sea un agente.
¿Necesito un framework como LangChain para construir un agente de IA?
No. El bucle central del agente es lo suficientemente simple como para implementarlo directamente contra cualquier API compatible con OpenAI. Los frameworks añaden valor para orquestación compleja (sistemas multi-agente, memoria persistente, salida en streaming), pero añaden capas de abstracción que pueden dificultar la depuración. Empieza con la API pura, luego añade un framework si realmente necesitas lo que ofrece.
¿Cómo evito que el agente ejecute código peligroso?
Usa un entorno de ejecución en sandbox. El código del agente se ejecuta dentro de un contenedor aislado que no tiene acceso a tu sistema anfitrión ni a otros procesos. El sandbox se descarta después de que la sesión termina, por lo que cualquier cambio que haga el agente está limitado a ese contenedor.
¿Pueden los modelos de código abierto manejar la llamada a funciones de forma fiable?
Sí — con matices según el tamaño del modelo. Llama 3.3 70B, DeepSeek-V3 y Qwen 2.5 72B manejan bien la llamada estructurada a herramientas. Los modelos más pequeños (7B–8B) funcionan para esquemas de herramientas simples y bien definidos, pero pueden tener dificultades con instrucciones ambiguas o cadenas de razonamiento complejas de varios pasos. Para agentes en producción, prueba en tu distribución real de tareas antes de comprometerte con un modelo.
¿Cuántos pasos debe tomar un agente por tarea?
Establece un límite máximo — de 10 a 20 pasos es típico para la mayoría de las tareas. Registra los pasos intermedios para que puedas ver exactamente lo que hizo el agente y depurar fallos. Las tareas que consistentemente necesitan más pasos generalmente se benefician de ser divididas en subagentes o subtareas en lugar de extender el límite del bucle.
