Go 클라이언트 라이브러리와 함께 Azure DocumentDB에서 벡터 검색을 사용합니다. 벡터 데이터를 효율적으로 저장하고 쿼리합니다.
이 빠른 시작에서는 모델의 미리 계산된 벡터가 있는 JSON 파일의 text-embedding-3-small 샘플 호텔 데이터 세트를 사용합니다. 데이터 세트에는 호텔 이름, 위치, 설명 및 벡터 포함이 포함됩니다.
GitHub에서 샘플 코드를 찾습니다.
필수 조건
Azure 구독
- Azure 구독이 없는 경우 체험 계정 만들기
기존 Azure DocumentDB 클러스터
클러스터가 없는 경우 새 클러스터를 만듭니다.
-
구성된 사용자 지정 도메인
text-embedding-3-small배포된 모델
Bash 환경을 Azure Cloud Shell에서 사용합니다. 자세한 내용은 Azure Cloud Shell을 시작하는 방법을 참조하세요.
CLI 참조 명령을 로컬에서 실행하려면 Azure CLI를 설치하십시오. Windows 또는 macOS에서 실행 중인 경우 Docker 컨테이너에서 Azure CLI를 실행하는 것이 좋습니다. 자세한 내용은 Docker 컨테이너에서 Azure CLI를 실행하는 방법을 참조하세요.
로컬 설치를 사용하는 경우 az login 명령을 사용하여 Azure CLI에 로그인합니다. 인증 프로세스를 완료하려면 터미널에 표시되는 단계를 수행합니다. 다른 로그인 옵션은 Azure CLI를 사용하여 Azure에 인증을 참조하세요.
메시지가 표시되면 처음 사용할 때 Azure CLI 확장을 설치합니다. 확장에 대한 자세한 내용은 Azure CLI로 확장 사용 및 관리를 참조하세요.
az version을 실행하여 설치된 버전과 관련 종속 라이브러리를 확인합니다. 최신 버전으로 업그레이드하려면 az upgrade를 실행합니다.
- Go 버전 1.24 이상
벡터를 사용하여 데이터 파일 만들기
호텔 데이터 파일에 대한 새 데이터 디렉터리를 만듭니다.
mkdir data벡터를 사용하여
Hotels_Vector.json원시 데이터 파일을 디렉터리에 복사합니다data.
Go 프로젝트 만들기
프로젝트에 대한 새 디렉터리를 만들고 Visual Studio Code에서 엽니다.
mkdir vector-search-quickstart cd vector-search-quickstart code .Go 모듈 초기화:
go mod init vector-search-quickstart필요한 Go 패키지를 설치합니다.
go get go.mongodb.org/mongo-driver go get github.com/Azure/azure-sdk-for-go/sdk/azcore go get github.com/Azure/azure-sdk-for-go/sdk/azidentity go get github.com/openai/openai-go/v3 go get github.com/joho/godotenv-
go.mongodb.org/mongo-driver: MongoDB Go 드라이버 -
github.com/Azure/azure-sdk-for-go/sdk/azcore: HTTP 파이프라인 및 인증을 위한 Azure SDK 핵심 유틸리티 -
github.com/Azure/azure-sdk-for-go/sdk/azidentity: 암호 없는 토큰 기반 인증을 위한 Azure ID 라이브러리 -
github.com/openai/openai-go/v3: OpenAI Go 클라이언트 라이브러리를 사용하여 벡터 만들기 -
github.com/joho/godotenv: .env 파일에서 환경 변수 로드
-
환경 변수에
.env대한 파일을 프로젝트 루트에 만듭니다.# Identity for local developer authentication with Azure CLI AZURE_TOKEN_CREDENTIALS=AzureCliCredential # Azure OpenAI Embedding Settings AZURE_OPENAI_EMBEDDING_MODEL=text-embedding-3-small AZURE_OPENAI_EMBEDDING_API_VERSION=2023-05-15 AZURE_OPENAI_EMBEDDING_ENDPOINT=<AZURE_OPENAI_ENDPOINT> EMBEDDING_SIZE_BATCH=16 # Azure DocumentDB configuration MONGO_CLUSTER_NAME=<DOCUMENTDB_NAME> # Data file DATA_FILE_WITH_VECTORS=../data/Hotels_Vector.json EMBEDDED_FIELD=DescriptionVector EMBEDDING_DIMENSIONS=1536 LOAD_SIZE_BATCH=50.env파일의 자리 표시자 값을 사용자 정보로 바꿉니다.-
AZURE_OPENAI_EMBEDDING_ENDPOINT: Azure OpenAI 리소스 엔드포인트 URL 주소 -
MONGO_CLUSTER_NAME: Azure DocumentDB 리소스 이름
항상 암호 없는 인증을 선호해야 하지만 추가 설정이 필요합니다. 관리 ID 및 인증 옵션의 전체 범위를 설정하는 방법에 대한 자세한 내용은 Azure ID 라이브러리를 사용하여 Azure 서비스로 이동 앱 인증을 참조하세요.
-
벡터 검색을 위한 Go 원본 파일 만들기
벡터 검색을 위한 코드 파일을 만들어 프로젝트를 계속합니다.
src Go 파일에 대한 디렉터리를 만듭니다. DiskANN 인덱스 구현에 대해 다음 두 개의 파일을 diskann.goutils.go 추가합니다.
mkdir src
touch src/diskann.go
touch src/utils.go
완료되면 프로젝트 구조는 다음과 같이 표시됩니다.
data
│── Hotels_Vector.json
vector-search-quickstart
├── .env
├── go.mod
├── src
│ ├── diskann.go
│ └── utils.go
벡터 검색을 위한 코드 만들기
src/diskann.go 파일에 다음 코드를 추가합니다.
package main
import (
"context"
"fmt"
"log"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"github.com/openai/openai-go/v3"
)
// CreateDiskANNVectorIndex creates a DiskANN vector index on the specified field
func CreateDiskANNVectorIndex(ctx context.Context, collection *mongo.Collection, vectorField string, dimensions int) error {
fmt.Printf("Creating DiskANN vector index on field '%s'...\n", vectorField)
// Drop any existing vector indexes on this field first
err := DropVectorIndexes(ctx, collection, vectorField)
if err != nil {
fmt.Printf("Warning: Could not drop existing indexes: %v\n", err)
}
// Use the native MongoDB command for DocumentDB vector indexes
// Note: Must use bson.D for commands to preserve order and avoid "multi-key map" errors
indexCommand := bson.D{
{"createIndexes", collection.Name()},
{"indexes", []bson.D{
{
{"name", fmt.Sprintf("diskann_index_%s", vectorField)},
{"key", bson.D{
{vectorField, "cosmosSearch"}, // DocumentDB vector search index type
}},
{"cosmosSearchOptions", bson.D{
// DiskANN algorithm configuration
{"kind", "vector-diskann"},
// Vector dimensions must match the embedding model
{"dimensions", dimensions},
// Vector similarity metric - cosine is good for text embeddings
{"similarity", "COS"},
// Maximum degree: number of edges per node in the graph
// Higher values improve accuracy but increase memory usage
{"maxDegree", 20},
// Build parameter: candidates evaluated during index construction
// Higher values improve index quality but increase build time
{"lBuild", 10},
}},
},
}},
}
// Execute the createIndexes command directly
var result bson.M
err = collection.Database().RunCommand(ctx, indexCommand).Decode(&result)
if err != nil {
// Check if it's a tier limitation and suggest alternatives
if strings.Contains(err.Error(), "not enabled for this cluster tier") {
fmt.Println("\nDiskANN indexes require a higher cluster tier.")
fmt.Println("Try one of these alternatives:")
fmt.Println(" • Upgrade your DocumentDB cluster to a higher tier")
fmt.Println(" • Use HNSW instead: go run src/hnsw.go")
fmt.Println(" • Use IVF instead: go run src/ivf.go")
}
return fmt.Errorf("error creating DiskANN vector index: %v", err)
}
fmt.Println("DiskANN vector index created successfully")
return nil
}
// PerformDiskANNVectorSearch performs a vector search using DiskANN algorithm
func PerformDiskANNVectorSearch(ctx context.Context, collection *mongo.Collection, openAIClient openai.Client, queryText, vectorField, modelName string, topK int) ([]SearchResult, error) {
fmt.Printf("Performing DiskANN vector search for: '%s'\n", queryText)
// Generate embedding for the query text
queryEmbedding, err := GenerateEmbedding(ctx, openAIClient, queryText, modelName)
if err != nil {
return nil, fmt.Errorf("error generating embedding: %v", err)
}
// Construct the aggregation pipeline for vector search
// DocumentDB uses $search with cosmosSearch
pipeline := []bson.M{
{
"$search": bson.M{
// Use cosmosSearch for vector operations in DocumentDB
"cosmosSearch": bson.M{
// The query vector to search for
"vector": queryEmbedding,
// Field containing the document vectors to compare against
"path": vectorField,
// Number of final results to return
"k": topK,
},
},
},
{
// Add similarity score to the results
"$project": bson.M{
"document": "$$ROOT",
// Add search score from metadata
"score": bson.M{"$meta": "searchScore"},
},
},
}
// Execute the aggregation pipeline
cursor, err := collection.Aggregate(ctx, pipeline)
if err != nil {
return nil, fmt.Errorf("error performing DiskANN vector search: %v", err)
}
defer cursor.Close(ctx)
var results []SearchResult
for cursor.Next(ctx) {
var result SearchResult
if err := cursor.Decode(&result); err != nil {
fmt.Printf("Warning: Could not decode result: %v\n", err)
continue
}
results = append(results, result)
}
if err := cursor.Err(); err != nil {
return nil, fmt.Errorf("cursor error: %v", err)
}
return results, nil
}
// main function demonstrates DiskANN vector search functionality
func main() {
ctx := context.Background()
// Load configuration from environment variables
config := LoadConfig()
fmt.Println("\nInitializing MongoDB and Azure OpenAI clients...")
mongoClient, azureOpenAIClient, err := GetClientsPasswordless()
if err != nil {
log.Fatalf("Failed to initialize clients: %v", err)
}
defer mongoClient.Disconnect(ctx)
// Get database and collection
database := mongoClient.Database(config.DatabaseName)
collection := database.Collection("hotels_diskann")
// Load data with embeddings
fmt.Printf("\nLoading data from %s...\n", config.DataFile)
data, err := ReadFileReturnJSON(config.DataFile)
if err != nil {
log.Fatalf("Failed to load data: %v", err)
}
fmt.Printf("Loaded %d documents\n", len(data))
// Verify embeddings are present
var documentsWithEmbeddings []map[string]interface{}
for _, doc := range data {
if _, exists := doc[config.VectorField]; exists {
documentsWithEmbeddings = append(documentsWithEmbeddings, doc)
}
}
if len(documentsWithEmbeddings) == 0 {
log.Fatalf("No documents found with embeddings in field '%s'. Please run create_embeddings.go first.", config.VectorField)
}
// Insert data into collection
fmt.Printf("\nInserting data into collection '%s'...\n", config.CollectionName)
// Clear existing data to ensure clean state
deleteResult, err := collection.DeleteMany(ctx, bson.M{})
if err != nil {
log.Fatalf("Failed to clear existing data: %v", err)
}
if deleteResult.DeletedCount > 0 {
fmt.Printf("Cleared %d existing documents from collection\n", deleteResult.DeletedCount)
}
// Insert the hotel data
stats, err := InsertData(ctx, collection, documentsWithEmbeddings, config.BatchSize, nil)
if err != nil {
log.Fatalf("Failed to insert data: %v", err)
}
if stats.Inserted == 0 {
log.Fatalf("No documents were inserted successfully")
}
fmt.Printf("Insertion completed: %d inserted, %d failed\n", stats.Inserted, stats.Failed)
// Create DiskANN vector index
err = CreateDiskANNVectorIndex(ctx, collection, config.VectorField, config.Dimensions)
if err != nil {
log.Fatalf("Failed to create DiskANN vector index: %v", err)
}
// Wait briefly for index to be ready
fmt.Println("Waiting for index to be ready...")
time.Sleep(2 * time.Second)
// Perform sample vector search
query := "quintessential lodging near running trails, eateries, retail"
results, err := PerformDiskANNVectorSearch(
ctx,
collection,
azureOpenAIClient,
query,
config.VectorField,
config.ModelName,
5,
)
if err != nil {
log.Fatalf("Failed to perform vector search: %v", err)
}
// Display results
PrintSearchResults(results, 5, true)
fmt.Println("\nDiskANN demonstration completed successfully!")
}
이 주 모듈은 다음과 같은 기능을 제공합니다.
- 유틸리티 함수 포함
- 환경 변수에 대한 구성 구조체를 만듭니다.
- Azure OpenAI 및 Azure DocumentDB에 대한 클라이언트를 만듭니다.
- MongoDB에 연결하고, 데이터베이스 및 컬렉션을 만들고, 데이터를 삽입하고, 표준 인덱스를 만듭니다.
- IVF, HNSW 또는 DiskANN을 사용하여 벡터 인덱스를 만듭니다.
- OpenAI 클라이언트를 사용하여 샘플 쿼리 텍스트에 대한 포함을 만듭니다. 주 함수에서 쿼리를 변경할 수 있습니다.
- 포함을 사용하여 벡터 검색을 실행하고 결과를 출력합니다.
유틸리티 함수 만들기
다음 코드를 src/utils.go에 추가합니다.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/joho/godotenv"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/azure"
"github.com/openai/openai-go/v3/option"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
// Config holds the application configuration
type Config struct {
ClusterName string
DatabaseName string
CollectionName string
DataFile string
VectorField string
ModelName string
Dimensions int
BatchSize int
}
// SearchResult represents a search result document
type SearchResult struct {
Document interface{} `bson:"document"`
Score float64 `bson:"score"`
}
// HotelData represents a hotel document structure
type HotelData struct {
HotelName string `bson:"HotelName" json:"HotelName"`
Description string `bson:"Description" json:"Description"`
DescriptionVector []float64 `bson:"DescriptionVector,omitempty" json:"DescriptionVector,omitempty"`
// Add other fields as needed
}
// InsertStats holds statistics about data insertion
type InsertStats struct {
Total int `json:"total"`
Inserted int `json:"inserted"`
Failed int `json:"failed"`
}
// LoadConfig loads configuration from environment variables
func LoadConfig() *Config {
// Load environment variables from .env file
// For production use, prefer Azure Key Vault or similar secret management
// services instead of .env files. For development/demo purposes only.
err := godotenv.Load()
if err != nil {
log.Printf("Warning: Error loading .env file: %v", err)
}
dimensions, _ := strconv.Atoi(getEnvOrDefault("EMBEDDING_DIMENSIONS", "1536"))
batchSize, _ := strconv.Atoi(getEnvOrDefault("LOAD_SIZE_BATCH", "100"))
return &Config{
ClusterName: getEnvOrDefault("MONGO_CLUSTER_NAME", "vectorSearch"),
DatabaseName: "Hotels",
CollectionName: "vectorSearchCollection",
DataFile: getEnvOrDefault("DATA_FILE_WITH_VECTORS", "../data/Hotels_Vector.json"),
VectorField: getEnvOrDefault("EMBEDDED_FIELD", "DescriptionVector"),
ModelName: getEnvOrDefault("AZURE_OPENAI_EMBEDDING_MODEL", "text-embedding-3-small"),
Dimensions: dimensions,
BatchSize: batchSize,
}
}
// getEnvOrDefault returns environment variable value or default if not set
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// GetClients creates MongoDB and Azure OpenAI clients with connection string authentication
func GetClients() (*mongo.Client, openai.Client, error) {
ctx := context.Background()
// Get MongoDB connection string
mongoConnectionString := os.Getenv("MONGO_CONNECTION_STRING")
if mongoConnectionString == "" {
return nil, openai.Client{}, fmt.Errorf("MONGO_CONNECTION_STRING environment variable is required. " +
"Set it to your DocumentDB connection string or use GetClientsPasswordless() for OIDC auth")
}
// Create MongoDB client with optimized settings for DocumentDB
clientOptions := options.Client().
ApplyURI(mongoConnectionString).
SetMaxPoolSize(50).
SetMinPoolSize(5).
SetMaxConnIdleTime(30 * time.Second).
SetServerSelectionTimeout(5 * time.Second).
SetSocketTimeout(20 * time.Second)
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, openai.Client{}, fmt.Errorf("failed to connect to MongoDB: %v", err)
}
// Test the connection
err = mongoClient.Ping(ctx, nil)
if err != nil {
return nil, openai.Client{}, fmt.Errorf("failed to ping MongoDB: %v", err)
}
// Get Azure OpenAI configuration
azureOpenAIEndpoint := os.Getenv("AZURE_OPENAI_EMBEDDING_ENDPOINT")
azureOpenAIKey := os.Getenv("AZURE_OPENAI_EMBEDDING_KEY")
if azureOpenAIEndpoint == "" || azureOpenAIKey == "" {
return nil, openai.Client{}, fmt.Errorf("Azure OpenAI endpoint and key are required")
}
// Create Azure OpenAI client
openAIClient := openai.NewClient(
option.WithBaseURL(fmt.Sprintf("%s/openai/v1", azureOpenAIEndpoint)),
option.WithAPIKey(azureOpenAIKey))
return mongoClient, openAIClient, nil
}
// GetClientsPasswordless creates MongoDB and Azure OpenAI clients with passwordless authentication
func GetClientsPasswordless() (*mongo.Client, openai.Client, error) {
ctx := context.Background()
// Get MongoDB cluster name
clusterName := os.Getenv("MONGO_CLUSTER_NAME")
if clusterName == "" {
return nil, openai.Client{}, fmt.Errorf("MONGO_CLUSTER_NAME environment variable is required")
}
// Create Azure credential
credential, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, openai.Client{}, fmt.Errorf("failed to create Azure credential: %v", err)
}
// Attempt OIDC authentication
mongoURI := fmt.Sprintf("mongodb+srv://%s.global.mongocluster.cosmos.azure.com/", clusterName)
fmt.Println("Attempting OIDC authentication...")
mongoClient, err := connectWithOIDC(ctx, mongoURI, credential)
if err != nil {
return nil, openai.Client{}, fmt.Errorf("OIDC authentication failed: %v", err)
}
fmt.Println("OIDC authentication successful!")
// Get Azure OpenAI endpoint
azureOpenAIEndpoint := os.Getenv("AZURE_OPENAI_EMBEDDING_ENDPOINT")
if azureOpenAIEndpoint == "" {
return nil, openai.Client{}, fmt.Errorf("AZURE_OPENAI_EMBEDDING_ENDPOINT environment variable is required")
}
// Create Azure OpenAI client with credential-based authentication
openAIClient := openai.NewClient(
option.WithBaseURL(fmt.Sprintf("%s/openai/v1", azureOpenAIEndpoint)),
azure.WithTokenCredential(credential))
return mongoClient, openAIClient, nil
}
// connectWithOIDC attempts to connect using OIDC authentication
func connectWithOIDC(ctx context.Context, mongoURI string, credential *azidentity.DefaultAzureCredential) (*mongo.Client, error) {
// Create OIDC machine callback using Azure credential
oidcCallback := func(ctx context.Context, args *options.OIDCArgs) (*options.OIDCCredential, error) {
scope := "https://ossrdbms-aad.database.windows.net/.default"
fmt.Printf("Getting token with scope: %s\n", scope)
token, err := credential.GetToken(ctx, policy.TokenRequestOptions{
Scopes: []string{scope},
})
if err != nil {
return nil, fmt.Errorf("failed to get token with scope %s: %v", scope, err)
}
fmt.Printf("Successfully obtained token")
return &options.OIDCCredential{
AccessToken: token.Token,
}, nil
}
// Set up MongoDB client options with OIDC authentication
clientOptions := options.Client().
ApplyURI(mongoURI).
SetConnectTimeout(30 * time.Second).
SetServerSelectionTimeout(30 * time.Second).
SetRetryWrites(true).
SetAuth(options.Credential{
AuthMechanism: "MONGODB-OIDC",
// For local development, don't set ENVIRONMENT=azure to allow custom callbacks
AuthMechanismProperties: map[string]string{
"TOKEN_RESOURCE": "https://ossrdbms-aad.database.windows.net",
},
OIDCMachineCallback: oidcCallback,
})
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return mongoClient, nil
}
// connectWithConnectionString attempts to connect using a connection string
func connectWithConnectionString(ctx context.Context, connectionString string) (*mongo.Client, error) {
clientOptions := options.Client().
ApplyURI(connectionString).
SetMaxPoolSize(50).
SetMinPoolSize(5).
SetMaxConnIdleTime(30 * time.Second).
SetServerSelectionTimeout(5 * time.Second).
SetSocketTimeout(20 * time.Second)
mongoClient, err := mongo.Connect(ctx, clientOptions)
if err != nil {
return nil, err
}
return mongoClient, nil
}
// ReadFileReturnJSON reads a JSON file and returns the data as a slice of maps
func ReadFileReturnJSON(filePath string) ([]map[string]interface{}, error) {
file, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading file '%s': %v", filePath, err)
}
var data []map[string]interface{}
err = json.Unmarshal(file, &data)
if err != nil {
return nil, fmt.Errorf("error parsing JSON in file '%s': %v", filePath, err)
}
return data, nil
}
// WriteFileJSON writes data to a JSON file
func WriteFileJSON(data []map[string]interface{}, filePath string) error {
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("error marshalling data to JSON: %v", err)
}
err = os.WriteFile(filePath, jsonData, 0644)
if err != nil {
return fmt.Errorf("error writing to file '%s': %v", filePath, err)
}
fmt.Printf("Data successfully written to '%s'\n", filePath)
return nil
}
// InsertData inserts data into a MongoDB collection in batches
func InsertData(ctx context.Context, collection *mongo.Collection, data []map[string]interface{}, batchSize int, indexFields []string) (*InsertStats, error) {
totalDocuments := len(data)
insertedCount := 0
failedCount := 0
fmt.Printf("Starting batch insertion of %d documents...\n", totalDocuments)
// Create indexes if specified
if len(indexFields) > 0 {
for _, field := range indexFields {
indexModel := mongo.IndexModel{
Keys: bson.D{{Key: field, Value: 1}},
}
_, err := collection.Indexes().CreateOne(ctx, indexModel)
if err != nil {
fmt.Printf("Warning: Could not create index on %s: %v\n", field, err)
} else {
fmt.Printf("Created index on field: %s\n", field)
}
}
}
// Process data in batches
for i := 0; i < totalDocuments; i += batchSize {
end := i + batchSize
if end > totalDocuments {
end = totalDocuments
}
batch := data[i:end]
batchNum := (i / batchSize) + 1
// Convert to []interface{} for MongoDB driver
documents := make([]interface{}, len(batch))
for j, doc := range batch {
documents[j] = doc
}
// Insert batch
result, err := collection.InsertMany(ctx, documents, options.InsertMany().SetOrdered(false))
if err != nil {
// Handle bulk write errors
if bulkErr, ok := err.(mongo.BulkWriteException); ok {
inserted := len(bulkErr.WriteErrors)
insertedCount += len(batch) - inserted
failedCount += inserted
fmt.Printf("Batch %d had errors: %d inserted, %d failed\n", batchNum, len(batch)-inserted, inserted)
// Print specific error details
for _, writeErr := range bulkErr.WriteErrors {
fmt.Printf(" Error: %s\n", writeErr.Message)
}
} else {
// Handle unexpected errors
failedCount += len(batch)
fmt.Printf("Batch %d failed completely: %v\n", batchNum, err)
}
} else {
insertedCount += len(result.InsertedIDs)
fmt.Printf("Batch %d completed: %d documents inserted\n", batchNum, len(result.InsertedIDs))
}
// Small delay between batches
time.Sleep(100 * time.Millisecond)
}
return &InsertStats{
Total: totalDocuments,
Inserted: insertedCount,
Failed: failedCount,
}, nil
}
// DropVectorIndexes drops existing vector indexes on the specified field
func DropVectorIndexes(ctx context.Context, collection *mongo.Collection, vectorField string) error {
// Get all indexes for the collection
cursor, err := collection.Indexes().List(ctx)
if err != nil {
return fmt.Errorf("could not list indexes: %v", err)
}
defer cursor.Close(ctx)
var vectorIndexes []string
for cursor.Next(ctx) {
var index bson.M
if err := cursor.Decode(&index); err != nil {
continue
}
// Check if this is a vector index on the specified field
if key, ok := index["key"].(bson.M); ok {
if indexType, exists := key[vectorField]; exists && indexType == "cosmosSearch" {
if name, ok := index["name"].(string); ok {
vectorIndexes = append(vectorIndexes, name)
}
}
}
}
// Drop each vector index found
for _, indexName := range vectorIndexes {
fmt.Printf("Dropping existing vector index: %s\n", indexName)
_, err := collection.Indexes().DropOne(ctx, indexName)
if err != nil {
fmt.Printf("Warning: Could not drop index %s: %v\n", indexName, err)
}
}
if len(vectorIndexes) > 0 {
fmt.Printf("Dropped %d existing vector index(es)\n", len(vectorIndexes))
} else {
fmt.Println("No existing vector indexes found to drop")
}
return nil
}
// PrintSearchResults prints search results in a formatted way
func PrintSearchResults(results []SearchResult, maxResults int, showScore bool) {
if len(results) == 0 {
fmt.Println("No search results found.")
return
}
if maxResults > len(results) {
maxResults = len(results)
}
fmt.Printf("\nSearch Results (showing top %d):\n", maxResults)
fmt.Println(strings.Repeat("=", 80))
for i := 0; i < maxResults; i++ {
result := results[i]
// Extract HotelName from document (assuming bson.D structure)
doc := result.Document.(bson.D)
var hotelName string
for _, elem := range doc {
if elem.Key == "HotelName" {
hotelName = fmt.Sprintf("%v", elem.Value)
break
}
}
// Display results
fmt.Printf("%d. HotelName: %s", i+1, hotelName)
if showScore {
fmt.Printf(", Score: %.4f", result.Score)
}
fmt.Println()
}
}
// GenerateEmbedding generates an embedding for the given text using Azure OpenAI
func GenerateEmbedding(ctx context.Context, client openai.Client, text, modelName string) ([]float64, error) {
resp, err := client.Embeddings.New(ctx, openai.EmbeddingNewParams{
Input: openai.EmbeddingNewParamsInputUnion{
OfString: openai.String(text),
},
Model: modelName,
})
if err != nil {
return nil, fmt.Errorf("failed to generate embedding: %v", err)
}
if len(resp.Data) == 0 {
return nil, fmt.Errorf("no embedding data received")
}
// Convert []float32 to []float64
embedding := make([]float64, len(resp.Data[0].Embedding))
for i, v := range resp.Data[0].Embedding {
embedding[i] = float64(v)
}
return embedding, nil
}
이 유틸리티 모듈은 다음과 같은 기능을 제공합니다.
-
Config: 환경 변수에 대한 구성 구조 -
SearchResult: 점수가 있는 검색 결과 문서의 구조 -
HotelData: 호텔 문서를 나타내는 구조 -
GetClients: Azure OpenAI 및 Azure DocumentDB에 대한 클라이언트를 만들고 반환합니다. -
GetClientsPasswordless: OIDC(암호 없는 인증)를 사용하여 클라이언트를 만들고 반환합니다. 두 리소스 각각에서 RBAC를 활성화하고 Azure CLI에 로그인하십시오. -
ReadFileReturnJSON: JSON 파일을 읽고 해당 내용을 맵 조각으로 반환합니다. -
WriteFileJSON: JSON 파일에 데이터를 씁니다. -
InsertData: MongoDB 컬렉션에 데이터를 일괄 처리로 삽입하고 지정된 필드에 표준 인덱스를 만듭니다. -
PrintSearchResults: 점수 및 호텔 이름을 포함하여 벡터 검색 결과를 출력합니다. -
GenerateEmbedding: Azure OpenAI를 사용하여 임베딩을 만듭니다.
Azure CLI를 사용하여 인증
Azure 리소스에 안전하게 액세스할 수 있도록 애플리케이션을 실행하기 전에 Azure CLI에 로그인합니다.
az login
이 코드는 로컬 개발자 인증을 사용하여 Azure DocumentDB 및 Azure OpenAI에 액세스합니다. 설정할 AZURE_TOKEN_CREDENTIALS=AzureCliCredential때 이 설정은 함수에 인증을 위해 Azure CLI 자격 증명을 결정적으로 사용하도록 지시합니다. 인증은 환경에서 Azure 자격 증명을 찾기 위해 azidentity의 DefaultAzureCredential을 사용합니다.
Azure ID 라이브러리를 사용하여 Azure 서비스에 Go 앱을 인증하는 방법에 대해 자세히 알아봅니다.
애플리케이션 빌드 및 실행
Go 애플리케이션을 빌드하고 실행합니다.
앱 로깅 및 출력은 다음을 보여줍니다.
- 컬렉션 만들기 및 데이터 삽입 상태
- 벡터 인덱스 만들기
- 호텔 이름 및 유사성 점수가 있는 검색 결과
Starting DiskANN vector search demonstration...
Initializing MongoDB and Azure OpenAI clients...
Attempting OIDC authentication...
OIDC authentication successful!
Loading data from ../data/Hotels_Vector.json...
Loaded 50 documents
Inserting data into collection 'hotels_diskann'...
Getting token with scope: https://ossrdbms-aad.database.windows.net/.default
Successfully obtained token
Starting batch insertion of 50 documents...
Batch 1 completed: 50 documents inserted
Insertion completed: 50 inserted, 0 failed
Creating DiskANN vector index on field 'DescriptionVector'...
No existing vector indexes found to drop
DiskANN vector index created successfully
Waiting for index to be ready...
Performing DiskANN vector search for: 'quintessential lodging near running trails, eateries, retail'
Search Results (showing top 5):
================================================================================
1. HotelName: Royal Cottage Resort, Score: 0.4991
2. HotelName: Country Comfort Inn, Score: 0.4785
3. HotelName: Nordick's Valley Motel, Score: 0.4635
4. HotelName: Economy Universe Motel, Score: 0.4461
5. HotelName: Roach Motel, Score: 0.4388
DiskANN demonstration completed successfully!
Visual Studio Code에서 데이터 보기 및 관리
Visual Studio Code에서 DocumentDB 확장을 선택하여 Azure DocumentDB 계정에 연결합니다.
Hotels 데이터베이스에서 데이터 및 인덱스를 봅니다.
자원을 정리하세요
추가 비용을 방지할 필요가 없는 경우 리소스 그룹, DocumentDB 계정 및 Azure OpenAI 리소스를 삭제합니다.