Desenvolvimento

RAG com .NET 10: usando SqlVector para buscas semânticas em aplicações corporativas

24 de junho de 2026 13 min de leitura
RAG com .NET 10: usando SqlVector para buscas semânticas em aplicações corporativas

Introdução

Modelos de linguagem de grande porte (LLMs) entregam fluência impressionante, mas carregam três limitações conhecidas em ambientes corporativos: alucinação, conhecimento desatualizado e raciocínio pouco rastreável. O padrão Retrieval-Augmented Generation (RAG) endereça exatamente esse ponto ao acoplar a geração de texto a uma etapa de recuperação de evidências em bases externas, melhorando precisão e credibilidade em tarefas intensivas em conhecimento (LEWIS et al., 2020) (GAO et al., 2024).

No ecossistema .NET 10, esse padrão se tornou notavelmente mais simples de operacionalizar. O EF Core 10 passou a oferecer suporte a operações vetoriais no provedor do SQL Server, incluindo o uso de SqlVector<float> na aplicação e a tradução de operações como EF.Functions.VectorDistance() para SQL. No banco, os embeddings podem ser persistidos com o tipo vector(n) do SQL Server 2025 e do Azure SQL, permitindo uma arquitetura enxuta para busca semântica e RAG, na qual a empresa aproveita a base relacional já existente em vez de adotar, obrigatoriamente, um banco vetorial especializado (MICROSOFT, 2026a) (MICROSOFT, 2025a) (MICROSOFT, 2025b).

Este artigo descreve, de ponta a ponta, como construir uma solução RAG corporativa com SqlVector, cobrindo modelagem, ingestão, busca, indexação aproximada, orquestração e governança.

Por que RAG em aplicações corporativas

Em um cenário corporativo, a pergunta do usuário raramente coincide literalmente com o texto armazenado. Alguém pergunta sobre “inadimplência recorrente”, mas a base registra “atrasos frequentes de pagamento”. Buscas léxicas tradicionais falham nesse vão semântico, e é aí que a recuperação densa baseada em embeddings agrega valor.

O ganho do RAG, porém, não é apenas de recall. Ao fundamentar a resposta em trechos efetivamente recuperados, a aplicação reduz alucinação, melhora a rastreabilidade e permite atualizar o conhecimento sem retreinar o modelo, características desejáveis em domínios regulados, como jurídico, financeiro e saúde (GAO et al., 2024).

Fluxo RAG

A figura acima resume a anatomia de uma solução RAG no .NET 10: a aplicação gera o embedding da pergunta, recupera trechos relevantes pela proximidade vetorial e entrega esse contexto ao modelo generativo, que responde fundamentado nas evidências recuperadas.

Embeddings e recuperação densa: a base semântica

A peça central do RAG é a representação densa do texto. Embeddings projetam frases e documentos em um espaço vetorial no qual a proximidade geométrica aproxima significados, permitindo comparar intenção em vez de coincidência de termos. Modelos de embeddings de sentença oferecem um bom equilíbrio entre qualidade e simplicidade operacional para busca corporativa (REIMERS; GUREVYCH, 2019).

A recuperação densa, isto é, recuperar passagens pela similaridade entre o vetor da pergunta e os vetores do corpus, demonstrou ganhos consistentes sobre métodos puramente léxicos em tarefas de perguntas e respostas de domínio aberto. Esse mecanismo é uma das bases da etapa de recuperação usada em arquiteturas RAG (KARPUKHIN et al., 2020).

Na prática, a melhor estratégia raramente é “léxico ou vetorial”, e sim uma composição dos dois mundos. Em cenários corporativos, filtros léxicos, metadados, permissões e Full-Text Search podem reduzir o universo de candidatos com baixo custo, enquanto a distância vetorial refina o ranking final por proximidade semântica (MICROSOFT, 2026a).

Modelagem com SqlVector e EF Core 10

A modelagem mais limpa mantém conteúdo textual, metadados e embedding na mesma entidade, preservando 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;

    // Metadados de governança: rastreiam qual modelo gerou o vetor e quando.
    [MaxLength(80)]
    public string EmbeddingModel { get; set; } = string.Empty;

    public DateTimeOffset IndexedAtUtc { get; set; } = DateTimeOffset.UtcNow;
}

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

O mapeamento nativo via SqlVector<float> é mais aderente ao estado atual da plataforma do que conversões artesanais para strings JSON ou tipos proprietários simulados. No SqlClient, dados do tipo vector são trocados com o SQL Server por meio da classe SqlVector<T>, enquanto o EF Core permite expressar operações vetoriais no LINQ e delegar a tradução para SQL Server (MICROSOFT, 2025b) (MICROSOFT, 2026a).

Ingestão e geração de embeddings

A geração do embedding deve ocorrer fora do banco, em um serviço de aplicação ou pipeline de ingestão. Isso mantém a flexibilidade para trocar de modelo, controlar custo por documento e versionar reprocessamentos.

Fluxo Embedding

O serviço de ingestão usa a abstração IEmbeddingGenerator de Microsoft.Extensions.AI, que desacopla o provedor de IA do código de domínio. Note o registro explícito da versão do modelo e do instante de indexação, fundamentais para governança.

