Alura > Cursos de Inteligência Artificial > Cursos de IA para Dados > Conteúdos de IA para Dados > Primeiras aulas do curso GraphRAG: recuperação de contexto com grafos

GraphRAG: recuperação de contexto com grafos

Armazenando e consultando embeddings com PostgreSQL - Introdução

Apresentando o instrutor e o roteiro do curso

Olá! Boas-vindas ao curso de Graph RAG. Vou me apresentar brevemente. Na comunidade me conhecem como MD; algumas pessoas se divertem com meu jargão — dizem "iai, iazeiro" —, mas, na verdade, meu nome é Michael Douglas, por isso o "MD". É um prazer ter você aqui. Se ainda não me conhece, eu sou instrutor na Alura. Já tenho um curso publicado sobre protocolos e arquitetura para a construção de agentes, com o MCP, o TWI e o BFA, que é um padrão de projeto criado por mim. Atualmente, trabalho no Itaú Unibanco, na comunidade de investimentos, onde atuo diretamente na construção de agentes para clientes do banco. Aqui está a apresentação do instrutor.

Agora, vamos entender o que veremos no curso de Graph RAG. Nós vamos abrir o repositório do curso. Preparamos algumas branches (ramificações) para o passo a passo do curso. Inicialmente, vamos revisar RAG, para relembrar alguns conceitos e entender o que pode estar faltando. Se você já conhece, será mais um reforço e vale a pena conferir. Para quem ainda não viu, será introdutório. Lembrando que, aqui na Alura, já criamos outros cursos de RAG sobre o tema.

Introduzindo Graph RAG e preparando a infraestrutura

Em seguida, veremos a grande estrela do curso, que é a parte de Graph RAG. Deixamos uma imagem de que gostamos muito; quando a vimos, lembramos da primeira vez em que vimos um banco de dados de grafos. Até brincamos na ocasião, dizendo que parecia fogos de artifício. Ao longo do curso, vamos entender esses "fogos de artifício": o que são as arestas e o que são as ligações existentes.

No curso, usaremos Docker para subir nossos contêineres. Também utilizaremos como tecnologia o FalkorDB.

Construindo com Cypher e desenvolvendo agentes

Neste curso, vamos utilizar uma tecnologia que pode ser usada de maneira open source (código aberto). Nós vamos construir um banco de dados, inicialmente usando FalkorDB com a linguagem Cypher. Ao longo do curso, explicaremos os fundamentos de Cypher, aprenderemos alguns algoritmos voltados para esse ecossistema e construiremos agentes sobre essa tecnologia.

Nós criaremos três agentes: um para consultas simples em Cypher, um segundo para consultas mais complexas em Cypher e um terceiro agente que receberá textos de pessoas usuárias da rede social que vamos desenvolver, realizando consultas em nosso banco de dados. Será interessante percorrer todos esses aspectos, com bastante conteúdo.

Praticando e explorando grafos

Durante o curso, haverá momentos de prática e de mão na massa. Procuramos tornar essas etapas o mais simples possível para que possamos executá-las com tranquilidade. Em vez de apenas observarmos arestas e labels (rótulos) que aprenderemos ao longo do curso, antes de mergulharmos diretamente nelas, nós vamos analisar e explorar os grafos em alguns arquivos HTML (linguagem de marcação de hipertexto) disponibilizados nos módulos. Assim, veremos o que são conjuntos de arestas e o que são vértices, de forma incremental. Também clicaremos em pontos do grafo para entender o que está acontecendo, o que esses grafos fazem e aprender o passo a passo de maneira simplificada.

A ideia é que, ao término do curso, já tenhamos passado de forma consistente pela tecnologia de grafos, aprendido a usar o conceito de GraphHug para nossos agentes e possamos realizar construções interessantes.

Conectando com Hug e encerrando a introdução

Vale lembrar que este curso complementa o que aprendemos sobre Hug. São duas tecnologias e, no dia a dia, caberá a nós escolher qual delas atende melhor cada problema: GraphHug ou Hug.

Eu espero que você avance para o primeiro módulo e que ele seja tão interessante quanto foi para mim criar este curso, contribuindo para o seu dia a dia. Isso é tudo por este vídeo de introdução. Eu te espero no próximo módulo. Um forte abraço de MD, e vamos!

