Leaders Logo

Além do Full-Text Search: explorando o potencial do SqlVector no ecossistema .NET 10

Introdução

Ir além do Full-Text Search (FTS) não significa abandonar uma tecnologia madura, mas reconhecer que muitas consultas modernas dependem menos de coincidência literal e mais de proximidade semântica. Quando a aplicação precisa entender que “inadimplência recorrente” e “atrasos frequentes de pagamento” descrevem o mesmo fenômeno, embeddings passam a complementar o índice textual clássico. Esse avanço decorre da consolidação de representações densas de linguagem e do uso de recuperação neural em produção (DEVLIN et al., 2019) (REIMERS; GUREVYCH, 2019).

No ecossistema .NET 10, esse movimento fica mais prático porque o EF Core 10 já oferece suporte direto ao tipo vetorial do SQL Server. Na camada de aplicação, o dado aparece como SqlVector<float>; no banco, ele é persistido como vector(n). O resultado é um desenho mais simples para cenários de busca semântica, recomendação, deduplicação de conteúdo e pipelines de RAG, sem exigir que toda a arquitetura seja deslocada para um banco especializado.

Por que o FTS sozinho não basta

O FTS continua excelente para filtros léxicos, operadores booleanos, prefixos, proximidade textual e regras de relevância baseadas em termos. O problema aparece quando o usuário formula perguntas com vocabulário diferente do texto indexado. Embeddings reduzem essa distância porque projetam o significado em um espaço vetorial no qual textos semanticamente próximos tendem a ficar vizinhos (DEVLIN et al., 2019).

Na prática, a melhor resposta raramente é “FTS ou vetores”, e sim “FTS antes, vetores depois”. O filtro textual reduz o universo de candidatos com baixo custo; a distância vetorial refina o ranking final em perguntas ambíguas, longas ou formuladas em linguagem natural. Esse padrão híbrido também conversa bem com pipelines de recuperação densa usados em QA e RAG (KARPUKHIN et al., 2020) (LEWIS et al., 2020).

Modelagem no .NET 10 com EF Core 10

A modelagem mais limpa é manter o conteúdo textual, metadados e embedding na mesma entidade quando a aplicação precisa consistência transacional e governança centralizada. Isso simplifica auditoria, backup, permissões e versionamento do domínio.

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;

public sealed class KnowledgeDocument
{
    [Key]
    public Guid Id { get; set; }

    [MaxLength(200)]
    public string Title { get; set; } = string.Empty;

    public string Content { get; set; } = string.Empty;

    [Column(TypeName = "vector(1536)")]
    public SqlVector<float> Embedding { get; set; } = default!;

    [MaxLength(80)]
    public string Category { get; set; } = string.Empty;
}

public sealed class SearchDbContext(DbContextOptions<SearchDbContext> options)
    : DbContext(options)
{
    public DbSet<KnowledgeDocument> Documents => Set<KnowledgeDocument>();
}

Esse formato é mais aderente ao estado atual da plataforma do que conversões artesanais para strings JSON ou tipos proprietários simulados. Em .NET 10, vale preferir o mapeamento nativo e deixar a camada ORM traduzir as operações vetoriais para SQL.

Geração e persistência de embeddings

A geração do embedding deve ficar fora do banco, em um serviço de aplicação ou pipeline de ingestão. Isso mantém flexibilidade para trocar modelo, controlar custo por documento e versionar reprocessamentos. Em cenários de busca corporativa, modelos de embeddings de sentença costumam oferecer melhor equilíbrio entre qualidade e simplicidade operacional (REIMERS; GUREVYCH, 2019).

using Microsoft.Extensions.AI;

