Cómo crear un sistema multiagente

1. Introducción

En este lab, irás más allá de los chatbots simples y crearás un sistema multiagente distribuido.

Si bien un solo LLM puede responder preguntas, la complejidad del mundo real a menudo requiere roles especializados. No le pides a tu ingeniero de backend que diseñe la IU, y no le pides a tu diseñador que optimice las consultas de la base de datos. Del mismo modo, podemos crear agentes de IA especializados que se enfoquen en una tarea y se coordinen entre sí para resolver problemas complejos.

Crearás un sistema de creación de cursos que constará de lo siguiente:

  • Agente de investigación: Usa google_search para encontrar información actualizada.
  • Agente de evaluación: Critica la investigación en cuanto a calidad y completitud.
  • Agente de Content Builder: Convierte la investigación en un curso estructurado.
  • Agente de organización: Administra el flujo de trabajo y la comunicación entre estos especialistas.

Qué aprenderás

  • Define un agente que usa herramientas (investigador) y que puede buscar en la Web.
  • Implementa resultados estructurados con Pydantic para el juez.
  • Conéctate a agentes remotos con el protocolo Agent-to-Agent (A2A).
  • Construye un LoopAgent para crear un ciclo de reacción entre el investigador y el juez.
  • Ejecuta el sistema distribuido de forma local con el ADK.
  • Implementa el sistema multiagente en Google Cloud Run.
  • Usar un modelo de Gemma en una GPU de Cloud Run para el agente de Content Builder

Requisitos

  • Un navegador web, como Chrome
  • Un proyecto de Google Cloud con la facturación habilitada.

2. Principios de arquitectura y organización

Primero, veamos cómo trabajan juntos estos agentes. Estamos creando una canalización de creación de cursos.

El diseño del sistema

Diagrama de arquitectura

Organización con agentes

Los agentes estándar (como el Investigador) funcionan. Los agentes organizadores (como LoopAgent o SequentialAgent) administran otros agentes. No tienen sus propias herramientas; su "herramienta" es la delegación.

  1. LoopAgent: Actúa como un bucle while en el código. Ejecuta una secuencia de agentes de forma repetida hasta que se cumple una condición (o se alcanza la cantidad máxima de iteraciones). Usamos esta información para la Investigación con Loop:
    • El investigador encuentra información.
    • El juez la critica.
    • Si Judge dice "Fail", EscalationChecker permite que continúe el bucle.
    • Si Judge dice "Pass", EscalationChecker interrumpe el bucle.
  2. SequentialAgent: Actúa como una ejecución de secuencia de comandos estándar. Ejecuta los agentes uno tras otro. Usamos esto para la canalización de alto nivel:
    • Primero, ejecuta el Circuito de investigación (hasta que finalice con datos adecuados).
    • Luego, ejecuta Content Builder (para escribir el curso).

Al combinar estos elementos, creamos un sistema sólido que puede autocorregirse antes de generar el resultado final.

3. Configuración

Configura el proyecto

Crea un proyecto de Google Cloud

  1. En la página del selector de proyectos de la consola de Google Cloud, selecciona o crea un proyecto de Google Cloud.
  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Obtén información para verificar si la facturación está habilitada en un proyecto.

Inicie Cloud Shell

Cloud Shell es un entorno de línea de comandos que se ejecuta en Google Cloud y que viene precargado con las herramientas necesarias.

  1. Haz clic en Activar Cloud Shell en la parte superior de la consola de Google Cloud.
  2. Una vez que te conectes a Cloud Shell, verifica tu autenticación:
    gcloud auth list
    
  3. Confirma que tu proyecto esté configurado:
    gcloud config get project
    
  4. Si tu proyecto no está configurado como se esperaba, configúralo:
    export PROJECT_ID=<YOUR_PROJECT_ID>
    gcloud config set project $PROJECT_ID
    