Armazenando e consultando embeddings com PostgreSQL - Apresentação das tecnologias e início do RAG

Apresentando o curso e contextualizando RAG

Bem-vindos à primeira aula do curso. Iniciaremos um percurso discutindo RAG. É uma boa ideia abordar esse tema desde o início para avançarmos até a implementação do conceito de grafos com RAG, que vem de Graph RAG, e que será o foco do curso.

Antes de tudo, todo o conteúdo do curso estará no repositório. Toda a evolução que fizermos no código será organizada por branches (ramificações). Esta primeira etapa, dedicada a RAG, ficará em uma branch específica. As seguintes, como Graph RAG e as demais evoluções, serão numeradas: RAG será a 1, Graph RAG a 2, e assim por diante. É importante acompanhar dessa forma.

O curso será bastante prático. A proposta é colocar a mão na massa e adquirir os conceitos conforme formos evoluindo o projeto.

Organizando o repositório e preparando os materiais

Vamos usar Docker e PostgreSQL neste exemplo, e utilizaremos um arquivo PDF no projeto para demonstrar a pipeline (fluxo de processamento) que construiremos, além de executar uma consulta sobre esse arquivo ao final.

Vamos criar uma pasta para RAG e mover o arquivo PDF mencionado, que poderá ser encontrado no repositório do curso. É um arquivo simples, encontrado na internet, sobre Alan Turing, utilizado apenas como exemplo para o curso. A partir daí, começaremos a implementar a primeira parte do projeto. Esse arquivo aparecerá como ignorado, pois a pasta "demos" do repositório já foi codificada anteriormente; estamos refazendo essa parte em conjunto no curso. Por isso a pasta "demos" está ignorada no repositório, de propósito, para que possamos realizar os passos juntos. O código completo foi deixado pronto posteriormente.

Configurando o Docker Compose e o PostgreSQL com pgvector

Vamos criar o arquivo de Compose e usar Docker Compose no projeto. Vamos adicionar services. O Copilot pode tentar autocompletar, mas vamos ignorar. Indicaremos que queremos Postgres, que é a base de dados que utilizaremos. Lembrando que na Alura existem vários cursos de RAG; portanto, não nos aprofundaremos em todos os detalhes aqui, pois estamos apenas estabelecendo um ponto de partida para enxergarmos a evolução de RAG até Graph RAG, que é o foco desta formação. Ainda assim, é importante observar essa evolução, razão pela qual iniciamos o curso por aqui.

Começamos criando a chave principal de serviços no arquivo de Compose:

services:

Agora adicionamos o serviço do PostgreSQL, que será nosso banco de dados:

services:
  postgres:

Em seguida, definimos a imagem desejada: queremos pgvector, e a versão do Postgres será a 16. Isso nos dá suporte nativo a vetores para trabalhar com embeddings:

services:
  postgres:
    image: pgvector/pgvector:pg16

Definimos também um nome de contêiner. Esse nome não é essencial e pode ser alterado, mas ajuda na organização:

services:
  postgres:
    image: pgvector/pgvector:pg16
    container_name: curso-alura-rag-postgres

Agora configuramos as variáveis de ambiente com usuário, senha e nome do banco. Notamos um erro: o campo environment havia sido digitado incorretamente como “entre point”; o correto é environment. Abaixo está a forma certa:

services:
  postgres:
    image: pgvector/pgvector:pg16
    container_name: curso-alura-rag-postgres
    environment:
      POSTGRES_USER: rag
      POSTGRES_PASSWORD: rag
      POSTGRES_DB: ragdb

Por fim, mapeamos a porta padrão do PostgreSQL para acesso local:

services:
  postgres:
    image: pgvector/pgvector:pg16
    container_name: curso-alura-rag-postgres
    environment:
      POSTGRES_USER: rag
      POSTGRES_PASSWORD: rag
      POSTGRES_DB: ragdb
    ports:
      - "5432:5432"

Definiremos a imagem desejada: queremos pgvector, e a versão do Postgres será a 16. O nome do contêiner será curso-alura-postgres. Esse nome não é essencial e pode ser alterado.

