> ## Content Index
> Fetch the complete content index at: https://www.azurebrasil.cloud/llms.txt
> Use this file to discover other available public pages before exploring further.

# Implementando o vector search com o pgvector e .NET
- URL: https://www.azurebrasil.cloud/blog/implementando-uma-busca-por-vetor-utilizando-o-pgvector-e-net/
- Published: 2024-01-18T20:51:11.000Z
- Updated: 2024-01-18T21:00:28.000Z
- Author: Talles Valiatti

A busca por vetores, ou "[*vector search*](https://learn.microsoft.com/en-us/azure/search/vector-search-how-to-generate-embeddings?ref=azurebrasil.cloud)", é uma abordagem inovadora na recuperação de informações, empregando [representações numéricas](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/understand-embeddings?ref=azurebrasil.cloud#embedding-models) para otimizar a busca em grandes conjuntos de dados. Essa técnica transcende as limitações dos métodos tradicionais, baseando-se na similaridade vetorial para encontrar relações semânticas entre entidades. Ao converter elementos de dados em vetores, a busca por vetores possibilita a comparação eficiente, não apenas considerando características físicas, mas também capturando significados semânticos. Amplamente aplicada em processamento de linguagem natural e aprendizado de máquina, essa abordagem impulsiona avanços em recomendações personalizadas, análise exploratória de dados e otimização de motores de busca, representando uma evolução notável na recuperação de informações.

Para esse post, utilizaremos o [*pgvector*](https://github.com/pgvector/pgvector?ref=azurebrasil.cloud) *q*ue é uma extensão para o [PostgreSQL](https://www.postgresql.org/?ref=azurebrasil.cloud) que facilita a implementação de busca por vetores no banco de dados. Já para a criação dos vetores ([*embeddings*](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/understand-embeddings?ref=azurebrasil.cloud)), utilizaremos o [Azure Open AI](https://azure.microsoft.com/pt-br/products/ai-services/openai-service?ref=azurebrasil.cloud) com o modelo [*embedding-ada-002*](https://learn.microsoft.com/pt-br/azure/ai-services/openai/concepts/models?ref=azurebrasil.cloud#embeddings)*!*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Group-493--1-.png)

Vamos inicialmente criar um diretório para servir como raiz para nosso projeto.

```
mkdir VectorSearch
```

Após isso, já podemos criar uma [*solution*](https://learn.microsoft.com/pt-br/visualstudio/ide/solutions-and-projects-in-visual-studio?view=vs-2022&ref=azurebrasil.cloud)e um [*gitignore*](https://git-scm.com/docs/gitignore?ref=azurebrasil.cloud).

````
 dotnet new sln
 dotnet new gitignore
```

Para a solução inicial do nosso projeto, vamos precisar dos seguintes pacotes NuGet.

```
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Design 

```

Além disso, vamos criar o arquivo *docker-compose.yaml*. Ele será responsável por criar uma instância do banco de dados PostgreSQL que já possui o [*pgvector*](https://github.com/pgvector/pgvector?ref=azurebrasil.cloud) instalado, por meio da imagem [*ankane/pgvector*](https://github.com/pgvector/pgvector?tab=readme-ov-file&ref=azurebrasil.cloud#additional-installation-methods)*.*

```
services:
  db:
    hostname: db
    image: ankane/pgvector
    ports:
     - 5432:5432
    restart: always
    environment:
      - POSTGRES_DB=VectorStore
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=Teste12345!
      - POSTGRES_HOST_AUTH_METHOD=trust
```

Vamos subir as dependências através do comando [*docker compose up*](https://docs.docker.com/engine/reference/commandline/compose%5Fup/?ref=azurebrasil.cloud)*.*

```
docker compose up -d
```

Nesse post, vou utilizar o [Rider](https://www.jetbrains.com/?ref=azurebrasil.cloud) como IDE. Mas fique a vontade para usar sua IDE preferida!

Dentro da *solution* criada anteriormente, vamos criar uma *minimal* w*eb api em* .[NET 8](https://dotnet.microsoft.com/pt-br/download/dotnet/8.0?ref=azurebrasil.cloud).

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-15.59.01.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-15.45.22.png)

Precisaremos adicionar a [configuração](https://learn.microsoft.com/en-ca/dotnet/core/extensions/configuration?ref=azurebrasil.cloud) da *string* de conexão do banco de dados no arquivo *appsettings.json.*

```
 "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=VectorStore;Port=5444;User Id=postgres;Password=Teste12345!"
  }
```

Vamos começar o desenvolvimento da web api para busca de livros em nossa base local. Inicialmente devemos criar a entidade *Book* e seu respectivo mapeamento para tabelas no PostgreSQL(recomendo uma leitura profunda sobre[*Entity framework core code-first*](https://learn.microsoft.com/en-ca/ef/core/get-started/overview/install?ref=azurebrasil.cloud)*!*)*.*

```
namespace VectorSearch.Api.Models;

public class Book
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public string Description { get; set; } = default!;
}
```

```
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace VectorSearch.Api.Models.Configuration_;

public class BookConfiguration : IEntityTypeConfiguration<Book>
{
    public void Configure(EntityTypeBuilder<Book> builder)
    {
        builder.HasKey(e => e.Id);

        builder.Property(e => e.Name);

        builder.Property(e => e.Description);

         builder.HasData( new List<Book>()
        {
            new Book
            {
                Id = Guid.Parse("e3e8e383-e69e-4c2d-94e6-d7e2a59d714d"),
                Name = "The Hobbit",
                Description = """
                              The Hobbit, written by J.R.R. Tolkien, follows Bilbo Baggins, a reluctant hobbit hero, as he joins a band of dwarves led by Thorin Oakenshield on a perilous quest to reclaim the Lonely Mountain and its treasure from the fearsome dragon Smaug, encountering trolls, goblins, elves, and the enigmatic Gollum, while discovering courage and cunning within himself, ultimately shaping the events that will unfold in the epic world of Middle-earth
                              """
            },
            new Book
            {
                Id = Guid.Parse("35cb9b12-85a8-46f4-86be-12e05778bef3"),
                Name = "The Lord of the Rings",
                Description = """
                              In J.R.R. Tolkien's 'The Lord of the Rings,' a young hobbit named Frodo Baggins embarks on a perilous journey with a diverse fellowship to destroy the One Ring and thwart the dark lord Sauron, facing battles, betrayals, and the complexities of Middle-earth, as alliances are forged, friendships tested, and destinies unfold in a sweeping epic that explores themes of power, sacrifice, and the enduring triumph of hope in the face of darkness.
                              """
            },
            new Book
            {
                Id = Guid.Parse("c4f49049-7731-4400-bcbd-afa977185c2b"),
                Name = "The Shining",
                Description = """
                              In Stephen King's 'The Shining,' the Torrance family—Jack, Wendy, and their psychic son Danny—takes on the winter caretaking of the haunted Overlook Hotel, where Jack's descent into madness, fueled by supernatural forces, threatens their lives and sanity; as Danny's psychic abilities intensify, the hotel's malevolent spirits come to life, and the family confronts a sinister past, culminating in a chilling battle between good and evil, exploring themes of isolation, addiction, and the eerie intersection of the supernatural with the vulnerabilities of the human psyche
                              """
            },
            new Book
            {
                Id = Guid.Parse("ed01fe5d-aff9-4f49-8b39-acc86e7bcef5"),
                Name = "The Iliad",
                Description = """
                              Homer's 'The Iliad' recounts the Trojan War's epic battles, centered around the wrath of Achilles, a Greek hero, delving into themes of honor, fate, and the human cost of war, as gods intervene and mortals grapple with mortality in a timeless narrative of heroism and tragedy
                              """
            },
            new Book
            {
                Id = Guid.Parse("6b406924-35df-452c-b306-1d91fc98fe81"),
                Name = "Mastering the Art of French Cooking",
                Description = """
                              Julia Child's 'Mastering the Art of French Cooking' is a culinary masterpiece, guiding aspiring chefs with meticulous detail through the intricacies of French cuisine, presenting a comprehensive blend of recipes, techniques, and anecdotes that demystify the culinary world and ignite a passion for the art of cooking, forever changing the landscape of American gastronomy
                              """
            }
            
        });
    }
}
```

Sobre o item acima:

- Fazemos o [*seeding*](https://learn.microsoft.com/en-ca/ef/core/modeling/data-seeding?ref=azurebrasil.cloud) diretamente na classe *BookConfiguration;*
- Gerei uma pequena descrição de cada livro no ChatGPT.

Temos o *AppDbContext* que herda de [*DbContext*](https://learn.microsoft.com/en-ca/ef/core/dbcontext-configuration/?ref=azurebrasil.cloud)*.*

```
using Microsoft.EntityFrameworkCore;
using VectorSearch.Api.Models;

namespace VectorSearch.Api.Data;

public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
    public DbSet<Book> Books { get; set; } = default!;
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

    
  modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }
}
```

Seguindo, é hora de criar a [*migration*](https://learn.microsoft.com/en-ca/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli&ref=azurebrasil.cloud)e aplicá-la ao banco de dados.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-16.52.35.png)

Temos o resultado:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-16.53.12.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-17.09.56.png)

Os dados já estão persistidos no banco dados, portanto, já podemos criar o serviço que irá fazer a busca com base nos parâmetros enviados.

```
using VectorSearch.Api.Models;