Configuración del entorno

  1. Abre Cloud Shell: Haz clic en el ícono de Activar Cloud Shell en la parte superior derecha de la consola de Google Cloud.

Obtén el código de partida

  1. Clona el repositorio de inicio en tu directorio principal:Ve a tu directorio principal
      cd ~
    
    Clona solo el código necesario para este codelab desde la carpeta de demostraciones de Google Cloud DevRel.
    git clone --depth 1 --filter=blob:none --sparse https://github.com/GoogleCloudPlatform/devrel-demos.git temp-repo && cd temp-repo && git sparse-checkout set agents/multi-agent-system && cd .. && mv temp-repo/agents/multi-agent-system . && rm -rf temp-repo
    
    Ve a la carpeta que contiene el código de este codelab.
    cd multi-agent-system
    
  2. Habilita las APIs: Ejecuta el siguiente comando para habilitar los servicios de Google Cloud necesarios:
    gcloud services enable \
        run.googleapis.com \
        artifactregistry.googleapis.com \
        cloudbuild.googleapis.com \
        aiplatform.googleapis.com \
        compute.googleapis.com
    
  3. Abre esta carpeta en tu editor.
    cloudshell edit .
    

Configura el entorno

  1. Configura las variables de entorno.Crearemos un archivo .env para almacenar estas variables, de modo que puedas volver a cargarlas fácilmente si se desconecta tu sesión.
    cat <<EOF > .env
    export GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    export GOOGLE_CLOUD_LOCATION=europe-west4
    export GOOGLE_GENAI_USE_VERTEXAI=true
    EOF
    
  2. Obtén las variables de entorno:
    source .env
    

4. 🕵️ El agente de investigación

Agente de investigador

El Investigador es un especialista. Su único trabajo es encontrar información. Para ello, necesita acceder a una herramienta: la Búsqueda de Google.

¿Por qué separar al investigador?

Análisis detallado: ¿Por qué no tener solo un agente que haga todo?

Los agentes pequeños y enfocados son más fáciles de evaluar y depurar. Si la investigación es deficiente, itera sobre la instrucción del investigador. Si el formato del curso es incorrecto, debes iterar en el Creador de contenido. En una instrucción monolítica que "lo hace todo", corregir una cosa a menudo rompe otra.

  1. Si trabajas en Cloud Shell, ejecuta el siguiente comando para abrir el editor de Cloud Shell:
    cloudshell workspace .
    
  2. Abre agents/researcher/agent.py.
  3. Revisa el siguiente código que define el agente researcher:
    # ... existing imports ...
    
    # Define the Researcher Agent
    researcher = Agent(
        name="researcher",
        model=MODEL,
        description="Gathers information on a topic using Google Search.",
        instruction="""
        You are an expert researcher. Your goal is to find comprehensive and accurate information on the user's topic.
        Use the `google_search` tool to find relevant information.
        Summarize your findings clearly.
        If you receive feedback that your research is insufficient, use the feedback to refine your next search.
        """,
        tools=[google_search],
    )
    
    root_agent = researcher
    

Concepto clave: Uso de herramientas

Observa que pasamos tools=[google_search]. El ADK controla la complejidad de describir esta herramienta al LLM. Cuando el modelo decide que necesita información, genera una llamada a herramienta estructurada, el ADK ejecuta la función de Python google_search y devuelve el resultado al modelo.

5. ⚖️ El agente juez

Agente de Judge

El investigador trabaja mucho, pero los LLM pueden ser perezosos. Necesitamos un juez para que revise el trabajo. El juez acepta la investigación y devuelve una evaluación estructurada de aprobado o rechazado.

Resultado estructurado

