Alura > Cursos de Inteligência Artificial > Cursos de IA para Dados > Conteúdos de IA para Dados > Primeiras aulas do curso Protocolos e arquitetura para construção de agentes: MCP, A2A, AG-UI e Backend for Agents (BFA)

Protocolos e arquitetura para construção de agentes: MCP, A2A, AG-UI e Backend for Agents (BFA)

Construindo sistemas multiagente - Introdução ao curso

Apresentando o instrutor e o curso

Olá! Bem-vindo ao curso de protocolos para Construção de Agentes Inteligentes: MCP, A2A, AG-UI e BFA. Meu nome é Michael Douglas, mas a comunidade me conhece como MD. Gosto de usar alguns termos da comunidade, como "IAZeiros" para quebrar o gelo. Compartilho muitas coisas e sinta-se à vontade para entrar em contato comigo. Estou contando um pouco sobre minha vida para que conheçam o instrutor do curso. Acredito que é importante esse ponto, já que vocês passarão uma jornada comigo na construção de um agente que chamaremos de MD Bank.

Para toda essa jornada, acho que é bom fazermos essa introdução e nos conhecermos. Estou compartilhando um pouco da minha vida pessoal e algumas coisas que gosto de fazer fora do ambiente de trabalho. Atualmente, trabalho no Itaú, na parte de investimentos, na construção de agentes de IA generativa para atendimento ao cliente, focando na materialização da gestão de patrimônio.

Explicando os objetivos do curso

Por que fazer este curso? Quero compartilhar um pouco da minha experiência e do que venho trabalhando no dia a dia, mostrar os protocolos e fazer com que entendam o que pretendemos aplicar aqui. Espero que também possam aplicar isso no dia a dia de vocês e aprender com o que será ensinado. Preparei este conteúdo para que acompanhem comigo o meu dia a dia e que pode ser tornar o seu.

Vocês aprenderão a criar a camada de agentes aqui no curso. Vamos desenvolver um agente bancário chamado MD Bank. Vocês aprenderão a criar os agentes de abertura de conta, cartão de crédito com MCP, A2A, AG-UI e BFA. Além disso, você irá aprender a criar um front-end com Streamlit e, posteriormente, um front-end com React, utilizando a GUI.

Detalhando o conteúdo e a estrutura do curso

Neste curso, vamos aprender a configurar nosso servidor MCP. Também aprenderemos a construir uma camada supervisora, que será responsável por receber os textos do MD Bank e exibi-los. Todo o nosso projeto será iniciado a partir deste Docker Compose. Tudo o que realizarmos aqui seguirá uma arquitetura de referência para o nosso projeto. Vamos abrir o projeto para visualizar nossa construção.

Aprenderemos desde o início, a construção de agentes profissionais e prontos para produção com os protocolos mais famosos de mercado.

Demonstrando o funcionamento do sistema

Nós veremos juntos o que construiremos e veremos como é incrível o nosso agente MDBank!

Vamos em frente!

Construindo sistemas multiagente - Selecione sua arquitetura de agentes: Single agents x Multiagents

Introduzindo o projeto do agente bancário

Como a ideia do nosso curso é ser prático, vamos iniciar escolhendo um caso. O caso escolhido é um banco, e vamos criar um agente bancário fictício chamado MD Bank. A primeira parte do nosso projeto será entender, de forma ilustrada, o que um cliente nos solicitou. Obviamente, o cliente não nos perguntará, como pessoas engenheiras de IA, qual arquitetura escolher para nosso agente. Ele simplesmente apresentará o problema.

Imaginando esse cenário, nosso cliente poderia dizer: "Gostaria de um agente bancário onde a pessoa pudesse, por exemplo, em um estilo GPT, enviar um texto e esse agente tentasse, por exemplo, abrir uma conta, solicitar um cartão de crédito e direcionar para o atendimento do banco." Dado esse contexto básico, pediríamos muitos mais detalhes sobre o banco, mas vamos começar escolhendo essa porta de entrada para o MD Bank.

Comparando arquiteturas de agentes

