Aprimoramento da precisão do LLM usando o Atlas Vector Search do MongoDB e os metadados Unstructured.io
Avalie esse Tutorial
Apesar dos progressos notáveis na inteligência artificial, especialmente na AI generativa (GenAI), a precisão continua sendo um objetivo indefinido para os resultados doLLM. De acordo com a última Pesquisa Global Anual da MacKinset, “The state of AI in 2023,“A AI teve um ano de destaque. Praticamente um quarto dos gerentes de nível C usa pessoalmente ferramentas gerais de AI para o trabalho, e mais 25% das empresas com implementações de AI têm a AI geral nas agendas de seus conselhos. Além disso, 40% dos pesquisados planeja aumentar o capital da organização em AI devido aos progressos da AI em geral. A pesquisa reflete o infinito potencial e a rápida aprovação das tecnologias de AI. No entanto, a pesquisa também aponta para uma preocupação significativa: imprecisão.
A imprecisão nos LLMs geralmente resulta em "alucinações" ou informações incorretas devido a limitações como a compreensão semântica superficial e a qualidade variável de dados. A incorporação da pesquisa vetorial semântica usando o MongoDB pode ajudar, permitindo a consulta em tempo real dos dados de treinamento, garantindo que as respostas geradas estejam alinhadas com o que o modelo aprendeu. Além disso, a adição de filtragem de metadados extraídos por ferramentas não estruturadas pode refinar a precisão, permitindo que o modelo avalie a confiabilidade de suas fontes de dados. Juntos, esses métodos podem minimizar significativamente o risco de alucinações e tornar os LLMs mais confiáveis.
Este artigo aborda esse desafio fornecendo um guia abrangente sobre como melhorar a precisão de suas saídas de LLM usando o Atlas Vector Search e as técnicas de extração de metadados não estruturados do MongoDB. O principal objetivo deste tutorial é equipar você com o conhecimento e as ferramentas necessárias para incorporar documentos de origem externa em seu LLM, enriquecendo assim as respostas do modelo com informações bem fundamentadas e contextualmente precisas. Ao final deste tutorial, você poderá gerar uma saída precisa do modelo OpenAI GPT-4 para citar o documento de origem, incluindo o nome do arquivo e o número da página. O bloco de notas completo deste tutorial está disponível no Google Colab, mas analisaremos as seções do tutorial juntos.
O MongoDB é um banco de dados NoSQL, que significa "not only SQL", destacando sua flexibilidade no manuseio de dados que não se encaixam bem em estruturas tabulares como as dos bancos de dados SQL. Os bancos de dados NoSQL são particularmente adequados para armazenar dados não estruturados e semiestruturados, oferecendo um esquema mais flexível, escalonamento horizontal mais fácil e a capacidade de lidar com grandes volumes de dados. Isso os torna ideais para aplicativos que exigem desenvolvimento rápido e a capacidade de gerenciar vastos arrays de metadados.
Os recursos robustos de pesquisa vetorial do MongoDB e a capacidade de lidar perfeitamente com dados e metadados vetoriais fazem dele uma plataforma ideal para aprimorar a precisão dos resultados do LLM. Permite pesquisas multifacetadas com base na similaridade semântica e em vários atributos de metadados. Esse conjunto exclusivo de recursos distingue o MongoDB das plataformas tradicionais de dados para desenvolvedores e aumenta significativamente a precisão e a confiabilidade dos resultados em tarefas de modelagem de linguagem.
A biblioteca de código aberto Unstructured fornece componentes para a ingestão e o pré-processamento de imagens e documentos de texto, como PDFs, HTML, documentos do Word e muitos outros. Os casos de uso da Unstructured giram em torno da simplificação e otimização do fluxo de trabalho de processamento de dados para LLMs. Os blocos modulares e conectores da Unstructured formam um sistema coeso que simplifica a ingestão e o pré-processamento de dados, tornando-a adaptável a diferentes plataformas e transformando com eficiência dados não estruturados em resultados estruturados.
Os metadados são geralmente chamados de "dados sobre dados". Eles fornecem informações contextuais ou descritivas sobre os dados primários, como a fonte, o formato e as características relevantes. Os metadados das ferramentas não estruturadas rastreiam vários detalhes sobre elementos extraídos de documentos, permitindo que os usuários filtrem e analisem esses elementos com base em metadados específicos de interesse. Os campos de metadados incluem informações sobre o documento de origem e conectores de dados.
O conceito de metadados é familiar, mas sua aplicação no contexto de dados não estruturados gera muitas oportunidades. O pacote Unstructured acompanha uma variedade de metadados no nível do elemento. Esses metadados podem ser acessados com
element.metadata
e convertidos em uma representação de dicionário Python usando element.metadata.to_dict()
.Neste artigo, nos concentramos particularmente nos metadados
filename
e page_number
para melhorar a rastreabilidade e a confiabilidade das saídas do LLM. Ao fazer isso, podemos citar a localização exata do arquivo PDF que fornece a resposta a uma consulta do usuário. Isso se torna especialmente importante quando o LLM responde a consultas relacionadas a tópicos delicados, como questões financeiras, jurídicas ou médicas.- Cadastre-se para obter uma conta do MongoDB Atlas e instale a biblioteca PyMongo no IDE de sua escolha ou no Colab.
- Extraia os textos e os metadados dos documentos de origem usando o partition_pdf do Unstructured.
- Prepare os dados para armazenamento e recuperação no MongoDB.
- Vetorize os textos usando a biblioteca SentenceTransformer.
- Conecte e carregue registros no MongoDB Atlas.
- Consulte o índice com base na similaridade de incorporação.
- Gere a saída LLM usando o modelo OpenAI.
Certifique-se de ter instalado as bibliotecas necessárias para executar o código necessário.
1 # Install Unstructured partition for PDF and dependencies 2 pip install unstructured[“pdf”] 3 !apt-get -qq install poppler-utils tesseract-ocr 4 !pip install -q --user --upgrade pillow 5 6 pip install pymongo 7 pip install sentence-transformers
Vamos nos aprofundar na extração de dados de um documento PDF, especificamente o artigo seminal "Attention is All You Need", usando a função
partition_pdf
da biblioteca Unstructured
em Python. Primeiro, você precisará importar a função com from unstructured.partition.pdf import partition_pdf
. Em seguida, você pode chamar partition_pdf
e passar os parâmetros necessários:filename
especifica o arquivo PDF a ser processado, que é "example-docs/Attention is All You Need.pdf."strategy
define o tipo de extração e, para uma varredura mais abrangente, usamos "hi_res."- Por fim,
infer_table_structured=True
diz à função para extrair também os metadados da tabela.
Configurado corretamente, como você pode ver em nosso arquivo Colab, o código fica assim:
1 from unstructured.partition.pdf import partition_pdf 2 3 elements = partition_pdf("example-docs/Attention is All You Need.pdf", 4 strategy="hi_res", 5 infer_table_structured=True)
Ao executar esse código, você preencherá a variável
elements
com todas as informações extraídas do PDF, prontas para análise ou manipulação adicionais. Nos trechos de código do Colab, você pode inspecionar os textos extraídos e os metadados dos elementos. Para observar as saídas de amostra – ou seja, o tipo e o texto do elemento – execute a linha abaixo. Use uma declaração impressa e certifique-se de que a saída recebida corresponda à seguinte:1 display(*[(type(element), element.text) for element in elements[14:18]])
Saída:
1 (unstructured.documents.elements.NarrativeText, 2 'The dominant sequence transduction models are based on complex recurrent or convolutional neural networks that include an encoder and a decoder. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new simple network architecture, the Transformer, based solely on attention mechanisms, dispensing with recurrence and convolutions entirely. Experiments on two machine translation tasks show these models to be superior in quality while being more parallelizable and requiring significantly less time to train. Our model achieves 28.4 BLEU on the WMT 2014 English- to-German translation task, improving over the existing best results, including ensembles, by over 2 BLEU. On the WMT 2014 English-to-French translation task, our model establishes a new single-model state-of-the-art BLEU score of 41.8 after training for 3.5 days on eight GPUs, a small fraction of the training costs of the best models from the literature. We show that the Transformer generalizes well to other tasks by applying it successfully to English constituency parsing both with large and limited training data.') 3 (unstructured.documents.elements.NarrativeText, 4 '∗Equal contribution. Listing order is random....
Você também pode usar o Counter da Python Collection para contar o número de tipos de elementos identificados no documento.
1 from collections import Counter 2 display(Counter(type(element) for element in elements)) 3 4 # outputs 5 Counter({unstructured.documents.elements.NarrativeText: 86, 6 unstructured.documents.elements.Title: 56, 7 unstructured.documents.elements.Text: 45, 8 unstructured.documents.elements.Header: 3, 9 unstructured.documents.elements.Footer: 9, 10 unstructured.documents.elements.Image: 5, 11 unstructured.documents.elements.FigureCaption: 5, 12 unstructured.documents.elements.Formula: 5, 13 unstructured.documents.elements.ListItem: 43, 14 unstructured.documents.elements.Table: 4})
Por fim, você pode converter os objetos de elemento em dicionários Python usando a função integrada
convert_to_dict
para extrair e modificar seletivamente os metadados do elemento.1 from unstructured.staging.base import convert_to_dict 2 3 # built-in function to convert elements into Python dictionary 4 records = convert_to_dict(elements) 5 6 # display the first record 7 records[0] 8 9 # output 10 {'type': 'NarrativeText', 11 'element_id': '6b82d499d67190c0ceffe3a99958e296', 12 'metadata': {'coordinates': {'points': ((327.6542053222656, 13 199.8135528564453), 14 (327.6542053222656, 315.7165832519531), 15 (1376.0062255859375, 315.7165832519531), 16 (1376.0062255859375, 199.8135528564453)), 17 'system': 'PixelSpace', 18 'layout_width': 1700, 19 'layout_height': 2200}, 20 'filename': 'Attention is All You Need.pdf', 21 'last_modified': '2023-10-09T20:15:36', 22 'filetype': 'application/pdf', 23 'page_number': 1, 24 'detection_class_prob': 0.5751863718032837}, 25 'text': 'Provided proper attribution is provided, Google hereby grants permission to reproduce the tables and figures in this paper solely for use in journalistic or scholarly works.'}
Etapa 2a: Vetorize os textos usando a biblioteca SentenceTransformer.
Devemos incluir os metadados do elemento extraído ao armazenar e recuperar os textos do MongoDB Atlas para permitir a recuperação de dados com metadados e pesquisa vetorial.
Primeiro, vetorizamos os textos para realizar uma pesquisa vetorial baseada em similaridade. Neste exemplo, usamos
microsoft/mpnet-base
da biblioteca Sentence Transformer. Esse modelo tem um tamanho de incorporação 768.1 from sentence_transformers import SentenceTransformer 2 from pprint import pprint 3 4 model = SentenceTransformer('microsoft/mpnet-base') 5 6 # Let's test and check the number of embedding size using this model 7 emb = model.encode("this is a test").tolist() 8 print(len(emb)) 9 print(emb[:10]) 10 print("\n") 11 12 # output 13 768 14 [-0.15820945799350739, 0.008249259553849697, -0.033347081393003464, …]
É importante usar um modelo com o mesmo tamanho de incorporação definido no Índice do MongoDB Atlas. Certifique-se de usar o tamanho de incorporação compatível com os índices do MongoDB Atlas. Você pode definir o índice usando a sintaxe JSON abaixo:
1 { 2 "type": "vectorSearch, 3 "fields": [{ 4 "path": "embedding", 5 "dimensions": 768, # the dimension of `mpnet-base` model 6 "similarity": "euclidean", 7 "type": "vector" 8 }] 9 }
Copie e cole o índice JSON em sua coleção MongoDB para que ele possa indexar o campo
embedding
nos registros. Consulte esta documentação sobre como indexar incorporações de vetores para Vector Search.Em seguida, crie a incorporação de texto para cada registro antes de carregá-los no MongoDB Atlas:
1 for record in records: 2 txt = record['text'] 3 4 # use the embedding model to vectorize the text into the record 5 record['embedding'] = model.encode(txt).tolist() 6 7 # print the first record with embedding 8 records[0] 9 10 # output 11 {'type': 'NarrativeText', 12 'element_id': '6b82d499d67190c0ceffe3a99958e296', 13 'metadata': {'coordinates': {'points': ((327.6542053222656, 14 199.8135528564453), 15 (327.6542053222656, 315.7165832519531), 16 (1376.0062255859375, 315.7165832519531), 17 (1376.0062255859375, 199.8135528564453)), 18 'system': 'PixelSpace', 19 'layout_width': 1700, 20 'layout_height': 2200}, 21 'filename': 'Attention is All You Need.pdf', 22 'last_modified': '2023-10-09T20:15:36', 23 'filetype': 'application/pdf', 24 'page_number': 1, 25 'detection_class_prob': 0.5751863718032837}, 26 'text': 'Provided proper attribution is provided, Google hereby grants permission to reproduce the tables and figures in this paper solely for use in journalistic or scholarly works.', 27 'embedding': [-0.018366225063800812, 28 -0.10861606895923615, 29 0.00344603369012475, 30 0.04939081519842148, 31 -0.012352174147963524, 32 -0.04383034259080887,...], 33 '_id': ObjectId('6524626a6d1d8783bb807943')} 34 }
Passo 2b: conectar e carregar registros no MongoDB Atlas
Antes de podermos armazenar nossos registros no MongoDB, usaremos a biblioteca PyMongo para estabelecer uma conexão com o MongoDB database e a coleção. Use este trecho de código para conectar e testar a conexão (consulte a documentação do MongoDB sobre como se conectar ao cluster).
1 from pymongo.mongo_client import MongoClient 2 from pymongo.server_api import ServerApi 3 4 uri = "<<REPLACE THE URI TO CONNECT TO CLUSTER>>" 5 6 # Create a new client and connect to the server 7 client = MongoClient(uri, server_api=ServerApi('1')) 8 9 # Send a ping to confirm a successful connection 10 try: 11 client.admin.command('ping') 12 print("Pinged your deployment. You successfully connected to MongoDB!") 13 except Exception as e: 14 print(e)
Uma vez executada, a saída: "Pinged your deployment. You successfully connected to MongoDB!" aparecerá.
Em seguida, podemos fazer o upload dos registros usando a função
insert_many
do PyMongo.Para isso, primeiro precisamos obter nossa string de conexão com o MongoDB database. Certifique-se de que os nomes do banco de dados e da coleção correspondem aos do MongoDB Atlas.
1 db_name = "unstructured_db" 2 collection_name = "unstructured_col" 3 4 # delete all first 5 client[db_name][collection_name].delete_many({}) 6 7 # insert 8 client[db_name][collection_name].insert_many(records)
Vamos visualizar os registros no MongoDB Atlas:
Passo 2c: Consulte o índice com base na similaridade de incorporação
Agora, podemos recuperar os registros relevantes calculando a pontuação de similaridade definida na pesquisa vetorial do índice. Quando um usuário envia uma consulta, precisamos vetorizá-la usando o mesmo modelo de incorporação que usamos para armazenar os dados. Usando a função
aggregate
, podemos passar um pipeline
que contém as informações para realizar uma pesquisa vetorial.Agora que temos os registros armazenados no MongoDB Atlas, podemos pesquisar os textos relevantes usando a pesquisa vetorial. Para isso, precisamos vetorizar a consulta usando o mesmo modelo de incorporação e usar a função de agregação para recuperar os registros do índice.
Na pipeline, especificaremos o seguinte:
- index: o nome do índice de pesquisa vetorial na coleção
- vector: a consulta vetorizada do usuário
- k: o número dos registros mais semelhantes que queremos extrair da coleção
- score: a pontuação de similaridade gerada pelo MongoDB Atlas
1 query = "Does the encoder contain self-attention layers?" 2 vector_query = model.encode(query).tolist() 3 4 pipeline = [ 5 { 6 "$vectorSearch": { 7 "index":"default", 8 "queryVector": vector_query, 9 "path": "embedding", 10 "limit": 5, 11 "numCandidates": 50 12 } 13 }, 14 { 15 "$project": { 16 "embedding": 0, 17 "_id": 0, 18 "score": { 19 "$meta": "searchScore" 20 }, 21 } 22 } 23 ] 24 25 results = list(client[db_name][collection_name].aggregate(pipeline))
A pipeline acima retornará os cinco principais registros mais próximos da incorporação da consulta do usuário. Podemos definir
k
para recuperar os registros top-k no MongoDB Atlas. Observe que os resultados contêm metadata
, text
e score
. Podemos usar essas informações para gerar a saída do LLM na etapa seguinte.Aqui está um exemplo dos cinco vizinhos mais próximos da consulta acima:
1 {'element_id': '7128012294b85295c89efee3bc5e72d2', 2 'metadata': {'coordinates': {'layout_height': 2200, 3 'layout_width': 1700, 4 'points': [[290.50477600097656, 5 1642.1170677777777], 6 [290.50477600097656, 7 1854.9523748867755], 8 [1403.820083618164, 9 1854.9523748867755], 10 [1403.820083618164, 11 1642.1170677777777]], 12 'system': 'PixelSpace'}, 13 'detection_class_prob': 0.9979791045188904, 14 'file_directory': 'example-docs', 15 'filename': 'Attention is All You Need.pdf', 16 'filetype': 'application/pdf', 17 'last_modified': '2023-09-20T17:08:35', 18 'page_number': 3, 19 'parent_id': 'd1375b5e585821dff2d1907168985bfe'}, 20 'score': 0.2526094913482666, 21 'text': 'Decoder: The decoder is also composed of a stack of N = 6 identical ' 22 'layers. In addition to the two sub-layers in each encoder layer, ' 23 'the decoder inserts a third sub-layer, which performs multi-head ' 24 'attention over the output of the encoder stack. Similar to the ' 25 'encoder, we employ residual connections around each of the ' 26 'sub-layers, followed by layer normalization. We also modify the ' 27 'self-attention sub-layer in the decoder stack to prevent positions ' 28 'from attending to subsequent positions. This masking, combined with ' 29 'fact that the output embeddings are offset by one position, ensures ' 30 'that the predictions for position i can depend only on the known ' 31 'outputs at positions less than i.', 32 'type': 'NarrativeText'}
Etapa 3: Gerar a saída do LLM com a citação do documento de origem
Podemos gerar a saída usando o modelo GPT-4 da OpenAI. Usaremos a função
ChatCompletion
da API OpenAI para essa etapa final. A API ChatCompletion processa uma lista de mensagens para gerar uma resposta orientada por modelo. Projetada para conversas com vários turnos, ela é igualmente adequada para tarefas de turno único. A entrada primária é o parâmetro "messages", que compreende uma matriz de objetos de mensagem com funções designadas ("system", "user", ou "assistant") e conteúdo. Geralmente iniciadas com uma mensagem do sistema para orientar o comportamento do assistente, as conversas podem variar em duração com mensagens alternadas do usuário e do assistente. Embora a mensagem do sistema seja opcional, sua ausência pode padronizar o modelo para um comportamento genérico de assistente útil.Você precisará de uma chave de API da OpenAI para executar as inferências. Antes de tentar executar essa etapa, verifique se você tem uma conta da OpenAI. Supondo que você armazene a chave de API da OpenAI na variável de ambiente, é possível importá-la usando a função
os.getenv
:1 import os 2 import openai 3 4 # Get the API key from the env 5 openai.api_key = os.getenv("OPENAI_API_KEY")
Em seguida, ter um prompt convincente é fundamental para gerar um resultado satisfatório. Aqui está o prompt para gerar a saída com referência específica da origem das informações – ou seja, nome do arquivo e número da página.
1 response = openai.ChatCompletion.create( 2 model="gpt-4", 3 messages=[ 4 {"role": "system", "content": "You are a useful assistant. Use the assistant's content to answer the user's query \ 5 Summarize your answer using the 'texts' and cite the 'page_number' and 'filename' metadata in your reply."}, 6 {"role": "assistant", "content": context}, 7 {"role": "user", "content": query}, 8 ], 9 temperature = 0.2 10 )
Nesse script em Python, é feita uma solicitação ao modelo GPT-4 da OpenAI por meio do método
ChatCompletion.create
para processar uma conversa. A conversa é estruturada com funções e mensagens predefinidas. Ela é instruída a gerar uma resposta com base no contexto fornecido e na consulta do usuário, resumindo a resposta e citando o número da página e o nome do arquivo. O parâmetro de temperature
definido como 0.2 influencia a aleatoriedade da saída, favorecendo respostas mais determinísticas.Um dos principais recursos da utilização de metadados não estruturados em conjunto com o Vector Search do MongoDB é a capacidade de fornecer resultados altamente precisos e rastreáveis.
1 User query: "Does the encoder contain self-attention layers?"
Você pode inserir essa consulta na API ChatCompletion como a função "user" e o contexto dos resultados de recuperação do MongoDB como a função "assistant". Para fazer com que o modelo responda com o nome do arquivo e o número da página, você pode fornecer a instrução na função "system".
1 response = openai.ChatCompletion.create( 2 model="gpt-4", 3 messages=[ 4 {"role": "system", "content": "You are a useful assistant. Use the assistant's content to answer the user's query \ 5 Summarize your answer using the 'texts' and cite the 'page_number' and 'filename' metadata in your reply."}, 6 {"role": "assistant", "content": context}, 7 {"role": "user", "content": query}, 8 ], 9 temperature = 0.2 10 ) 11 12 print(response) 13 14 # output 15 { 16 "id": "chatcmpl-87rNcLaEYREimtuWa0bpymWiQbZze", 17 "object": "chat.completion", 18 "created": 1696884180, 19 "model": "gpt-4-0613", 20 "choices": [ 21 { 22 "index": 0, 23 "message": { 24 "role": "assistant", 25 "content": "Yes, the encoder does contain self-attention layers. This is evident from the text on page 5 of the document \"Attention is All You Need.pdf\"." 26 }, 27 "finish_reason": "stop" 28 } 29 ], 30 "usage": { 31 "prompt_tokens": 1628, 32 "completion_tokens": 32, 33 "total_tokens": 1660 34 } 35 }
Documento de origem:
Saída do LLM:
O resultado altamente específico cita informações do documento de origem, "Attention is All You Need.pdf," armazenado no diretório "example-docs". As respostas são referenciadas com números de página exatos, facilitando a verificação das informações por qualquer pessoa. Esse nível de detalhe é crucial ao responder a consultas relacionadas a questões médicas, jurídicas ou de pesquisa, e aumenta significativamente a confiabilidade dos resultados do LLM.
Este artigo apresenta um método para aumentar a precisão do LLM usando as técnicas de pesquisa vetorial e extração de metadados não estruturados do MongoDB. Essas abordagens, que facilitam a consulta em tempo real e a filtragem de metadados, reduzem substancialmente o risco de geração de informações incorretas. Os recursos do MongoDB, especialmente no tratamento de dados vetoriais e na facilitação de pesquisas multifacetadas, juntamente com a eficiência do processamento de dados da biblioteca Unstructured, surgem como soluções robustas. Essas técnicas não apenas melhoram a precisão, mas também aprimoram a rastreabilidade e a confiabilidade das saídas do LLM, especialmente ao lidar com tópicos delicados, equipando os usuários com as ferramentas necessárias para gerar saídas mais precisas e contextualmente corretas a partir dos LLMs.
Pronto para começar? Solicite sua chave de API Unstructured hoje mesmo e desbloqueie o poder da API e dos conectores Unstructured. Junte-se ao grupo da comunidade Unstructured para se conectar com outros usuários, fazer perguntas, compartilhar suas experiências e obter as atualizações mais recentes. Mal podemos esperar para ver o que você vai criar.