Análisis detallado: Para automatizar los flujos de trabajo, necesitamos resultados predecibles. Una opinión de texto divagante es difícil de analizar de forma programática. Al aplicar un esquema JSON (con Pydantic), nos aseguramos de que Judge devuelva un valor booleano pass o fail sobre el que nuestro código pueda actuar de manera confiable.

  1. Abre agents/judge/agent.py.
  2. Revisa el siguiente código que define el esquema JudgeFeedback y el agente judge.
    # 1. Define the Schema
    class JudgeFeedback(BaseModel):
        """Structured feedback from the Judge agent."""
        status: Literal["pass", "fail"] = Field(
            description="Whether the research is sufficient ('pass') or needs more work ('fail')."
        )
        feedback: str = Field(
            description="Detailed feedback on what is missing. If 'pass', a brief confirmation."
        )
    
    # 2. Define the Agent
    judge = Agent(
        name="judge",
        model=MODEL,
        description="Evaluates research findings for completeness and accuracy.",
        instruction="""
        You are a strict editor.
        Evaluate the 'research_findings' against the user's original request.
        If the findings are missing key info, return status='fail'.
        If they are comprehensive, return status='pass'.
        """,
        output_schema=JudgeFeedback,
        # Disallow delegation because it should only output the schema
        disallow_transfer_to_parent=True,
        disallow_transfer_to_peers=True,
    )
    
    root_agent = judge
    

Concepto clave: Restricción del comportamiento del agente

Establecemos disallow_transfer_to_parent=True y disallow_transfer_to_peers=True. Esto obliga al juez a solo devolver el JudgeFeedback estructurado. No puede decidir "chatear" con el usuario ni delegar la tarea a otro agente. Esto lo convierte en un componente determinístico en nuestro flujo de lógica.

6. ✍️ El agente Creador de contenido

Creador de contenido

El Creador de contenido es el escritor creativo. Toma la investigación aprobada y la convierte en un curso. Utiliza un modelo de Gemma que entrega Cloud Run.

Primero, veamos el servicio de Cloud Run que aloja el modelo.

  1. Abrir ollama_backend/Dockerfile
  2. Aquí puedes ver cómo el Dockerfile usa una imagen de Ollama, escucha solicitudes en el puerto 8080 y almacena el modelo solicitado en una carpeta /model.
FROM ollama/ollama:latest

# Listen on all interfaces, port 8080 (Cloud Run default)
ENV OLLAMA_HOST 0.0.0.0:8080

# Store model weight files in /models
ENV OLLAMA_MODELS /models

⚙️ Cuando realices la implementación, establecerás los siguientes parámetros de configuración:

  • GPU: Se eligió la NVIDIA L4 por su excelente relación precio-rendimiento para las cargas de trabajo de inferencia. La L4 proporciona 24 GB de memoria de GPU y operaciones de tensor optimizadas, lo que la hace ideal para modelos de 270 millones de parámetros, como Gemma.
  • Memoria: 16 GB de memoria del sistema para controlar la carga del modelo, las operaciones de CUDA y la administración de memoria de Ollama
  • CPU: 8 núcleos para un manejo óptimo de las tareas de E/S y de preprocesamiento
  • Simultaneidad: 4 solicitudes por instancia equilibran la capacidad de procesamiento con el uso de memoria de la GPU
  • Tiempo de espera: 600 segundos para la carga inicial del modelo y el inicio del contenedor

Ahora veamos el agente Creador de contenido que usa el modelo de Gemma.

  1. Abre agents/content_builder/agent.py.
  2. Revisa el siguiente código que define el agente content_builder.
# the `ollama-gemma-gpu` Cloud Run service URL which hosts the Gemma model
target_url = os.environ.get("OLLAMA_API_BASE")

# ... existing code ...

# (Note: We use 'ollama/gemma3:270m' to align with ADK's expected prefix)
gemma_model_name = os.environ.get("GEMMA_MODEL_NAME", "gemma3:270m")
model = LiteLlm(
    model=f"ollama_chat/{gemma_model_name}",
    api_base=target_url
)

