Adicionando o MongoDB Atlas Vector Search a um aplicativo .NET Blazor C#
Avalie esse Tutorial
Quando foi a última vez que você conseguiu se lembrar dos detalhes aproximados de algo, mas não conseguiu se lembrar do nome? Isso acontece com muitas pessoas, portanto, é muito importante poder pesquisar semanticamente em vez de usar pesquisas de texto exato.
É aqui que o MongoDB Atlas Vector Search se torna útil. Ele permite que você realize pesquisas semânticas em incorporações vetoriais em seus documentos armazenados no MongoDB Atlas. Como as incorporações são armazenadas dentro do Atlas, você pode criar as incorporações em qualquer tipo de dados, estruturados e não estruturados.
Neste tutorial, você aprenderá como adicionar pesquisa vetorial com o Atlas Vector Search, usando o driver C# do MongoDB, a um aplicativo .NET Blaszor. O aplicativo Blozor usa o banco de dados sample_mflix, disponível no conjunto de dados de amostra que qualquer um pode carregar em seu Atlas cluster. Você adicionará suporte para pesquisar semanticamente no campo de trama, para encontrar quaisquer filmes que possam se encaixar na trama inserida na caixa de pesquisa.
Para acompanhar este tutorial, você precisará de algumas coisas antes de começar:
- .NET 8 SDK instalado em seu computador
- Um IDE ou editor de texto que pode dar suporte a C# e Blazor para a experiência de desenvolvimento mais perfeita, como Visual Studio, Visual Studio Code com a Extensão C# DevKit instalada ou JetBrains Rider
- Uma cópia local da ferramenta Hugging Face Dataset Upload
- Uma conta OpenAI e uma chave de APIgratuita gerada — você usará a API OpenAI para criar uma incorporação de vetor para nosso termo de pesquisa
Depois de bifurcar e clonar o repositório e usá-lo localmente, você precisará adicionar sua connection string a
appsettings.Development.json
e appsettings.json
na seção de espaço reservado para se conectar ao seu cluster ao executar o projeto.Se você não quiser acompanhar, o repositório tem uma ramificação chamada “vector-search” que tem o resultado final implementado. No entanto, você precisará garantir que tenha os dados incorporados no Atlas cluster.
A primeira coisa que você precisa é de alguns dados armazenados em seu cluster que tenham incorporações de vetores disponíveis como um campo em seus documentos. O MongoDB já forneceu uma versão da coleção de filmes do sample_mflix, chamada incorporado_movies, que possui 1500 documentos, usando um subconjunto da coleção principal de filmes que foi carregado como um conjunto de dados para o Hugging Face que será usado neste tutorial.
É aqui que entra o Hugging Face Dataset Uploader baixado como parte dos pré-requisitos. Ao executar essa ferramenta usando
dotnet run
na raiz do projeto e passando sua connection string para o console quando solicitado, ele Go e fará o download do conjunto de dados de Abraçando a face e, em seguida, carregue-o em uma coleçãoembedded_movies
dentro do banco de dadossample_mflix
. Se você não tiver o mesmo conjunto de dados carregado, esse banco de dados estará ausente, ele apenas o criará para você graças ao C#!Você pode gerar incorporações vetoriais para seus próprios dados usando ferramentas como Hugging Face, OpenAI, LlamaIndex e outras. Você pode ler mais sobre como gerar embeddings usando modelos de código aberto lendo um tutorial de Prakul Agarwal sobre IA generativa, pesquisa vetorial e modelos de código aberto aqui no Developer Center.
Agora que você tem uma coleção de documentos de filmes com um campo
plot_embedding
de embeddings vetoriais para cada documento, é hora de criar o índice Atlas Vector Search. Isso serve para habilitar os recursos de pesquisa vetorial no cluster e para permitir que o MongoDB saiba onde encontrar os embeddings vetoriais.- Dentro do Atlas, clique em “Browse Collections” para abrir o explorador de dados e visualizar seu banco de dados sample_mflix recém-carregado.
- Selecione a aba “Atlas Search” na parte superior.
- Clique no botão verde “Create Search Index” para carregar o assistente de criação de índice.
- Selecione Editor JSON no título Vector Search e clique em "Next. "
- Selecione a collection embedded_movies em sample_mflix à esquerda.
- O nome não importa muito aqui, desde que você se lembre dele para mais tarde, mas, por enquanto, deixe-o como o valor padrão de 'vector_index'.
- Copie e cole o seguinte JSON, substituindo o conteúdo atual da caixa no assistente:
1 { 2 "fields": [ 3 { 4 "type": "vector", 5 "path": "plot_embedding", 6 "numDimensions": 1536, 7 "similarity": "dotProduct" 8 } 9 ] 10 }
Ele contém alguns campos que você talvez não tenha visto antes.
- caminho é o nome do campo que contém as incorporações. No caso do conjunto de dados da Hugging Face, isso é plot_embedding.
- numDimensions refere-se às dimensões do modelo utilizado.
- similaridade refere-se ao tipo de função usada para encontrar resultados semelhantes.
Clique em "Next " e, na próxima página, clique em "Create Search Index. "
Após alguns minutos, o índice de pesquisa vetorial será configurado, você será notificado por e-mail e o aplicativo estará pronto para adicionar a pesquisa vetorial.
Você tem os dados com plot embeddings e um índice de pesquisa vetorial criado nesse campo, portanto, é hora de começar a trabalhar no aplicativo para adicionar a pesquisa, começando pela funcionalidade de backend.
A chave de API OpenAI será usada para solicitar incorporações da API para o termo de pesquisa inserido, pois a pesquisa vetorial entende números e não texto. Por esse motivo, o aplicativo precisa que sua chave API OpenAI seja armazenada para uso posterior.
- Adicione o seguinte na raiz do seu
appsettings.Development.json
eappsettings.json
, após a seção MongoDB, substituindo o texto do espaço reservado por sua própria chave:
1 "OpenAPIKey": "<YOUR OPENAI API KEY>"
- Dentro
program.cs
, após a criação do construtor var, adicione a seguinte linha de código para extrair o valor da configuração do aplicativo:
1 var openAPIKey = builder.Configuration.GetValue<string>("OpenAPIKey");
- Altere o código que cria a instância do MongoDBService para também passar o
openAPIKey variable
. Você alterará o construtor da classe posteriormente para fazer uso disso.
1 builder.Services.AddScoped(service => new MongoDBService(mongoDBSettings, openAPIKey));
Você precisará adicionar um novo método à interface que suporte pesquisa, incluindo o termo a ser pesquisado e retornando uma lista de filmes encontrados na pesquisa.
Abra
IMongoDBService.cs
e adicione o seguinte código:1 public IEnumerable<Movie> MovieSearch(string textToSearch);
Agora, faça as alterações na classe de implementação para dar suporte à pesquisa.
- Abra
MongoDBService.cs
e adicione as seguintes instruções using na parte superior do arquivo:
1 using System.Text; 2 using System.Text.Json;
- Adicione as seguintes novas variáveis locais abaixo das existentes no topo da classe:
1 private readonly string _openAPIKey; 2 private readonly HttpClient _httpClient = new HttpClient();
- Atualize o construtor para usar o novo parâmetro de string openAPIKey, bem como o parâmetro MongoDBSettings. Deve ficar assim:
1 public MongoDBService(MongoDBSettings settings, string openAPIKey)
- Dentro do construtor, adicione uma nova linha para atribuir o valor de openAPIKey a _openAPIKey.
- Também dentro do construtor, atualize o nome da collection de "movies " para "embedded_movies ", onde chama
.GetCollection
.
A seguir está a aparência do construtor concluído:
1 public MongoDBService(MongoDBSettings settings, string openAPIKey) 2 { 3 _client = new MongoClient(settings.AtlasURI); 4 _mongoDatabase = _client.GetDatabase(settings.DatabaseName); 5 _movies = _mongoDatabase.GetCollection<Movie>("embedded_movies"); 6 _openAPIKey = openAPIKey; 7 }
O driver C# atua como um mapeador de documentos de objetos (ODM), tratando do mapeamento entre um objeto C# antigo e simples (POCO) que é usado em C# e os documentos em sua coleção.
No entanto, os campos do modelo de filme existentes precisam ser atualizados para corresponder aos documentos dentro da sua collection embedded_movies.
Substitua o conteúdo de
Models/Movie.cs
pelo seguinte código:1 using MongoDB.Bson; 2 using MongoDB.Bson.Serialization.Attributes; 3 4 namespace SeeSharpMovies.Models; 5 6 7 public class Movie 8 { 9 [ ]10 [ ]11 public ObjectId Id { get; set; } 12 13 [ ]14 public string Plot { get; set; } 15 16 [ ] 17 public string[] Genres { get; set; } 18 19 [ ]20 public int Runtime { get; set; } 21 22 [ ]23 public string[] Cast { get; set; } 24 25 [ ]26 public int NumMflixComments { get; set; } 27 28 [ ]29 public string Poster { get; set; } 30 31 [ ]32 public string Title { get; set; } 33 34 [ ]35 public string FullPlot { get; set; } 36 37 [ ]38 public string[] Languages { get; set; } 39 40 [ ]41 public string[] Directors { get; set; } 42 43 [ ]44 public string[] Writers { get; set; } 45 46 [ ]47 public Awards Awards { get; set; } 48 49 [ ]50 public string Year { get; set; } 51 52 [ ]53 public Imdb Imdb { get; set; } 54 55 [ ]56 public string[] Countries { get; set; } 57 58 [ ]59 public string Type { get; set; } 60 61 [ ]62 public float[] PlotEmbedding { get; set; } 63 64 } 65 66 public class Awards 67 { 68 [ ]69 public int Wins { get; set; } 70 71 [ ]72 public int Nominations { get; set; } 73 74 [ ]75 public string Text { get; set; } 76 } 77 78 public class Imdb 79 { 80 [ ]81 public float Rating { get; set; } 82 83 [ ]84 public int Votes { get; set; } 85 86 [ ]87 public int Id { get; set; } 88 }
Ele contém propriedades para todos os campos no documento, bem como classes e propriedades que representam subdocumentos encontrados dentro do documento do filme, como “critic.”. Você também observará o uso do atributo BSONElement, que informa ao motorista como mapear entre os nomes dos campos e os nomes das propriedades devido às suas diferentes convenções de nomenclatura.
Está quase na hora de começar a implementar a pesquisa no back-end. Ao chamar o endpoint de incorporação da API OpenAI, você receberá muitos dados, incluindo as incorporações. A maneira mais fácil de lidar com isso é criar uma classe EmbeddingResponse.cs que modela essa resposta para uso posterior.
Adicione uma nova classe chamada EmbeddingResponse dentro da pasta Modelo e substitua o conteúdo do arquivo pelo seguinte:
1 namespace SeeSharpMovies.Models 2 { 3 public class EmbeddingResponse 4 { 5 public string @object { get; set; } 6 public List<Data> data { get; set; } 7 public string model { get; set; } 8 public Usage usage { get; set; } 9 } 10 11 public class Data 12 { 13 public string @object { get; set; } 14 public int index { get; set; } 15 public List<double> embedding { get; set; } 16 } 17 18 public class Usage 19 { 20 public int prompt_tokens { get; set; } 21 public int total_tokens { get; set; } 22 } 23 }
É hora de usar a chave da API do OpenAI e escrever a funcionalidade para criar embeddings de vetor para o termo pesquisado, chamando o endpointEmbeddings da API do OpenAI.
Dentro de
MongoDBService.cs
, adicione o seguinte código:1 private async Task<List<double>> GetEmbeddingsFromText(string text) 2 { 3 Dictionary<string, object> body = new Dictionary<string, object> 4 { 5 { "model", "text-embedding-ada-002" }, 6 { "input", text } 7 }; 8 9 _httpClient.BaseAddress = new Uri("https://api.openai.com"); 10 _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_openAPIKey}"); 11 12 string requestBody = JsonSerializer.Serialize(body); 13 StringContent requestContent = 14 new StringContent(requestBody, Encoding.UTF8, "application/json"); 15 16 var response = await _httpClient.PostAsync("/v1/embeddings", requestContent) 17 .ConfigureAwait(false); 18 19 if (response.IsSuccessStatusCode) 20 { 21 string responseBody = await response.Content.ReadAsStringAsync(); 22 EmbeddingResponse embeddingResponse = JsonSerializer.Deserialize<EmbeddingResponse>(responseBody); 23 return embeddingResponse.data[0].embedding; 24 } 25 26 return new List<double>(); 27 }
O dicionário corporal é necessário pela API para saber o modelo usado e qual é a entrada. O modelo text-embedding-ada-002 é o modelo padrão de incorporação de texto.
O método GetEmbeddingsFromText retornou as incorporações para o termo de pesquisa, então agora ele está disponível para ser utilizado pelo Atlas Vector Search e pelo driver C#.
Cole o código a seguir para implementar a pesquisa:
1 public IEnumerable<Movie> MovieSearch(string textToSearch) 2 { 3 4 var vector = GetEmbeddingsFromText(textToSearch).Result.ToArray(); 5 6 var vectorOptions = new VectorSearchOptions<Movie>() 7 { 8 IndexName = "vector_index", 9 NumberOfCandidates = 150 10 }; 11 12 var movies = _movies.Aggregate() 13 .VectorSearch(movie => movie.PlotEmbedding, vector, 150, vectorOptions) 14 .Project<Movie>(Builders<Movie>.Projection 15 .Include(m => m.Title) 16 .Include(m => m.Plot) 17 .Include(m => m.Poster)) 18 .ToList(); 19 20 21 return movies; 22 }
Se você escolheu um nome diferente ao criar o índice de pesquisa vetorial anteriormente, certifique-se de atualizar esta linha dentro de vectorOptions.
A pesquisa de vetores está disponível dentro do driver C# como parte do pipeline de agregação. Ele recebe quatro argumentos: o nome do campo com as incorporações, as incorporações de vetor do termo pesquisado, o número de resultados a serem retornados e as opções de vetor.
Em seguida, métodos adicionais são encadeados para especificar quais campos devem ser retornados dos documentos resultantes.
Como o documento do filme mudou ligeiramente, o código atual dentro do método
GetMovieById
não está mais correto.Substitua a linha atual que chama
.Find
pelo seguinte:1 var movie = _movies.Find(movie => movie.Id.ToString() == id).FirstOrDefault();
O back-end agora está completo e é hora de passar para o front-end, adicionando a capacidade de pesquisar na interface do usuário e enviando essa pesquisa de volta ao código que acabamos de escrever.
A funcionalidade de front-end será dividida em duas partes: o código no front-end para falar com o back-end e a barra de pesquisa em HTML para digitar.
Como este é um aplicativo existente, já há código disponível para extrair os filmes e até mesmo a paginação. É aqui que você adicionará a funcionalidade de pesquisa e ela pode ser encontrada dentro
Home.razor
na pastaComponents/Pages
.- Dentro do bloco
@code
, adicione uma nova variável de string para searchTerm:
1 string searchTerm;
- Cole o novo método a seguir no bloco de código:
1 private void SearchMovies() 2 { 3 if (string.IsNullOrWhiteSpace(searchTerm)) 4 { 5 movies = MongoDBService.GetAllMovies(); 6 } 7 else 8 { 9 movies = MongoDBService.MovieSearch(searchTerm); 10 } 11 }
Isso é bastante simples. Se a string search Term estiver vazia, mostre tudo. Caso contrário, pesquise sobre isso.
Adicionar a barra de pesquisa é muito simples. Ele será adicionado ao componente de cabeçalho já presente na página inicial.
Substitua a marcação de cabeçalho existente pelo seguinte HTML:
1 <header class="top-bar"> 2 <a href="/">See Sharp Movies</a> 3 <div class="form-inline search-bar"> 4 <input class="form-control mr-sm-2" 5 type="search" placeholder="Search" 6 aria-label="Search" 7 @bind="searchTerm"> 8 <button class="btn btn-outline-success my-2 my-sm-0" @onclick="SearchMovies">Search</button> 9 </div> 10 </header>
Isso cria uma entrada de pesquisa com o valor vinculado à string searchterm e a um botão que, quando clicado, chama o método SearchMovies que você acabou de chamar.
Neste ponto, a funcionalidade está implementada. Mas se você o executasse agora, a barra de pesquisa estaria em um lugar estranho no cabeçalho, então vamos consertar isso, apenas por beleza.
Dentro de
wwwroot/app.css
, adicione o seguinte código:1 .search-bar { 2 padding: 5%; 3 } 4 5 .search-bar button { 6 padding: 4px; 7 }
Isso apenas dá à barra de pesquisa e ao botão um pouco de preenchimento para posiciona-lo mais bem dentro do cabeçalho. Embora não seja perfeita, CSS definitivamente não é o meu ponto forte. C# é a minha linguagem favorita!
Eba! Implementamos a funcionalidade de backend e frontend, então agora é hora de executar o aplicativo e vê-lo em ação!
Execute o aplicativo, insira um termo de pesquisa na caixa, clique no botão “Search” e veja quais filmes têm gráficos semanticamente próximos ao seu termo de pesquisa.
Incrível! Agora você tem um aplicativo Blazor funcional com a capacidade de pesquisar o enredo por significado em vez de texto exato. Esse também é um ótimo ponto de partida para implementar mais recursos de pesquisa vetorial em seu aplicativo.
Se você quiser saber mais sobre o Atlas Vector Search, leia nossa documentação. O MongoDB também tem um espaço no Abraçando o Face onde você pode ver mais alguns exemplos do que pode ser feito e até mesmo jogar com ele. Go!
Há também um artigo surpreendente sobre o uso do Vector Search para áudio, co-escrito por Cloud Developer Advocate no MongoDB Pavel Duchovny.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.