Antes de escolher o estilo de arquitetura, seja multi-agente ou single agent, gosto de apresentar uma comparação entre agente único e multi-agente. Por isso, temos uma imagem mostrando a entrada, onde ilustramos um cachorro, e ao final pedimos a uma rede neural que nos diga se é um cachorro. Muitas vezes, ao iniciar um projeto de agente, é importante lembrar que tudo reflete um percurso. Se o projeto reflete o MD Bank, que é um banco, teremos uma entrada, que seria um texto, e esperamos como saída uma avaliação determinando o caminho para onde queremos que o cliente vá. Tudo isso depende de um modelo.

Ao criar um agente, utilizaremos LinkedIn, que solicitará um modelo. Esses modelos, conhecidos como modelos de linguagem grande, são executados usando redes neurais. A capacidade desse modelo é entender a entrada do texto, que chamamos de Token e depois Embed, de forma matemática, não textual. Isso gera diversas bifurcações no banco, onde podemos cair em diferentes lugares.

Explorando a arquitetura de agente único

Dependendo do projeto, o estilo chamado Single Agent, ou agente único, pode ser entendido como uma arquitetura de agente único. O papel desse agente único é ajudar uma pessoa com questões do banco. Basicamente, esse agente atua como um agente de suporte, respondendo perguntas. Não entraremos ainda na questão de recursos, que são as ferramentas, pois teremos uma aula especial sobre isso. Aqui, focaremos apenas na parte do agente, que ajuda uma pessoa.

Esse agente de suporte pode tomar diferentes caminhos para o cliente, e esses caminhos, dependendo do crescimento do projeto, podem resultar em decisões de engenharia não muito favoráveis. No dia a dia, sem aplicar questões de engenharia de IA, mas sim de engenharia de software, poderíamos até usar o termo "monolito" para descrever nosso agente. No entanto, isso não significa uma decisão ruim de arquitetura, pois depende de cada caso. Em determinado momento, um agente único pode fazer sentido, sem necessidade de divisão em vários agentes ou domínios de entendimento.

Considerando a arquitetura multi-agente

Por se tratar de um projeto bancário, pode ser interessante dividir esses entendimentos. No MD Bank, seria interessante ter uma camada ou pasta com um agente especialista em cartão de crédito e outro em abertura de conta. Esses percursos são longos e complexos, e manter uma arquitetura de agente único poderia resultar em um monolito modular. Novamente, isso não é necessariamente ruim, pois depende de cada caso. Para nosso projeto, dada a volumetria desejada, escolhemos a arquitetura estilo multi-agente.

Então, isso nos remete a ter um agente que receberá o cliente, e seu papel é entender se a pessoa deseja uma cartão de crédito, abrir uma conta ou qualquer outra necessidade, podendo até ser suporte. Esse é o seu papel. Após essa decisão, o cliente será encaminhado para o agente de domínio que possui o conhecimento necessário. Ou seja, se for sobre cartão de crédito, encaminharemos para um subagente, ou utilizaremos outras técnicas de arquitetura para cada agente.

Explorando arquiteturas de roteamento e subagentes

Na documentação da LangChain, ao rolarmos um pouco, chegaremos às arquiteturas estilo multi-agente. Cada uma dessas arquiteturas representa uma capacidade, uma forma de construir nossos agentes. Se escolhermos uma de subagente, estamos dizendo que o agente principal depende dos subagentes para responder. Em muitos casos, precisa do agente A, do agente B e do C para dar uma resposta. Ou, às vezes, pode ser apenas o agente A. Esse estilo de arquitetura é onde temos uma divisão de decisões, mas que necessita de seus subagentes para executar.

Existem outros padrões, não vamos passar por todos agora, mas vamos abrir outro, que é do estilo de roteamento. Esse roteamento decide para onde encaminhar, como se fosse uma rota que estamos acostumados a fazer no dia a dia, e combina a resposta para devolvê-la. Podemos ir ao agente A, depois ao B, depois ao C, sintetizar todas essas respostas e combiná-las. No caso do subagente, não há um sintetizador, então passa por agentes que têm contextos isolados, como podemos ver na documentação: cada um possui um contexto, o que implica muita engenharia de contexto, sobre a qual também falaremos.

Iniciando o design conceitual do agente MDB

Dado todo esse contexto e entendimento, podemos passar ao design conceitual da nossa arquitetura do agente MDB. Imagine que, juntos, estaremos construindo o agente. Mas, antes de tudo, precisamos desenhar essa arquitetura, precisamos visualizá-la, saber o que vai acontecer no MDB.

