> labs.luisguisado.cloud
Intermedio~75 min·por Luis Guisado·actualizado 16 de mayo de 2026

Multi-agente con CrewAI: investigador, redactor, revisor

Tres agentes con roles distintos (investigador, redactor, revisor) coordinados por CrewAI. Aprendes qué resuelve un framework de orquestación cuando ya entiendes a mano lo que automatiza.

Costo estimado: free

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

Por qué este lab existe

El TechnicalExplainerAgent del Lab 3 funciona para tareas individuales. Probemos algo que mezcle varias responsabilidades en una sola llamada:

from agent import TechnicalExplainerAgent

agent = TechnicalExplainerAgent()
respuesta = agent.run(
    "Investiga qué es AWS EventBridge. "
    "Después redacta un post de 300 palabras para backend devs. "
    "Después revisa el post y arregla cualquier afirmación dudosa. "
    "Devuelve la versión final lista para publicar."
)
print(respuesta)

Lo que sale es un texto mediocre: el modelo intenta hacer las tres cosas a la vez sin separar dónde investiga, dónde redacta y dónde revisa. Las instrucciones del system prompt del Lab 3 están escritas para un explainer técnico, no para un revisor crítico ni para un copywriter. Cuando le pides tres roles a la vez, te entrega el mínimo común denominador.

El problema no es el modelo. Es que un solo agente tiene una sola identidad. Para un flujo de tres pasos con criterios distintos en cada uno, necesitas tres agentes con sus propios roles y un orquestador que les pase la salida de uno como entrada del siguiente.

CrewAI es un framework que da las primitivas para hacer eso: Agent (rol + objetivo + backstory), Task (descripción + output esperado + agente asignado) y Crew (la ejecución coordinada).

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


Lo que vas a tener al terminar

  1. Un crew_setup.py con tres agentes definidos: un Researcher, un Writer y un Reviewer, cada uno con su propio role, goal y backstory.
  2. Tres Task en tasks.py que se encadenan: research, write, review.
  3. Un main.py reescrito que ejecuta el Crew y produce un post técnico final.
  4. Criterio claro de cuándo conviene un framework de orquestación frente a hacerlo a mano (como en Labs 2 y 3).

Qué vas a aprender

  • Definir un Agent en CrewAI con role, goal y backstory.
  • Encadenar Task pasando contexto entre etapas con Process.sequential.
  • Configurar un LLM de Groq compatible con CrewAI vía litellm.
  • Leer el output final y el consumo de tokens agregado de un Crew.

Conceptos clave

Crew como orquestador

Qué es: un Crew (también llamado equipo en español; los dos nombres refieren al mismo objeto de CrewAI) es la entidad que coordina la ejecución de varios agentes. Tú declaras los agentes, declaras las tasks (cada task asignada a un agente) y el Crew las ejecuta en el orden definido por su process. La salida de cada task queda disponible como contexto para las siguientes, sin que tengas que pasársela a mano.

Mini-ejemplo:

from crewai import Crew, Process

crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, write_task, review_task],
    process=Process.sequential,
)
result = crew.kickoff(inputs={"topic": "AWS EventBridge"})

Agent y Task: la separación de roles

Qué es: un Agent en CrewAI es un AI Agent (mismo concepto que en Labs 2 y 3) con tres campos distintivos: role, goal y backstory. El backstory es texto extra que enriquece el system prompt con contexto sobre quién es el agente, qué experiencia tiene y qué actitud asumir. Una Task (también tarea en español) es una unidad de trabajo: una description de qué hacer, un expected_output que orienta el formato de salida y un agent asignado. La separación importa porque el framework genera un system prompt distinto por agente y le pasa contexto controlado, no un historial mezclado.

Mini-ejemplo:

researcher = Agent(
    role="Investigador técnico cloud",
    goal="Reunir hechos verificables sobre el tema solicitado",
    backstory="Cloud architect con 10 años en AWS, prefiere fuentes oficiales.",
    llm=llm,
)

research_task = Task(
    description="Investiga qué es {topic} y cuándo conviene usarlo.",
    expected_output="3-5 viñetas con hechos verificables.",
    agent=researcher,
)

Para profundizar: la diferencia entre Process.sequential y Process.hierarchical es central en CrewAI. En el primero, las tasks corren en el orden del array. En el segundo, un manager-agent extra decide a qué agente asignarle cada task y en qué orden. Para este lab usamos sequential porque el flujo research → write → review es estrictamente lineal. Ref: CrewAI · Processes.