using Microsoft.Data.SqlClient;
using Microsoft.Extensions.AI;

public sealed class DocumentIngestionService(
    SearchDbContext dbContext,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
{
    private const string ModelId = "text-embedding-3-small";

    public async Task<Guid> IngestAsync(
        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),
            EmbeddingModel = ModelId,
            IndexedAtUtc = DateTimeOffset.UtcNow
        };

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

        return document.Id;
    }
}

Quando o modelo de embedding 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, e não como efeitos colaterais silenciosos.

Busca semântica: exata e híbrida

Para bases pequenas ou conjuntos previamente filtrados, a busca exata costuma ser suficiente. O EF Core 10 expõe a função VectorDistance, traduzida para SQL Server, permitindo ordenar candidatos pela distância entre o vetor da consulta e os vetores persistidos (MICROSOFT, 2026a).

public sealed class SemanticSearchService(
    SearchDbContext dbContext,
    IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator)
{
    public async Task<IReadOnlyList<KnowledgeDocument>> SearchAsync(
        string question,
        int take = 5,
        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(take)
            .ToListAsync(cancellationToken);
    }
}

É importante diferenciar busca exata de busca aproximada. A função VECTOR_DISTANCE calcula a distância entre dois vetores de forma exata e não usa índice vetorial, mesmo que um índice exista. Para usar índice vetorial e executar uma busca aproximada, o caminho indicado pela documentação é a função VECTOR_SEARCH (MICROSOFT, 2025c).

Em produção, porém, o desenho mais pragmático costuma ser híbrido: primeiro o Full-Text Search, filtros estruturados ou metadados reduzem o conjunto de candidatos; depois a distância vetorial reordena os candidatos restantes. Esse arranjo melhora a precisão percebida sem sacrificar previsibilidade operacional e está alinhado ao uso combinado de busca textual e busca vetorial em aplicações sobre SQL Server (MICROSOFT, 2026a).

Fluxo Full-Text Search

A consulta híbrida combina CONTAINS (filtro léxico) com VECTOR_DISTANCE (refinamento semântico exato sobre o conjunto filtrado). Em .NET 10, convém preferir interpolação parametrizada do EF em vez de concatenar SQL manualmente, sobretudo quando as palavras-chave vêm de entrada externa, o que mitiga injeção de SQL.

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

    var queryVector = new SqlVector<float>(queryEmbedding);

    // A interpolação $"" no FromSql vira parâmetros, não concatenação de texto.
    return await dbContext.Documents
        .FromSql($"""
            SELECT TOP (10) Id, Title, Content, Category, Embedding, EmbeddingModel, IndexedAtUtc
            FROM dbo.Documents
            WHERE CONTAINS(Content, {keywords})
            ORDER BY VECTOR_DISTANCE('cosine', Embedding, {queryVector})
            """)
        .ToListAsync(cancellationToken);
}

Índices vetoriais e escala

Enquanto a coleção é pequena, a busca exata resolve. Quando o corpus cresce, o gargalo migra do ORM para a estratégia de busca: a varredura exata passa a custar mais CPU e latência. É nesse ponto que entram os índices aproximados, também conhecidos como Approximate Nearest Neighbors (ANN).

Fluxo Vector Search

A literatura mostra que estruturas baseadas em grafos reduzem drasticamente o custo de busca com perda controlada de recall. Os grafos navegáveis hierárquicos de mundo pequeno (HNSW) são uma referência clássica nessa frente (MALKOV; YASHUNIN, 2020). Já os índices vetoriais do SQL Database Engine são baseados no algoritmo DiskANN, uma abordagem projetada para busca aproximada em grandes volumes, apoiando-se em estruturas de grafo e armazenamento eficiente (SUBRAMANYA et al., 2019) (MICROSOFT, 2026b).

Criar Índice Banco

Um índice vetorial aproximado pode ser criado sobre a coluna de embedding para melhorar a performance de buscas de vizinhos mais próximos. A sintaxe abaixo ilustra a criação de um índice vetorial do tipo DISKANN, conforme o modelo de indexação vetorial do SQL Server 2025/Azure SQL (MICROSOFT, 2026c).

Com o índice criado, a busca aproximada é expressa pela função VECTOR_SEARCH, com a cláusula WITH APPROXIMATE indicando explicitamente a tolerância à aproximação. Essa diferença é importante: VECTOR_DISTANCE calcula distância exata; VECTOR_SEARCH é a função associada à busca aproximada com índice vetorial (MICROSOFT, 2025c) (MICROSOFT, 2026b).

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;

A escolha entre busca exata e aproximada não deve ser ideológica: ela depende de cardinalidade, SLA de latência e tolerância à aproximação. Em projetos corporativos, é comum começar com busca exata e migrar para índice aproximado quando o volume, a latência ou o custo computacional justificam a mudança.

Orquestração do RAG: recuperação + geração

A etapa final amarra recuperação e geração. A aplicação recupera trechos semanticamente relevantes, organiza o contexto e entrega ao modelo generativo, que responde com base nas evidências. Esse desacoplamento entre recuperar e gerar é o que reduz alucinação e melhora a rastreabilidade (LEWIS et al., 2020).