Dado isso, o primeiro de todos os agentes, se não for multimodal, ou seja, se não tiver vídeo e áudio, mas considerarmos apenas como texto, a primeira questão é: se chega um texto, o que fazemos com ele? Essa é a primeira pergunta que devemos nos fazer sobre nossa arquitetura. Esse texto deveria executar um modelo para tomar uma decisão? E essa decisão já é a resposta para nosso cliente? Esse é um ponto inicial que devemos considerar. Se gostaríamos que aqui já estivesse nossa resposta, então colocaremos a resposta na tela, que seria nossa saída. Nosso modelo dá uma saída que remete a uma resposta.

Definindo o papel do roteador na arquitetura

Podemos começar a nos orientar para o que gostaríamos, que já não é chamar isso de modelo e saída, mas sim de um decisor ou um roteador. Lembramos que vimos que pode ser um roteador, um subagente, ou até mesmo skills (habilidades), que também veremos a seguir.

Dado esse primeiro aspecto de decisão da nossa arquitetura do MD Bank, o que já definimos? Inicialmente, vamos utilizar roteamento. Por quê? Porque queremos delimitar domínios em nosso projeto. Que domínio é esse do MD? O domínio é cartão de crédito, por exemplo. Teremos aqui a abertura de conta. Também podemos ter um suporte ao cliente, então colocaremos como suporte ao cliente.

Explorando fluxos de decisão e roteamento

Qual é o papel do nosso roteador na arquitetura do nosso projeto? É decidir o seguinte: quando chega um texto, vamos fazer isso juntos. Quando chega um texto do tipo "quero abrir uma conta", vamos colocar "quero abrir uma conta". Olhando para isso e deixando de lado a questão gerativa, se fosse uma situação cotidiana, o que diríamos a esse cliente que enviou essa pergunta? A questão de abrir conta deveria ir para um fluxo específico. Chamaremos isso de fluxo 1. Se nosso cliente diz "quero um cartão de crédito agora", então iremos para outro fluxo. Nesse caso, por querer um cartão de crédito, iremos ao roteamento e diremos que entendemos que a pessoa quer um cartão de crédito, então isso seria o fluxo 2. No fluxo 2, entendemos que a pessoa deve ser direcionada para cartão de crédito.

Considerações finais e próximos passos

Neste primeiro vídeo, já começamos a perceber algo. Se queremos um cartão de crédito, qual é a primeira pergunta que pode vir à mente? A nossa foi: se queremos um cartão de crédito, temos uma conta? Então, já temos uma questão aqui. Nosso roteador não deveria estar buscando um recurso? Que recurso é esse? Conta: se a pessoa tem uma conta, antes de mais nada. Queremos deixar isso em mente, para que comecemos a pensar juntos. Na próxima aula, desmembramos mais nosso design e começaremos a falar sobre tools (ferramentas), que são esses recursos de decisão do nosso projeto. Vamos encerrar o vídeo por aqui e esperamos vocês na próxima aula, onde falaremos sobre tools.

Construindo sistemas multiagente - Orquestração dos agentes com o padrão Supervisor

Introduzindo a prática da arquitetura

Vamos colocar em prática a arquitetura que desenvolvemos. Para que o vídeo não fique muito longo, já preparamos o código, que estará disponível para download. Vamos copiar, colar e explicar o que estamos fazendo. Ao final, teremos nosso front-end, escrito em Streamlit, mostrando o resultado do nosso agente.

Vamos começar criando um arquivo docker-compose, que será responsável por levantar nosso projeto. Inicialmente, colocamos os serviços que vamos utilizar. O primeiro é o Supervisor, que supervisiona nossos agentes e decide como executá-los. Neste vídeo, deixaremos os agentes, tanto o Supervisor quanto o Sub, dentro do mesmo script. No próximo vídeo, moveremos esses agentes para seus locais definitivos, mas nesta primeira versão, não há problema.

Configurando o docker-compose e estrutura de pastas

Aqui está o trecho do docker-compose que define os serviços:

services:
  supervisor:
    build: ./supervisor
    container_name: mdbank_supervisor_agents
    ports:
      - "8080:8000"
    volumes:
      - ./supervisor:/app

  frontend:
    build: ./frontend
    container_name: mdbank_frontend
    ports:
      - "9090:9090"
    volumes:
      - ./frontend:/app

