> labs.luisguisado.cloud
Principiante~25 min·por Luis Guisado·actualizado 5 de mayo de 2026

Tu primer agente sin framework: rol, objetivo e instrucciones

Construyes una clase Python que encapsula el rol, el objetivo y las instrucciones de un AI Agent. Sin frameworks. Entiendes qué compone un agente antes de que un framework lo haga invisible.

Costo estimado: free

Aparece en: Agentic AI desde cero para Backend y Cloud Developers

Por qué este lab existe

Tu call_llm.py del Lab 1 termina así:

Archivo: call_llm.py (recap del Lab 1)

MODEL = "llama-3.3-70b-versatile"
SYSTEM_PROMPT = "Eres un experto en AWS que explica con ejemplos breves."
USER_PROMPT = "Explícame qué es AWS Lambda en 3 oraciones."

client = OpenAI(
    base_url="https://api.groq.com/openai/v1",
    api_key=os.environ["GROQ_API_KEY"],
)

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": USER_PROMPT},
    ],
)

Funciona para una tarea. Ahora imagínate que necesitas un segundo caso de uso: el mismo modelo, otras instrucciones, para revisar código. Copias el archivo a review_code.py, cambias SYSTEM_PROMPT, cambias USER_PROMPT, dejas todo lo demás igual. Tercer caso, traducir documentación: repites.

Quedas con tres archivos casi idénticos. El cliente, el modelo y la key se duplican; solo cambian dos strings.

La identidad (el rol, las instrucciones, el objetivo) vive mezclada con la mecánica de la llamada (cliente HTTP, modelo, credenciales). En este lab construyes una clase Python que separa ambas. Esa clase es lo que en el área de IA generativa se llama un AI Agent.

Duración: ~25 min · Nivel: Beginner · Costo: gratis (free tier de Groq).


Lo que vas a tener al terminar

  1. Una clase TechnicalExplainerAgent en agent.py con role, goal e instructions declarados como atributos.
  2. Un script main.py que instancia el agente y lo ejecuta con tres tareas distintas.
  3. El consumo de tokens loggeado por cada tarea, igual que en el Lab 1.
  4. Claridad sobre qué compone un AI Agent: rol, objetivo, instrucciones y acceso a un modelo.

Qué vas a aprender

  • Encapsular la llamada al LLM dentro de una clase con identidad estable.
  • Definir role, goal e instructions como atributos del agente, no como constantes sueltas.
  • Separar la identidad del agente de la mecánica de la llamada al modelo.

Conceptos clave

Un AI Agent es una clase con identidad estable

Qué es: un AI Agent (también llamado agent en inglés o simplemente agente en español; los tres nombres refieren al mismo concepto) es software con un rol fijo, un objetivo declarado y un conjunto de instrucciones que no cambian entre tareas. La clase encapsula esa identidad. La interfaz pública es solo run(task): el llamador no sabe nada del cliente HTTP ni del modelo.

Mini-ejemplo:

class TechnicalExplainerAgent:
    role = "Experto en AWS y cloud computing"
    goal = "Explicar conceptos a backend developers con ejemplos claros"
    instructions = f"Eres un {role}. Tu objetivo: {goal}. Máximo 5 oraciones."

    def run(self, task: str) -> str:
        ...  # llama al modelo y retorna la respuesta

Por qué encapsular en clase

Qué es: sin clase, la identidad del agente vive en constantes globales junto al cliente, el modelo y la key. Cualquier cambio en la mecánica (cambiar de proveedor, ajustar el timeout) toca el mismo archivo donde viven las instrucciones. Encapsulado en clase, la identidad queda adentro de un objeto y la mecánica del lado de la infraestructura. Cambias el modelo o el proveedor sin que el código que usa el agente se entere.

Mini-ejemplo: el llamador no ve la mecánica:

agent = TechnicalExplainerAgent()
respuesta = agent.run("¿Qué es AWS Lambda?")
print(respuesta)

Para profundizar: Robert C. Martin, Clean Architecture: separación de dominio (la identidad del agente) e infraestructura (cliente, credenciales, modelo).


Mapa visual

   main.py

     │  agent = TechnicalExplainerAgent()
     │  result = agent.run("Explícame Lambda")


   ┌──────────────────────────────────────┐
   │  TechnicalExplainerAgent             │
   │                                      │
   │  role: "Experto en AWS..."           │
   │  goal: "Explicar conceptos..."       │
   │  instructions: f"{role}. {goal}..."  │
   │                                      │
   │  run(task) ──────────────────────▶  │──▶ Groq API
   │              ◀────────────────────  │◀── respuesta
   └──────────────────────────────────────┘

     │  return content (str)

   print(result)

El llamador (main.py) no sabe nada del cliente HTTP, la API key ni el modelo. Solo sabe que run() recibe una tarea y devuelve una respuesta.


Punto de partida

Antes de crear archivos nuevos, verifica que el Lab 1 esté funcionando.

cd agentic-ai-lab
source .venv/bin/activate
python call_llm.py

Respuesta esperada: ves una explicación de AWS Lambda y la línea de tokens al final:

Respuesta:
[explicación de Lambda]

Tokens usados | prompt: 35, completion: 78, total: 113

Si el script falla, vuelve al Lab 1 y verifica tu .env y la API key.


Paso 1. Crear la clase del agente

Archivo: agent.py (NUEVO)

import os
from dotenv import load_dotenv
from openai import OpenAI