Diagrama de Sequência

O serviço de orquestração usa a abstração IChatClient, também de Microsoft.Extensions.AI, mantendo o provedor generativo intercambiável. O prompt instrui o modelo a responder apenas com base no contexto recuperado, uma salvaguarda simples e eficaz contra respostas inventadas.

using Microsoft.Extensions.AI;

public sealed class RagAnswerService(
    SemanticSearchService searchService,
    IChatClient chatClient)
{
    public async Task<string> AnswerAsync(
        string question,
        CancellationToken cancellationToken = default)
    {
        var matches = await searchService.SearchAsync(
            question,
            take: 5,
            cancellationToken: cancellationToken);

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

        var prompt = $"""
            Responda à pergunta usando apenas o contexto fornecido.
            Se a resposta não estiver no contexto, informe que não há dados suficientes.

            Pergunta: {question}

            Contexto:
            {context}
            """;

        var response = await chatClient.GetResponseAsync(
            prompt,
            cancellationToken: cancellationToken);

        return response.Text;
    }
}

A composição final em uma Minimal API do .NET 10 fica direta. Note o uso de injeção de dependência, TypedResults e CancellationToken propagado ponta a ponta.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<SearchDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Sql")));

builder.Services.AddScoped<DocumentIngestionService>();
builder.Services.AddScoped<SemanticSearchService>();
builder.Services.AddScoped<RagAnswerService>();

// Provedores de IA registrados via Microsoft.Extensions.AI.
builder.Services.AddChatClient(/* provedor generativo configurado */);
builder.Services.AddEmbeddingGenerator(/* provedor de embeddings configurado */);

var app = builder.Build();

app.MapPost("/ask", async (
    AskRequest request,
    RagAnswerService rag,
    CancellationToken cancellationToken) =>
{
    var answer = await rag.AnswerAsync(request.Question, cancellationToken);
    return TypedResults.Ok(new { answer });
});

app.Run();

public sealed record AskRequest(string Question);

Governança, segurança e custo

Embeddings não são neutros do ponto de vista de risco. Dependendo do pipeline, podem preservar pistas sobre os dados originais, o que exige o mesmo rigor aplicado ao texto-fonte: controle de acesso, mascaramento quando cabível, trilhas de auditoria e políticas de retenção.

Três práticas merecem prioridade em produção: registrar a versão do modelo de embedding, acompanhar latência e taxa de acerto por tipo de consulta, e reconstruir ou revisar índices após grandes reprocessamentos. Essas medidas evitam a falsa percepção de que a busca semântica “degrada do nada”; quase sempre há mudança de distribuição, dados duplicados, filtros mal calibrados ou mistura de embeddings gerados por modelos diferentes por trás da queda de qualidade (GAO et al., 2024).

Do ponto de vista de custo, manter o RAG sobre SQL Server e .NET evita a operação de um banco vetorial adicional, reduzindo superfície de integração e duplicação de dados. Essa vantagem é relevante para organizações que já operam fortemente sobre esse stack, especialmente quando governança, backup, permissões, auditoria e operação do banco relacional já estão estabelecidos.

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 Full-Text Search 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 integrar o fluxo principal.

Essa evolução gradual combina melhor com cenários empresariais do que uma troca brusca de tecnologia. A busca textual continua útil para filtros, termos obrigatórios, regras de negócio e restrições de acesso; a busca vetorial entra para capturar proximidade semântica; e o RAG usa essa recuperação para gerar respostas mais úteis, rastreáveis e alinhadas ao conteúdo disponível.

Conclusão

RAG com SqlVector no .NET 10 não se trata de substituir o que já funciona, e sim de adicionar semântica à plataforma existente. O Full-Text Search continua relevante para filtros léxicos; o vetor entra para resolver o que o termo literal não alcança; e o modelo generativo fecha o ciclo respondendo com base em evidências recuperadas.

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, custo controlado e melhor aderência a cenários reais de busca corporativa e RAG.

Referências

Nagib Sabbag Filho

Nagib Sabbag Filho

Senior Technical Lead

Microsoft MVP em .NET, com sólida atuação em liderança técnica, arquitetura de sistemas, desenvolvimento de software e capacitação técnica, tendo passado por empresas como Mercado Livre, Unimed e Órigo Energia. Também atuei como professor universitário na FIAP, palestrante em eventos relevantes da comunidade de tecnologia e autor de dezenas de artigos técnicos, combinando experiência prática de mercado com forte contribuição para a formação de profissionais de TI.

Microsoft MVP em .NET, com sólida atuação em liderança técnica, arquitetura de sistemas, desenvolvimento de software e capacitação técnica, tendo passado por empresas como Mercado Livre, Unimed e Órigo Energia. Também atuei como professor universitário na FIAP, palestrante em eventos relevantes da comunidade de tecnologia e autor de dezenas de artigos técnicos, combinando experiência prática de mercado com forte contribuição para a formação de profissionais de TI.

0 comentários

Faça login para comentar.

Posts relacionados