Em seguida, definiremos as variáveis de ambiente, configurando as envs de usuário, senha e banco de dados. Notamos um erro: o campo environment estava digitado incorretamente como "entre point"; o correto é environment. Em seguida, configuramos a porta 5432:5432. Não há mistério aqui: trata-se da imagem do contêiner com a qual trabalharemos neste projeto.

Instalando e testando modelos no Ollama

Mostraremos a conexão a seguir. Além disso, já no início usaremos um modelo, então recomendamos a instalação do Ollama. Já temos o Ollama instalado localmente e sugerimos instalá-lo para aproveitarmos ao máximo o open source (código aberto) na nossa tecnologia.

Para este caso, usaremos o modelo nomic-embed-text. Recomendamos baixá-lo, pois é simples. No terminal, execute o pull para fazer o download do modelo:

ollama pull nomic-embed-text

Já temos esse modelo instalado; ao executar o comando, ele tentará baixá-lo e informará que já está disponível.

Vamos executar a listagem para conferir os modelos instalados:

ollama list

Temos o modelo GPT-OSS disponível para uso na máquina local e o Nomic Embed, porque vamos incorporar nosso conteúdo. É importante que, desde o início, façamos o download do Ollama e o instalemos a partir do site oficial. Acessamos o site, clicamos para baixar, seguimos as instruções e concluímos a instalação. Há versões para todos os sistemas operacionais, e o processo é simples.

Após instalar, executamos list e, se quisermos, podemos testar com o comando run, executando o GPT-OSS localmente:

ollama run gpt-oss

Ao digitarmos "Oi", estamos rodando o GPT-OSS na máquina, que responde algo como "Oi, como posso ajudar?". Com isso, o modelo está pronto.

oi

Executando o Docker Compose e validando a conexão

Voltando ao nosso código, com essa primeira parte concluída, podemos ir ao terminal, por aqui ou no terminal da máquina, e executar docker compose up --build. Confirmando: isso deve ser feito na pasta "RAG". Vamos subir o serviço. Caso já exista um contêiner criado, removemos para que seja recriado e possamos acompanhar o processo desde o início. Em seguida, o serviço é criado e iniciado. Os logs indicam que o PostgreSQL foi iniciado, o que é um bom sinal: tudo está funcionando, e podemos nos conectar para realizar um teste.

Podemos navegar até a pasta do projeto (no exemplo abaixo, Windows/PowerShell):

cd .\rag\

Dependendo da sua versão do Docker, o comando pode aparecer com ou sem hífen. No exemplo capturado, está com hífen:

docker-compose up --build

Ou, em versões mais recentes do Docker:

docker compose up --build

Para esses testes, recomendamos instalar o PgAdmin, uma interface gráfica para visualizar a base de dados, sem restrições de licença. Vamos abrir o PgAdmin para verificar que nossa conexão está funcionando. Já definimos usuário e senha: usuário RAG, senha RAG. A base de dados PostgreSQL se chamará RAGDB, como planejado.

Ao abrir o PgAdmin, há uma seção chamada "Servers", onde vamos registrar um servidor. Informamos um nome; utilizamos "curso-alura" e, neste exemplo, "curso-alura-2". Ajustamos a cor de fundo para azul e a cor de primeiro plano para branco. Na aba de conexão, configuramos: host "localhost", porta 5432, usuário RAG, senha RAG e base de dados RAGDB. Salvamos em "Save". A nova conexão aparece e, ao expandi-la, confirmamos que conectou porque conseguimos visualizar o esquema "public". Se quisermos, já podemos executar um comando de criação ou inserir um script de criação; por exemplo, um select 1 + 1 retorna "2", confirmando que está tudo correto. Ainda não temos tabelas para consulta, mas nós as criaremos a seguir. A conexão é simples de realizar e já está configurada.

Para validar rapidamente a conexão, podemos rodar:

SELECT 1 + 1;

Planejando a pipeline de processamento do PDF

Nos próximos vídeos, vamos construir nossa pipeline (fluxo) de transformação do PDF. O objetivo é torná-lo consultável. Para isso, vamos converter o PDF; chamaremos essa etapa de conversão. Nós vamos:

Recomendamos, para quem não estiver familiarizado com embeddings (incorporações), realizar os cursos da Alura sobre geração de embeddings (incorporações). Para gerar as incorporações, usaremos o Ollama com o modelo chamado embedding (incorporação), já mencionado.