# 5. Define the Agent
content_builder = Agent(
    name="content_builder",
    model=model,
    description="Transforms research findings into a structured course.",
    instruction="""
    You are an expert course creator.
    Take the approved 'research_findings' and transform them into a well-structured, engaging course module.

    **Formatting Rules:**
    1. Start with a main title using a single `#` (H1).
    2. Use `##` (H2) for main section headings. These will be used for the Table of Contents.
    3. Use `###` (H3) for sub-sections within main sections.
    4. Use bullet points and clear paragraphs.
    5. Maintain a professional but engaging tone.

    **Structure Requirements:**
    - Begin with a brief Introduction section explaining what the learner will gain.
    - Organize content into 3-5 main sections with clear headings.
    - Include Key Takeaways at the end as a bulleted summary.
    - Keep each section focused and concise.

    Ensure the content directly addresses the user's original request.
    Do not include any preamble or explanation outside the course content itself.
    """,
)

root_agent = content_builder

Concepto clave: Propagación del contexto

Quizás te preguntes: "¿Cómo sabe el Creador de contenido lo que encontró el Investigador?". En el ADK, los agentes de una canalización comparten un session.state. Más adelante, en Orchestrator, configuraremos Researcher y Judge para que guarden sus resultados en este estado compartido. La instrucción del Creador de contenido tiene acceso de manera eficaz a este historial.

7. 🎻 El organizador

Agente de Orchestrator

El Orchestrator es el administrador de nuestro equipo de varios agentes. A diferencia de los agentes especializados (Investigador, Juez y Creador de contenido) que realizan tareas específicas, el trabajo del Organizador es coordinar el flujo de trabajo y garantizar que la información fluya correctamente entre ellos.

🌐 La arquitectura: Agent-to-Agent (A2A)

Arquitectura A2A

En este lab, crearemos un sistema distribuido. En lugar de ejecutar todos los agentes en un solo proceso de Python, los implementamos como microservicios independientes. Esto permite que cada agente se escale de forma independiente y falle sin que se bloquee todo el sistema.

Para que esto sea posible, usamos el protocolo Agent-to-Agent (A2A).

El protocolo A2A

Análisis detallado: En un sistema de producción, los agentes se ejecutan en diferentes servidores (o incluso en diferentes nubes). El protocolo A2A crea una forma estándar para que se descubran y se comuniquen entre sí a través de HTTP. RemoteA2aAgent es el cliente del ADK para este protocolo.

  1. Abre agents/orchestrator/agent.py.
  2. Revisa cómo el siguiente código define las conexiones.
    # ... existing code ...
    
    # Connect to the Researcher (Localhost port 8001)
    researcher_url = os.environ.get("RESEARCHER_AGENT_CARD_URL", "http://localhost:8001/a2a/agent/.well-known/agent-card.json")
    researcher = RemoteA2aAgent(
        name="researcher",
        agent_card=researcher_url,
        description="Gathers information using Google Search.",
        # IMPORTANT: Save the output to state for the Judge to see
        after_agent_callback=create_save_output_callback("research_findings"),
        # IMPORTANT: Use authenticated client for communication
        httpx_client=create_authenticated_client(researcher_url)
    )
    
    # Connect to the Judge (Localhost port 8002)
    judge_url = os.environ.get("JUDGE_AGENT_CARD_URL", "http://localhost:8002/a2a/agent/.well-known/agent-card.json")
    judge = RemoteA2aAgent(
        name="judge",
        agent_card=judge_url,
        description="Evaluates research.",
        after_agent_callback=create_save_output_callback("judge_feedback"),
        httpx_client=create_authenticated_client(judge_url)
    )
    
    # Content Builder (Localhost port 8003)
    content_builder_url = os.environ.get("CONTENT_BUILDER_AGENT_CARD_URL", "http://localhost:8003/a2a/agent/.well-known/agent-card.json")
    content_builder = RemoteA2aAgent(
        name="content_builder",
        agent_card=content_builder_url,
        description="Builds the course.",
        httpx_client=create_authenticated_client(content_builder_url)
    )
    