Já temos um front-end, que será responsável por mostrar o resultado do nosso projeto. Vamos criar uma pasta chamada "Supervisor" para manter a didática do curso. Talvez fosse interessante chamá-la de "mdbank", mas manteremos esse nome. Também criaremos uma pasta para o front-end, onde estará nosso Streamlit, a interface visual do projeto.

Configurando o ambiente e ignorando arquivos desnecessários

Dentro de "Supervisor", criaremos um arquivo de environment. Não deixaremos nossa chave da OpenAI, mas você deverá colocá-la no seu código. Aqui está como você pode definir a chave da API:

OPENAI_API_KEY=""

Vamos adicionar um .gitignore para itens que não queremos no projeto e um Dockerfile, necessário para o projeto. O conteúdo do .gitignore pode ser o seguinte:

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
.venv

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

Criando o Dockerfile e script principal

No Dockerfile, utilizamos Python na versão 3.13 Slim, com algumas instalações. Definimos que funcionará na app, copiamos o requirements e, embora pudéssemos usar venv ou outro gerenciador, manteremos o padrão do PIP. Realizamos um pip install, copiamos nosso projeto e utilizamos o uvicorn para levantar nossa app, onde ficará a estrutura do Supervisor.

FROM python:3.13-slim

RUN apt-get update && apt-get install -y \
    build-essential \
    libffi-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

Agora, criaremos um script chamado app.py. Este script será responsável por levantar o FastAPI e o logging, para registrar as atividades do projeto. Instanciamos o FastAPI e definimos um app.post para uma rota padrão de chat, criando um endpoint de chat. Basicamente, criamos uma função que receberá o payload com a request. Validamos se o payload está no formato correto, pois criaremos um padrão para ele. Utilizamos um try/except para garantir que tudo se execute corretamente. Quando a mensagem é recebida no chat, o agente responde e executa o Supervisor, passando o texto do usuário e o payload de mensagens, que o Supervisor supervisionará, roteará e escolherá o agente correto para o projeto.

import logging
from fastapi import FastAPI
from fastapi.responses import JSONResponse

from src.schemas import ChatRequest
from src.agents import executar_supervisor

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI()

@app.post("/chat")
async def chat_endpoint(payload: ChatRequest):
    if not payload.message:
        return JSONResponse(status_code=400, content={"error": "Campo 'message' é obrigatório"})
    
    try:
        logger.info(f"Mensagem recebida no /chat: {payload.message}")
        resposta = await executar_supervisor(texto_usuario=payload.message)
        logger.info(f"Resposta gerada: {resposta}")
        return {"resposta": resposta}
    except Exception as e:
        logger.exception("Erro ao processar requisição no endpoint /chat")
        return JSONResponse(status_code=500, content={"error": str(e)})

Instalando dependências e criando agentes

Com o Dockerfile pronto, vamos adicionar o arquivo de requirements, que instala as dependências do projeto. Utilizaremos fastapi, uvicorn, langchain, langchain.openai, langchain.community, httpx e python-dotenv para carregar as variáveis de ambiente.

fastapi
uvicorn
langchain
langchain_openai
langchain-community
httpx
python-dotenv

Agora, criaremos uma pasta chamada "source", onde teremos um arquivo agents, o principal do nosso estudo. Vamos criar o agents.py e começar a escrever o que precisamos. A parte de carregar os pacotes já está pronta, pois não precisamos gastar tempo com isso. Importamos o createAgent, human message para enviar mensagens, o WinChatModel para iniciar nosso modelo, .env, os para carregar as variáveis e load_dotenv.

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
import os

load_dotenv()

A primeira parte será escrever o LLM. Usaremos um WinChatModel. Não aprofundamos na escolha do modelo, pois não temos avaliação para validar, então utilizamos o gpt4o. Poderia ser outro modelo, mas seguiremos com este. Vamos definir a API key usando os.getenv para carregar a OpenAI API key que já escrevemos. Definimos uma temperatura de 0.7 para o modelo, completando a primeira parte do nosso LLM.

__llm = init_chat_model(
    model="gpt-4o",
    api_key=os.getenv("OPENAI_API_KEY"),
    temperature=0.7
)

Desenvolvimento de agentes e classificação de perguntas

