Em “Geração aumentada de recuperação, passo a passo”, percorremos um exemplo de RAG muito simples. Nosso pequeno aplicativo aumentou um grande modelo de linguagem (LLM) com nossos próprios documentos, permitindo que o modelo de linguagem respondesse a perguntas sobre nosso próprio conteúdo. Esse exemplo usou um modelo de incorporação da OpenAI, o que significava que tínhamos que enviar nosso conteúdo para os servidores da OpenAI – uma possível violação da privacidade de dados, dependendo do aplicativo. Também usamos o LLM público da OpenAI.

Desta vez construiremos uma versão totalmente local de um sistema de geração de recuperação aumentada, usando um modelo de incorporação local e um LLM local. Como no artigo anterior, usaremos LangChain para unir os vários componentes de nossa aplicação. Em vez de FAISS (Facebook AI Similarity Search), usaremos SQLite-vss para armazenar nossos dados vetoriais. SQLite-vss é nosso amigo familiar SQLite com uma extensão que o torna capaz de pesquisar por similaridade.

Lembre-se de que a pesquisa por similaridade de texto corresponde melhor ao significado (ou semântica) usando embeddings, que são representações numéricas de palavras ou frases em um espaço vetorial. Quanto menor for a distância entre duas incorporações no espaço vetorial, mais próximo será o significado das duas palavras ou frases. Portanto, para alimentar um LLM com nossos próprios documentos, primeiro precisamos convertê-los em embeddings, que é a única matéria-prima que um LLM pode tomar como entrada.

Salvamos a incorporação no armazenamento de vetores local e, em seguida, integramos esse armazenamento de vetores ao nosso LLM. Usaremos o Llama 2 como nosso LLM, que executaremos localmente usando um aplicativo chamado Ollama, que está disponível para macOS, Linux e Windows (este último em versão prévia). Você pode ler sobre a instalação do Ollama neste artigo do InfoWorld.

Aqui está a lista de componentes que precisaremos para construir um sistema RAG simples e totalmente local:

  1. Um corpus documental. Aqui usaremos apenas um documento, o texto do Discurso sobre o Estado da União do Presidente Biden, de 7 de fevereiro de 2023. Você pode baixar este texto no link abaixo.
  2. Um carregador para o documento. Este código extrairá o texto do documento e o pré-processará em partes para gerar uma incorporação.
  3. Um modelo de incorporação. Este modelo pega os pedaços de documentos pré-processados ​​como entrada e gera uma incorporação (ou seja, um conjunto de vetores que representam os pedaços de documentos).
  4. Um armazenamento de dados vetoriais local com um índice para pesquisa.
  5. Um LLM ajustado para seguir instruções e rodar em sua própria máquina. Esta máquina pode ser um desktop, um laptop ou uma VM na nuvem. No meu exemplo, é um modelo Llama 2 rodando no Ollama no meu Mac.
  6. Um modelo de bate-papo para fazer perguntas. Este modelo cria uma estrutura para o LLM responder em um formato que os seres humanos compreenderão.

Agora o código com mais explicações nos comentários.

download

Arquivo de texto do discurso do presidente Biden de 7 de fevereiro de 2023, sobre o estado da União

Exemplo de RAG totalmente local – código de recuperação

# LocalRAG.py
# LangChain is a framework and toolkit for interacting with LLMs programmatically

from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import SQLiteVSS
from langchain.document_loaders import TextLoader

# Load the document using a LangChain text loader
loader = TextLoader("./sotu2023.txt")
documents = loader.load()