Eu te espero na próxima parte. Um grande abraço.

Armazenando e consultando embeddings com PostgreSQL - Convertendo PDF em embed no PostgreSQL

Apresentando a pipeline e criando o script

Na segunda parte, nós vamos construir uma pipeline (fluxo de processamento) muito simples, que permitirá converter o arquivo, realizar os splits (divisões), como mencionamos, gerar os embeddings (vetores de representação) e, de fato, depois executar consultas no PostgreSQL.

Primeiro, vamos criar um script chamado conversao.py. Ele será responsável por executar essas etapas. Poderíamos escolher muitos outros nomes, mas vamos manter conversao.py.

Inicializando o projeto e instalando dependências

Vamos abrir o terminal para melhorar a visualização e aumentar a tela. Em seguida, vamos usar nosso projeto em Python e iniciar com uv init, para que possamos instalar as dependências que utilizaremos no projeto. Para conferir os arquivos, vamos executar ls.

Para iniciar o projeto com o gerenciador uv, executamos:

uv init

Vamos entrar no ambiente do projeto e usar uv add (o de adicionar). Chegamos a pensar em uv shell, mas o comando correto aqui é uv add. Quando executamos uv add, ele já cria para nós a 'env'. Vamos adicionar docling, que será utilizado na parte de conversão.

Vale mencionar que utilizaremos o projeto docling. Ele é muito eficiente porque consegue processar arquivos e realizar a conversão que queremos. Como vamos utilizar o Ollama, isso já ajuda nessa etapa. O docling traz modelos que entendem o arquivo e permitem realizar a conversão. Portanto, usaremos docling com Ollama. Também utilizaremos psycopg (binário), que será empregado na integração com PostgreSQL.

Para instalar tudo de uma vez (docling, ollama e psycopg[binary]), usamos:

uv add docling ollama psycopg[binary]

Importando bibliotecas no script

Vamos proceder com as instalações. Observe que o gerenciador já criou a 'env' e está instalando as dependências necessárias para o projeto. Enquanto isso, vamos voltar ao arquivo de conversão. No script, vamos começar importando os componentes de que precisamos: Ollama, psycopg para a parte de PostgreSQL (isto é, nossa conexão), e docling com DocumentConverter, que permitirá realizar a conversão.

Para organizar nossos imports no conversao.py:

import ollama
import psycopg

from docling.document_converter import DocumentConverter

Ativando o ambiente virtual

Assim que a instalação terminar, vamos ativar o ambiente virtual para eliminar os erros de importação. No PowerShell, utilizamos venv\Scripts\Activate.ps1. Em alguns ambientes, pode ser necessário usar source venv/bin/activate. Com o ambiente ativado (Python 3.14), vamos executar uv add novamente apenas para confirmar; tudo certo.

No Windows, por exemplo, a ativação pode ser assim:

.\.venv\Scripts\activate

Conectando ao PostgreSQL e criando a extensão de vetores

Agora, vamos criar a conexão com PostgreSQL. Definiremos a variável de conexão e utilizaremos psycopg para estabelecer a conexão com o host localhost, porta 5432, banco de dados ragdb, usuário rag e senha rag, conforme já havíamos definido.

conn = psycopg.connect(
    host="localhost",
    port=5432,
    dbname="ragdb",
    user="rag",
    password="rag"
)

A primeira parte da conversão é criar a extensão de vetor. Para que o PostgreSQL entenda a parte de vetores, utilizaremos a extensão do projeto pgvector (no comando, a extensão é registrada como vector). Se a extensão ainda não estiver ativa, vamos criá-la.

Antes disso, abrimos um cursor para executar os comandos SQL:

cursor = conn.cursor()

Em seguida, criamos a extensão de vetores:

cursor.execute("""
CREATE EXTENSION IF NOT EXISTS vector;
""")

Criando a tabela e executando o script

Em seguida, vamos criar a tabela. Teremos, de fato, a tabela documentos, com uma chave primária id única, uma coluna conteudo do tipo texto e a coluna de vetores de embedding, onde ficará toda a vetorização ao longo do curso. Se tudo estiver correto, vamos realizar o commit (confirmação) para aplicar as mudanças no banco de dados. Com isso, teremos concluído a primeira parte necessária: as estruturas (tabelas) no banco de dados.

