Alura > Cursos de Inteligência Artificial > Cursos de IA para Dados > Conteúdos de IA para Dados > Primeiras aulas do curso LangChain: Técnicas Avanças de RAG

LangChain: Técnicas Avanças de RAG

Revisitando o RAG - Introdução

Apresentando o instrutor e o curso

Olá! Meu nome é Alambras, sou engenheiro de software especialista em IA.

Audiodescrição: Alambras é um homem branco, de aproximadamente quarenta anos, com barba e cabelo castanho-claro raspado, e olhos verdes. Ele está em um ambiente com fundo roxo, com o tema da Alura.

Introduzindo o LangChain e técnicas de HAG

Hoje, vamos discutir o curso de LangChain, que é o framework mais utilizado para manipulação de LLMs (Large Language Models, ou Modelos de Linguagem Grande), e as técnicas avançadas de HAG, que é o padrão de arquitetura também mais empregado para LLMs.

Explorando técnicas de manipulação de LLMs

Vamos abordar desde a indexação dos documentos, as técnicas de chunking (fatiamento dos documentos), como armazenar e buscar esses documentos na base de dados vetorial, até enriquecer o prompt para obter o melhor uso dos LLMs.

Configurando o ambiente de trabalho

Vamos começar configurando nosso ambiente?

Revisitando o RAG - Revisão sobre RAG

Configurando o ambiente Python para o curso

Vamos configurar nosso ambiente Python para o curso de LangChain Técnicas Avançadas de HAAC. Primeiramente, assumimos que já concluímos o curso de Python e temos o ambiente configurado. No entanto, vamos reconfigurá-lo para utilizar Jupyter Notebooks, permitindo uma execução mais iterativa e clara dos códigos Python.

A primeira etapa é garantir que o Python esteja instalado, preferencialmente na versão 3.11. Verificamos que a versão atual é 3.13.5, o que é adequado. Para confirmar a versão do Python instalada, podemos usar o seguinte comando:

python --version

Criando e ativando o ambiente virtual

Criamos uma pasta para o projeto e, para manter os ambientes separados, criamos um ambiente virtual usando o virtualenv do Python. O comando utilizado é:

python -m venv env

Isso cria uma pasta env com o ambiente virtual, que separa os pacotes Python do projeto, evitando contaminação da máquina.

Para ativar o ambiente virtual, utilizamos source env/bin/activate no Linux ou Mac:

source env/bin/activate

E no Windows, usamos:

.\env\Scripts\activate.bat

Caso ocorra um problema de permissão no PowerShell, podemos usar o prompt de comando para ativar o ambiente.

Atualizando pacotes e configurando o Jupyter Notebook

Com o ambiente ativado, atualizamos o pip, nosso gerenciador de pacotes, com:

pip install -U pip

Após a atualização, configuramos o Jupyter Notebook, que mistura código Markdown com código Python. Selecionamos um kernel, ou seja, o motor que executará o código, escolhendo o ambiente Python detectado na pasta env.

Revisamos o fluxo de RAG (Retrieval-Augmented Generation), que aumenta o contexto e fornece mais informações para o LLM (Large Language Model). Inicialmente, codificamos páginas e indexamos em um banco de dados vetorial, criando um pipeline de indexação. Quando utilizamos o sistema, ao enviar uma pergunta, passamos pelos embeddings e buscamos no banco de dados vetorial as informações mais similares. Os textos relevantes são usados para enriquecer o prompt, que é então enviado ao LLM para gerar uma resposta contextualizada.

Instalando dependências e configurando variáveis de ambiente

Instalamos as dependências necessárias, começando pelo LangChain, usando:

!pip install langchain langchain-openai langchain-community

O LangChain é um framework que simplifica o uso de LLMs, abstraindo a complexidade de lidar diretamente com modelos como o da OpenAI. Ele oferece um ecossistema completo, incluindo o LangSmith para rastreabilidade e o LangGraph para orquestração de agentes.

Com o kernel instalado e pacotes configurados, lemos as variáveis de ambiente, como a chave da OpenAI. Podemos inserir as chaves diretamente ou criar um arquivo .env com pares chave-valor. No site da OpenAI, criamos uma chave API, que é copiada e salva no arquivo .env. No notebook, usamos o pacote dotenv para carregar o arquivo e verificar se a chave foi lida corretamente:

from dotenv import load_dotenv
load_dotenv()
import os
openai_api_key = os.getenv("OPENAI_API_KEY")

Garantindo segurança e iniciando o Pipeline RAG

Para garantir segurança, podemos usar o pacote getpass para solicitar a chave diretamente no Jupyter Notebook, evitando armazená-la em arquivos:

import getpass

if not openai_api_key:
    openai_api_key = getpass.getpass()