Vamos criar um agente de cartão de crédito, que é o objetivo do nosso estudo. Utilizaremos a função create_agent para isso. O agente usará o mesmo LLM que definimos anteriormente. Pode haver um problema, pois estamos usando o GPT-4, mas seguiremos assim. Não há tools (ferramentas) aqui, e passaremos um system prompt simples. Não há muito trabalho de engenharia de prompts envolvido, então não ajustaremos muito este prompt. O prompt será: "Você é um especialista em cartões de crédito do Banco Miderbank, ajuda o cliente com dúvidas, solicitações e limites." Com isso, temos nosso primeiro agente.

agente_cartao_credito = create_agent(
    __llm,
    tools=[],
    system_prompt=(
        "Você é um especialista em cartão de crédito do banco MDBank. "
        "Ajude o cliente com dúvidas, solicitação e limites."
    )
)

Agora, criaremos o segundo agente, que é o agente de abertura de contas. Utilizaremos novamente a função create_agent, com o mesmo LLM e tools. Passaremos também um system prompt: "Você é um especialista em abertura de contas do MD Bank, ajuda o cliente a abrir uma conta e explica os tipos disponíveis."

agente_abertura_conta = create_agent(
    __llm,
    tools=[],
    system_prompt=(
        "Você é um especialista em abertura de contas do banco MDBank. "
        "Ajude o cliente a abrir uma conta e explique os tipos disponíveis."
    ),
)

Classificação de perguntas

Vamos trabalhar na classificação das perguntas para entender se são sobre cartão de crédito ou abertura de conta. Para isso, criaremos a função classificar_pergunta, que receberá uma pergunta como string e devolverá uma string. Faremos um prompt com a responsabilidade de classificar. O texto do prompt será: "Classifique a intenção do usuário. Os possíveis agentes que resolvem são o de cartão de crédito e o de abertura de conta. Aqui está a pergunta e você responderá com um agente."

def classificar_pergunta(pergunta: str) -> str:
    prompt = f"""
    Classifique a intenção do usuário.

    Possíveis agentes:
    cartao_credito
    abrir_conta

    Pergunta: {pergunta}

    Responda apenas com o nome do agente.
    """
    resposta = __llm.invoke(prompt)
    return str(resposta.content).strip()

Execução do Supervisor

Escreveremos a função executarSupervisor, que encontrará os agentes. A função receberá um texto do usuário como string e devolverá uma string. O papel do Supervisor é encontrar um agente classificando a pergunta. Se o agente for do tipo cartão de crédito, invocaremos o agente de cartão de crédito e devolveremos uma mensagem do tipo human message com o conteúdo do texto do usuário.

async def executar_supervisor(texto_usuario: str) -> str:
    agente = classificar_pergunta(texto_usuario)

    if agente == "cartao_credito":
        resultado = agente_cartao_credito.invoke(
            {"messages": [HumanMessage(content=texto_usuario)]}
        )
    elif agente == "abrir_conta":
        resultado = agente_abertura_conta.invoke(
            {"messages": [HumanMessage(content=texto_usuario)]}
        )
    else:
        resultado = "Não consegui entender sua solicitação"

    mensagem_ia = resultado["messages"][-1]
    return str(mensagem_ia.content)

Estrutura do código e finalização

Podemos agora definir o esquema do nosso código, necessário para validar o que está sendo enviado. O esquema incluirá message, sessão do usuário e a parte client do projeto, que é o client ID. Na pasta do Supervisor, adicionaremos um dockerignore para evitar subir arquivos indesejados. A estrutura do Supervisor incluirá dockerignore, env, gitignore, app, Dockerfile e requirements.

from pydantic import BaseModel

class ChatRequest(BaseModel):
    message: str
    session_id: str
    client_id: str

Vamos pausar o vídeo aqui para não se estender muito. Em seguida, testaremos nosso Supervisor e, ao final, integraremos o front-end chamando toda a nossa estrutura. Isso é tudo por agora. Nos vemos no próximo vídeo.

Sobre o curso Protocolos e arquitetura para construção de agentes: MCP, A2A, AG-UI e Backend for Agents (BFA)

O curso Protocolos e arquitetura para construção de agentes: MCP, A2A, AG-UI e Backend for Agents (BFA) possui 291 minutos de vídeos, em um total de 49 atividades. Gostou? Conheça nossos outros cursos de IA para Dados em Inteligência Artificial, ou leia nossos artigos de Inteligência Artificial.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda IA para Dados acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas