Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Saiba por que o MongoDB foi selecionado como um líder no 2024 Gartner_Magic Quadrupnt()
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Crie uma pesquisa de e-commerce usando o MongoDB Vector Search e OpenAI

Ashiq Sultan11 min read • Published Mar 11, 2024 • Updated Mar 12, 2024
IAAtlas
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty

Introdução

Neste artigo, construiremos um sistema de pesquisa de produtos usando as APIs MongoDB Vector Search e OpenAI. Construiremos um endpoint de API de pesquisa que receberá queries de linguagem natural e fornecerá produtos relevantes como resultados no formato JSON. Neste artigo, veremos como gerar incorporações vetoriais usando o modelo de incorporação OpenAI, armazená-las no MongoDB e consultá-las usando o Vector Search. Também veremos como usar o modelo de geração de texto da OpenAI para classificar as entradas de pesquisa do usuário e criar nossa consulta de banco de dados.
O servidor da API é construído usando Node.js e Express. Criaremos endpoints de API para criação, atualização e pesquisa. Observe também que este guia se concentra apenas no back-end e, para facilitar os testes, usaremos o Postman. Capturas de tela relevantes serão fornecidas nas respectivas seções para maior clareza. O GIF abaixo mostra um vislumbre do que estaremos construindo.
demonstração de uma solicitação de pesquisa com linguagem natural como entrada retorna produtos relevantes como saída

Design de alto nível

Abaixo, você encontrará um design de alto nível para criação de produtos e funcionalidade de pesquisa. Por favor, não se sinta sobrecarregado, pois fornecemos explicações para cada seção para ajudá-lo a entender o processo.
design de alto nível para operação de criação
design de alto nível para operação de pesquisa

Configuração do projeto

  1. Clone o repositório do GitHub.
1git clone https://github.com/ashiqsultan/mongodb-vector-openai.git
2. Crie um .env arquivo no diretório raiz do projeto.
1touch .env
3. Crie duas variáveis no seu arquivo.env: MONGODB_URI e OPENAI_API_KEY.
Você pode seguir as etapas fornecidas nos OpenAI Docs para obter a API.
1echo "MONGODB_URI=your_mongodb_uri" >> .env
2echo "OPENAI_API_KEY=your_openai_api_key" >> .env
4. Install node modules.
1npm install # (or) yarn install
5. Execute yarn run dev ou npm run dev para iniciar o servidor.
1npm run dev # (or) yarn run dev
Se o MONGODB_URI estiver correto, ele deverá se conectar sem nenhum erro e iniciar o servidor na porta 5000. Para a chave de API OpenAI, você precisa criar uma nova conta.
saída do terminal se o servidor for iniciado com sucesso

Conectando-se ao banco de dados

Conectar-se ao MongoDB Atlas a partir do Node.js deve ser bastante simples. Você pode obter a cadeia de conexão consultando a página do Docs. Depois de ter a connection string, basta colá-la no arquivo.env como MONGODB_URI. Em nossa base de código, criamos um arquivo dbclient.tsseparado que exporta uma função singleton para se conectar ao MongoDB. Agora, podemos chamar essa função no arquivo de ponto de entrada de nosso aplicativo, como abaixo.
1// server.ts
2import dbClient from './dbClient';
3server.listen(app.get('port'), async () => {
4 try {
5 await dbClient();
6 } catch (error) {
7 console.error(error);
8 }
9});

Visão geral do esquema de coleção

Você pode consultar o arquivo do modelo de esquema na base de código. Manteremos o esquema de coleção simples. Cada item do produto manterá a interface mostrada abaixo.
1interface IProducts {
2 name: string;
3 category: string;
4 description: string;
5 price: number;
6 embedding: number[];
7}
Essa interface é autoexplicativa, com propriedades como nome, categoria, descrição e preço, representando atributos típicos de um produto. A adição exclusiva é a propriedade de incorporação, que será explicada nas seções subsequentes. Esse esquema simples fornece uma base para organizar e armazenar dados do produto com eficiência.

Configurar índice vetorial para collection

Para habilitar a pesquisa semântica em nossa coleção MongoDB, precisamos configurar índices vetoriais. Se isso parece sofisticado, em termos mais simples, isso nos permite consultar a coleção usando linguagem natural.
Siga o procedimento passo a passo descrito na documentação para criar um índice vetorial a partir da UI do Atlas.
Abaixo está a configuração que precisamos fornecer no editor JSON ao criar o índice vetorial.
1{
2 "mappings": {
3 "dynamic": true,
4 "fields": {
5 "embedding": {
6 "dimensions": 1536,
7 "similarity": "euclidean",
8 "type": "knnVector"
9 }
10 }
11 }
12}
Para quem prefere guias visuais, assista ao nosso vídeo explicando o processo.
As principais variáveis na configuração do índice são o nome do campo na collection a ser indexada (aqui, é chamado de embedding) e o valor das dimensões (aqui, definido como 1536). A importância deste valor será discutida na próxima seção.
Criando índice vetorial da Atlas ui para collection de produtos

Incorporações em resumo

Um modelo de incorporação nos permite transformar texto em vetores. O vetor retornado pelo modelo de incorporação é simplesmente uma matriz de números de ponto flutuante. Isso se reflete em nossa collection, onde definimos o tipo do campo de incorporação como number[].
Para este artigo, usaremos o modelo de incorporação OpenAI, cujo padrão é o retorno de vetores de tamanho 1536. Esse número é o que usamos como o valordimensions quando criamos o índice vetorial na seção anterior. Saiba mais sobre como inserir modelos.

Gerando incorporação usando OpenAI

Criamos uma função util reutilizável em nossa base de código que receberá uma string como entrada e retornará uma incorporação vetorial como saída. Essa função pode ser usada em locais onde precisamos chamar o modelo de incorporação OpenAI.
1async function generateEmbedding(inputText: string): Promise<number[] | null> {
2 try {
3 const vectorEmbedding = await openai.embeddings.create({
4 input: inputText,
5 model: 'text-embedding-ada-002',
6 });
7 const embedding = vectorEmbedding.data[0].embedding;
8 return embedding;
9 } catch (error) {
10 console.error('Error generating embedding:', error);
11 return null;
12 }
13}
A função é bastante simples. O modelo específico empregado em nosso exemplo é text-embedding-ada-002. No entanto, você tem a flexibilidade de escolher outros modelos de incorporação, mas é fundamental garantir que as dimensões de saída do modelo selecionado correspondam às dimensões que definimos ao criar inicialmente o índice do vetor.

O que devemos incorporar para a Pesquisa Vetorial?

Agora que sabemos o que é uma incorporação, vamos discutir o que incorporar. Para a pesquisa semântica, você deve incorporar todos os campos que pretende consultar. Isso inclui todas as informações ou recursos relevantes que você deseja usar como critérios de pesquisa. Em nosso exemplo de produto, incorporaremos o nome do produto, sua categoria e sua descrição.

Incorporar na criação

Para criar um novo item de produto, precisamos fazer uma chamada POST para "localhost:5000/product/ " com as propriedades necessárias {nome, categoria, descrição, preço}. Isso chamará o serviçocreateOne que lida com a criação de um novo item de produto.
1// Example Product item
2// product = {
3// name: 'foo phone',
4// category: Electronics,
5// description: 'This phone has good camera',
6// price: 150,
7// };
8
9const toEmbed = {
10 name: product.name,
11 category: product.category,
12 description: product.description,
13};
14
15// Generate Embedding
16const embedding = await generateEmbedding(JSON.stringify(toEmbed));
17const documentToInsert = {
18…product,
19embedding,
20}
21
22await productCollection.insertOne(documentToInsert);
No trecho de código acima, primeiro criamos um objeto chamado toEmbed contendo os campos destinados à incorporação. Esse objeto é então convertido em um JSON em string e passado para a funçãogenerateEmbedding. Conforme discutido na seção anterior, gerarEmbedding chamará o modelo de incorporação OpenAPI e nos retornará a array de incorporação necessária. Após a incorporação, o novo documento do produto é criado usando a funçãoinsertOne. A captura de tela abaixo mostra a solicitação de criação e sua resposta.
Captura de tela do Postman da solicitação de criação com resposta
E, a partir da interface do usuário do MongoDB Atlas, devemos conseguir ver o documento inserido.
Captura de tela dos dados criados do MongoDB Atlas

Incorporar na atualização