8. 🛑 El verificador de derivaciones

Un bucle necesita una forma de detenerse. Si el juez dice "Aprobar", queremos salir del bucle de inmediato y pasar al Creador de contenido.

Lógica personalizada con BaseAgent

Análisis detallado: No todos los agentes usan LLMs. A veces, necesitas una lógica de Python simple. BaseAgent te permite definir un agente que solo ejecuta código. En este caso, verificamos el estado de la sesión y usamos EventActions(escalate=True) para indicarle a LoopAgent que se detenga.

  1. Aún en agents/orchestrator/agent.py.
  2. Revisa el siguiente código, analiza los comentarios del juez y continúa con el siguiente paso cuando esté listo.
    class EscalationChecker(BaseAgent):
        """Checks the judge's feedback and escalates (breaks the loop) if it passed."""
    
        async def _run_async_impl(
            self, ctx: InvocationContext
        ) -> AsyncGenerator[Event, None]:
            # Retrieve the feedback saved by the Judge
            feedback = ctx.session.state.get("judge_feedback")
            print(f"[EscalationChecker] Feedback: {feedback}")
    
            # Check for 'pass' status
            is_pass = False
            if isinstance(feedback, dict) and feedback.get("status") == "pass":
                is_pass = True
            # Handle string fallback if JSON parsing failed
            elif isinstance(feedback, str) and '"status": "pass"' in feedback:
                is_pass = True
    
            if is_pass:
                # 'escalate=True' tells the parent LoopAgent to stop looping
                yield Event(author=self.name, actions=EventActions(escalate=True))
            else:
                # Continue the loop
                yield Event(author=self.name)
    
    escalation_checker = EscalationChecker(name="escalation_checker")
    

Concepto clave: Flujo de control a través de eventos

Los agentes se comunican no solo con texto, sino también con eventos. Cuando se genera un evento con escalate=True, este agente envía un indicador a su elemento superior (el LoopAgent). El LoopAgent está programado para captar este indicador y finalizar el bucle.

9. 🔁 El ciclo de investigación

Bucle de investigación

Necesitamos un circuito de retroalimentación: Investigación -> Evaluación -> (Falla) -> Investigación -> …

  1. In agents/orchestrator/agent.py,
  2. Revisa cómo el siguiente código define la definición de research_loop.
    research_loop = LoopAgent(
        name="research_loop",
        description="Iteratively researches and judges until quality standards are met.",
        sub_agents=[researcher, judge, escalation_checker],
        max_iterations=3,
    )
    

Concepto clave: LoopAgent

El LoopAgent recorre sus sub_agents en orden.

  1. researcher: Busca datos.
  2. judge: Evalúa datos.
  3. escalation_checker: Decide si se debe yield Event(escalate=True). Si sucede escalate=True, el bucle se interrumpe antes. De lo contrario, se reinicia en el investigador (hasta max_iterations).

10. 🔗 La canalización final

Canalización final

Reunir todo…

  1. In agents/orchestrator/agent.py,
  2. Revisa cómo se define root_agent en la parte inferior del archivo.
    root_agent = SequentialAgent(
        name="course_creation_pipeline",
        description="A pipeline that researches a topic and then builds a course from it.",
        sub_agents=[research_loop, content_builder],
    )
    

Concepto clave: Composición jerárquica

Ten en cuenta que research_loop es en sí mismo un agente (un LoopAgent). Lo tratamos como cualquier otro agente secundario en SequentialAgent. Esta componibilidad te permite crear lógica compleja anidando patrones simples (bucles dentro de secuencias, secuencias dentro de routers, etcétera).

11. 🚀 Implementa en Cloud Run

Implementaremos cada agente como un servicio independiente en Cloud Run, incluido un servicio de Cloud Run para la IU del creador del curso y un servicio de Cloud Run que usa GPUs para el modelo de Gemma.

Información sobre la configuración de la implementación

