Aula 49 – Retrieval Augmented Generation com Mistral AI e Langchain
Aula 49 – Retrieval Augmented Generation com Mistral AI e Langchain
Voltar para página principal do blog
Todas as aulas desse curso
Aula 48 Aula 50
Redes Sociais:
Site das bibliotecas
Tensorflow
Keras
Cursos Gratuitos
Digital Innovation
Quer aprender python3 de graça e com certificado? Acesse então:
workover
Meus link de afiliados:
Hostinger
Digital Ocean
One.com
Canais do Youtube
Toti
Lofi Music Zone Beats
Backing Track / Play-Along
Código Fluente
Putz!
Vocal Techniques and Exercises
Fiquem a vontade para me adicionar ao linkedin.
PIX para doações
Links da Aula:
Notebook da aula: Google Colab
Aula 49 – Retrieval Augmented Generation com Mistral AI e Langchain
Mistral AI
A Mistral AI é uma empresa francesa de inteligência artificial fundada em 2023 por ex-funcionários da Meta e do Google DeepMind.
A Mistral AI é comprometida com a ciência aberta, comunidade e software livre.
Grande parte de seus modelos e ferramentas de implementação são lançados sob licenças permissivas, como Apache 2.0.
Isso significa que:
- O código está disponível para qualquer pessoa inspecionar, modificar e usar.
- A comunidade pode contribuir para o desenvolvimento e aprimoramento da tecnologia.
- A IA se torna mais democrática, transparente e acessível a todos.
LangChain
Framework de código aberto para:
- Conectar LLMs a dados
- Criar pipelines de NLP
- Personalizar LLMs
- Implementar interfaces de usuário
Benefícios:
- Acelera o desenvolvimento
- Simplifica o uso de LLMs
- Promove a reutilização de código
- Amplia a comunidade de LLMs
O RAG
Começaremos inicializando o Modelo de Linguagem (LLM) tanto para a incorporação(embeddings) de texto quanto para a geração de resposta.
Para os embeddings vamos fazer usar o all-mpnet-base-v2 e o distilbert-base-uncased
Para gerar a resposta, utilizaremos o modelo “Mistral-7B-Instruct-v0.2“, especificamente sua versão quantizada.
Obs. Os parâmetros são os pesos, isto é, a quantidade de pesos que compõem o modelo, ou seja, os pesos ajustáveis da rede neural que compõe o modelo.
Carregar esse modelo quantizado requer uma GPU com pelo menos 16 GB de RAM.
Por favor, verifique se sua GPU atende a esse requisito.
Precisaremos de uma configuração na qual possamos carregar o modelo em 4 bits, como na quantização FP4.
Para isso, utilizamos uma conta Colab Pro equipada com uma GPU T4.
Passo 1
Aqui nesse passo, importamos as bibliotecas necessárias.
Bibliotecas usadas
- chromadb: Biblioteca para armazenamento e recuperação eficiente de dados estruturados usando índices de proximidade. Estamos usando esta biblioteca para armazenar e recuperar os chunks de texto e suas embeddings, facilitando a busca durante o processo de geração de respostas.
- langchain: Uma biblioteca para processamento de linguagem natural (NLP) que fornece diversas ferramentas, como divisão de texto, embeddings de texto e geração de respostas. Estamos usando langchain para dividir o texto do documento PDF em partes gerenciáveis, processar as embeddings de texto e implementar a geração de respostas.
- pypdf: Uma biblioteca para processamento de arquivos PDF. Estamos utilizando esta biblioteca para carregar e extrair texto de documentos PDF fornecidos como entrada para o sistema.
- sentencepiece: Uma biblioteca para tokenização de texto, frequentemente usada em modelos de aprendizado de máquina para dividir texto em tokens.
- bitsandbytes: Uma biblioteca para manipulação eficiente de bits e bytes em Python.
- transformers: Uma biblioteca desenvolvida pela Hugging Face para implementação e uso de modelos de linguagem pré-treinados, como BERT, GPT, etc. Estamos usando esta biblioteca para carregar o modelo quantizado Mistral-7B-Instruct-v0.2 e seu tokenizer associado.
- peft: Biblioteca que oferece aceleração para pipelines da Hugging Face.
- accelerate: Outra biblioteca para aceleração de treinamento de modelos de aprendizado de máquina, desenvolvida pela Hugging Face.
- sentence-transformers: Uma biblioteca para criar embeddings de texto semântico de alta qualidade usando modelos pré-treinados. Estamos usando esta biblioteca para obter embeddings de texto que serão armazenadas no banco de dados.
Passo 2
Aqui é realizada a técnica de quantização e em seguida, carregamos o modelo quantizado usando o Pipeline da HuggingFace.
# carrega a biblioteca necessária
import torch
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.llms.huggingface_pipeline import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, BitsAndBytesConfig
from langchain.document_loaders import PyPDFLoader
from langchain.chains.question_answering import load_qa_chain
from langchain.prompts import PromptTemplate
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
model_kwargs = {'device': 'cuda'}
#embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2", model_kwargs=model_kwargs)
embeddings = HuggingFaceEmbeddings(model_name="distilbert-base-uncased", model_kwargs=model_kwargs)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2")
model = AutoModelForCausalLM.from_pretrained("mistralai/Mistral-7B-Instruct-v0.2", device_map='auto', quantization_config=quantization_config)
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=150)
llm = HuggingFacePipeline(pipeline=pipe)
O código que carrega várias bibliotecas e configurações necessárias para implementar a Retrieval Augmented Generation (RAG) usando o Langchain e o Hugging Face Transformers.
No quantization_config, a configuração prepara o modelo para usar uma quantização agressiva (redução para 4 bits) para armazenamento de dados e utiliza o tipo de dado bfloat16 nas computações, visando otimização de desempenho e eficiência de memória.
Na linha do embeddings, onde temos a criação de um objeto para gerar embeddings (representações numéricas) de texto, vamos testar dois modelos.
Primeiro o sentence-transformers/all-mpnet-base-v2 e depois o distilbert-base-uncased.
Na parte do model_kwargs é criado um dicionário chamado model_kwargs, o qual contém uma configuração para especificar o dispositivo em que um modelo de inteligência artificial deve ser executado.
Ao passar model_kwargs para um modelo ou função em bibliotecas que suportam operações em GPU, como PyTorch, você está instruindo o modelo a executar suas operações na GPU em vez de na CPU, buscando um processamento mais rápido.
Passo 3
Nesse passo o PDF é carregado, dividido em pedaços com alguma sobreposição por questões de contexto.
# Carrega o arquivo PDF
pdf_link = "/content/sample_data/CF.pdf"
loader = PyPDFLoader(pdf_link, extract_images=False)
pages = loader.load_and_split()
# Divida os dados em partes(chunks)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 4000,
chunk_overlap = 20,
length_function = len,
add_start_index = True,
)
chunks = text_splitter.split_documents(pages)
Código que carrega um arquivo PDF e divide seu conteúdo em partes gerenciáveis.
- Carregamento do Arquivo PDF:
pdf_link = "/content/sample_data/CF.pdf"
: Especifica o caminho do arquivo PDF a ser carregado. Neste caso, o caminho é para o arquivo “CF.pdf” localizado em “/content/sample_data/”.loader = PyPDFLoader(pdf_link, extract_images=False)
: Inicializa um objetoPyPDFLoader
para carregar o arquivo PDF especificado. O parâmetroextract_images=False
indica que as imagens presentes no PDF não serão extraídas durante o carregamento.pages = loader.load_and_split()
: Carrega o PDF e divide seu conteúdo em páginas individuais.
- Divisão do Texto em Chunks:
text_splitter = RecursiveCharacterTextSplitter(...)
: Inicializa um objetoRecursiveCharacterTextSplitter
para dividir o texto em partes menores, chamadas de “chunks”.chunk_size = 4000
: Define o tamanho máximo de cada chunk em caracteres. Neste caso, cada chunk terá no máximo 4000 caracteres.chunk_overlap = 20
: Define o número de caracteres de sobreposição entre os chunks adjacentes. Isso garante que não haja perda de informação entre os chunks.length_function = len
: Define a função que será usada para calcular o comprimento do texto. Aqui, estamos usando a funçãolen()
padrão do Python.add_start_index = True
: Indica se deve ser adicionado um índice de início ao texto dividido.chunks = text_splitter.split_documents(pages)
: Divide o texto das páginas do PDF em chunks usando os parâmetros especificados.
No final deste trecho de código, teremos uma lista de chunks, onde cada chunk contém uma parte do texto do PDF. Essa divisão é útil para processar grandes volumes de texto de forma mais eficiente e para facilitar a busca e análise do conteúdo.
Passo 4
Agora o código que armazena os dados divididos em chunks em um banco de dados usando a biblioteca Chroma.
# Armazena dados no banco de dados
db=Chroma.from_documents(chunks,embedding=embeddings, persist_directory="test_index")
db.persist()
- Armazenamento dos Dados no Banco de Dados:
db = Chroma.from_documents(chunks, embedding=embeddings, persist_directory="test_index")
: Cria um objeto Chroma e adiciona os chunks de texto como documentos no banco de dados. O parâmetrochunks
contém a lista de chunks de texto que foram divididos anteriormente. O parâmetroembedding=embeddings
especifica as embeddings de texto associadas aos chunks, que foram calculadas anteriormente. O parâmetropersist_directory="test_index"
especifica o diretório onde os dados serão armazenados no disco.
- Persistência do Banco de Dados:
db.persist()
: Salva o banco de dados no disco. Esta operação garante que os dados permaneçam armazenados mesmo após o encerramento do programa.
Em resumo, o código armazena os chunks de texto e suas embeddings associadas em um banco de dados usando a biblioteca Chroma.
Isso permite uma recuperação eficiente dos dados durante a geração de respostas às perguntas dos usuários.
Passo 5
Carrega o banco de dados de vetores, configura um recuperador para buscar documentos relevantes no banco de dados e carrega uma cadeia de questionamento e resposta (QNA) usando o LangChain.
# Carrega o banco de dados de vetores
vectordb = Chroma(persist_directory="test_index", embedding_function=embeddings)
# Carrega o recuperador
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
# Template de prompt para Pergunta e Resposta
qna_prompt_template = """### [INST] Instrução: Serão fornecidas perguntas e dados relacionados. Sua tarefa é encontrar as respostas para as perguntas usando os dados fornecidos. Se os dados não contiverem a resposta para a pergunta, então você deve retornar 'Informação insuficiente.'
{context}
### Pergunta: {question} [/INST]"""
# Cria o template de prompt
PROMPT = PromptTemplate(
template=qna_prompt_template, input_variables=["context", "question"]
)
# Carrega a cadeia de questionamento e resposta
chain = load_qa_chain(llm, chain_type="stuff", prompt=PROMPT)
Explicação do que cada parte faz:
- Carregamento do Banco de Dados de Vetores:
vectordb = Chroma(persist_directory="test_index", embedding_function=embeddings)
: Cria um objeto Chroma para carregar o banco de dados previamente criado e armazenado no diretório “test_index”. O parâmetroembedding_function=embeddings
especifica a função de embeddings a ser usada para recuperar as embeddings de texto associadas aos documentos no banco de dados.
- Configuração do Recuperador:
retriever = vectordb.as_retriever(search_kwargs={"k": 3})
: Configura o banco de dados de vetores como um recuperador para buscar documentos relevantes com base nas embeddings de texto. O parâmetrosearch_kwargs={"k": 3}
especifica que o recuperador buscará três partes, de pedaços do documento, que sejam mais relevantes para cada pergunta.
- Template de Prompt para Pergunta e Resposta:
qna_prompt_template
: Define um template de prompt para perguntas e respostas, que será utilizado para formatar a entrada para a cadeia de perguntas e resposta (QNA). Este template inclui placeholders para o contexto e a pergunta.
- Criação do Template de Prompt:
PROMPT = PromptTemplate(template=qna_prompt_template, input_variables=["context", "question"])
: Cria um objeto de template de prompt utilizando o template definido anteriormente e especificando as variáveis de entrada como “context” e “question”.
- Carregamento da Cadeia de Pergunta e Resposta:
chain = load_qa_chain(llm, chain_type="stuff", prompt=PROMPT)
: Carrega a cadeia de pergunta e resposta (QNA) utilizando o modelo de linguagem pré-treinado (llm) e o template de prompt configurado anteriormente. O parâmetrochain_type="stuff"
especifica o tipo de cadeia a ser carregado.
Em resumo, este trecho de código configura o ambiente para responder a perguntas com base nos dados armazenados no banco de dados, utilizando uma cadeia de pergunta e resposta (QNA) personalizada.
Passo 6
def ask(question):
max_length = 200
aviso_fora_de_contexto = "Aviso: A pergunta está fora do contexto dos documentos fornecidos. Fornecendo uma resposta com base no conhecimento geral do modelo."
prompt = "Responda em português: " + question
# Tenta encontrar documentos relevantes para a pergunta
context = retriever.get_relevant_documents(question)
try:
if not context:
print(aviso_fora_de_contexto)
full_response = llm.invoke(prompt, max_length=max_length)
else:
chain_response = chain.run({"input_documents": context, "question": question})
if "Informação insuficiente" in chain_response or chain_response.strip() == "":
print(aviso_fora_de_contexto)
full_response = llm.invoke(prompt, max_length=max_length)
else:
full_response = chain_response
# Encontra um ponto de corte lógico
response = full_response.strip().split("\n")[0]
if len(response) >= max_length - 1:
# Procura o último ponto final antes do limite
last_period = response.rfind('.')
response = response[:last_period + 1] if last_period != -1 else response
response += " [Resposta truncada. Consulte a fonte para mais detalhes.]"
if not context:
response = aviso_fora_de_contexto + "\n\nResposta: " + response
else:
response = "Resposta: " + response
except Exception as e:
response = f"Erro ao gerar resposta: {e}"
return response
A função ask(question)
é responsável por receber uma pergunta do usuário, buscar documentos relevantes no banco de dados usando o recuperador configurado, e gerar uma resposta usando a cadeia de questionamento e resposta (QNA). Aqui está uma explicação do que cada parte da função faz:
max_length = 200
: Define o comprimento máximo da resposta gerada.aviso_fora_de_contexto = "Aviso: A pergunta está fora do contexto dos documentos fornecidos. Fornecendo uma resposta com base no conhecimento geral do modelo."
: Define uma mensagem de aviso para indicar que a pergunta está fora do contexto do documento fornecido.prompt = "Responda em português: " + question
: Define o prompt que será usado para gerar a resposta, incluindo a pergunta feita pelo usuário.context = retriever.get_relevant_documents(question)
: Busca documentos relevantes no banco de dados com base na pergunta fornecida pelo usuário.if not context:
: Verifica se não há documentos relevantes encontrados para a pergunta.- Se não houver contexto, a função tentará gerar uma resposta com base no conhecimento geral do modelo usando o modelo de linguagem pré-treinado (
llm.invoke()
). - Se ocorrer um erro durante a geração da resposta, a função captura a exceção e retorna uma mensagem de erro.
- Se não houver contexto, a função tentará gerar uma resposta com base no conhecimento geral do modelo usando o modelo de linguagem pré-treinado (
else:
: Se houver contexto (trechos relevantes do documento forma encontrados), a função executa a cadeia de questionamento e resposta (QNA) para gerar a resposta.- Se a resposta da cadeia for “Informação insuficiente” ou estiver vazia, a função tentará gerar uma resposta com base no conhecimento geral do modelo.
- Se ocorrer um erro durante a geração da resposta, a função captura a exceção e retorna uma mensagem de erro.
return response
: Retorna a resposta gerada para o usuário.
Essa função encapsula todo o processo de geração de respostas com base na pergunta do usuário, lidando com diferentes cenários, como contexto ausente, resposta insuficiente e erros durante a geração da resposta.
Passo 7
# Pega a entrada do usuário e chama a função para gerar a saída
user_question = input("Usuário: ")
answer = ask(user_question)
print("Resposta:", answer)
Permite que o usuário faça uma pergunta e em seguida chama a função ask(user_question)
para gerar uma resposta com base na pergunta fornecida. Aqui está uma explicação do que cada parte faz:
user_question = input("Usuário: ")
: Solicita ao usuário que insira uma pergunta através da entrada padrão (teclado) e armazena a pergunta na variáveluser_question
.answer = ask(user_question)
: Chama a funçãoask(user_question)
passando a pergunta do usuário como argumento e armazena a resposta gerada na variávelanswer
.print("Resposta:", answer)
: Imprime a resposta gerada para a pergunta do usuário na tela, precedida pelo texto “Resposta:”.
Essa parte do código permite que o usuário interaja com o sistema fazendo perguntas e recebendo respostas geradas com base nos dados disponíveis no banco de dados e no modelo de linguagem pré-treinado.