Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
98322ae
feat: add Article 2 select-algorithm samples for all 5 languages
diberry Apr 29, 2026
5114591
fix: review findings - auth scope, consistency, env vars
diberry Apr 29, 2026
7185bb9
refactor(.NET): replace DotNetEnv with appsettings.json + Configurati…
diberry Apr 29, 2026
f9d5f10
docs: add azd env get-values config section to Article 2 READMEs
diberry Apr 29, 2026
2cde68a
feat: add compare-all runner for all 5 languages
diberry Apr 29, 2026
48b9f12
merge: pull compare-all changes from origin
diberry Apr 29, 2026
4d421ad
refactor: make compare-all self-contained with create/cleanup
diberry Apr 29, 2026
edcfe2a
Standardize collection lifecycle: conditional drop at start, always d…
diberry Apr 30, 2026
e17a32d
feat(java): Add individual algorithm runner files (IVF, HNSW, DiskANN)
diberry May 5, 2026
3fe2ce1
Add TypeScript individual runners and fix compare-all
diberry May 5, 2026
5918b62
feat(python): Add individual algorithm runners and fix utils
diberry May 5, 2026
85a6bf9
feat(go): Implement complete vector search algorithm comparison sample
diberry May 5, 2026
a9408ee
refactor: Move vector-search updates to separate PR #79
diberry May 6, 2026
a2d1762
fix: resolve merge conflicts and build errors in select-algorithm-typ…
diberry May 6, 2026
4900b92
fix: resolve merge conflicts and add missing getConfig in select-algo…
diberry May 6, 2026
be79978
fix: use create/search/drop pattern for compare-all across all languages
diberry May 6, 2026
ff8b0a3
feat: show top 2 results with score diff instead of latency
diberry May 6, 2026
4927a9c
feat: standardize output format with key insights across all languages
diberry May 6, 2026
7302e72
Update select-algorithm samples to use local data/ folder
diberry May 6, 2026
609cd61
fix: run all samples, fix Python search API, add real output
diberry May 6, 2026
84c6ffa
fix(java): fix OIDC auth and compilation errors, re-capture all outpu…
diberry May 6, 2026
90509f8
fix: address review findings - standardize EMBEDDED_FIELD, fix Go err…
diberry May 6, 2026
9ec1a56
fix: OIDC auth + search retry for all 5 compare-all samples
diberry May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions ai/select-algorithm-dotnet/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "Azure DocumentDB Select Algorithm - .NET 8",
"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm",

"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": true,
"configureZshAsDefaultShell": true,
"installOhMyZsh": true
}
},

"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csdevkit",
"ms-dotnettools.vscodeintellicode-csharp",
"ms-azuretools.vscode-azureresourcegroups",
"ms-azuretools.vscode-cosmosdb",
"mongodb.mongodb-vscode"
],
"settings": {
"dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true,
"files.exclude": {
"**/bin": true,
"**/obj": true
}
}
}
},

"postCreateCommand": "dotnet restore && dotnet build",
"remoteUser": "vscode",

"containerEnv": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
"DOTNET_NOLOGO": "1"
},

"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.azure,target=/home/vscode/.azure,type=bind,consistency=cached"
],

"capAdd": ["SYS_PTRACE"],
"securityOpt": ["seccomp:unconfined"]
}
7 changes: 7 additions & 0 deletions ai/select-algorithm-dotnet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bin/
obj/
.env