Com pacotes instalados e chave configurada, iniciamos o Pipeline RAG. O primeiro passo é ler documentos e criar uma base de conhecimento. Utilizamos o TextLoader do LangChain Community para carregar arquivos de texto:

from langchain_community.document_loaders import TextLoader
documento = TextLoader("documentos/GTB_gold_Nov23.txt", encoding="utf-8").load()

Após carregar o documento, ele é dividido em pedaços menores, ou chunks, usando o RecursiveCharacterSplitter:

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=100
)
pedacos = splitter.split_documents(documento)

Dividindo documentos e criando embeddings

O documento carregado é dividido em pedaços, que são armazenados em um array. Podemos verificar o número de pedaços e analisar um deles para confirmar que o documento foi corretamente dividido:

len(pedacos)
pedacos[2]

Veja que essa é uma técnica que utilizamos, pois não necessariamente, e quase nunca, passamos um documento inteiro no prompt. Passamos pedaços relevantes, e para isso existe a técnica do RAG. Para sabermos quais chunks vamos usar, precisamos convertê-los em vetores, que é a forma utilizada pelo RAG, pelos bancos de dados vetoriais, e é uma forma muito pesquisada e eficiente para realizar buscas dentro de textos.

Na primeira parte de chunks, precisamos de um modelo de embeddings. O embeddings é um modelo também, no caso, um transformers. Vamos usar o modelo padrão da OpenAI para este exemplo. Vamos importar aqui:

from langchain_openai import OpenAIEmbeddings
embeddings_model = OpenAIEmbeddings()

Testando o modelo de embeddings e criando banco de dados vetorial

Para testar esse modelo, vamos pegar um pedaço e ver o que ele faz:

embeddings_model.embed_query(pedacos[0].page_content)

Agora, estamos prontos para armazená-los em um banco de dados, que criará esse espaço de embeddings ou espaço vetorial. Para criar um banco de dados, também usaremos o banco de dados mais simples, que é o banco de dados vetorial em memória:

from langchain_community.vectorstores import InMemoryVectorStore

vectorstore = InMemoryVectorStore.from_documents(
    documents=pedacos, embedding=embeddings_model
)

Usando o retriever para consultas

Para usar esse banco de dados vetorial, precisamos de um buscador ou um retriever, que é o R do RAG:

retriever = vectorstore.as_retriever(search_kwargs={"k": 2})

Para usar o retriever agora, assim como um modelo, podemos invocar com o método invoke e passar uma consulta qualquer:

query = "Como devo proceder caso tenha um item comprado roubado?"
similar_chunks = retriever.invoke(query)

Agora, já temos o nosso banco de dados, já temos o nosso documento carregado, fatiado em cinco pedaços, guardado no banco de dados vetorial, devidamente criados os seus embeddings, os seus vetores.

Realizando a recuperação e preparando a consulta

Agora queremos usar ele. Terminamos a parte de indexação, agora vamos entrar na parte de recuperação, ou retrieval. Quando o usuário faz uma pergunta, ele fará o mesmo sistema que fizemos no teste. Vamos criar agora uma consulta que queremos fazer nesse banco de dados. A política do texto que carregamos é de cartão de crédito. Vamos perguntar como devemos proceder caso tenhamos um item comprado ou roubado. Uma pergunta meio mal formulada, mas é uma pergunta. Essa é a pergunta que faremos. O que ele buscará no banco de dados vetorial é o embeddings dessa pergunta, que é basicamente o mesmo que faremos aqui no embed carry e passaremos essa consulta. O que ele buscará lá é o vetor que representa essa pergunta. Essa parte já é um pouco invisível. Vamos fazer a busca, preparar aquele embeddings, e buscar. Vamos supor que esse laranjinha aqui é o vetor que representa a nossa pergunta. O que pegaremos, na verdade, são os vizinhos mais próximos e apenas aqueles chunks. Agora vamos fazer isso.

Parecido com o que fizemos lá em cima para testar, vamos buscar aqui os similar chunks pelo retriever que definimos lá em cima. Invocar, mas dessa vez queremos apenas a query. Não precisamos passar a query já com o embeddings, pois o próprio retriever já tem o modelo de embeddings dentro. Ali fizemos apenas para testar. Ele buscou novamente dois chunks. Parece que são diferentes daquela outra da perda do seguro viagem que colocamos lá em cima. Certo? Legal. Temos nossos documentos aqui, mas ainda não os usamos. Vamos preparar aqui as fontes que vamos usar. Na verdade, agora queremos apenas os textos. Vamos criar um vetor com os textos, extrair, e fazer essa operação em Python mesmo. Para cada pedaço, pegaremos o page contents, pois agora só precisamos do texto para passar para o nosso modelo, não precisamos do documento LangChain. Para cada pedaço dentro dos similar chunks, agora estamos percorrendo essa lista de dois e pegando apenas o texto. Não precisamos, nesse caso, das fontes. Agora temos um array de dois elementos, duas strings grandes que são os nossos chunks.