namespace VectorSearch.Api.Services;

public interface ISearchService
{
    public Task<List<Book>> SearcAsync(string? name, string? description);
}
```

```
using Microsoft.EntityFrameworkCore;
using VectorSearch.Api.Data;
using VectorSearch.Api.Models;

namespace VectorSearch.Api.Services;

public class SearchService(AppDbContext appDbContext, IEmbeddingService embeddingService, IConfiguration configuration) : ISearchService
{
    public async Task<List<Book>> SearcAsync(string? name, string? description)
    {
        var query = appDbContext.Books.AsQueryable();

        if (!string.IsNullOrWhiteSpace(name))
        {
            query = query.Where(x => x.Name.ToLower().Contains(name.Trim().ToLower()));
        }
        
        if (!string.IsNullOrWhiteSpace(description))
        {
            query = query.Where(x => x.Description.ToLower().Contains(description.Trim().ToLower()));
        }
        
        return await query.ToListAsync();
    }
}
```

Sobre a implementação acima:

- É uma filtragem simples que utiliza o método *Contains()*;
- É feito uma filtragem com base no nome do livro e outra com base na sua descrição;
- Utilizamos o [*IQueryable*](https://learn.microsoft.com/en-us/dotnet/api/system.linq.iqueryable?view=net-8.0&ref=azurebrasil.cloud) para implementação da filtragem.

Para executarmos a aplicação, falta só configuramos a[ injeção de dependência](https://learn.microsoft.com/en-ca/dotnet/core/extensions/dependency-injection?ref=azurebrasil.cloud) do serviço de busca, o Entity Framework Core e o endpoint "*/search-books*":

```\
using Microsoft.EntityFrameworkCore;
using VectorSearch.Api.Data;
using VectorSearch.Api.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<ISearchService, SearchService>();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(connectionString)
);