Mapa visual

   main.py

     │  crew.kickoff(inputs={"topic": "AWS EventBridge"})


   ┌─ Researcher ──────────────────────────────────┐
   │ role:    "Investigador técnico cloud"         │
   │ task:    "investiga {topic}"                  │
   │ output:  "EventBridge es un bus de eventos..."│
   └───────────────────────────────────────────────┘

     │  context = output del Researcher

   ┌─ Writer ──────────────────────────────────────┐
   │ role:    "Redactor técnico"                   │
   │ task:    "redacta un post de 300 palabras"    │
   │ output:  "# AWS EventBridge para backend..."  │
   └───────────────────────────────────────────────┘

     │  context = outputs del Researcher + Writer

   ┌─ Reviewer ────────────────────────────────────┐
   │ role:    "Editor crítico"                     │
   │ task:    "revisa y arregla afirmaciones..."   │
   │ output:  "# AWS EventBridge (versión final)"  │
   └───────────────────────────────────────────────┘


   crew.kickoff() retorna el output del último task

Cada agente tiene su propio system prompt construido a partir de role + goal + backstory. CrewAI le pasa al Writer el output del Researcher como contexto inicial; al Reviewer le pasa los outputs de Researcher y Writer. Ese paso de contexto entre agentes es lo que el framework te ahorra escribir a mano.


Punto de partida

Antes de instalar CrewAI, verifica que el Lab 3 esté funcionando.

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

Respuesta esperada: ves la respuesta del agente con las dos tools invocadas y summary.md generado o actualizado.

Si el script falla, regresa al Lab 3 y verifica que tools.py, agent.py y main.py estén correctos.


Sobre las versiones y los modelos

Igual que en los labs anteriores, no pineamos versiones en el pip install. Cuando corras el comando, instalas lo último que haya en PyPI ese día. Por si necesitas reproducir la versión validada (al updatedAt: 2026-05-16):

CrewAI sigue versionado 0.x, lo que en la convención de SemVer admite breaking changes entre minors. Si entre la versión validada y la que descargues hubo un salto de minor (por ejemplo, 0.95.x0.110.x), revisa el changelog antes de seguir.


Paso 1. Instalar CrewAI

pip install crewai

Probar:

python -c "import crewai; print(crewai.__version__)"

Respuesta esperada: un número de versión, por ejemplo:

0.95.0

CrewAI trae como dependencia transitiva litellm, la librería que abstrae más de 100 proveedores de LLM detrás de la misma interfaz. Por eso un modelo de Groq se referencia como groq/llama-3.3-70b-versatile: el prefix le indica a litellm cuál proveedor usar. Esto va a ser central en el Lab 5.


Paso 2. Definir tres agentes con roles distintos

Archivo: crew_setup.py (NUEVO)

import os
from dotenv import load_dotenv
from crewai import Agent, LLM

load_dotenv()

llm = LLM(
    model="groq/llama-3.3-70b-versatile",
    api_key=os.environ["GROQ_API_KEY"],
    temperature=0.3,
)

researcher = Agent(
    role="Investigador técnico cloud",
    goal="Reunir hechos verificables sobre el tema solicitado, sin opiniones ni embellecimiento",
    backstory=(
        "Eres un cloud architect con 10 años de experiencia en AWS. "
        "Prefieres citar documentación oficial antes que generalidades. "
        "Si un hecho no estás seguro de él, lo marcas como '(verificar)'."
    ),
    llm=llm,
    verbose=True,
)

writer = Agent(
    role="Redactor técnico para backend developers",
    goal="Convertir hechos en un post claro de 300 palabras dirigido a devs con experiencia",
    backstory=(
        "Has escrito durante 5 años para blogs técnicos. "
        "Tono directo, sin marketing, con ejemplos concretos. "
        "Estructuras los posts en: contexto, qué resuelve, cuándo usarlo, cuándo no."
    ),
    llm=llm,
    verbose=True,
)

reviewer = Agent(
    role="Editor crítico de contenido técnico",
    goal="Detectar afirmaciones vagas, marketing implícito y errores fácticos; reescribir donde haga falta",
    backstory=(
        "Has revisado más de 1000 posts técnicos. "
        "Aplicas claridad sobre fluidez: si una frase no aporta, la cortas. "
        "Si una afirmación no se verifica con los hechos del Researcher, la marcas."
    ),
    llm=llm,
    verbose=True,
)

Tres cosas para notar:

  1. Cada Agent tiene su propio role, goal y backstory. CrewAI compone con esos campos el system prompt antes de cada llamada al modelo.
  2. verbose=True hace que CrewAI imprima en consola el output intermedio de cada agente. Útil mientras desarrollas; en producción se baja a False.
  3. El mismo llm se reutiliza entre los tres. No hay razón en este lab para usar modelos distintos por rol, aunque CrewAI lo permite.