Definindo o template e chamando o modelo

Agora vamos finalmente fazer a consulta. Para isso, precisamos definir um template para chamar todo o modelo. Vamos usar o LangChain, do LangChain Core, que é todo modelo de A tem um prompt. Vamos fazer o básico, que é um prompt template. Vamos importar o chat prompt template e definir como nosso prompt:

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "Responda usando exclusivamente os conteudo fornecido. \n\nContexto:\n{contexto}"),
    ("human", "{query}")
])

Agora, efetivamente, vamos chamar um modelo para isso. Precisamos instanciar o modelo que vamos usar. Vamos usar o modelo padrão da OpenAI, que é o chat OpenAI. Nossa saída, como vocês fizeram no curso introdutório de LangChain, será uma saída, um parser padrão:

from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

modelo = ChatOpenAI(
    model="gpt-4.1-nano",
    temperature=0.2,
    api_key=openai_api_key
)

Testando o modelo e criando a cadeia de métodos

Para testar esse modelo, podemos simplesmente dar um invoke nele, da mesma forma que fizemos com o modelo de embeddings, e passar a consulta. Vamos passar a mesma pergunta, que é a pergunta de como proceder em caso de item roubado, que é a variável que definimos ali em cima. Veja que aqui já está demorando mais, ele está usando a nossa chave, chamando o modelo na OpenAI, e agora ele retornou uma mensagem de IA. "Se você tem um item comprado ou roubado, é importante agir rapidamente." Ou seja, deu aquela resposta genérica que o modelo daria sobre como proceder se tivermos um item roubado, fazer um BO. Não é isso que queremos, pois é uma resposta genérica feita pelo modelo de GPT Nano aqui.

Como temos o nosso RAG, vamos criar uma cadeia, ou um chain, usando a linguagem LCL, que é o Language Chain Expression Language, ou seja, vamos encadear métodos. Nossa cadeia será composta pelo prompt, começará com o prompt. Depois, ele montará o prompt já com o nosso contexto, e chamaremos o modelo, e queremos essa resposta feita com um padrão, um parser padrão:

cadeia = prompt | modelo | StrOutputParser()

Executando a cadeia e verificando a resposta

Para chamar essa cadeia, precisamos passar duas informações: o contexto, lembrando que o nosso prompt tem duas variáveis, o contexto e a consulta. Para pegar os trechos, usaremos o Retriever, daquele jeito que fizemos aquela vez. Aqui estamos fazendo na mão, até o final do curso veremos como fazer isso usando realmente o LangChain. Vamos chamar aqui e pegar aqueles trechos usando a consulta, e definir qual é o nosso contexto. O nosso contexto terá que ser uma string, pois o nosso prompt só recebe string. Vamos pegar duas quebras de linha e fazer um join de um array, que terá um trecho, que será o trecho com apenas o conteúdo, parecido com o que fizemos ali em cima, para cada trecho nos trechos que ele fará o Retriever ali. No final, chamaremos a nossa cadeia, agora daremos um invoke na cadeia, não no modelo, e ao invés de passar apenas a consulta, teremos que passar duas variáveis, que são as variáveis que estão no nosso prompt. Veja, a cadeia receberá o prompt, injetará as variáveis no prompt. Aqui pegaremos a nossa consulta, será a consulta mesmo, pura, a mesma pergunta, e o contexto, que é o nome da variável que está no nosso prompt, passaremos o contexto aqui:

trechos = retriever.invoke(query)
contexto = "\n\n".join([trecho.page_content for trecho in trechos])

cadeia.invoke({"query": query, "contexto": contexto})

Agora, se tudo der certo, ele preencherá o nosso prompt e chamará o mesmo modelo, passando o modelo, com a consulta e o system prompt lá. "Para dar entrada em uma ocorrência do sinistro, item roubado, você deve ligar para o número gratuito do MasterCard Gold no seu país." Podemos até conferir no documento se realmente tem esse número, é o mesmo número aqui, estamos falando do cartão MasterCard. Veja que está 100% contextualizado, 100% seguro, não inventou nenhum tipo de informação, o nosso system prompt foi claro sobre a pergunta, e conseguimos fazer a geração com o RAG.

Essa foi a nossa revisão de todos os passos do RAG, desde extrair o documento, criar o banco de dados vetorial, depois fazer a recuperação da informação, enriquecer o prompt, e chamar o LLM com aquele contexto aumentado, e 100% restrito à informação que queríamos. Vamos agora partir para as técnicas avançadas nos próximos vídeos. Até lá.

