Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Aprenda a usar la búsqueda vectorial en Azure DocumentDB con el controlador mongoDB de .NET para almacenar y consultar datos vectoriales de forma eficaz.
En este inicio rápido se proporciona un recorrido guiado por las técnicas de búsqueda de vectores clave mediante una aplicación de ejemplo de .NET en GitHub.
La aplicación usa un conjunto de datos de hotel de ejemplo en un archivo JSON con vectores calculados previamente del text-embedding-ada-002 modelo, aunque también puede generar los vectores usted mismo. Los datos del hotel incluyen nombres de hotel, ubicaciones, descripciones e incrustaciones vectoriales.
Prerrequisitos
Una suscripción de Azure
- Si no tiene ninguna suscripción a Azure, cree una cuenta gratuita
Un clúster de Azure DocumentDB existente
- Si no tiene un clúster, cree un nuevo clúster.
Firewall configurado para permitir el acceso a la dirección IP del cliente
-
text-embedding-ada-002modelo implementado
Use el entorno de Bash en Azure Cloud Shell. Para más información, consulte Introducción a Azure Cloud Shell.
Si prefieres ejecutar comandos de referencia CLI localmente, instala la CLI de Azure. Si estás utilizando Windows o macOS, considera ejecutar Azure CLI en un contenedor Docker. Para obtener más información, consulte Cómo ejecutar el Azure CLI en un contenedor de Docker.
Si estás utilizando una instalación local, inicia sesión en Azure CLI utilizando el comando az login. Siga los pasos que se muestran en el terminal para completar el proceso de autenticación. Para ver otras opciones de inicio de sesión, consulte Autenticación en Azure mediante la CLI de Azure.
En caso de que se le solicite, instale las extensiones de la CLI de Azure la primera vez que la use. Para obtener más información sobre las extensiones, consulte Uso y administración de extensiones con la CLI de Azure.
Ejecute az version para ver la versión y las bibliotecas dependientes que están instaladas. Para actualizar a la versión más reciente, ejecute az upgrade.
Dependencias de la aplicación
La aplicación usa los siguientes paquetes NuGet:
-
Azure.Identity: biblioteca de identidades de Azure para la autenticación sin contraseña con microsoft Entra ID -
Azure.AI.OpenAI: biblioteca cliente de Azure OpenAI para comunicarse con modelos de IA y crear incrustaciones de vectores -
Microsoft.Extensions.Configuration: gestión de la configuración de ajustes de la aplicación -
MongoDB.Driver: controlador oficial de .NET de MongoDB para la conectividad y las operaciones de base de datos -
Newtonsoft.Json: biblioteca de serialización y deserialización de JSON popular
Configuración y ejecución de la aplicación
Complete los pasos siguientes para configurar la aplicación con sus propios valores y ejecutar búsquedas en el clúster de Azure DocumentDB.
Configuración de la aplicación
Actualice los valores del marcador de posición appsettings.json con los suyos propios.
{
"AzureOpenAI": {
"EmbeddingModel": "text-embedding-ada-002",
"ApiVersion": "2023-05-15",
"Endpoint": "https://<your-openai-service-name>.openai.azure.com/"
},
"DataFiles": {
"WithoutVectors": "HotelsData_toCosmosDB.JSON",
"WithVectors": "HotelsData_toCosmosDB_Vector.json"
},
"Embedding": {
"FieldToEmbed": "Description",
"EmbeddedField": "text_embedding_ada_002",
"Dimensions": 1536,
"BatchSize": 16
},
"MongoDB": {
"TenantId": "<your-tenant-id>",
"ClusterName": "<your-cluster-name>",
"LoadBatchSize": 100
},
"VectorSearch": {
"Query": "quintessential lodging near running trails, eateries, retail",
"DatabaseName": "Hotels",
"TopK": 5
}
}
Autentíquese en Azure
La aplicación de ejemplo usa la autenticación sin contraseña a través de DefaultAzureCredential y Microsoft Entra ID.
Inicie sesión en Azure con una herramienta compatible , como la CLI de Azure o Azure PowerShell, antes de ejecutar la aplicación para que pueda acceder a los recursos de Azure de forma segura.
Nota:
Asegúrese de que la identidad de inicio de sesión tiene los roles de plano de datos necesarios en la cuenta de Azure DocumentDB y en el recurso de Azure OpenAI.
Compila y ejecuta el proyecto
La aplicación de ejemplo rellena los datos de ejemplo vectorizados en una colección de MongoDB y permite ejecutar diferentes tipos de consultas de búsqueda.
Use el
dotnet runcomando para iniciar la aplicación:dotnet runLa aplicación imprime un menú para seleccionar las opciones de base de datos y búsqueda:
=== Cosmos DB Vector Samples Menu === Please enter your choice (0-5): 1. Create embeddings for data 2. Show all database indexes 3. Run IVF vector search 4. Run HNSW vector search 5. Run DiskANN vector search 0. ExitEscriba
5y presione Entrar.Una vez que la aplicación rellena la base de datos y ejecuta la búsqueda, verá los cinco hoteles principales que coinciden con la consulta de búsqueda vectorial seleccionada y sus puntuaciones de similitud.
El registro y la salida de la aplicación muestran:
- Estado de creación e inserción de datos de colección
- Confirmación de creación de índices vectoriales
- Resultados de búsqueda con nombres de hotel, ubicaciones y puntuaciones de similitud
Salida de ejemplo (abreviada por brevedad):
MongoDB client initialized with passwordless authentication Starting DiskANN vector search workflow Collection is empty, loading data from file Successfully loaded 50 documents into collection Creating vector index 'vectorIndex_diskann' Vector index 'vectorIndex_diskann' is ready for DiskANN search Executing DiskANN vector search for top 5 results Search Results (5 found using DiskANN): 1. Roach Motel (Similarity: 0.8399) 2. Royal Cottage Resort (Similarity: 0.8385) 3. Economy Universe Motel (Similarity: 0.8360) 4. Foot Happy Suites (Similarity: 0.8354) 5. Country Comfort Inn (Similarity: 0.8346)
Exploración del código de la aplicación
En las secciones siguientes se proporcionan detalles sobre los servicios y el código más importantes de la aplicación de ejemplo. Visite el repositorio de GitHub para explorar el código completo de la aplicación.
Exploración del servicio de búsqueda
El paquete VectorSearchService orquesta una búsqueda de similitud vectorial de extremo a extremo utilizando técnicas de búsqueda IVF, HNSW y DiskANN con inserciones de Azure OpenAI.
using Azure.AI.OpenAI;
using Azure.Identity;
using CosmosDbVectorSamples.Models;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Reflection;
namespace CosmosDbVectorSamples.Services.VectorSearch;
/// <summary>
/// Service for performing vector similarity searches using different algorithms (IVF, HNSW, DiskANN).
/// Handles data loading, vector index creation, query embedding generation, and search execution.
/// </summary>
public class VectorSearchService
{
private readonly ILogger<VectorSearchService> _logger;
private readonly AzureOpenAIClient _openAIClient;
private readonly MongoDbService _mongoService;
private readonly AppConfiguration _config;
public VectorSearchService(ILogger<VectorSearchService> logger, MongoDbService mongoService, AppConfiguration config)
{
_logger = logger;
_mongoService = mongoService;
_config = config;
// Initialize Azure OpenAI client with passwordless authentication
_openAIClient = new AzureOpenAIClient(new Uri(_config.AzureOpenAI.Endpoint), new DefaultAzureCredential());
}
/// <summary>
/// Executes a complete vector search workflow: data setup, index creation, query embedding, and search
/// </summary>
/// <param name="indexType">The vector search algorithm to use (IVF, HNSW, or DiskANN)</param>
public async Task RunSearchAsync(VectorIndexType indexType)
{
try
{
_logger.LogInformation($"Starting {indexType} vector search workflow");
// Setup collection
var collectionSuffix = indexType switch
{
VectorIndexType.IVF => "ivf",
VectorIndexType.HNSW => "hnsw",
VectorIndexType.DiskANN => "diskann",
_ => throw new ArgumentException($"Unknown index type: {indexType}")
};
var collectionName = $"hotels_{collectionSuffix}_fixed";
var indexName = $"vectorIndex_{collectionSuffix}";
var collection = _mongoService.GetCollection<HotelData>(_config.VectorSearch.DatabaseName, collectionName);
// Load data from file if collection is empty
var assemblyLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty;
var dataFilePath = Path.Combine(assemblyLocation, _config.DataFiles.WithVectors);
await _mongoService.LoadDataIfNeededAsync(collection, dataFilePath);
// Create the vector index with algorithm-specific search options
var searchOptions = indexType switch
{
VectorIndexType.IVF => CreateIVFSearchOptions(_config.Embedding.Dimensions),
VectorIndexType.HNSW => CreateHNSWSearchOptions(_config.Embedding.Dimensions),
VectorIndexType.DiskANN => CreateDiskANNSearchOptions(_config.Embedding.Dimensions),
_ => throw new ArgumentException($"Unknown index type: {indexType}")
};
await _mongoService.CreateVectorIndexAsync(
_config.VectorSearch.DatabaseName, collectionName, indexName,
_config.Embedding.EmbeddedField, searchOptions);
_logger.LogInformation($"Vector index '{indexName}' is ready for {indexType} search");
await Task.Delay(5000); // Allow index to be fully initialized
// Create embedding for the query
var embeddingClient = _openAIClient.GetEmbeddingClient(_config.AzureOpenAI.EmbeddingModel);
var queryEmbedding = (await embeddingClient.GenerateEmbeddingAsync(_config.VectorSearch.Query)).Value.ToFloats().ToArray();
_logger.LogInformation($"Generated query embedding with {queryEmbedding.Length} dimensions");
// Build MongoDB aggregation pipeline for vector search
var searchPipeline = new BsonDocument[]
{
// Vector similarity search using cosmosSearch
new BsonDocument("$search", new BsonDocument
{
["cosmosSearch"] = new BsonDocument
{
["vector"] = new BsonArray(queryEmbedding.Select(f => new BsonDouble(f))),
["path"] = _config.Embedding.EmbeddedField, // Field containing embeddings
["k"] = _config.VectorSearch.TopK // Number of results to return
}
}),
// Project results with similarity scores
new BsonDocument("$project", new BsonDocument
{
["score"] = new BsonDocument("$meta", "searchScore"),
["document"] = "$$ROOT"
})
};
// Execute and process the search
_logger.LogInformation($"Executing {indexType} vector search for top {_config.VectorSearch.TopK} results");
var searchResults = (await collection.AggregateAsync<BsonDocument>(searchPipeline)).ToList()
.Select(result => new SearchResult
{
Document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<HotelData>(result["document"].AsBsonDocument),
Score = result["score"].AsDouble
}).ToList();
// Print the results
if (searchResults?.Count == 0)
{
_logger.LogInformation("❌ No search results found. Check query terms and data availability.");
}
else
{
_logger.LogInformation($"\n✅ Search Results ({searchResults!.Count} found using {indexType}):");
for (int i = 0; i < searchResults.Count; i++)
{
var result = searchResults[i];
var hotelName = result.Document?.HotelName ?? "Unknown Hotel";
_logger.LogInformation($" {i + 1}. {hotelName} (Similarity: {result.Score:F4})");
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{indexType} vector search failed");
throw;
}
}
/// <summary>
/// Creates IVF (Inverted File) search options - good for large datasets with fast approximate search
/// </summary>
private BsonDocument CreateIVFSearchOptions(int dimensions) => new BsonDocument
{
["kind"] = "vector-ivf",
["similarity"] = "COS",
["dimensions"] = dimensions,
["numLists"] = 1
};
/// <summary>
/// Creates HNSW (Hierarchical Navigable Small World) search options - best accuracy/speed balance
/// </summary>
private BsonDocument CreateHNSWSearchOptions(int dimensions) => new BsonDocument
{
["kind"] = "vector-hnsw",
["similarity"] = "COS",
["dimensions"] = dimensions,
["m"] = 16,
["efConstruction"] = 64
};
/// <summary>
/// Creates DiskANN search options - optimized for very large datasets stored on disk
/// </summary>
private BsonDocument CreateDiskANNSearchOptions(int dimensions) => new BsonDocument
{
["kind"] = "vector-diskann",
["similarity"] = "COS",
["dimensions"] = dimensions
};
}
En el código anterior, VectorSearchService realiza las siguientes tareas:
- Determina los nombres de colección e índice en función del algoritmo solicitado.
- Crea u obtiene la colección de MongoDB y carga datos JSON si está vacío.
- Compila las opciones de índice específicas del algoritmo (DAC / HNSW / DiskANN) y garantiza que el índice vectorial existe.
- Genera una inserción para la consulta configurada a través de Azure OpenAI.
- Construye y ejecuta el flujo de trabajo de búsqueda por agregación
- Deserializa e imprime los resultados
Exploración del servicio Azure DocumentDB
MongoDbService Administra las interacciones con Azure DocumentDB para controlar tareas como la carga de datos, la creación de índices vectoriales, la lista de índices y las inserciones masivas para la búsqueda de vectores de hotel.
using Azure.Identity;
using CosmosDbVectorSamples.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
namespace CosmosDbVectorSamples.Services;
/// <summary>
/// Service for MongoDB operations including data insertion, index management, and vector index creation.
/// Supports Azure Cosmos DB for MongoDB with passwordless authentication.
/// </summary>
public class MongoDbService
{
private readonly ILogger<MongoDbService> _logger;
private readonly AppConfiguration _config;
private readonly MongoClient _client;
public MongoDbService(ILogger<MongoDbService> logger, IConfiguration configuration)
{
_logger = logger;
_config = new AppConfiguration();
configuration.Bind(_config);
// Validate configuration
if (string.IsNullOrEmpty(_config.MongoDB.ClusterName))
throw new InvalidOperationException("MongoDB connection not configured. Please provide ConnectionString or ClusterName.");
// Configure MongoDB connection for Azure Cosmos DB with OIDC authentication
var connectionString = $"mongodb+srv://{_config.MongoDB.ClusterName}.global.mongocluster.cosmos.azure.com/?tls=true&authMechanism=MONGODB-OIDC&retrywrites=false&maxIdleTimeMS=120000";
var settings = MongoClientSettings.FromUrl(MongoUrl.Create(connectionString));
settings.UseTls = true;
settings.RetryWrites = false;
settings.MaxConnectionIdleTime = TimeSpan.FromMinutes(2);
settings.Credential = MongoCredential.CreateOidcCredential(new AzureIdentityTokenHandler(new DefaultAzureCredential(), _config.MongoDB.TenantId));
settings.Freeze();
_client = new MongoClient(settings);
_logger.LogInformation("MongoDB client initialized with passwordless authentication");
}
/// <summary>Gets a database instance by name</summary>
public IMongoDatabase GetDatabase(string databaseName) => _client.GetDatabase(databaseName);
/// <summary>Gets a collection instance from the specified database</summary>
public IMongoCollection<T> GetCollection<T>(string databaseName, string collectionName) =>
_client.GetDatabase(databaseName).GetCollection<T>(collectionName);
/// <summary>
/// Creates a vector search index for Cosmos DB MongoDB, with support for IVF, HNSW, and DiskANN algorithms
/// </summary>
public async Task<BsonDocument> CreateVectorIndexAsync(string databaseName, string collectionName, string indexName, string embeddedField, BsonDocument cosmosSearchOptions)
{
var database = _client.GetDatabase(databaseName);
var collection = database.GetCollection<BsonDocument>(collectionName);
// Check if index already exists to avoid duplication
var indexList = await (await collection.Indexes.ListAsync()).ToListAsync();
if (indexList.Any(index => index.TryGetValue("name", out var nameValue) && nameValue.AsString == indexName))
{
_logger.LogInformation($"Vector index '{indexName}' already exists, skipping creation");
return new BsonDocument { ["ok"] = 1 };
}
// Create the specified vector index type
_logger.LogInformation($"Creating vector index '{indexName}' on field '{embeddedField}'");
return await database.RunCommandAsync<BsonDocument>(new BsonDocument
{
["createIndexes"] = collectionName,
["indexes"] = new BsonArray
{
new BsonDocument
{
["name"] = indexName,
["key"] = new BsonDocument { [embeddedField] = "cosmosSearch" },
["cosmosSearchOptions"] = cosmosSearchOptions
}
}
});
}
/// <summary>
/// Displays all indexes across all user databases, excluding system databases
/// </summary>
public async Task ShowAllIndexesAsync()
{
try
{
// Get user databases (exclude system databases)
var databases = (await (await _client.ListDatabaseNamesAsync()).ToListAsync())
.Where(name => !new[] { "admin", "config", "local" }.Contains(name)).ToList();
if (!databases.Any())
{
_logger.LogInformation("No user databases found or access denied");
return;
}
foreach (var dbName in databases)
{
var database = _client.GetDatabase(dbName);
var collections = await (await database.ListCollectionNamesAsync()).ToListAsync();
if (!collections.Any())
{
_logger.LogInformation($"Database '{dbName}': No collections found");
continue;
}
_logger.LogInformation($"\n📂 DATABASE: {dbName} ({collections.Count} collections)");
// Display indexes for each collection
foreach (var collName in collections)
{
try
{
var indexList = await (await database.GetCollection<BsonDocument>(collName).Indexes.ListAsync()).ToListAsync();
_logger.LogInformation($"\n 🗃️ COLLECTION: {collName} ({indexList.Count} indexes)");
indexList.ForEach(index => _logger.LogInformation($" Index: {index.ToJson()}"));
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to list indexes for collection '{collName}'");
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to retrieve database indexes");
throw;
}
}
/// <summary>
/// Loads data from file into collection if the collection is empty
/// </summary>
/// <param name="collection">Target collection to load data into</param>
/// <param name="dataFilePath">Path to the JSON data file containing vector embeddings</param>
/// <returns>Number of documents loaded, or 0 if collection already had data</returns>
public async Task<int> LoadDataIfNeededAsync<T>(IMongoCollection<T> collection, string dataFilePath) where T : class
{
var existingDocCount = await collection.CountDocumentsAsync(Builders<T>.Filter.Empty);
// Skip loading if collection already has data
if (existingDocCount > 0)
{
_logger.LogInformation("Collection already contains data, skipping load operation");
return 0;
}
// Load and validate data file
_logger.LogInformation("Collection is empty, loading data from file");
if (!File.Exists(dataFilePath))
throw new FileNotFoundException($"Vector data file not found: {dataFilePath}");
var jsonContent = await File.ReadAllTextAsync(dataFilePath);
var data = JsonConvert.DeserializeObject<List<T>>(jsonContent) ?? new List<T>();
if (data.Count == 0)
throw new InvalidOperationException("No data found in the vector data file");
// Insert data using existing method
var summary = await InsertDataAsync(collection, data);
_logger.LogInformation($"Successfully loaded {summary.Inserted} documents into collection");
return summary.Inserted;
}
/// <summary>
/// Inserts data into MongoDB collection, converts JSON embeddings to float arrays, and creates standard indexes
/// </summary>
public async Task<InsertSummary> InsertDataAsync<T>(IMongoCollection<T> collection, IEnumerable<T> data)
{
var dataList = data.ToList();
_logger.LogInformation($"Processing {dataList.Count} items for insertion");
// Convert JSON array embeddings to float arrays for vector search compatibility
foreach (var hotel in dataList.OfType<HotelData>().Where(h => h.ExtraElements != null))
foreach (var kvp in hotel.ExtraElements.ToList().Where(k => k.Value is Newtonsoft.Json.Linq.JArray))
hotel.ExtraElements[kvp.Key] = ((Newtonsoft.Json.Linq.JArray)kvp.Value).Select(token => (float)token).ToArray();
int inserted = 0, failed = 0;
try
{
// Use unordered insert for better performance
await collection.InsertManyAsync(dataList, new InsertManyOptions { IsOrdered = false });
inserted = dataList.Count;
_logger.LogInformation($"Successfully inserted {inserted} items");
}
catch (Exception ex)
{
failed = dataList.Count;
_logger.LogError(ex, $"Batch insert failed for {dataList.Count} items");
}
// Create standard indexes for common query fields
var indexFields = new[] { "HotelId", "Category", "Description", "Description_fr" };
foreach (var field in indexFields)
await collection.Indexes.CreateOneAsync(new CreateIndexModel<T>(Builders<T>.IndexKeys.Ascending(field)));
return new InsertSummary { Total = dataList.Count, Inserted = inserted, Failed = failed };
}
/// <summary>Disposes the MongoDB client and its resources</summary>
public void Dispose() => _client?.Cluster?.Dispose();
}
En el código anterior, MongoDbService realiza las siguientes tareas:
- Lee la configuración y compila un cliente sin contraseña con credenciales de Azure.
- Proporciona referencias de base de datos o recopilación a petición
- Crea un índice de búsqueda vectorial solo si aún no existe.
- Enumera todas las bases de datos que no son del sistema, sus colecciones y los índices de cada colección
- Inserta datos de ejemplo si la colección está vacía y agrega índices auxiliares.
Visualización y administración de datos en Visual Studio Code
Instale la extensión DocumentDB y la extensión de C# en Visual Studio Code.
Conéctese a su cuenta de Azure DocumentDB mediante la extensión DocumentDB.
Vea los datos e índices en la base de datos Hotels.
Limpieza de recursos
Elimine el grupo de recursos, el clúster de Azure DocumentDB y el recurso de Azure OpenAI cuando ya no los necesite para evitar costos innecesarios.