class TechnicalExplainerAgent:
    role = "Experto en AWS y cloud computing"
    goal = "Explicar conceptos técnicos de cloud a backend developers con ejemplos claros"
    instructions = (
        f"Eres un {role}. "
        f"Tu objetivo: {goal}. "
        "Responde en español. Sé conciso: máximo 5 oraciones. "
        "Usa ejemplos de código cuando aporten."
    )

    def __init__(self):
        load_dotenv()
        self._client = OpenAI(
            base_url="https://api.groq.com/openai/v1",
            api_key=os.environ["GROQ_API_KEY"],
        )
        self.last_usage = None

    def run(self, task: str) -> str:
        response = self._client.chat.completions.create(
            model="llama-3.3-70b-versatile",
            messages=[
                {"role": "system", "content": self.instructions},
                {"role": "user", "content": task},
            ],
            temperature=0.3,
        )
        self.last_usage = response.usage
        return response.choices[0].message.content

Tres cosas para notar:

  1. role, goal e instructions son atributos de clase, no de instancia. Son parte de la identidad del agente, no cambian entre instancias ni entre tareas.
  2. run() retorna el contenido como string. No imprime nada. Quien llama decide qué hacer con la respuesta.
  3. last_usage guarda el consumo de tokens de la última llamada. El agente lo expone como atributo para que el llamador pueda loggearlo si quiere, sin que run() haga I/O por su cuenta.

Probar:

python -c "from agent import TechnicalExplainerAgent; print('OK')"

Respuesta esperada:

OK

Si Python no puede importar el módulo, verifica que estés en agentic-ai-lab/ con el virtualenv activo.


Paso 2. Usar el agente con múltiples tareas

Archivo: main.py (NUEVO)

from agent import TechnicalExplainerAgent

agent = TechnicalExplainerAgent()

tareas = [
    "Explícame qué es AWS Lambda en 3 oraciones.",
    "¿Cuándo usarías SQS en lugar de llamadas síncronas entre servicios?",
    "Dame un ejemplo mínimo de una función Lambda en Python.",
]

for tarea in tareas:
    print(f"\n── Tarea: {tarea}")
    respuesta = agent.run(tarea)
    u = agent.last_usage
    print(f"[tokens] prompt: {u.prompt_tokens}, completion: {u.completion_tokens}, total: {u.total_tokens}")
    print(respuesta)

Probar:

python main.py

Respuesta esperada:

── Tarea: Explícame qué es AWS Lambda en 3 oraciones.
[tokens] prompt: 58, completion: 72, total: 130
AWS Lambda es un servicio de cómputo serverless...

── Tarea: ¿Cuándo usarías SQS en lugar de llamadas síncronas...
[tokens] prompt: 65, completion: 91, total: 156
SQS es útil cuando quieres desacoplar productores de consumidores...

── Tarea: Dame un ejemplo mínimo de una función Lambda en Python.
[tokens] prompt: 61, completion: 88, total: 149

El mismo agente, tres tareas distintas, cero configuración repetida. El rol y las instrucciones se aplican automáticamente en cada run().


Errores comunes

Error 1. instructions como f-string no resuelve role y goal

Síntoma: el system prompt que recibe el modelo contiene el texto literal {role} o {goal} en lugar del valor.

Causa raíz: role y goal deben estar declarados antes de instructions en el cuerpo de la clase. Python evalúa los atributos de clase de arriba a abajo; si instructions aparece antes, falla con NameError.

Corrección: mantén el orden del Paso 1: primero role, luego goal, luego instructions.

Error 2. run() hace print() de la respuesta en vez de return

Síntoma: respuesta = agent.run(tarea) siempre es None, aunque el contenido aparece en la terminal.

Causa raíz: print() escribe a stdout pero retorna None. Si run() termina con print(content) en lugar de return content, el código en main.py que espera el string nunca lo recibe.

Corrección: run() debe terminar con return response.choices[0].message.content. El print() de la respuesta va en main.py, no dentro del método.


Buenas prácticas

  • run() retorna, no imprime. Un método que termina con print() no se puede componer: no puedes pasarle su resultado a otro agente, guardarlo en una variable ni testearlo. Retorna el string; quien llama decide qué hacer con él. Ref: Bertrand Meyer, principio CQS (Command-Query Separation).
  • Un agente, un rol. Si necesitas un agente revisor, crea ReviewerAgent. No metas condicionales en run() para manejar múltiples roles; eso viola SRP y hace el system prompt ambiguo para el modelo. Ref: Martin, Clean Code, capítulo sobre funciones y responsabilidad única.
  • Observabilidad expuesta, no impuesta. El agente registra el consumo de tokens en last_usage y deja que el llamador decida si lo loggea, lo persiste o lo ignora. Si run() imprimiera tokens por su cuenta, la observabilidad sería un acoplamiento, no una opción. Ref: OpenAI · Usage object.

Estado del proyecto al finalizar

agentic-ai-lab/
├── .venv/
├── .env
├── .gitignore
├── call_llm.py          ← del Lab 1, sin cambios
├── agent.py             ← NUEVO (TechnicalExplainerAgent)
└── main.py              ← NUEVO (ejecuta el agente con 3 tareas)

Checklist:

  • python main.py imprime tres respuestas distintas, cada una precedida por su línea de tokens.
  • run() retorna el string del contenido, no imprime la respuesta.
  • role, goal e instructions son atributos de clase, no variables globales sueltas.
  • agent.last_usage da acceso a los tokens de la última llamada después de cada run().

¿Qué viene en el Lab 3?

Tu AI Agent responde preguntas. Pero todavía solo redacta: no puede actuar sobre el entorno. En el Lab 3 vas a darle herramientas, funciones Python que el modelo puede elegir invocar: leer un archivo local, guardar el resultado a disco. Ahí está la diferencia entre un chatbot y un agente que actúa.


Referencias

Documentación oficial

Libros canónicos

  • Martin, R. C. Clean Code, capítulo sobre funciones y responsabilidad única.
  • Martin, R. C. Clean Architecture, separación de capas de dominio e infraestructura.

Lecturas recomendadas