# Split the document into chunks
text_splitter = CharacterTextSplitter (chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
texts = (doc.page_content for doc in docs)

# Use the sentence transformer package with the all-MiniLM-L6-v2 embedding model
embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")

# Load the text embeddings in SQLiteVSS in a table named state_union
db = SQLiteVSS.from_texts(
    texts = texts,
    embedding = embedding_function,
    table = "state_union",
    db_file = "/tmp/vss.db"
)

# First, we will do a simple retrieval using similarity search
# Query
question = "What did the president say about Nancy Pelosi?"
data = db.similarity_search(question)

# print results
print(data(0).page_content)

Exemplo de RAG totalmente local – saída de recuperação

Senhor Presidente. Senhora vice-presidente. Nossa primeira-dama e segundo cavalheiro.

Membros do Congresso e do Gabinete. Líderes de nossos militares.

Senhor Chefe de Justiça, Juízes Associados e Juízes aposentados da Suprema Corte.

E vocês, meus compatriotas americanos.

Começo esta noite felicitando os membros do 118º Congresso e o novo Presidente da Câmara, Kevin McCarthy.

Senhor Presidente, estou ansioso para trabalharmos juntos.

Também quero parabenizar o novo líder dos Democratas na Câmara e o primeiro líder da minoria na Casa Negra na história, Hakeem Jeffries.

Parabéns ao líder do Senado mais antigo da história, Mitch McConnell.

E parabéns a Chuck Schumer por mais um mandato como líder da maioria no Senado, desta vez com uma maioria ainda maior.

E quero dar um reconhecimento especial a alguém que penso que será considerado o maior orador da história deste país, Nancy Pelosi.

Observe que o resultado inclui um pedaço literal de texto do documento que é relevante para a consulta. É o que é retornado pela busca por similaridade do banco de dados vetorial, mas não é a resposta à consulta. A última linha da saída é a resposta à consulta. O resto da saída é o contexto da resposta.

Observe que pedaços de seus documentos são exatamente o que você obterá se fizer uma pesquisa de similaridade bruta em um banco de dados vetorial. Freqüentemente, você obterá mais de um pedaço, dependendo da sua pergunta e de quão ampla ou restrita ela é. Como nossa pergunta de exemplo era bastante restrita e porque há apenas uma menção a Nancy Pelosi no texto, recebemos apenas um trecho de volta.

Agora usaremos o LLM para ingerir o pedaço de texto que veio da pesquisa por similaridade e gerar uma resposta compacta para a consulta.

Antes de poder executar o código a seguir, o Ollama deve ser instalado e o modelo llama2:7b baixado. Observe que no macOS e no Linux, Ollama armazena o modelo no subdiretório .ollama no diretório inicial do usuário.

RAG totalmente local – código de consulta

# LLM
from langchain.llms import Ollama
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
llm = Ollama(
    model = "llama2:7b",
    verbose = True,
    callback_manager = CallbackManager((StreamingStdOutCallbackHandler())),
)

# QA chain
from langchain.chains import RetrievalQA
from langchain import hub

# LangChain Hub is a repository of LangChain prompts shared by the community
QA_CHAIN_PROMPT = hub.pull("rlm/rag-prompt-llama")
qa_chain = RetrievalQA.from_chain_type(
    llm,
    # we create a retriever to interact with the db using an augmented context
    retriever = db.as_retriever(), 
    chain_type_kwargs = {"prompt": QA_CHAIN_PROMPT},
)

result = qa_chain({"query": question})

Exemplo de RAG totalmente local – saída de consulta

No contexto recuperado, o presidente Biden refere-se a Nancy Pelosi como

“alguém que penso que será considerado o maior orador da história deste país.”

Isto sugere que o Presidente tem uma opinião elevada sobre as capacidades de liderança e realizações de Pelosi como Presidente da Câmara.

Observe a diferença na saída dos dois trechos. O primeiro é um pedaço literal de texto do documento relevante para a consulta. A segunda é uma resposta destilada à consulta. No primeiro caso não estamos utilizando o LLM. Estamos apenas usando o armazenamento de vetores para recuperar um pedaço de texto do documento. Somente no segundo caso estamos utilizando o LLM, que gera uma resposta compacta à consulta.

Para usar o RAG em aplicações práticas, você precisará importar vários tipos de documentos, como PDF, DOCX, RTF, XLSX e PPTX. Tanto LangChain quanto LlamaIndex (outra estrutura popular para construção de aplicativos LLM) possuem carregadores especializados para uma variedade de tipos de documentos.

Além disso, você pode querer explorar outras lojas de vetores além de FAISS e SQLite-vss. Assim como os grandes modelos de linguagem e outras áreas da IA ​​generativa, o espaço do banco de dados vetorial está evoluindo rapidamente. Iremos nos aprofundar em outras opções em todas essas frentes em artigos futuros aqui.