Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this quickstart, you run the Go create-index sample for Azure Cosmos DB for NoSQL. The sample creates two containers with different vector index types—DiskANN and QuantizedFlat—loads a pre-vectorized hotel dataset, and compares the scores and rankings produced by Cosine, DotProduct, and Euclidean distance functions.
DiskANN and QuantizedFlat are two of the vector index types available in Azure Cosmos DB for NoSQL. For a comparison of when to use each, see Vector indexing policies.
The sample uses a JSON dataset with hotel names, regions, descriptions, and 1536-dimension vectors generated by the text-embedding-3-small model. The sample partitions the hotel documents by geographic region.
Prerequisites
- An Azure subscription. If you don't have one, create a free account.
- Azure CLI installed.
- Git installed.
- Azure Developer CLI (azd) installed.
- Go 1.27 or later installed.
Azure resources are created in this quickstart with the Azure Developer CLI.
Tip
Agent Kit helps coding agents work with Azure Cosmos DB quickly and efficiently using recommended best practices. To get started, run:
npx skills add AzureCosmosDB/cosmosdb-agent-kit
To learn more, see Azure Cosmos DB Agent Kit.
App dependencies
The sample uses the following Go module dependencies:
github.com/Azure/azure-sdk-for-go/sdk/azidentity: Authenticates with Microsoft Entra ID by usingDefaultAzureCredential.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos/v3: Creates and deletes the vector-indexed containers through Azure Resource Manager.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos: Ingests documents and runs vector queries.github.com/Azure/azure-sdk-for-go/sdk/azcore: Supplies Azure SDK credential, token, and response types.
The Go sample calls the Azure OpenAI embeddings REST API directly instead of using an Azure OpenAI client library.
Authenticate to Azure
The sample uses passwordless authentication through DefaultAzureCredential and Microsoft Entra ID. Sign in to Azure before you run the sample so it can access your Azure resources securely.
azd auth login
Provision the resources
Clone the sample repository.
git clone https://github.com/Azure-Samples/cosmos-db-vector-samples.git cd cosmos-db-vector-samplesCreate an Azure Developer CLI environment.
azd env new cosmos-nosqlSet the database name for the create-index samples.
azd env set AZURE_COSMOSDB_CREATE_INDEX_DATABASENAME "HotelsCreateIndex"Provision the Azure resources and role assignments.
azd upChange to the Go sample directory.
cd nosql-create-index-go
Set up environment variables
This sample uses a local .env file to store the Azure resource and project settings. Generate the file from the active Azure Developer CLI environment.
azd env get-values > .env
Load the values into the current shell because the Go app reads process environment variables and doesn't load .env automatically.
Get-Content .env | Where-Object { $_ -match '^[^#].*=' } | ForEach-Object { $k,$v = $_ -split '=',2; [Environment]::SetEnvironmentVariable($k.Trim(), $v.Trim().Trim('"').Trim("'")) }
The checked-in example shows the default configuration shape:
AZURE_COSMOSDB_ENDPOINT=https://YOUR-COSMOS-ACCOUNT.documents.azure.com:443/
AZURE_COSMOSDB_CREATE_INDEX_DATABASENAME=HotelsCreateIndex
AZURE_OPENAI_EMBEDDING_ENDPOINT=https://YOUR-OPENAI-RESOURCE.openai.azure.com/
AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000
AZURE_RESOURCE_GROUP=YOUR-RESOURCE-GROUP
AZURE_COSMOSDB_ACCOUNT_NAME=YOUR-COSMOS-ACCOUNT
AZURE_LOCATION=eastus2
DATA_FILE_WITH_VECTORS_AND_REGIONS=./data/HotelsData_toCosmosDB_Vector_byRegion.json
# Optional. These documented defaults can be deleted and recreated without an opt-in flag.
AZURE_COSMOSDB_CREATE_INDEX_DISKANN_CONTAINER_NAME=hotels_diskann
AZURE_COSMOSDB_CREATE_INDEX_QUANTIZEDFLAT_CONTAINER_NAME=hotels_quantizedflat
# Required only when either container name above is customized.
AZURE_COSMOSDB_CREATE_INDEX_ALLOW_DESTRUCTIVE_OPERATIONS=false
The source defaults to the hotels_diskann and hotels_quantizedflat containers. The sample deletes and recreates these containers, and then deletes them again during cleanup.
Build and run the project
Download the project dependencies.
go mod downloadIf you opened a new terminal after setup, load the
.envvalues into that shell again.Run the sample.
go run .Review an excerpt from the recorded output.
Query: "hotel near the ocean" Embedding generated (1536 dimensions) Running search (top 5 results for each distance function)...
The sample demonstrates two goals:
- Control plane: Authenticate, use the existing database, and delete and recreate the two configured vector-indexed containers.
- Data plane: Load the same 50-document dataset, validate 1536-dimension embeddings, ingest documents by using
Regionas the partition key, generate a query embedding, run Cosine, DotProduct, and Euclidean queries, display ranked results and a comparison summary, and clean up the two containers.
Understand the vector distance functions
The sample's core purpose is comparing all three distance functions against the same containers and query embedding. Each function measures "closeness" differently, and that difference affects both the numeric scores and, for near ties, the ranking.
What each function measures
| Function | What it measures | Score range |
|---|---|---|
| Cosine | The angle between two vectors — magnitude-independent | -1 to 1; higher values indicate greater similarity |
| DotProduct | The inner product — accounts for both direction and magnitude | Any real number; higher values indicate greater similarity |
| Euclidean | Straight-line (L2) distance between vector endpoints — magnitude-sensitive | 0 to 2 for unit-normalized vectors; lower values indicate greater similarity |
How the example output reflects these differences
Across the sample's recorded output for the 50-hotel dataset, Cosine and DotProduct produce nearly identical scores and return the hotels in the same order. Euclidean produces scores in a different magnitude range for the same hotels.
Why Cosine and DotProduct can align closely here: This sample uses embeddings from the same model and compares the same documents across each distance function. As a result, Cosine and DotProduct often produce similar rankings. Depending on your data distribution and whether embeddings are normalized, the exact scores and ordering can still differ.
Cosine and DotProduct are similarity measures, not mathematical distance metrics. Cosine measures how closely the vectors point in the same direction. DotProduct measures that alignment while also accounting for vector magnitude. For both measures, a larger score means greater similarity.
Why Euclidean differs: Euclidean is a distance metric that measures the straight-line distance between vector endpoints. A smaller score means the vectors are closer together. The exact score ranges depend on the embeddings in your dataset.
How the distance function override works
Each query in this sample passes the function through the distanceFunction option in VectorDistance():
VectorDistance(c.embedding, @embedding, false, {'distanceFunction': 'Cosine'})
In this query, false tells Azure Cosmos DB to use the vector index instead of performing a brute-force search. The distanceFunction option tells it to calculate similarity by using cosine distance. This setting applies only to the current query—it doesn't change the distance function configured for the container. For the full signature, see the VectorDistance reference.
Tip
Match the query distance function to how your embedding model was trained. cosine is the standard default for OpenAI text embedding models, including text-embedding-3-small. Querying with a different function than the one stored in the container's vector policy still computes correctly, but might not use the vector index as efficiently.
Explore the app code
The following sections describe the main code paths in the Go sample.
nosql-create-index-go/
├── .env.example
├── config.go
├── controlplane.go
├── dataplane.go
├── go.mod
├── main.go
└── output/
└── sample-output.txt
| File | Purpose |
|---|---|
main.go |
Loads configuration and orchestrates control-plane and data-plane operations. |
config.go |
Loads and validates environment variables. |
controlplane.go |
Creates, recreates, verifies, and deletes the vector-indexed containers. |
dataplane.go |
Loads documents, calls the embeddings REST API, ingests data, and runs vector queries. |
Explore the credential and client setup
The orchestration code loads the configuration, creates one DefaultAzureCredential, and uses that credential to create the Azure Cosmos DB client.
cfg, err := LoadConfig()
if err != nil {
log.Fatalf("configuration error: %v", err)
}
credential, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
log.Fatalf("failed to create DefaultAzureCredential: %v", err)
}
cosmosClient, err := azcosmos.NewClient(cfg.CosmosEndpoint, credential, nil)
if err != nil {
log.Fatalf("failed to create Azure Cosmos DB client: %v", err)
}
databaseClient, err := cosmosClient.NewDatabase(cfg.DatabaseName)
if err != nil {
log.Fatalf("failed to access database %q: %v", cfg.DatabaseName, err)
}
The same credential is passed to the control-plane method and later obtains the bearer token for the Azure OpenAI embeddings REST request.
Explore the control-plane container creation
The CreateContainersWithVectorIndexes function uses the Azure Resource Manager SDK to recreate one DiskANN container and one QuantizedFlat container in the existing database.
// CreateContainersWithVectorIndexes creates SQL containers with vector indexes using the ARM SDK
func CreateContainersWithVectorIndexes(
ctx context.Context,
credential *azidentity.DefaultAzureCredential,
config *Config,
) error {
if err := validateContainerDeletionSafety(config); err != nil {
return fmt.Errorf("unsafe container deletion configuration: %w", err)
}
// Create ARM client
client, err := armcosmos.NewSQLResourcesClient(config.SubscriptionID, credential, nil)
if err != nil {
return fmt.Errorf("failed to create ARM client: %w", err)
}
// Create containers with vector indexes
indexConfigs := []struct {
indexType armcosmos.VectorIndexType
containerName string
}{
{armcosmos.VectorIndexTypeDiskANN, config.DiskANNContainerName},
{armcosmos.VectorIndexTypeQuantizedFlat, config.QuantizedFlatContainerName},
}
embeddingPath := "/" + strings.TrimPrefix(config.EmbeddingFieldName, "/")
partitionKeyPath := "/" + config.PartitionKeyFieldName
for _, indexConfig := range indexConfigs {
fmt.Printf("\n=== Creating Container: %s ===\n", indexConfig.containerName)
fmt.Printf(" Index type: %s\n", string(indexConfig.indexType))
fmt.Printf(" Embedding path: %s\n", embeddingPath)
fmt.Printf(" Dimensions: %d\n", config.EmbeddingDimensions)
fmt.Printf(" Distance func: cosine (queried with all 3 metrics)\n")
// Build container resource with vector embedding policy
containerResource := &armcosmos.SQLContainerResource{
ID: ptr(indexConfig.containerName),
PartitionKey: &armcosmos.ContainerPartitionKey{
Paths: []*string{ptr(partitionKeyPath)},
Kind: ptr(armcosmos.PartitionKindHash),
},
VectorEmbeddingPolicy: &armcosmos.VectorEmbeddingPolicy{
VectorEmbeddings: []*armcosmos.VectorEmbedding{
{
Path: ptr(embeddingPath),
DataType: ptr(armcosmos.VectorDataTypeFloat32),
Dimensions: ptr(int32(config.EmbeddingDimensions)),
DistanceFunction: ptr(armcosmos.DistanceFunctionCosine),
},
},
},
IndexingPolicy: &armcosmos.IndexingPolicy{
IndexingMode: ptr(armcosmos.IndexingModeConsistent),
Automatic: ptr(true),
IncludedPaths: []*armcosmos.IncludedPath{
{Path: ptr("/*")},
},
ExcludedPaths: []*armcosmos.ExcludedPath{
{Path: ptr(`/"_etag"/?`)},
{Path: ptr(embeddingPath + "/*")},
},
VectorIndexes: []*armcosmos.VectorIndex{
{
Path: ptr(embeddingPath),
Type: ptr(indexConfig.indexType),
},
},
},
}
// Delete existing container for idempotent re-runs
fmt.Printf(" Pre-creation delete target: %s/%s/%s/%s\n", config.ResourceGroup, config.AccountName, config.DatabaseName, indexConfig.containerName)
deleteStart := time.Now()
deleteCtx, deleteCancel := context.WithTimeout(ctx, armDeleteTimeout)
deletePoller, err := client.BeginDeleteSQLContainer(
deleteCtx,
config.ResourceGroup,
config.AccountName,
config.DatabaseName,
indexConfig.containerName,
nil,
)
if err != nil {
deleteCancel()
if isNotFound(err) {
fmt.Printf(" ✓ Pre-creation deletion completed in %.1fs; container did not exist\n", time.Since(deleteStart).Seconds())
} else {
return fmt.Errorf("failed to begin pre-creation deletion for container %q: %w", indexConfig.containerName, err)
}
} else {
fmt.Printf(" Waiting for pre-creation deletion to complete (polling every %s)...\n", armPollFrequency)
_, err = deletePoller.PollUntilDone(
deleteCtx,
&runtime.PollUntilDoneOptions{Frequency: armPollFrequency},
)
deleteCancel()
if err != nil {
if isNotFound(err) {
fmt.Printf(" ✓ Pre-creation deletion completed in %.1fs; container no longer exists\n", time.Since(deleteStart).Seconds())
} else {
return fmt.Errorf("failed to wait for pre-creation deletion of container %q: %w", indexConfig.containerName, err)
}
} else {
fmt.Printf(" ✓ Pre-creation deletion completed in %.1fs\n", time.Since(deleteStart).Seconds())
}
}
// Create the container
fmt.Printf(" Creating container with vector index...\n")
start := time.Now()
createCtx, createCancel := context.WithTimeout(ctx, armCreateTimeout)
// Note: Options is nil to support serverless accounts (which don't allow explicit throughput).
// For provisioned accounts, you could set Options.Throughput or Options.AutoscaleSettings.
containerPoller, err := client.BeginCreateUpdateSQLContainer(
createCtx,
config.ResourceGroup,
config.AccountName,
config.DatabaseName,
indexConfig.containerName,
armcosmos.SQLContainerCreateUpdateParameters{
Location: ptr(config.Location),
Properties: &armcosmos.SQLContainerCreateUpdateProperties{
Resource: containerResource,
Options: nil, // nil = serverless compatible (no throughput setting)
},
},
nil,
)
if err != nil {
createCancel()
return fmt.Errorf("failed to begin creating container %s: %w", indexConfig.containerName, err)
}
fmt.Printf(" Waiting for container creation to complete (polling every %s)...\n", armPollFrequency)
if _, err := containerPoller.PollUntilDone(createCtx, &runtime.PollUntilDoneOptions{Frequency: armPollFrequency}); err != nil {
createCancel()
return fmt.Errorf("failed to create container %s: %w", indexConfig.containerName, err)
}
createCancel()
elapsed := time.Since(start)
fmt.Printf(" ✓ Container created in %.2fs\n", elapsed.Seconds())
// Verify the container exists and has the correct configuration
readCtx, readCancel := context.WithTimeout(ctx, armReadTimeout)
got, err := client.GetSQLContainer(
readCtx,
config.ResourceGroup,
config.AccountName,
config.DatabaseName,
indexConfig.containerName,
nil,
)
readCancel()
if err != nil {
return fmt.Errorf("failed to verify container %s: %w", indexConfig.containerName, err)
}
res := got.Properties.Resource
if res == nil {
return fmt.Errorf("read-back resource for %s is nil", indexConfig.containerName)
}
// Log verification
if res.VectorEmbeddingPolicy != nil && len(res.VectorEmbeddingPolicy.VectorEmbeddings) > 0 {
emb := res.VectorEmbeddingPolicy.VectorEmbeddings[0]
fmt.Printf(" ✓ Verified: Vector embedding policy configured\n")
fmt.Printf(" - Path: %s\n", *emb.Path)
fmt.Printf(" - DataType: %s\n", *emb.DataType)
fmt.Printf(" - Dimensions: %d\n", *emb.Dimensions)
fmt.Printf(" - DistanceFunction: %s\n", *emb.DistanceFunction)
}
if res.IndexingPolicy != nil && len(res.IndexingPolicy.VectorIndexes) > 0 {
idx := res.IndexingPolicy.VectorIndexes[0]
fmt.Printf(" ✓ Verified: Vector index configured\n")
fmt.Printf(" - Path: %s\n", *idx.Path)
fmt.Printf(" - Type: %s\n", *idx.Type)
}
}
fmt.Printf("\n✓ All containers created successfully\n")
return nil
}
The function completes the control-plane goal by applying /Region as the partition key path and /embedding as the 1536-dimension float32 vector path to both containers. The vector policy and index are immutable, so the function deletes and recreates both containers on every run.
Explore the document ingestion
The InsertDocuments function associates each of the 50 hotel documents with its Region partition key and writes the same data to both containers.
func InsertDocuments(ctx context.Context, container *azcosmos.ContainerClient, documents []map[string]any) (InsertStats, error) {
stats := InsertStats{Total: len(documents)}
semaphore := make(chan struct{}, maxInsertConcurrency)
var mu sync.Mutex
var wg sync.WaitGroup
for _, document := range documents {
document := document
wg.Add(1)
go func() {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
// Extract Region from document for partition key
region, ok := document["Region"].(string)
if !ok {
mu.Lock()
stats.Failed++
mu.Unlock()
return
}
partitionKey := azcosmos.NewPartitionKey().AppendString(region)
body, err := json.Marshal(document)
if err != nil {
mu.Lock()
stats.Failed++
mu.Unlock()
return
}
// Retry with backoff on 429 (rate limiting)
maxRetries := 5
for attempt := 0; attempt <= maxRetries; attempt++ {
response, err := container.CreateItem(ctx, partitionKey, body, nil)
if err != nil {
var responseError *azcore.ResponseError
if errors.As(err, &responseError) {
if responseError.StatusCode == http.StatusConflict {
mu.Lock()
stats.Skipped++
mu.Unlock()
return
}
if responseError.StatusCode == http.StatusTooManyRequests && attempt < maxRetries {
wait := time.Duration(attempt+1) * time.Second
time.Sleep(wait)
continue
}
}
mu.Lock()
stats.Failed++
mu.Unlock()
return
}
mu.Lock()
stats.Inserted++
stats.RequestCharge += float64(response.RequestCharge)
mu.Unlock()
return
}
}()
}
wg.Wait()
if stats.Failed > 0 {
return stats, fmt.Errorf("failed to insert %d documents", stats.Failed)
}
return stats, nil
}
The function limits insertion concurrency to one operation at a time, treats 409 responses as existing documents, retries 429 responses, and returns an error if any document fails.
Explore the vector similarity queries
The GenerateEmbedding function gets an Azure OpenAI bearer token from the shared credential, submits the sample query text to the embeddings REST API, and validates the returned vector dimensions.
func GenerateEmbedding(ctx context.Context, httpClient *http.Client, credential azcore.TokenCredential, cfg *Config, text string) ([]float32, error) {
token, err := credential.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{"https://cognitiveservices.azure.com/.default"}})
if err != nil {
return nil, fmt.Errorf("get Azure OpenAI bearer token: %w", err)
}
requestBody, err := json.Marshal(embeddingsRequest{Input: text})
if err != nil {
return nil, fmt.Errorf("marshal embeddings request: %w", err)
}
endpoint, err := url.Parse(strings.TrimRight(cfg.OpenAIEmbeddingEndpoint, "/"))
if err != nil {
return nil, fmt.Errorf("parse AZURE_OPENAI_EMBEDDING_ENDPOINT: %w", err)
}
endpoint.Path = fmt.Sprintf("/openai/deployments/%s/embeddings", cfg.OpenAIEmbeddingDeployment)
query := endpoint.Query()
query.Set("api-version", cfg.OpenAIAPIVersion)
endpoint.RawQuery = query.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(requestBody))
if err != nil {
return nil, fmt.Errorf("create embeddings request: %w", err)
}
request.Header.Set("Authorization", "Bearer "+token.Token)
request.Header.Set("Content-Type", "application/json")
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("call Azure OpenAI embeddings API: %w", err)
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("read embeddings response: %w", err)
}
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
return nil, fmt.Errorf("Azure OpenAI embeddings request failed: status=%d body=%s", response.StatusCode, strings.TrimSpace(string(responseBody)))
}
var payload embeddingsResponse
if err := json.Unmarshal(responseBody, &payload); err != nil {
return nil, fmt.Errorf("parse embeddings response: %w", err)
}
if payload.Error != nil {
return nil, fmt.Errorf("Azure OpenAI embeddings error: %s", payload.Error.Message)
}
if len(payload.Data) == 0 {
return nil, errors.New("Azure OpenAI embeddings response did not include any vectors")
}
embedding := make([]float32, len(payload.Data[0].Embedding))
for index, value := range payload.Data[0].Embedding {
embedding[index] = float32(value)
}
if len(embedding) != cfg.EmbeddingDimensions {
return nil, fmt.Errorf("embedding dimensions mismatch: got %d, expected %d", len(embedding), cfg.EmbeddingDimensions)
}
return embedding, nil
}
The orchestration code passes that embedding to QueryTopHotels for every combination of the two containers and three comparison functions.
// --- Query ---
embedding, err := GenerateEmbedding(ctx, httpClient, credential, cfg, cfg.QueryText)
if err != nil {
log.Fatalf("failed to generate Azure OpenAI embedding: %v", err)
}
fmt.Printf("\nQuery: %q\n", cfg.QueryText)
fmt.Printf("Embedding generated (%d dimensions)\n", len(embedding))
fmt.Println("\nRunning search (top 5 results for each distance function)...")
type metricResult struct {
containerName string
metric string
results []VectorSearchResult
ru float64
}
var allResults []metricResult
distanceFunctions := []string{"Cosine", "DotProduct", "Euclidean"}
for _, containerName := range cfg.ContainerNames {
containerClient, err := databaseClient.NewContainer(containerName)
if err != nil {
log.Fatalf("failed to access container %q: %v", containerName, err)
}
for _, distanceFunction := range distanceFunctions {
results, ru, err := QueryTopHotels(ctx, containerClient, embedding, cfg.EmbeddingFieldName, distanceFunction, cfg.PartitionKeyFieldValue)
if err != nil {
log.Fatalf("container %q query failed: %v", containerName, err)
}
fmt.Printf(" ✓ %s queried (%.2f RUs)\n", containerName, ru)
allResults = append(allResults, metricResult{
containerName: containerName,
metric: distanceFunction,
results: results,
ru: ru,
})
}
}
The QueryTopHotels function validates the embedding field name, binds the embedding as a SQL parameter, scopes the request to one partition, and reads the ranked results.
func QueryTopHotels(ctx context.Context, container *azcosmos.ContainerClient, embedding []float32, embeddingField, distanceFunction, partitionKeyValue string) ([]VectorSearchResult, float64, error) {
if !validIdentifier.MatchString(embeddingField) {
return nil, 0, fmt.Errorf("invalid embedding field name: %q", embeddingField)
}
// Convert float32 to float64 for parameter binding
// The Cosmos DB SDK expects []float64 for numeric array parameters
embedding64 := make([]float64, len(embedding))
for i, v := range embedding {
embedding64[i] = float64(v)
}
// ORDER BY VectorDistance(...) is required to rank results nearest-first.
// Without it, SELECT TOP 5 returns 5 arbitrary documents from the partition,
// not the nearest neighbors. SDK partition-key routing scopes the search to a
// single partition, so the SQL stays focused on vector ranking.
queryText := fmt.Sprintf(`SELECT TOP 5
c.HotelId,
c.HotelName,
c.Description,
VectorDistance(c.%s, @embedding, false, {'distanceFunction': '%s'}) AS SimilarityScore
FROM c
ORDER BY VectorDistance(c.%s, @embedding, false, {'distanceFunction': '%s'})`, embeddingField, distanceFunction, embeddingField, distanceFunction)
options := azcosmos.QueryOptions{
QueryParameters: []azcosmos.QueryParameter{
{
Name: "@embedding",
Value: embedding64,
},
},
}
// SDK-level partition key routing scopes the vector query to one partition.
partitionKey := azcosmos.NewPartitionKey().AppendString(partitionKeyValue)
pager := container.NewQueryItemsPager(queryText, partitionKey, &options)
var results []VectorSearchResult
var totalRequestCharge float64
for pager.More() {
page, err := pager.NextPage(ctx)
if err != nil {
return nil, totalRequestCharge, fmt.Errorf("query page: %w", err)
}
totalRequestCharge += float64(page.RequestCharge)
for _, item := range page.Items {
var result VectorSearchResult
if err := json.Unmarshal(item, &result); err != nil {
return nil, totalRequestCharge, fmt.Errorf("parse vector search result: %w", err)
}
results = append(results, result)
}
}
return results, totalRequestCharge, nil
}
These steps complete the data-plane comparison goal by running the same query embedding with Cosine, DotProduct, and Euclidean against both vector index types. For guidance on choosing a distance function for your own data, see the vector distance function overview earlier in this article.
Explore the single-partition query pattern
The sample scopes each query to the configured Region value, which defaults to Northeast, through the SDK partition key option.
| Mechanism | How it works | Sample code |
|---|---|---|
| SDK partition key | Passes a partition key to NewQueryItemsPager so the request targets the configured partition key value. |
container.NewQueryItemsPager(queryText, partitionKey, &options) |
| Comparison | Single-partition query | Cross-partition query |
|---|---|---|
| Query scope | One configured region. | All regions. |
| Documents in the supplied dataset | 10 documents in the default Northeast region. |
50 documents across all regions. |
| Routing | Uses the SDK partition key value. | Requires cross-partition fan-out. |
You can alternatively add a WHERE c.Region = @region predicate to the SQL statement. This sample uses the SDK partition key option instead.
An ORDER BY VectorDistance(...) clause is required for nearest-neighbor ranking. The expression repeats the same VectorDistance(...) call used in the SELECT clause.
View and manage data in Visual Studio Code
Use the Azure Databases extension for Visual Studio Code to connect to your Azure Cosmos DB account and browse the hotels_diskann and hotels_quantizedflat containers.
- In Visual Studio Code, select the Azure icon in the Activity Bar.
- Under Resources, expand Azure Cosmos DB, and locate your account.
- Expand your account > HotelsCreateIndex > hotels_diskann or hotels_quantizedflat.
- Select a document to view its hotel fields and the
embeddingvector array.
Note
The sample deletes both containers at the end of each run. To retain the containers for inspection, comment out the DeleteContainers call in main.go before you run the sample.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
configuration error: missing required environment variables |
The generated configuration is missing or isn't loaded into the current shell. | Verify the .env values, load them into the current shell, and rerun the sample. |
DefaultAzureCredential can't authenticate |
The current shell doesn't have an authenticated Azure identity. | Run azd auth login, and then rerun the sample. |
| 403 response during container creation | The identity lacks control-plane access. | Verify that the identity has permission to create and delete Azure Cosmos DB containers through Azure Resource Manager. |
| 403 response during document ingestion or query | The identity lacks data-plane access. | Verify that the identity has Cosmos DB Built-in Data Contributor. Role assignments can take several minutes to propagate. |
| Azure OpenAI authorization error | The identity lacks access to the embedding deployment. | Verify that the identity has Cognitive Services OpenAI User on the Azure OpenAI resource. |
| Embedding dimension mismatch | The embedding deployment returns a vector size other than 1536. | Verify that AZURE_OPENAI_EMBEDDING_DEPLOYMENT identifies the expected text-embedding-3-small deployment. |
| Database or container configuration error | The configured database doesn't exist or the two container names are identical. | Verify the database and container settings, and then rerun the sample. |
Clean up resources
The sample automatically deletes its vector-indexed containers at the end of each run. To remove the remaining Azure resources that the sample provisions, run azd down.
azd down