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.
-
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.
-
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.
-
LEWIS, Patrick et al. Retrieval-augmented generation for knowledge-intensive nlp tasks. Advances in neural information processing systems, v. 33, p. 9459-9474, 2020.
-
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.
-
CARLINI, Nicholas et al. Extracting training data from large language models. In: 30th USENIX security symposium (USENIX Security 21). 2021. p. 2633-2650.