Para garantir que nossa pesquisa funcione conforme o esperado nas atualizações de dados, devemos gerar incorporações após a modificação dos registros do produto. Para atualizar um produto, podemos fazer uma solicitação PATCH para "localhost:5000/product/ " onde id é o ID do documento MongoDB. Isso chamará o serviçoupdateOne.ts.
Vamos fazer uma solicitação PATCH para atualizar o nome do telefone de “foo phone” para “Super Phone.”
1// updateObj contains the extracted request body with updated data
2const updateObj = {
3 name: “Super Phone"
4};
5
6const product = await collection.findOne({ _id });
7
8const objToEmbed = {
9 name: updateObj.name || product.name,
10 category: updateObj.category || product.category,
11 description: updateObj.description || product.description,
12};
13
14const embedding = await generateEmbedding(JSON.stringify(objToEmbed));
15
16updateObj.embedding = embedding;
17
18const updatedDoc = await collection.findOneAndUpdate(
19 { _id },
20 { $set: updateObj },
21 {
22 returnDocument: 'after',
23 projection: { embedding: 0 },
24 }
25);
No código acima, a variável updateObj contém os dados do corpo da solicitação PATCH. Aqui, estamos apenas atualizando o nome. Em seguida, usamos findOne para obter o item de produto existente. O objetoobjToEmbed é construído para determinar quais campos incorporar no documento. Ele incorpora os novos valores de updateObj e os valores existentes do documentoproduct, garantindo que todos os campos inalterados sejam mantidos.
Em termos simples, estamos gerando novamente a array de incorporação com os dados atualizados com o mesmo conjunto de campos que usamos na criação do documento. Isso é importante para garantir que nossa função de pesquisa funcione corretamente e que o documento atualizado permaneça relevante em seu contexto.
captura de tela da solicitação de atualização e resposta do Postman
captura de tela do MongoDB Atlas do documento atualizado

Pesquisar com OpenAI