var app = builder.Build();

app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();

app.MapGet("/search-books", async (string? name, string? description ,ISearchService searchService) =>
    {
        return Results.Ok(await searchService.SearcAsync(name, description));
    })
.WithName("GetSearchBooks")
.WithOpenApi();

app.Run();
```

A versão inicial da web api está pronta, vamos executá-la. 

Com a *web api* rodando, podemos chamar o endpoint de busca, como feito no seguinte exemplo:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-17.02.05.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.05.02.png)

Já podemos refatorar nossa aplicação para fazer a busca com base na semântica da descrição dos livros!

Primeiramente vamos instalar os seguintes pacotes NuGet:

```
dotnet add package Azure.AI.OpenAI --version 1.0.0-beta.12
dotnet add package Pgvector.EntityFrameworkCore
```

Vamos precisar que o serviço do Azure Open AI seja criado. No momento de escrita deste post, precisar [aplicar](https://customervoice.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR7en2Ais5pxKtso%5FPz4b1%5FxUNTZBNzRKNlVQSFhZMU9aV09EVzYxWFdORCQlQCN0PWcu&ref=azurebrasil.cloud) uma requisição para ter acesso a esse recurso de nuvem.

Com o recurso de nuvem criado, iremos criar um [*deployment*](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal&ref=azurebrasil.cloud) do modelo *text-embedding-ada-002,* especializado em criar[*Embeddings*](https://learn.microsoft.com/en-ca/azure/ai-services/openai/tutorials/embeddings?tabs=python%2Ccommand-line&pivots=programming-language-python&ref=azurebrasil.cloud)*.*

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-17.26.55.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-08-at-17.27.05.png)

Já na nossa aplicação, precisaremos criar um novo campo na tabela de Books para armazenar os *Embeddings* que serão criados. Esse novo campo será do tipo *Vector!*

```
    [JsonIgnore]
    public Vector? Embedding { get; set; }
