<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Edson Miziara</title>
    <description>The latest articles on DEV Community by Edson Miziara (@edsonmiziara).</description>
    <link>https://dev.to/edsonmiziara</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3540056%2Fc9efd52b-f02b-4c82-abfb-dfcf6e9c0d32.png</url>
      <title>DEV Community: Edson Miziara</title>
      <link>https://dev.to/edsonmiziara</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/edsonmiziara"/>
    <language>en</language>
    <item>
      <title>Minha Jornada para Fazer a Busca Vetorial Funcionar em C# com Pgvector e Alibaba-NLP</title>
      <dc:creator>Edson Miziara</dc:creator>
      <pubDate>Tue, 30 Sep 2025 15:19:45 +0000</pubDate>
      <link>https://dev.to/edsonmiziara/minha-jornada-para-fazer-a-busca-vetorial-funcionar-em-c-com-pgvector-e-alibaba-nlp-22g2</link>
      <guid>https://dev.to/edsonmiziara/minha-jornada-para-fazer-a-busca-vetorial-funcionar-em-c-com-pgvector-e-alibaba-nlp-22g2</guid>
      <description>&lt;p&gt;Olá, comunidade Dev.to!&lt;/p&gt;

&lt;p&gt;Recentemente, em um projeto pessoal, mergulhei no mundo da busca semântica e da vetorização de dados em C#. Meu objetivo era claro: usar o PostgreSQL com a extensão Pgvector, combinado com o poderoso modelo de embedding Alibaba-NLP/gte-base-en-v1.5 da Hugging Face.&lt;/p&gt;

&lt;p&gt;Parecia simples, mas a jornada revelou alguns desafios interessantes que gostaria de compartilhar. Este post documenta os problemas que enfrentei e, mais importante, como os resolvi.&lt;/p&gt;

&lt;p&gt;Parte 1: A Primeira Tentativa e a Biblioteca "Fantasma"&lt;br&gt;
A recomendação mais comum na internet para usar modelos como o gte-base em .NET era a biblioteca SentenceTransformers. A documentação e os exemplos (principalmente em Python) mostravam um uso simples e direto, geralmente instanciando uma classe chamada SentenceTransformer.&lt;/p&gt;

&lt;p&gt;Entretanto, após instalar o pacote NuGet e tentar diversas versões, deparei-me com um problema frustrante: a classe SentenceTransformer simplesmente não estava acessível publicamente na versão da biblioteca compatível com meu projeto. A API para C# era fundamentalmente diferente da sua contraparte em Python, e a documentação não deixava isso claro. Cheguei a um beco sem saída.&lt;/p&gt;

&lt;p&gt;Parte 2: A Nova Abordagem - Mergulhando no ONNX&lt;br&gt;
Decidi abandonar o SentenceTransformers e partir para uma abordagem mais direta e robusta: usar uma biblioteca que interagisse diretamente com o formato ONNX (Open Neural Network Exchange), que é como o modelo gte-base é disponibilizado.&lt;/p&gt;

&lt;p&gt;A escolha foi o ecossistema do OnnxStack, um wrapper poderoso para o Microsoft.ML.OnnxRuntime. A documentação inicial e alguns exemplos que encontrei online também mostravam uma certa forma de usar a biblioteca, mas, novamente, a realidade se mostrou diferente. Os nomes das classes, interfaces e a forma de instanciar os objetos na versão que instalei não batiam com os exemplos.&lt;/p&gt;

&lt;p&gt;Foi aí que aprendi a lição mais valiosa: quando a documentação falha, investigue a DLL diretamente.&lt;/p&gt;

&lt;p&gt;Parte 3: A Solução - O Código que Realmente Funciona&lt;br&gt;
Usando o "Object Browser" do Visual Studio, analisei as classes e interfaces que a biblioteca OnnxStack.FeatureExtractor realmente me oferecia. Em vez de ficar frustrado, decidi entender as peças que eu tinha em mãos e montar o quebra-cabeça.&lt;/p&gt;

&lt;p&gt;O resultado foi um serviço de embedding que funciona perfeitamente.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O Serviço de Vetorização (GteEmbeddingService.cs)&lt;/strong&gt;&lt;br&gt;
Após a análise, descobri que a melhor forma de instanciar o modelo era através da classe FeatureExtractorModel e que a interface IFeatureExtractor exigia float como TResult para me devolver um IEnumerable no método Transform.&lt;/p&gt;

&lt;p&gt;Este é o serviço final que criei para gerar os vetores:&lt;/p&gt;