cursor.execute("""
CREATE TABLE IF NOT EXISTS documents (
    id SERIAL PRIMARY KEY,
    content TEXT,
    embedding vector(768)
);
""")

conn.commit()

Podemos executar o script conversao.py.

python .\conversao.py

Diagnosticando a execução e inspecionando o banco

Vamos remover a segunda conexão duplicada, pois a conexão estava repetida. Depois, vamos atualizar o cliente do banco (refresh — atualizar). A execução aparentemente ocorreu e o commit (confirmação) foi feito, mas, por alguma razão, o processo ficou parado. Vamos cancelar essa execução, desconectar do banco, verificar a conexão e conectar novamente. Aparentemente está tudo correto com a conexão e as tabelas. A seguir, vamos verificar o que aconteceu com a conversão, porque o processo não deveria ter parado nesse ponto. Pode ser que não tenha ocorrido o commit (confirmação). Seguiremos com o código e, se houver qualquer falha, faremos o ajuste no próximo vídeo.

Para inspecionar os dados, usamos algumas consultas simples no banco:

SELECT * FROM public.documents;

Extraindo e convertendo PDF em Markdown

Na sequência, vamos implementar a parte de extração do PDF, isto é, o que realmente vai extrair e converter o conteúdo. Para isso, utilizaremos docling com DocumentConverter, que converte o arquivo PDF e exporta seu conteúdo em Markdown (formatação em texto).

Aqui mostramos o resultado apenas para verificarmos o que foi feito. Agora vamos criar uma função para realizar o split (divisão) do texto. Enquanto isso, vamos retornar à conversão. Vamos verificar uma coisa: executamos o comando pwd na pasta "demo" e enviamos o comando novamente, deixando-o em execução enquanto concluímos aqui.

Primeiro, vamos converter um PDF e visualizar o Markdown gerado:

converter = DocumentConverter()
result = converter.convert("arquivo.pdf")
text = result.document.export_to_markdown()

print(text)

Explicando a estratégia de splits e definindo a função

Colocamos um print para visualizar a exportação em Markdown (sintaxe de marcação). Agora vamos criar a função que de fato realiza o split (divisão). O que isso significa? Vamos pegar as partes do nosso arquivo PDF e dividi-las em "chunkings" (fragmentações), gerando várias divisões. Em outras palavras, estamos pegando esse PDF, recortando em trechos, quase como um "slice" (fatia): tomamos um trecho aqui e outro ali e formamos um embedding (vetorização). Assim, o sistema consegue reconhecer um parágrafo, por assim dizer. Há várias questões de RAG (Geração Aumentada por Recuperação) envolvidas nesses splits (divisões) de texto, pois às vezes não se captura o trecho desejado e algo fica faltando. Essa etapa deve ser analisada com muito cuidado.

Definimos a função de splitting com tamanho de chunk e overlap:

def split_text(
    text: str,
    chunk_size: int = 500,
    overlap: int = 100
) -> list[str]:
    chunks: list[str] = []
    for i in range(0, len(text), chunk_size - overlap):
        chunk = text[i:i + chunk_size]
        chunks.append(chunk)
    return chunks

Preparando os chunks e verificando a população da tabela

Depois de gerar os "chunkings" (fragmentações), vamos realizar a geração do embedding (vetorização), isto é, indicar ao modelo que queremos transformar esses "chunkings". Aqui a execução ocorreu; demorou um pouco — provavelmente por uma questão da máquina —, deixamos rodando e a tabela já está disponível. Fizemos um SELECT * FROM para visualizar o documento, em public documents aqui. Inserimos um SELECT específico. Teoricamente estava tudo certo; se tivéssemos usado public documents, estaria bem. Talvez tenhamos digitado algo errado. Clicamos com o botão direito, pedimos para gerar um script (roteiro) e um SELECT. Em seguida, voltamos ao uso do * e conferimos. Observamos que em id, content e embedding ainda não há dados.

Por enquanto, criamos os chunks e preparamos um loop para percorrê-los:

chunks = split_text(text)
for chunk in chunks:
    ...

Nesse meio-tempo, o ambiente está baixando um modelo, o RapidOCR, que vai pegar o arquivo PDF e convertê-lo para a parte textual. Basicamente é isso que ele faz. Já conseguimos ver que o texto foi transformado enquanto revisávamos a conexão.