```

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.06.17.png)

Já nas configurações do mapeamento, vamos definir esse novo campo e criar um *index* ([veja aqui!](https://github.com/pgvector/pgvector?tab=readme-ov-file&ref=azurebrasil.cloud#indexing)). Os vetores criados pelo *Embeddings* do Azure Open AI possuem um tamanho de 1536, mesmo valor que foi definido neste novo campo.

```
    builder.Property(x => x.Embedding)
            .HasColumnType("vector(1536)");
        
        builder.HasIndex(x => x.Embedding)
            .HasMethod("hnsw")
            .HasOperators("vector_cosine_ops");
```

Além disso, precisamos dizer para o banco de dados, que iremos utilizar a extensão "[vector](https://github.com/pgvector/pgvector?ref=azurebrasil.cloud)". Isso é feito no método [*OnModelCreating*](https://learn.microsoft.com/en-us/ef/core/modeling/?ref=azurebrasil.cloud)do Entity Framework Core, como mostrado abaixo:

```
protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasPostgresExtension("vector");
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
        base.OnModelCreating(modelBuilder);
    }
```

Vamos gerar uma nova *migration.* Temos o resultado após a sua implantação: 

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.38.02.png)

Para transformar textos em *Embeddings*, vamos criar um serviço com base no [Azure Open AI](https://learn.microsoft.com/en-ca/azure/ai-services/openai/overview?ref=azurebrasil.cloud).

```
namespace VectorSearch.Api.Services;

public interface IEmbeddingService
{
    public float[] CreateEmbedding(string text);
}
```

```
using Azure;
using Azure.AI.OpenAI;

namespace VectorSearch.Api.Services;

public class AzureOpenAiService(IConfiguration configuration) : IEmbeddingService
{
    public float[] CreateEmbedding(string text)
    {
        Uri oaiEndpoint = new (configuration.GetSection("AzureOpenAI:Url").Value!);
        string oaiKey = configuration.GetSection("AzureOpenAI:Key").Value!;

        var credentials = new AzureKeyCredential(oaiKey);

        var client = new OpenAIClient(oaiEndpoint, credentials);

        EmbeddingsOptions embeddingOptions = new()
        {
            DeploymentName = "text-embedding-ada-002",
            Input = { text }
        };

        var returnValue = client.GetEmbeddings(embeddingOptions);
        return returnValue.Value.Data[0].Embedding.ToArray();
    }
}
```

Precisamos adicionar [novas configurações, relativas ao Azure Open AI](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart?tabs=command-line%2Cpython&pivots=programming-language-csharp&ref=azurebrasil.cloud#retrieve-key-and-endpoint), ao *appsettings.json*.

```
  "AzureOpenAI": {
    "Url": "https://<your-service-name>.openai.azure.com/",
    "Key": "<your-key>"
  },
```

Para configurar o [DI](https://learn.microsoft.com/pt-br/dotnet/core/extensions/dependency-injection?ref=azurebrasil.cloud) desse serviço recém criado, vamos adicionar o seguinte trecho de código ao *Program.cs.*

```
builder.Services.AddScoped<IEmbeddingService, AzureOpenAiService>();
```

Para transformar todas as descrições das entidades de *Books* em *Embeddings,* iremos criar um novo *endpoint*:

```