Probar:

python -c "from crew_setup import researcher, writer, reviewer; print(researcher.role)"

Respuesta esperada:

Investigador técnico cloud

Paso 3. Definir las tasks y enlazarlas

Archivo: tasks.py (NUEVO)

from crewai import Task

from crew_setup import researcher, writer, reviewer


research_task = Task(
    description=(
        "Investiga qué es {topic}. "
        "Reúne entre 4 y 6 hechos verificables: definición, caso de uso típico, "
        "comparación con alternativas conocidas y limitaciones documentadas. "
        "Cita fuentes oficiales cuando puedas (docs.aws.amazon.com, etc)."
    ),
    expected_output=(
        "Una lista de viñetas con los hechos. Si alguno requiere verificación, "
        "marca con '(verificar)' al final de la línea."
    ),
    agent=researcher,
)

write_task = Task(
    description=(
        "Convierte los hechos del Researcher en un post de 300 palabras dirigido a backend developers. "
        "Estructura: contexto del problema, qué resuelve {topic}, cuándo conviene usarlo, "
        "cuándo NO conviene. Sin marketing, sin frases vacías como 'solución poderosa'."
    ),
    expected_output="Un post en Markdown de ~300 palabras, sin código (eso lo agrega otro lab).",
    agent=writer,
)

review_task = Task(
    description=(
        "Lee el post del Writer y verifica que: "
        "1) las afirmaciones técnicas sean correctas comparándolas con los hechos del Researcher, "
        "2) no haya frases vacías ni marketing implícito, "
        "3) el tono sea directo y conciso. "
        "Reescribe solo lo que haga falta; preserva el resto literal. "
        "Devuelve la versión final lista para publicar."
    ),
    expected_output="El post final en Markdown, listo para publicar.",
    agent=reviewer,
)

Notas:

  1. \{topic\} es un placeholder; CrewAI lo sustituye con los inputs que le pases al kickoff(). Si olvidas pasarlo, el texto literal \{topic\} viaja al modelo.
  2. Las tasks no se enlazan explícitamente entre sí. CrewAI infiere el contexto por el orden del array cuando process=Process.sequential.
  3. Cada Task declara expected_output. Eso le dice al modelo cómo estructurar la salida. Sin esa pista, el modelo improvisa el formato y el siguiente agente recibe algo más difícil de procesar.

Probar:

python -c "from tasks import research_task, write_task, review_task; print('OK')"

Respuesta esperada:

OK

Paso 4. Ejecutar el crew y observar el flujo

Archivo: main.py (REESCRITO)

from crewai import Crew, Process

from crew_setup import researcher, writer, reviewer
from tasks import research_task, write_task, review_task


crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, write_task, review_task],
    process=Process.sequential,
    verbose=True,
)

resultado = crew.kickoff(inputs={"topic": "AWS EventBridge"})

print("\n" + "=" * 60)
print("POST FINAL")
print("=" * 60)
print(resultado.raw)

print("\n" + "=" * 60)
print("CONSUMO DE TOKENS (agregado del Crew)")
print("=" * 60)
print(resultado.token_usage)

Probar:

python main.py

Respuesta esperada (recortada; con verbose=True cada agente imprime su sección):

[INFO]: Working Agent: Investigador técnico cloud
[INFO]: Starting Task: Investiga qué es AWS EventBridge...
...
[INFO]: Working Agent: Redactor técnico para backend developers
[INFO]: Starting Task: Convierte los hechos del Researcher...
...
[INFO]: Working Agent: Editor crítico de contenido técnico
[INFO]: Starting Task: Lee el post del Writer y verifica...
...

============================================================
POST FINAL
============================================================
# AWS EventBridge para backend developers

AWS EventBridge es un bus de eventos serverless...
[post de ~300 palabras]

============================================================
CONSUMO DE TOKENS (agregado del Crew)
============================================================
total_tokens=4523 prompt_tokens=3201 completion_tokens=1322 ...

Lo que pasa paso a paso:

  1. El Researcher produce viñetas con hechos.
  2. El Writer recibe esas viñetas como contexto y redacta el post.
  3. El Reviewer recibe ambos outputs y devuelve la versión final.

El resultado.token_usage agrega el consumo de las llamadas a Groq que hicieron los tres agentes en suma. Ese número es lo que vas a ver crecer rápido cuando agregues tools, contextos largos o más agentes a un crew.


Errores comunes

Error 1. LLM no resuelve el proveedor (404 not_found)