&lt;p&gt;using BioGalactic.API.Services; // Namespace da minha interface IEmbeddingService&lt;br&gt;
using OnnxStack.FeatureExtractor.Common;&lt;br&gt;
using OnnxStack.FeatureExtractor.Pipelines;&lt;br&gt;
using Pgvector;&lt;br&gt;
using System;&lt;br&gt;
using System.IO;&lt;br&gt;
using System.Linq; // Necessário para o método .ToArray()&lt;br&gt;
using System.Threading.Tasks;&lt;/p&gt;

&lt;p&gt;public class GteEmbeddingService : IEmbeddingService&lt;br&gt;
{&lt;br&gt;
    // A interface, como descoberto na análise, espera 'float' para retornar um IEnumerable&lt;br&gt;
    private readonly IFeatureExtractor _featureExtractor;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public GteEmbeddingService()
{
    // O caminho para os arquivos do modelo ONNX que baixei manualmente
    var modelPath = Path.Combine(AppContext.BaseDirectory, "models", "gte-base");

    // A forma correta de criar a instância do modelo nesta versão da lib
    _featureExtractor = (IFeDatureExtractor&amp;lt;float, string&amp;gt;)FeatureExtractorModel.Create(modelPath);
}

public async Task&amp;lt;Vector&amp;gt; GenerateEmbeddingAsync(string text)
{
    // O método 'Transform' é síncrono, então o rodo em uma Task para não bloquear
    var embeddingArray = await Task.Run(() =&amp;gt; _featureExtractor.Transform(text).ToArray());

    // Crio o tipo Vector do Pgvector a partir do array de floats
    return new Vector(embeddingArray);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O Endpoint de Busca na API&lt;/strong&gt;&lt;br&gt;
Com o serviço de vetorização pronto e funcionando, o passo final foi criar um endpoint na minha API que o utilizasse para buscar os dados já vetorizados no banco de dados.&lt;/p&gt;

&lt;p&gt;Usando o Entity Framework Core e a biblioteca Pgvector.EntityFrameworkCore, o endpoint ficou limpo e eficiente:&lt;/p&gt;

&lt;p&gt;// No arquivo PassageExtensions (usando Minimal APIs)&lt;/p&gt;

&lt;p&gt;//...&lt;/p&gt;

&lt;p&gt;group.MapGet("/api/search", async (&lt;br&gt;
    [FromQuery] string query,&lt;br&gt;
    BiogalacticContext dbContext,&lt;br&gt;
    IEmbeddingService embeddingService) =&amp;gt;&lt;br&gt;
{&lt;br&gt;
    if (string.IsNullOrWhiteSpace(query))&lt;br&gt;
    {&lt;br&gt;
        return Results.BadRequest("A query não pode ser vazia.");&lt;br&gt;
    }&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1. Vetoriza o texto da busca em tempo real
var queryVector = await embeddingService.GenerateEmbeddingAsync(query);

// 2. Usa a função L2Distance do Pgvector para encontrar os vizinhos mais próximos
var results = await dbContext.Passages
    .OrderBy(p =&amp;gt; p.Embedding.L2Distance(queryVector))
    .Take(10)
    .ToListAsync();

return Results.Ok(results);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;});&lt;/p&gt;

&lt;p&gt;// ...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependências Finais&lt;/strong&gt;&lt;br&gt;
Para que tudo isso funcione, este é o conjunto final de pacotes NuGet no meu projeto:&lt;/p&gt;

&lt;p&gt;Microsoft.ML.OnnxRuntime&lt;/p&gt;

&lt;p&gt;Microsoft.ML.OnnxRuntime.Managed&lt;/p&gt;

&lt;p&gt;OnnxStack.Core&lt;/p&gt;

&lt;p&gt;OnnxStack.FeatureExtractor&lt;/p&gt;

&lt;p&gt;Npgsql.EntityFrameworkCore.PostgreSQL&lt;/p&gt;

&lt;p&gt;Pgvector.EntityFrameworkCore&lt;/p&gt;

&lt;p&gt;Microsoft.EntityFrameworkCore&lt;/p&gt;

&lt;p&gt;Microsoft.EntityFrameworkCore.Tools&lt;/p&gt;

&lt;p&gt;Microsoft.EntityFrameworkCore.Design&lt;/p&gt;

&lt;p&gt;Conclusão&lt;br&gt;
Trabalhar com o ecossistema de IA em C# pode ter seus desafios, especialmente quando se trata de documentação versus a realidade das versões dos pacotes. A maior lição que aprendi foi: não tenha medo de "abrir o capô" e investigar as bibliotecas por conta própria. A solução é muitas vezes mais simples do que parece e está esperando para ser descoberta.&lt;/p&gt;

&lt;p&gt;Espero que minha jornada ajude outros desenvolvedores a economizar tempo e a implementar busca semântica em seus projetos .NET!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