Gerando embeddings com Ollama e inserindo no Postgres

O que falta agora é solicitar que esses "chunkings" (fragmentações) sejam enviados para a base de dados. Para isso, vamos iterar em um loop (laço) e solicitar que realize os inserts (inserções) desses "chunkings". Removemos alguns trechos para facilitar a visualização; no repositório, o arquivo estará com todos os comentários nas linhas. Estamos com o "chunking" e os "chunkings" resultantes do split (divisão) do texto e estamos pedindo ao Ollama (modelo de embeddings "nomic-embed-text") que gere os embeddings (vetores). Ele transformará cada trecho em vetores matemáticos, ou seja, fará o embed (vetorização). Ao concluir, realizará os inserts (inserções) na base, o que permitirá depois fazermos a consulta. Veremos essa consulta no próximo vídeo. Colocamos um print aqui e outro ali e vamos deixar essa etapa executando.

Agora, completamos o loop com a geração de embeddings pelo Ollama e a inserção no Postgres:

for chunk in chunks:
    response = ollama.embeddings(
        model="nomic-embed-text",
        prompt=chunk
    )

    embedding = response["embedding"]

    cursor.execute(
        """
        INSERT INTO documents (content, embedding)
        VALUES (%s, %s)
        """,
        (chunk, embedding)
    )

Após processar e inserir todos os registros, emitimos uma mensagem de confirmação e, por fim, encerramos cursor e conexão:

print("Embeddings salvos com sucesso!")
cursor.close()
conn.close()

Recapitulando o pipeline e executando consultas

Relembrando o que já foi feito no código: na primeira parte, estabelecemos a conexão com a base, criamos a extensão de vetor para poder trabalhar com vetores no Postgres e criamos uma tabela de documentos bem simples, com os campos id, content e embedding. Não há nada muito complexo aqui: em essência, trabalhamos com dois itens principais — o conteúdo em texto e os embeds (vetores) — para que possamos visualizar tanto em forma textual quanto em forma vetorizada. Solicitamos ao Docling que convertesse o arquivo PDF em texto utilizável. Com o texto em mãos, realizamos o split (divisão) em partes menores, que chamamos de "chunk" (fragmento). É como aplicar um "slice" (fatia) no texto, cortando trechos que podem corresponder a um parágrafo ou a um fragmento. Essa parte do seu pipeline (fluxo de processamento) precisa ser avaliada com muito cuidado, pois é comum o RAG (Geração Aumentada por Recuperação) não funcionar bem se cortarmos um trecho importante que estava, por exemplo, no parágrafo seguinte.

Com o texto dividido, obtemos os seus "chunks" (fragmentos), e cada um deles é submetido ao embedding (vetorização), transformando-o em um vetor matemático. Depois, podemos solicitar ao Postgres uma consulta baseada nesses vetores. Quando passamos um texto para o Postgres, ele o vetoriza para a consulta, permitindo encontrar o trecho mais relevante. Cada "chunk" (fragmento) do texto é inserido na base, resultando em vários registros. Em seguida, fazemos o insert (inserção) desses "chunks", salvando o conteúdo e os embeds (vetores), registrando que o embed foi armazenado, executando o commit na base, fechando a conexão do cursor e, por fim, a conexão com o banco. Há diferenças entre esses dois: um é o cursor de execução de comandos e o outro é a conexão aberta com o banco.

Para checar o que foi inserido, podemos executar consultas como:

SELECT id, content, embedding
FROM public.documents;

Finalizando a execução e preparando os próximos passos

Neste momento, o processo está tratando cada trecho do texto. Verificamos se já inseriu; ainda não. Está finalizando a etapa de "slice" (fatia) e, na sequência, transformará em "chunk" (fragmento). Para que o vídeo não fique muito longo, vamos deixar essa execução em andamento e iniciar o próximo vídeo já com o resultado e prontos para realizar as consultas.

Para rodar novamente o script de ponta a ponta:

python .\conversao.py

Eu te espero na continuação. Um grande abraço.

Sobre o curso GraphRAG: recuperação de contexto com grafos

O curso GraphRAG: recuperação de contexto com grafos possui 310 minutos de vídeos, em um total de 38 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