Esta seção é a parte central do artigo. Aqui, examinaremos a definição de prompt e a análise de saída. Também analisaremos como usar o pipeline de agregação MongoDB para filtrar valores não incorporados.
Para executar uma pesquisa, precisamos fazer uma solicitação GET para "localhost:5000/product " com o parâmetro de query?search .
1http://localhost:5000/product?search=phones with good camera under 160 dollars
captura de tela da solicitação de pesquisa de produto
A solicitação de procura de produto chama o arquivo de serviço de procura de produtos. Vejamos passo a passo a função de pesquisa de produto.
1const searchProducts = async (searchText: string): Promise&lt;IProductDocument[]> => {
2 try {
3 const embedding = await generateEmbedding(searchText); // Generate Embedding
4 const gptResponse = (await searchAssistant(searchText)) as IGptResponse;
5
Na primeira linha, estamos criando incorporação usando a mesma funçãogenerateEmbedding que usamos para criar e atualizar. Vamos estacionar isso por enquanto e focar na segunda função, searchAssistant.

Função do assistente de pesquisa

Esta é uma função reutilizável responsável por chamar o modelo de conclusão OpenAI. Você pode encontrar o arquivo searchAssistant no GitHub. É aqui que descrevemos o prompt do modelo generativo com instruções de saída.
1async function main(userMessage: string): Promise&lt;any> {
2 const completion = await openai.chat.completions.create({
3 messages: [
4 {
5 role: 'system',
6 content: `You are an e-commerce search assistant. Follow the below list of instructions for generating the response.
7 - You should only output JSON strictly following the Output Format Instructions.
8 - List of Categories: Books, Clothing, Electronics, Home & Kitchen, Sports & Outdoors.
9 - Identify whether user message matches any category from the List of Categories else it should be empty string. Do not invent category outside the provided list.
10 - Identify price range from user message. minPrice and maxPrice must only be number or null.
11 - Output Format Instructions for JSON: { category: 'Only one category', minPrice: 'Minimum price if applicable else null', maxPrice: 'Maximum Price if applicable else null' }
12 `,
13
14 },
15 { role: 'user', content: userMessage },
16 ],
17 model: 'gpt-3.5-turbo-1106',
18 response_format: { type: 'json_object' },
19 });
20
21 const outputJson = JSON.parse(completion.choices[0].message.content);
22
23 return outputJson;
24}

Explicação do prompt

Consulte a Docs de conclusão do Open AI chat para entender a definição da função. Aqui, explicaremos o prompt do sistema. Este é o lugar onde fornecemos algum contexto ao modelo.
  • Primeiro, informamos o modelo sobre seu papel e o instruímos a seguir o conjunto de regras que estamos prestes a definir.
  • Nós o instruímos explicitamente a gerar somente JSON após o "Output Instruction " que fornecemos no prompt.
  • Em seguida, fornecemos uma lista de categorias para classificar a solicitação do usuário. Isso é codificado aqui, mas em um cenário em tempo real, podemos gerar uma lista de categorias do banco de dados.
  • Em seguida, estamos instruindo-o a identificar se os usuários mencionaram algum preço para que possamos usá-lo em nossa consulta de agregação.
Vamos adicionar alguns logs de console antes da instrução de retorno e testar a função.
1// … Existing code
2const outputJson = JSON.parse(completion.choices[0].message.content);
3console.log({ userMessage });
4console.log({ outputJson });
5return outputJson;
Com os logs do console em vigor, faça uma solicitação GET para /products com o parâmetro de consulta de pesquisa. Exemplo:
1// Request
2http://localhost:5000/product?search=phones with good camera under 160 dollars
3
4// Console logs from terminal
5{ userMessage: 'phones with good camera under 160 dollars' }
6{ outputJson: { category: 'Electronics', minPrice: null, maxPrice: 160 } }
A partir da resposta da OpenAI acima, podemos ver que o modelo classificou a mensagem do usuário na categoria "Electronics" e identificou a faixa de preço. Ele também seguiu nossas instruções de saída e retornou o JSON que desejamos. Agora, vamos usar essa saída e estruturar nosso pipeline de agregação.

Pipeline de agregação

Em nosso arquivosearchProducts, logo após obtermos gptResponse, estamos chamando uma função chamada constructMatch. O objetivo desta função é construir o objeto de query doestágio $match usando a saída que recebermos do modelo GPT — ou seja, ela extrairá a categoria e os preços mínimos e máximos da resposta GPT para gerar a query.
Exemplo
Vamos fazer uma pesquisa que inclua uma faixa de preço: "?search=show me some good programming books between 100 to 150 dollars ".
registros do console da resposta GPT e consulta de correspondência
Na imagem acima, podemos descobrir como nosso modelo GPT foi capaz de reconhecer a faixa de preço, e nossa consulta de estágio de correspondência tem esses valores refletidos.
Assim que tivermos a query de correspondência, prosseguiremos com o aggregation pipeline.
1const aggCursor = collection.aggregate&lt;IProductDocument>([
2 {
3 $vectorSearch: {
4 index: VECTOR_INDEX_NAME,
5 path: 'embedding',
6 queryVector: embedding,
7 numCandidates: 150,
8 limit: 10,
9 },
10 },
11 matchStage,
12 {
13 $project: {
14 _id: 1,
15 name: 1,
16 category: 1,
17 description: 1,
18 price: 1,
19 score: { $meta: 'vectorSearchScore' },
20 },
21 },
22 ]);
O primeiro estágio do nosso pipeline é o $vector-search-stage.
  • index: refere-se ao nome do índice vetorial que fornecemos ao criar inicialmente o índice na seção **Configurando o índice vetorial para collection (mynewvectorindex). **
  • path: o nome do campo em nosso documento que contém os valores do vetor - em nosso caso, o próprio nome do campo é **embedding. **
  • **queryVector: **o formato embutido do texto de pesquisa. Geramos a incorporação para o texto de pesquisa do usuário usando a mesma funçãogenerateEmebdding e seu valor é adicionado aqui.
  • Número de vizinhos mais próximos a utilizar durante a pesquisa. O valor deve ser menor ou igual a (<=) 10000. Você não pode especificar um número menor que o número de documentos a serem retornados (limite).
  • **Limite: **número de documentos para retornar no resultado.
Consulte a Docs dos campos de pesquisa vetorial para obter mais informações sobre esses campos. Você pode ajustar o numCandidates e o limite com base nos requisitos.
O segundo estágio é o estágio de correspondência, que contém apenas o objeto de consulta que geramos usando a função constructMatch, conforme explicado anteriormente.
A terceira etapa é a fase $project, que trata apenas do que mostrar e como mostrar. Aqui você pode omitir os campos que não deseja retornar.

Demonstração

Vejamos nossa funcionalidade de pesquisa em ação. Para fazer isso, criaremos um novo produto e fará uma pesquisa com palavras-chave relacionadas. Posteriormente, atualizaremos o mesmo produto e fará uma pesquisa com palavras-chave correspondentes ao documento atualizado.
Podemos criar um novo livro usando nossa solicitação POST.
Reserve 01
1{"name": "JavaScript 101",
2 "category": "Books",
3 "description": "This is a good book for learning JavaScript for beginners. It covers fundamental concepts such as variables, data types, operators, control flow, functions, and more.",
4 "price": 60
5}
O Gif abaixo mostra como podemos criar um livro a partir do Postman e visualizar o livro criado na interface do usuário do MongoDB Atlas filtrando a categoria com livros.
Gif mostrando a criação de um livro pelo carteiro e a visualização do mesmo no MongoDB Atlas
Vamos criar mais dois livros usando a mesma solicitação POST para termos alguns dados para testes.
Reserve 2
1{"name": "Go lang Essentials",
2 "category": "Books",
3 "description": "A comprehensive guide to learning the Go programming language for beginners. This book is perfect for anyone looking to dive into Go programming.",
4 "price": 70}
Reserve 3
1{"name": "Cracking the Coding Interview",
2 "category": "Books",
3 "description": "This book is a comprehensive guide to preparing for coding interviews, offering practice questions and solutions.",
4 "price": 80}
Após a inserção, devemos ter pelo menos três documentos na categoria Livros.
Lista de livros inseridos na UI do MongoDB Atlas
Vamos pesquisar livros de JavaScript usando o termo de pesquisa “I want to learn JavaScript..
Pesquise a chamada da API com o texto de pesquisaQuero aprender Javascript
Agora, vamos pesquisar, “I’m preparing for coding interview. "
Pesquise a chamada da API com o texto de pesquisa que estou me preparando para a pesquisa de codificação
Como podemos ver nas duas capturas de tela, nosso algoritmo de pesquisa é capaz de responder com livros relacionados à programação, embora não tenhamos mencionado explicitamente que estamos procurando livros. Além disso, os livros são ordenados com base na intenção de pesquisa. Fique atento ao campo de pontuação em nossos dados de resposta. Quando pesquisamos "I want to learn JS, ", obtemos o livroJavaScript 101 na parte superior e, quando pesquisamos, "I'm preparing for coding interview, ", o livro **Craking the CodingInterview ** ficou no topo. Saiba mais sobre pontuações de pesquisa vetorial.
Se você se pergunta por que vemos todos os livros em nossa resposta, isso se deve aos nossos dados de amostra limitados de três livros. No entanto, em cenários do mundo real, se itens mais relevantes estiverem disponíveis no banco de dados, com base no termo de pesquisa, eles terão pontuações mais altas e serão priorizados.
Vamos atualizar algo em nossos livros usando nossa solicitação PATCH. Aqui, atualizaremos nosso livro de JavaScript 101 para um livro de Python usando seu documento ID.
Solicitação de patch para atualizar o livro de JavaScript para o livro de Python com resposta
Agora, nossa collection deve ter a seguinte aparência na categoria Leituras.
Lista de livros na UI do Atlas mostrando que o livro de Javascript foi renomeado para livro de python
Podemos ver que nosso livro de JavaScript 101 foi alterado para um livro de Python. Agora, vamos Atlas Search o livro Python usando o termo de pesquisa “Python for beginners..
Pesquise a chamada da API com o texto de pesquisa Python para novatos
Na captura de tela acima, podemos ver que nossa pesquisa funciona conforme o esperado. Isso é possível porque estamos incorporando nossos dados tanto na criação quanto na atualização.

Conclusão

Concluindo, é importante observar que este artigo apresenta um design de alto nível para a criação de uma pesquisa semântica utilizando os modelos MongoDB Vector Search e OpenAI. Isso pode servir como um ponto de partida para desenvolvedores que procuram construir uma solução de pesquisa semântica semelhante. Certifique-se de verificar os Docs do Vector Search para obter mais detalhes. Obrigado por ler.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Artigo
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Como implementar o MongoDB Atlas com o AWS CDK no TypeScript


Jan 23, 2024 | 5 min read
Tutorial

MongoDB Atlas com Terraform


Jan 23, 2024 | 9 min read
Início rápido

Começando com o Atlas e a API de consulta do MongoDB


Oct 01, 2024 | 11 min read
Tutorial

Criando de um pipeline de entrega contínua em vários ambientes para o MongoDB Atlas


Jan 23, 2024 | 8 min read
Sumário