app.MapPost("/create-embeddings", async (IEmbeddingService embeddingService, AppDbContext appDbContext) =>
    {
        var books = await appDbContext.Books.ToListAsync();
        foreach (var book in books)
        {
            if (book.Embedding == null)
            {
                var embedding = embeddingService.CreateEmbedding(book.Description);
                book.Embedding = new Vector(embedding);
            }
            
            await appDbContext.SaveChangesAsync();
        }
        return Results.Ok();
    })
    .WithName("CreateEmbeddings")
    .WithOpenApi();
```

Vamos executar a aplicação e chamar esse novo *endpoint*.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.07.39.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.10.05.png)

Observe que para cada registro no banco dados, foi gerado um vetor de dimensão 1536, e foi salvo no campo *Embeddings* da tabela *Books*.

Com os dados prontos, já podemos refatorar o serviço de busca para filtrar os itens com base na semântica da descrição.

```
public interface ISearchService
{
    public Task<List<Book>> SearcAsync(string? text);
}
```

```

public class SearchService(AppDbContext appDbContext, IEmbeddingService embeddingService, IConfiguration configuration) : ISearchService
{
    public async Task<List<Book>> SearcAsync(string? text)
    {
        var query = appDbContext.Books.AsQueryable();

        if (!string.IsNullOrWhiteSpace(text))
        {
            var  = configuration.GetValue<double>("Application:SearchThreshold");

            // Embeddings
            var embedding = embeddingService.CreateEmbedding(text);
            var embeddingVector = new Vector(embedding);

            query = query
                .Where(x => x.Embedding!.CosineDistance(embeddingVector) <= searchThreshold);
        }
        
        return await query.ToListAsync();
    }
}
```

Sobre a implementação acima:

- Ainda estamos utilizamos o *IQueryable* como base da pesquisa;
- Geramos o *embeddings* do texto enviado como parâmetro;
- Com o *embeddings* do valor enviado, fazemos uma busca por similaridade entre vetores, *utilizando o* método de *s*imilaridade por cosseno [*CosineDistance*](https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/understand-embeddings?ref=azurebrasil.cloud)do [*pgvector*](https://github.com/pgvector/pgvector?ref=azurebrasil.cloud)*;*
- Retornamos apenas os itens do banco em que a *s*imilaridade por cosseno é menor ou igual ao valor *searchThreshold;*
- O *searchThreshold* é definido no *appsettings.json,* e seu valor foi escolhido empiricamente com base nos dados desse post. Ele serve como uma sensibilidade para o método de busca de *s*imilaridade por cosseno.

```
  "Application":   {
    "SearchThreshold": 0.20
  }
```

Com essa refatoração do método de busca feito, temos uma visão geral do projeto:

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-16.48.42.png)

Já podemos executar a aplicação e fazer algumas buscas por semântica.

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.12.00.png)

![](https://storage.ghost.io/c/00/52/0052dced-0017-4d07-b190-1f5c48e0ab59/content/images/2024/01/Screen-Shot-2024-01-16-at-14.12.39.png)

E finalizamos o post aqui!

Você já pode baixar o projeto por esse [](https://github.com/TallesValiatti/MediatRValidationPipeline?ref=azurebrasil.cloud)[link](https://github.com/TallesValiatti/VectorSearch?ref=azurebrasil.cloud), e não esquece de me seguir no [LinkedIn](https://www.linkedin.com/in/tallesvaliatti/?ref=azurebrasil.cloud)!

[GitHub - TallesValiatti/VectorSearchContribute to TallesValiatti/VectorSearch development by creating an account on GitHub.![](https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg)GitHubTallesValiatti![](https://opengraph.githubassets.com/a9c3d7be13847dde4eade3c1dc58ac4d4006ad2ab986daaeb7ac1fe52ae7fc7d/TallesValiatti/VectorSearch)](https://github.com/TallesValiatti/VectorSearch?ref=azurebrasil.cloud)

Até a próxima, abraços!