Cuando implementamos agentes en Cloud Run, pasamos varias variables de entorno para configurar su comportamiento y conectividad:

  • GOOGLE_CLOUD_PROJECT: Garantiza que el agente use el proyecto de Google Cloud correcto para el registro y las llamadas a Vertex AI.
  • GOOGLE_GENAI_USE_VERTEXAI: Indica al framework del agente (ADK) que use Vertex AI para la inferencia del modelo en lugar de llamar directamente a las APIs de Gemini.
  • [AGENT]_AGENT_CARD_URL: Es fundamental para el orquestador. Le indica al orquestador dónde encontrar los agentes remotos. Si configuras este parámetro en la URL de Cloud Run implementada (específicamente, la ruta de la tarjeta del agente), permitimos que el orquestador descubra y se comunique con el investigador, el juez y el creador de contenido a través de Internet.

Para implementar todos los agentes en los servicios de Cloud Run, ejecuta la siguiente secuencia de comandos.

Primero, asegúrate de que la secuencia de comandos sea ejecutable.

chmod u+x ~/multi-agent-system/deploy.sh

Nota: La ejecución tardará varios minutos, ya que cada servicio se implementa de forma secuencial.

~/multi-agent-system/deploy.sh

12. ¡Crea un curso!

Abre el sitio web de Creador de cursos. El servicio de Cloud Run de Course Creator es el último servicio que se implementa desde la secuencia de comandos. Puedes identificar la URL del creador del curso como https://course-creator-..run.app, que debería ser la línea de resultado final de la secuencia de comandos de implementación.

Escribe una idea de curso, p.ej., "álgebra lineal".

Tus agentes comenzarán a trabajar en tu curso.

Canalización final

13. Limpieza

Sigue estos pasos para borrar tus servicios y las imágenes de contenedor y, así, evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos que usaste en este codelab.

1. Borra los servicios de Cloud Run

La forma más eficiente de limpiar es borrar los servicios que implementaste en Cloud Run.

# Delete the main agent and app services
gcloud run services delete researcher content-builder judge orchestrator course-creator \
    --region $REGION --quiet

# Delete the GPU backend (Ollama)
gcloud run services delete ollama-gemma-gpu \
    --region $OLLAMA_REGION --quiet

2. Borra imágenes de Artifact Registry

Cuando usaste la marca --source para realizar la implementación, Google Cloud creó un repositorio en Artifact Registry para almacenar tus imágenes de contenedor. Para quitar estos archivos y ahorrar en costos de almacenamiento, borra el repositorio:

gcloud artifacts repositories delete cloud-run-source-deploy --location us-east4 --quiet

3. Cómo quitar archivos locales y el entorno

Para mantener limpio tu entorno de Cloud Shell, quita la carpeta del proyecto y cualquier configuración local:

cd ~
rm -rf multi-agent-system

4. Borra el proyecto (opcional)

Si creaste un proyecto solo para este codelab, puedes asegurarte de que no se produzca más facturación cerrando el proyecto a través de la página Administrar recursos.

14. ¡Felicitaciones!

Compilaste e implementaste correctamente un sistema multiagente distribuido y listo para producción.

Objetivos logrados

  • Descomposición de una tarea compleja: En lugar de una instrucción gigante, dividimos el trabajo en roles especializados (investigador, juez y creador de contenido).
  • Control de calidad implementado: Utilizamos un LoopAgent y un Judge estructurado para garantizar que solo la información de alta calidad llegue al paso final.
  • Creado para la producción: Con el protocolo Agent-to-Agent (A2A) y Cloud Run, creamos un sistema en el que cada agente es un microservicio independiente y escalable. Esto es mucho más sólido que ejecutar todo en una sola secuencia de comandos de Python.
  • Orquestación: Usamos SequentialAgent y LoopAgent para definir patrones de flujo de control claros. *. GPUs de Cloud Run: Se implementó un modelo de Gemma en una GPU de Cloud Run.