Revisitando o RAG - Debug com o LangSmith

Compreendendo o fluxo do RAG e a importância do debug

Antes de começarmos a explorar os detalhes do nosso fluxo do RAG, é fundamental compreender que, à medida que o LangChain abstrai as chamadas aos componentes, esses componentes se tornarão cada vez mais complexos e abstratos. Portanto, é essencial entendermos o que está sendo chamado nos bastidores.

Retomando o exemplo anterior, no qual utilizamos nosso modelo para realizar o RAG, vamos chamá-lo novamente. Ele nos fornece apenas a resposta. Para compreendermos melhor, vamos explorar dois níveis para visualizar o que ocorre por trás.

Ativando o debug no LangChain

A primeira maneira é ativando o debug do LangChain. Para isso, precisamos garantir que temos as bibliotecas necessárias instaladas. Vamos começar instalando o LangChain e suas dependências:

!pip install langchain langchain-openai langchain-community

Agora, importamos o debug global do LangChain. Vamos em LangChain globals, importamos set_debug:

from langchain.globals import set_debug

Em seguida, ativamos o debug chamando set_debug com o valor true. A partir desse momento, ao realizarmos uma chamada, como a que fizemos anteriormente, será exibida toda a cadeia de chamadas:

set_debug(True)

Visualizando o processo de chamada

O processo começa com a entrada, que inclui o input, a query e o contexto que buscamos anteriormente. Em seguida, o segundo passo é montar o chat template com essas duas variáveis. Depois, ocorre a chamada ao LLM, onde o prompt é injetado com o contexto, e a parte do usuário também está presente. Finalmente, temos a saída do LLM, que é o texto puro para nós.

Para visualizar isso em ação, podemos invocar a cadeia com os parâmetros necessários:

cadeia.invoke({"query": query, "contexto": contexto})

Essa forma de depuração é essencial à medida que as cadeias se tornam mais complexas. No entanto, existe uma maneira ainda mais eficaz dentro do ecossistema do LangChain, que é utilizando o LangSmith. Vamos configurar uma chave no LangSmith e executar com ele para observar o que acontece.

Configurando e utilizando o LangSmith

Ao acessar o LangSmith, podemos fazer login com uma conta comum do Google. Ele criará automaticamente uma área para nós, que é gratuita e não requer configuração adicional, além de criar um projeto padrão.

Dentro da área pessoal do projeto padrão, vamos configurar nos settings uma chave de API para incluir em nosso código. Vamos criar um novo personal access token, que expirará em 30 dias, e gerar a chave de API. Após isso, copiaremos a chave e a incluiremos no arquivo .env, que criamos no início. No arquivo .env de exemplo, já existem outras variáveis, e para criar o LangSmith, precisamos definir LangSmith como true e adicionar uma variável com a chave:

LANGSMITH_TRACING=true
LANGSMITH_API_KEY=lsv2_pt_5801b7f3b4dba8fd5be7e03427b16_e4af7bd3fd

Após salvar, para que isso se reflita no nosso notebook, é necessário ler o ambiente novamente. Vamos subir até onde o ambiente está sendo lido, instalar o necessário e recarregar. Verificaremos rapidamente se a chave do LangSmith foi carregada, o que já deveria ter ocorrido:

import os
os.environ.get("LANGSMITH_API_KEY")

Desativando o debug e verificando o painel do LangSmith

Em seguida, faremos a mesma chamada no final, desativando o debug para evitar desordem:

set_debug(False)
cadeia.invoke({"query": query, "contexto": contexto})

Vamos acessar o painel do LangSmith para verificar como ele realizou a rastreabilidade da chamada. Isso será muito útil quando formos avaliar outras formas de fatiamento e busca.

Após rodar, a resposta específica foi obtida novamente. Vamos ao navegador para verificar a chamada feita pelo LangSmith. A chave já foi salva, então retornaremos ao dashboard padrão para ver as chamadas realizadas. Precisamos reiniciar o kernel no notebook para que ele reconheça as variáveis de ambiente novamente. Após reiniciar o kernel e rodar tudo novamente, concluímos a execução. No painel do LangSmith, podemos ver a última chamada, que foi feita ao OpenAI. Como estamos construindo todo o prompt manualmente, ele não realiza o retrieval pelo LangChain, mostrando apenas o system prompt, a entrada do usuário e a saída.

Agora que o LangChain está configurado, podemos avançar na parte de carregamento e busca, visualizando como será feita toda a rastreabilidade.

Sobre o curso LangChain: Técnicas Avanças de RAG

O curso LangChain: Técnicas Avanças de RAG possui 205 minutos de vídeos, em um total de 34 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:

Escolha a duração do seu plano e aproveite até 44% OFF

Conheça os Planos para Empresas