Síntoma: al correr python main.py te sale litellm.exceptions.NotFoundError: ... llama-3.3-70b-versatile.

Causa raíz: sin el prefix groq/, litellm asume OpenAI por defecto y le pide al endpoint de OpenAI un modelo de Llama que ahí no existe.

Corrección: asegúrate de instanciar el LLM con el prefix del proveedor:

llm = LLM(model="groq/llama-3.3-70b-versatile", api_key=...)

Error 2. El placeholder \{topic\} queda literal en el prompt

Síntoma: los agentes investigan el tema \{topic\} literal en vez de AWS EventBridge.

Causa raíz: olvidaste pasar inputs=\{"topic": "AWS EventBridge"\} en crew.kickoff(). CrewAI no falla; deja el placeholder sin reemplazar.

Corrección:

resultado = crew.kickoff(inputs={"topic": "AWS EventBridge"})

Error 3. El Reviewer regenera todo el post desde cero

Síntoma: la versión final del post se parece muy poco al post del Writer; el Reviewer reescribió todo.

Causa raíz: la description del review_task es demasiado abierta. Cuando le pides “revisa”, el modelo siente libertad para reescribir entero.

Corrección: sé explícito sobre qué debe cambiar y qué debe preservar:

description=(
    "Revisa el post sin reescribirlo entero. "
    "Solo cambia las frases donde detectes errores técnicos o marketing implícito. "
    "Preserva el resto literal."
),

Buenas prácticas

  • Un rol por agente. No fusiones “Writer + Reviewer” en un mismo agente para ahorrar tokens. La separación de roles le da al modelo un system prompt enfocado por etapa y produce mejor calidad que un agente único pidiéndose autorrevisión. Ref: principio SRP (Martin, Clean Code, capítulo sobre funciones y responsabilidad única), aplicado a agentes.
  • expected_output siempre presente. Una task sin expected_output produce formato inestable: a veces JSON, a veces prosa, a veces viñetas. El siguiente agente recibe algo distinto cada corrida y la coordinación se vuelve frágil. Trata expected_output como el contrato de salida del agente. Ref: principio de Tell Don’t Ask (Hunt & Thomas, The Pragmatic Programmer).
  • Empezar con sequential, no hierarchical. Process.hierarchical agrega un manager-agent que decide a qué agente asignar cada task. Es más flexible pero más caro en tokens y más difícil de debuggear. Empieza siempre por sequential; pasa a hierarchical solo cuando el orden de ejecución sea realmente dinámico. Ref: CrewAI · Sequential vs Hierarchical.

Estado del proyecto al finalizar

agentic-ai-lab/
├── .venv/
├── .env
├── .gitignore
├── call_llm.py          ← del Lab 1, sin cambios
├── tools.py             ← del Lab 3, sin cambios
├── agent.py             ← del Lab 3, sin cambios
├── crew_setup.py        ← NUEVO (researcher, writer, reviewer)
├── tasks.py             ← NUEVO (research_task, write_task, review_task)
├── main.py              ← REESCRITO (kickoff del Crew)
└── summary.md           ← del Lab 3, sin cambios

Checklist:

  • python main.py corre los tres agentes en orden y termina con un post final coherente.
  • El output de verbose=True muestra cada agente actuando con su rol propio.
  • resultado.raw contiene el post final; resultado.token_usage el consumo agregado.
  • El \{topic\} se reemplaza al invocar crew.kickoff(inputs=...).

¿Qué viene en el Lab 5?

El crew corre con Groq porque así lo dejamos hardcodeado en crew_setup.py. En la práctica, cambiar de proveedor es algo que vas a querer hacer: por costos, por privacidad (datos que no salen de tu VPC), por probar otro modelo. En el Lab 5 vas a desacoplar el proveedor del crew y comparar la misma corrida contra OpenAI, Anthropic, Amazon Bedrock y Ollama local. Vas a confirmar de manera práctica que el agente es independiente del proveedor.


Referencias

Documentación oficial

Libros canónicos

  • Martin, R. C. Clean Code, capítulo sobre funciones y responsabilidad única (SRP aplicado a separar roles entre agentes en vez de mezclarlos).
  • Hunt, A. & Thomas, D. The Pragmatic Programmer, sección sobre Tell Don’t Ask aplicada al diseño del contrato de salida de cada task.

Repositorios y librerías

  • crewAIInc/crewAI: repo oficial del framework.
  • BerriAI/litellm: la librería que CrewAI usa por debajo para abstraer proveedores; explica por qué groq/ es el prefix correcto y prepara el terreno del Lab 5.