public sealed class DocumentIngestionService(
    SearchDbContext dbContext,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
{
    public async Task<Guid> SaveAsync(string title, string content, string category, CancellationToken cancellationToken = default)
    {
        var embedding = await embeddingGenerator.GenerateVectorAsync(content, cancellationToken: cancellationToken);

        var document = new KnowledgeDocument
        {
            Id = Guid.NewGuid(),
            Title = title,
            Content = content,
            Category = category,
            Embedding = new SqlVector<float>(embedding)
        };

        dbContext.Documents.Add(document);
        await dbContext.SaveChangesAsync(cancellationToken);

        return document.Id;
    }
}

Do ponto de vista de governança, também é recomendável persistir a versão do modelo de embeddings e a data de geração. Quando o modelo muda, a comparação entre vetores antigos e novos pode degradar a consistência do ranking; por isso, reindexações amplas devem ser tratadas como eventos operacionais explícitos.

Busca vetorial exata com LINQ

Para bases pequenas ou conjuntos previamente filtrados, a busca exata costuma ser suficiente. O ganho aqui não está em “magia semântica”, mas em expor uma forma de ordenar candidatos pela distância entre o vetor da consulta e os vetores persistidos.

public sealed class SemanticSearchService(
    SearchDbContext dbContext,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
{
    public async Task<List<KnowledgeDocument>> SearchAsync(string question, CancellationToken cancellationToken = default)
    {
        var queryEmbedding = await embeddingGenerator.GenerateVectorAsync(question, cancellationToken: cancellationToken);
        var queryVector = new SqlVector<float>(queryEmbedding);

        return await dbContext.Documents
            .OrderBy(d => EF.Functions.VectorDistance("cosine", d.Embedding, queryVector))
            .Take(5)
            .ToListAsync(cancellationToken);
    }
}

Esse padrão resolve o caso central do artigo: sair do FTS puro para uma busca mais próxima da intenção do usuário, sem romper o modelo relacional já existente.

Busca híbrida: filtro textual com refinamento semântico

Em produção, o desenho mais pragmático costuma ser híbrido. Primeiro, usa-se o FTS para garantir recorte temático e baixo custo de seleção inicial. Depois, a distância vetorial reordena os candidatos restantes. Esse arranjo tende a melhorar precisão percebida sem sacrificar previsibilidade operacional (KARPUKHIN et al., 2020).

public async Task<List<KnowledgeDocument>> SearchHybridAsync(
    string question,
    string keywords,
    CancellationToken cancellationToken = default)
{
    var queryEmbedding = await embeddingGenerator.GenerateVectorAsync(question, cancellationToken: cancellationToken);
    var queryVector = new SqlVector<float>(queryEmbedding);

    return await dbContext.Documents
        .FromSql($"""
            SELECT TOP (10) Id, Title, Content, Category, Embedding
            FROM dbo.Documents
            WHERE CONTAINS(Content, {keywords})
            ORDER BY VECTOR_DISTANCE('cosine', Embedding, {queryVector})
            """)
        .ToListAsync(cancellationToken);
}

Também vale observar um detalhe de segurança: em .NET 10, convém preferir APIs parametrizadas do EF em vez de concatenar SQL manualmente, principalmente quando palavras-chave ou filtros vêm de entrada externa.

Índices vetoriais e limites operacionais

Quando a coleção cresce, o gargalo sai do ORM e vai para a estratégia de busca. A busca exata continua útil, mas passa a custar mais CPU e latência. É nesse ponto que entram índices aproximados. Em termos conceituais, a literatura de approximate nearest neighbors mostra que estruturas baseadas em grafos conseguem reduzir drasticamente o custo de busca com perda controlada de recall (MALKOV; YASHUNIN, 2020).

CREATE VECTOR INDEX IX_Documents_Embedding
ON dbo.Documents (Embedding)
WITH (METRIC = 'COSINE', TYPE = 'DISKANN');

No SQL Server atual, isso exige atenção ao estágio de maturidade do recurso: a busca vetorial aproximada com índice segue o caminho mais moderno em SQL Server 2025 e Azure SQL, enquanto a busca exata continua sendo a opção mais direta para ambientes que ainda não usam o conjunto completo de funcionalidades vetoriais. Em projetos corporativos, a escolha não deve ser ideológica; ela depende de cardinalidade, SLA e tolerância a aproximação.

DECLARE @queryVector VECTOR(1536) = @p0;

SELECT TOP (10) WITH APPROXIMATE
    d.Id,
    d.Title,
    vs.distance
FROM VECTOR_SEARCH(
        TABLE = dbo.Documents AS d,
        COLUMN = Embedding,
        SIMILAR_TO = @queryVector,
        METRIC = 'cosine'
    ) AS vs
ORDER BY vs.distance;

RAG no .NET 10 sem inflar a arquitetura

O uso mais convincente desse stack aparece em RAG. O banco recupera trechos semanticamente relevantes, a aplicação organiza o contexto e o modelo generativo responde com base em evidências recuperadas. Esse desenho reduz alucinação, melhora rastreabilidade e mantém a recuperação desacoplada da etapa de geração (LEWIS et al., 2020).

public async Task<string> AnswerAsync(string question, CancellationToken cancellationToken = default)
{
    var matches = await SearchAsync(question, cancellationToken);

    var context = string.Join(
        "\n\n",
        matches.Select(d => $"[{d.Title}] {d.Content}"));

    return await chatClient.GetResponseAsync(
        $"""
        Answer the question using only the context below.

        Question: {question}

        Context:
        {context}
        """,
        cancellationToken: cancellationToken);
}

Essa abordagem é especialmente útil em bases jurídicas, centrais de conhecimento, manuais técnicos e políticas internas, onde precisão contextual vale mais do que volume bruto de documentos.

Governança, segurança e manutenção

Embeddings não são neutros do ponto de vista de risco. Dependendo do pipeline, eles podem preservar pistas sobre os dados originais, e isso exige o mesmo nível de disciplina aplicado ao texto-fonte: controle de acesso, mascaramento quando cabível, trilhas de auditoria e políticas de retenção. A literatura de segurança em modelos de linguagem já demonstrou que artefatos vetoriais e modelos podem expor traços sensíveis quando mal governados (CARLINI et al., 2021).

Operacionalmente, três práticas merecem prioridade: registrar a versão do modelo de embedding, acompanhar latência e taxa de acerto por tipo de consulta, e reconstruir o índice vetorial após grandes reprocessamentos de corpus. Isso evita a falsa percepção de que a busca semântica “degrada do nada”; quase sempre há mudança de distribuição, dados duplicados ou filtros mal calibrados por trás da queda de qualidade.

Migração progressiva a partir do Full-Text Search

A adoção mais segura é incremental. Começa-se com um recorte de documentos, mede-se qualidade em um conjunto controlado de perguntas reais e compara-se o ranking do FTS com o ranking híbrido. Se o ganho for consistente, expande-se o corpus, introduzem-se observabilidade e políticas de reindexação, e só então a busca vetorial passa a ser parte do fluxo principal. Essa migração gradual combina melhor com cenários empresariais do que uma troca brusca de tecnologia.

Conclusão

Em síntese, o potencial do SqlVector no ecossistema .NET 10 está menos em “substituir tudo” e mais em adicionar semântica ao que já funciona. O FTS continua relevante; o vetor entra para resolver o que o termo literal não alcança. Quando a solução é bem modelada, o ganho não é apenas técnico: ele aparece na experiência do usuário, na qualidade do contexto recuperado e na capacidade de evoluir a plataforma sem ruptura. Para organizações que já operam sobre SQL Server e .NET, esse caminho oferece uma evolução pragmática, com adoção gradual, governança centralizada e melhor aderência a cenários de busca corporativa e RAG.

Referências

  • DEVLIN, Jacob et al. Bert: Pre-training of deep bidirectional transformers for language understanding. In: Proceedings of the 2019 conference of the North American chapter of the association for computational linguistics: human language technologies, volume 1 (long and short papers). 2019. p. 4171-4186. reference.Description
  • REIMERS, Nils; GUREVYCH, Iryna. Sentence-bert: Sentence embeddings using siamese bert-networks. In: Proceedings of the 2019 conference on empirical methods in natural language processing and the 9th international joint conference on natural language processing (EMNLP-IJCNLP). 2019. p. 3982-3992. reference.Description
  • KARPUKHIN, Vladimir et al. Dense passage retrieval for open-domain question answering. In: Proceedings of the 2020 conference on empirical methods in natural language processing (EMNLP). 2020. p. 6769-6781. reference.Description
  • LEWIS, Patrick et al. Retrieval-augmented generation for knowledge-intensive nlp tasks. Advances in neural information processing systems, v. 33, p. 9459-9474, 2020. reference.Description
  • MALKOV, Yu A.; YASHUNIN, Dmitry A. Efficient and robust approximate nearest neighbor search using hierarchical navigable small world graphs. IEEE transactions on pattern analysis and machine intelligence, v. 42, n. 4, p. 824-836, 2018. reference.Description
  • CARLINI, Nicholas et al. Extracting training data from large language models. In: 30th USENIX security symposium (USENIX Security 21). 2021. p. 2633-2650. reference.Description
Sobre o autor