# Local data copy (user copies from ai/data/)
data/*.json
!data/README.md
197 changes: 197 additions & 0 deletions ai/select-algorithm-dotnet/AlgorithmRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System.Diagnostics;
using MongoDB.Driver;
using MongoDB.Bson;
using OpenAI.Embeddings;
using SelectAlgorithm.Models;

namespace SelectAlgorithm;

public static class AlgorithmRunner
{
private record IndexConfig(string Name, string Kind, string Similarity, BsonDocument ExtraParams);

public static void RunSingleAlgorithm(AppConfiguration config, string algorithm)
{
Console.WriteLine(new string('=', 60));
Console.WriteLine($" {algorithm.ToUpper()} Vector Search");
Console.WriteLine(new string('=', 60));

var mongoClient = Utils.GetMongoClientPasswordless(config);
var embeddingClient = Utils.GetEmbeddingClient(config);

try
{
var database = mongoClient.GetDatabase(config.DocumentDB.DatabaseName);

var collectionName = $"hotels_{algorithm}";
var collectionNames = database.ListCollectionNames().ToList();
if (collectionNames.Contains(collectionName))
{
database.DropCollection(collectionName);
Console.WriteLine($"Dropped existing '{collectionName}' collection.");
}

var collection = database.GetCollection<BsonDocument>(collectionName);

var data = Utils.ReadJsonFile(config.DataFiles.WithVectors);
var documents = data.Where(d => d.Contains(config.Embedding.EmbeddedField)).ToList();
Console.WriteLine($"\nLoaded {documents.Count} documents with embeddings");
Utils.InsertData(collection, documents, config.DocumentDB.LoadBatchSize);

Console.WriteLine($"\nQuery: \"{config.VectorSearch.Query}\"");
var embeddingResult = embeddingClient.GenerateEmbedding(config.VectorSearch.Query);
var queryVector = embeddingResult.Value.ToFloats().ToArray();
Console.WriteLine("Embedding generated\n");

var indexConfig = BuildIndexConfig(algorithm, config.Embedding.Dimensions);
Console.WriteLine($"Creating {algorithm} index...");
CreateIndex(collection, config.Embedding.EmbeddedField, indexConfig);

Console.WriteLine("Waiting for index to build...");
Thread.Sleep(5000);

Console.WriteLine("Running search...\n");
var sw = Stopwatch.StartNew();
var results = RunVectorSearch(collection, queryVector, config.Embedding.EmbeddedField, indexConfig.Name, config.VectorSearch.TopK, algorithm);
sw.Stop();

PrintResults(results, algorithm, sw.ElapsedMilliseconds);
}
finally
{
mongoClient.Cluster.Dispose();
}
}

private static IndexConfig BuildIndexConfig(string algorithm, int dimensions)
{
var algo = algorithm.ToLower();
return algo switch
{
"ivf" => new IndexConfig(
$"vector_ivf",
"vector-ivf",
"COS",
new BsonDocument { { "numLists", 1 } }
),
"hnsw" => new IndexConfig(
$"vector_hnsw",
"vector-hnsw",
"COS",
new BsonDocument { { "m", 16 }, { "efConstruction", 64 } }
),
"diskann" => new IndexConfig(
$"vector_diskann",
"vector-diskann",
"COS",
new BsonDocument { { "maxDegree", 32 }, { "lBuild", 50 } }
),
_ => throw new ArgumentException($"Unknown algorithm: {algorithm}")
};
}

private static void CreateIndex(IMongoCollection<BsonDocument> collection, string vectorField, IndexConfig config)
{
try
{
collection.Indexes.DropOne(config.Name);
}
catch (MongoCommandException)
{
}

var cosmosSearchOptions = new BsonDocument
{
{ "kind", config.Kind },
{ "dimensions", int.Parse(Environment.GetEnvironmentVariable("EMBEDDING_DIMENSIONS") ?? "1536") },
{ "similarity", config.Similarity }
};

foreach (var param in config.ExtraParams)
{
cosmosSearchOptions.Add(param);
}

var command = new BsonDocument
{
{ "createIndexes", collection.CollectionNamespace.CollectionName },
{ "indexes", new BsonArray
{
new BsonDocument
{
{ "name", config.Name },
{ "key", new BsonDocument(vectorField, "cosmosSearch") },
{ "cosmosSearchOptions", cosmosSearchOptions }
}
}
}
};

try
{
collection.Database.RunCommand<BsonDocument>(command);
}
catch (MongoCommandException ex) when (ex.Message.Contains("already exists"))
{
}
}

private static List<BsonDocument> RunVectorSearch(
IMongoCollection<BsonDocument> collection,
float[] queryVector,
string vectorField,
string indexName,
int topK,
string algorithm)
{
var cosmosSearch = new BsonDocument
{
{ "vector", new BsonArray(queryVector.Select(f => (double)f)) },
{ "path", vectorField },
{ "k", topK }
};

switch (algorithm.ToLower())
{
case "diskann":
cosmosSearch.Add("lSearch", 100);
break;
case "hnsw":
cosmosSearch.Add("efSearch", 80);
break;
case "ivf":
cosmosSearch.Add("nProbes", 1);
break;
}

var pipeline = new[]
{
new BsonDocument("$search", new BsonDocument("cosmosSearch", cosmosSearch)),
new BsonDocument("$project", new BsonDocument
{
{ "HotelName", 1 },
{ "score", new BsonDocument("$meta", "searchScore") }
})
};

return collection.Aggregate<BsonDocument>(pipeline).ToList();
}

private static void PrintResults(List<BsonDocument> results, string algorithm, long latencyMs)
{
Console.WriteLine(new string('=', 60));
Console.WriteLine($" {algorithm.ToUpper()} Results ({results.Count} found, {latencyMs}ms)");
Console.WriteLine(new string('=', 60));
Console.WriteLine();

for (var i = 0; i < results.Count; i++)
{
var doc = results[i];
var name = doc.Contains("HotelName") ? doc["HotelName"].AsString : "Unknown";
var score = doc.Contains("score") ? doc["score"].ToDouble() : 0.0;
Console.WriteLine($" {i + 1}. {name} (score: {score:F4})");
}

Console.WriteLine();
}
}
Loading
Loading