Rövid útmutató: Vektorkeresés Java használatával az Azure DocumentDB-ben

Ismerje meg, hogyan használhat vektorkeresést az Azure DocumentDB-ben a Java MongoDB-illesztőprogrammal a vektoradatok hatékony tárolásához és lekérdezéséhez.

Ez a rövid útmutató a fő vektorkeresési technikákat ismerteti egy Java-mintaalkalmazással a GitHubon.

Az alkalmazás egy szállodai mintaadatkészletet használ egy JSON-fájlban, amelyben előre kiszámított vektorok vannak a text-embedding-3-small modellből, de a vektorokat saját maga is létrehozhatja. A szálloda adatai közé tartoznak a szállodanevek, a helyek, a leírások és a vektoros beágyazások.

Prerequisites

  • Azure-előfizetés

Adatfájl létrehozása vektorokkal

  1. Hozzon létre egy új adatkönyvtárat a hotels adatfájlhoz:

    mkdir data
    
  2. Másolja a Hotels_Vector.jsonnyers adatfájlt vektorokkal a data könyvtárba.

Java-projekt létrehozása

  1. Hozzon létre egy új testvérkönyvtárat a projekthez az adatkönyvtárral megegyező szinten, és nyissa meg a Visual Studio Code-ban:

    mkdir vector-search-quickstart
    mkdir vector-search-quickstart/src
    code vector-search-quickstart
    
  2. Hozzon létre egy pom.xml fájlt a projekt gyökérkönyvtárában a következő tartalommal:

    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.azure.documentdb.samples</groupId>
        <artifactId>vector-search-quickstart</artifactId>
        <version>1.0-SNAPSHOT</version>
            
        <properties>
            <maven.compiler.release>21</maven.compiler.release>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
            
        <dependencies>
            <dependency>
                <groupId>org.mongodb</groupId>
                <artifactId>mongodb-driver-sync</artifactId>
                <version>5.6.2</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-identity</artifactId>
                <version>1.18.1</version>
            </dependency>
            <dependency>
                <groupId>com.azure</groupId>
                <artifactId>azure-ai-openai</artifactId>
                <version>1.0.0-beta.16</version>
            </dependency>
            <dependency>
                <groupId>tools.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>3.0.3</version>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-nop</artifactId>
                <version>2.0.17</version>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    </project>
    

    Az alkalmazás a következő Maven-függőségeket használja a pom.xml:

    • mongodb-driver-sync: Hivatalos MongoDB Java-illesztőprogram adatbázis-kapcsolathoz és műveletekhez
    • azure-identity: Azure Identity-kódtár jelszó nélküli hitelesítéshez a Microsoft Entra-azonosítóval
    • azure-ai-openai: Azure OpenAI-ügyfélkódtár AI-modellekkel való kommunikációhoz és vektoros beágyazások létrehozásához
    • jackson-databind: JSON szerializálási és deszerializálási kódtár
    • slf4j-nop: Művelet nélküli SLF4J-kötés a MongoDB-illesztőprogram naplózási kimenetének letiltásához
  3. Hozzon létre egy .env fájlt a projekt gyökerében környezeti változókhoz:

    # 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=
    EMBEDDING_SIZE_BATCH=16
    
    # Azure DocumentDB configuration
    MONGO_CLUSTER_NAME=
    
    # Data file
    DATA_FILE_WITH_VECTORS=../data/Hotels_Vector.json
    EMBEDDED_FIELD=DescriptionVector
    EMBEDDING_DIMENSIONS=1536
    LOAD_SIZE_BATCH=50
    

    Cserélje le a .env fájl helyőrzőit a saját adataival.

    • AZURE_OPENAI_EMBEDDING_ENDPOINT: Az Azure OpenAI-erőforrásvégpont URL-címe.
    • MONGO_CLUSTER_NAME: Az Azure DocumentDB-erőforrás neve.
  4. Töltse be a környezeti változókat:

    set -a && source .env && set +a
    
  5. A projektszerkezetnek így kell kinéznie:

    data
    └── Hotels_Vector.json
    vector-search-quickstart
    ├── .env
    ├── pom.xml
    └── src
    

Hozzon létre egy DiskAnn.java fájlt a src könyvtárban, és illessze be a következő kódba:

package com.azure.documentdb.samples;

import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.EmbeddingsOptions;
import com.azure.identity.DefaultAzureCredentialBuilder;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.AggregateIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Indexes;
import org.bson.Document;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Vector search sample using DiskANN index.
 */
public class DiskAnn {
    private static final String SAMPLE_QUERY = "quintessential lodging near running trails, eateries, retail";
    private static final String DATABASE_NAME = "Hotels";
    private static final String COLLECTION_NAME = "hotels_diskann";
    private static final String VECTOR_INDEX_NAME = "vectorIndex_diskann";

    private final JsonMapper jsonMapper = JsonMapper.builder().build();

    public static void main(String[] args) {
        new DiskAnn().run();
        System.exit(0);
    }

    public void run() {
        try (var mongoClient = createMongoClient()) {
            var openAIClient = createOpenAIClient();

            var database = mongoClient.getDatabase(DATABASE_NAME);
            var collection = database.getCollection(COLLECTION_NAME, Document.class);

            // Drop and recreate collection
            collection.drop();
            database.createCollection(COLLECTION_NAME);
            System.out.println("Created collection: " + COLLECTION_NAME);

            // Load and insert data
            var hotelData = loadHotelData();
            insertDataInBatches(collection, hotelData);

            // Create standard indexes
            createStandardIndexes(collection);

            // Create vector index
            createVectorIndex(database);

            // Perform vector search
            var queryEmbedding = createEmbedding(openAIClient, SAMPLE_QUERY);
            performVectorSearch(collection, queryEmbedding);

        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private MongoClient createMongoClient() {
        var clusterName = System.getenv("MONGO_CLUSTER_NAME");
        var managedIdentityPrincipalId = System.getenv("AZURE_MANAGED_IDENTITY_PRINCIPAL_ID");
        var azureCredential = new DefaultAzureCredentialBuilder().build();

        MongoCredential.OidcCallback callback = (MongoCredential.OidcCallbackContext context) -> {
            var token = azureCredential.getToken(
                new com.azure.core.credential.TokenRequestContext()
                    .addScopes("https://ossrdbms-aad.database.windows.net/.default")
            ).block();

            if (token == null) {
                throw new RuntimeException("Failed to obtain Azure AD token");
            }

            return new MongoCredential.OidcCallbackResult(token.getToken());
        };

        var credential = MongoCredential.createOidcCredential(null)
            .withMechanismProperty("OIDC_CALLBACK", callback);

        var connectionString = new ConnectionString(
            String.format("mongodb+srv://%s@%s.mongocluster.cosmos.azure.com/?authMechanism=MONGODB-OIDC&tls=true&retrywrites=false&maxIdleTimeMS=120000",
                managedIdentityPrincipalId, clusterName)
        );

        var settings = MongoClientSettings.builder()
            .applyConnectionString(connectionString)
            .credential(credential)
            .build();

        return MongoClients.create(settings);
    }

    private OpenAIClient createOpenAIClient() {
        var endpoint = System.getenv("AZURE_OPENAI_EMBEDDING_ENDPOINT");
        var credential = new DefaultAzureCredentialBuilder().build();

        return new OpenAIClientBuilder()
            .endpoint(endpoint)
            .credential(credential)
            .buildClient();
    }

    private List<Map<String, Object>> loadHotelData() throws IOException {
        var dataFile = System.getenv("DATA_FILE_WITH_VECTORS");
        var filePath = Path.of(dataFile);

        System.out.println("Reading JSON file from " + filePath.toAbsolutePath());
        var jsonContent = Files.readString(filePath);

        return jsonMapper.readValue(jsonContent, new TypeReference<List<Map<String, Object>>>() {});
    }

    private void insertDataInBatches(MongoCollection<Document> collection, List<Map<String, Object>> hotelData) {
        var batchSizeStr = System.getenv("LOAD_SIZE_BATCH");
        var batchSize = batchSizeStr != null ? Integer.parseInt(batchSizeStr) : 100;
        var batches = partitionList(hotelData, batchSize);

        System.out.println("Processing in batches of " + batchSize + "...");

        for (int i = 0; i < batches.size(); i++) {
            var batch = batches.get(i);
            var documents = batch.stream()
                .map(Document::new)
                .toList();

            collection.insertMany(documents);
            System.out.println("Batch " + (i + 1) + " complete: " + documents.size() + " inserted");
        }
    }

    private void createStandardIndexes(MongoCollection<Document> collection) {
        collection.createIndex(Indexes.ascending("HotelId"));
        collection.createIndex(Indexes.ascending("Category"));
        collection.createIndex(Indexes.ascending("Description"));
        collection.createIndex(Indexes.ascending("Description_fr"));
    }

    private void createVectorIndex(MongoDatabase database) {
        var embeddedField = System.getenv("EMBEDDED_FIELD");
        var dimensionsStr = System.getenv("EMBEDDING_DIMENSIONS");
        var dimensions = dimensionsStr != null ? Integer.parseInt(dimensionsStr) : 1536;

        var indexDefinition = new Document()
            .append("createIndexes", COLLECTION_NAME)
            .append("indexes", List.of(
                new Document()
                    .append("name", VECTOR_INDEX_NAME)
                    .append("key", new Document(embeddedField, "cosmosSearch"))
                    .append("cosmosSearchOptions", new Document()
                        .append("kind", "vector-diskann")
                        .append("dimensions", dimensions)
                        .append("similarity", "COS")
                        .append("maxDegree", 20)
                        .append("lBuild", 10)
                    )
            ));

        database.runCommand(indexDefinition);
        System.out.println("Created vector index: " + VECTOR_INDEX_NAME);
    }

    private List<Double> createEmbedding(OpenAIClient openAIClient, String text) {
        var model = System.getenv("AZURE_OPENAI_EMBEDDING_MODEL");
        var options = new EmbeddingsOptions(List.of(text));

        var response = openAIClient.getEmbeddings(model, options);
        return response.getData().get(0).getEmbedding().stream()
                .map(Float::doubleValue)
                .toList();
    }

    private void performVectorSearch(MongoCollection<Document> collection, List<Double> queryEmbedding) {
        var embeddedField = System.getenv("EMBEDDED_FIELD");

        var searchStage = new Document("$search", new Document()
            .append("cosmosSearch", new Document()
                .append("vector", queryEmbedding)
                .append("path", embeddedField)
                .append("k", 5)
            )
        );

        var projectStage = new Document("$project", new Document()
            .append("score", new Document("$meta", "searchScore"))
            .append("document", "$$ROOT")
        );

        var pipeline = List.of(searchStage, projectStage);

        System.out.println("\nVector search results for: \"" + SAMPLE_QUERY + "\"");

        AggregateIterable<Document> results = collection.aggregate(pipeline);
        var rank = 1;

        for (var result : results) {
            var document = result.get("document", Document.class);
            var hotelName = document.getString("HotelName");
            var score = result.getDouble("score");
            System.out.printf("%d. HotelName: %s, Score: %.4f%n", rank++, hotelName, score);
        }
    }

    private static <T> List<List<T>> partitionList(List<T> list, int batchSize) {
        var partitions = new ArrayList<List<T>>();
        for (int i = 0; i < list.size(); i += batchSize) {
            partitions.add(list.subList(i, Math.min(i + batchSize, list.size())));
        }
        return partitions;
    }
}

Ez a kód a következő feladatokat hajtja végre:

  • Jelszó nélküli kapcsolatot hoz létre az Azure DocumentDB-vel a MongoDB OIDC mechanizmussal DefaultAzureCredential
  • Azure OpenAI-ügyfél létrehozása beágyazások létrehozásához
  • Elveti és újra létrehozza a gyűjteményt, majd betölti a JSON-fájlból származó szállodai adatokat kötegekben
  • Szabványos indexeket és vektorindexeket hoz létre algoritmusspecifikus beállításokkal
  • Beágyazást hoz létre egy minta lekérdezéshez, és egy összesítő keresési folyamatot futtat
  • Az öt legjobb egyező hotelt nyomtatja ki hasonlósági pontszámmal

Autentikálj az Azure-hoz

Jelentkezzen be az Azure-ba az alkalmazás futtatása előtt, hogy biztonságosan hozzáférhessen az Azure-erőforrásokhoz.

Megjegyzés:

Győződjön meg arról, hogy a bejelentkezett felhasználói fiók rendelkezik a szükséges adatsík-szerepkörökkel mind az Azure DocumentDB-fiókon, mind az Azure OpenAI-erőforráson.

az login

A kód a helyi fejlesztői hitelesítést használja az Azure DocumentDB és az Azure OpenAI eléréséhez. Ha beállítja AZURE_TOKEN_CREDENTIALS=AzureCliCredential, ez a beállítás arra utasítja a függvényt, hogy determinisztikus módon használja az Azure CLI hitelesítő adatait a hitelesítéshez. A hitelesítés a DefaultAzureCredential-re a azure-identity-ből támaszkodik, hogy megtalálja az Azure-beli hitelesítő adatait a környezetben. További információ arról, hogyan lehet a Java-alkalmazásokat hitelesíteni az Azure szolgáltatásokhoz az Azure Identity könyvtár használatával.

Az alkalmazás létrehozása

Fordítsa le az alkalmazást

mvn clean compile

DiskANN (lemezalapú hozzávetőleges legközelebbi szomszéd) keresés elindítása:

mvn exec:java -Dexec.mainClass="com.azure.documentdb.samples.DiskAnn"

A DiskANN olyan nagy adathalmazokhoz van optimalizálva, amelyek nem férnek el a memóriában, hatékony lemezalapú tárolásra, valamint a sebesség és a pontosság megfelelő egyensúlyára.

A kimenet példája:

Created collection: hotels_diskann
Reading JSON file from /workspaces/documentdb-samples/ai/vector-search-java/../data/Hotels_Vector.json
Processing in batches of 50...
Batch 1 complete: 50 inserted
Created vector index: vectorIndex_diskann

Vector search results for: "quintessential lodging near running trails, eateries, retail"
1. HotelName: Royal Cottage Resort, Score: 0.4991
2. HotelName: Country Comfort Inn, Score: 0.4786
3. HotelName: Nordick's Valley Motel, Score: 0.4635
4. HotelName: Economy Universe Motel, Score: 0.4462
5. HotelName: Roach Motel, Score: 0.4389

Adatok megtekintése és kezelése a Visual Studio Code-ban

  1. Telepítse a Java DocumentDB bővítményét és bővítménycsomagját a Visual Studio Code-ban.

  2. Csatlakozzon Azure DocumentDB-fiókjához a DocumentDB bővítmény használatával.

  3. Az adatok és indexek megtekintése a Hotels adatbázisban.

    A DocumentDB-gyűjteményt megjelenítő DocumentDB-bővítmény képernyőképe.

Erőforrások tisztítása

Törölje az erőforráscsoportot, az Azure DocumentDB-fürtöt és az Azure OpenAI-erőforrást, ha már nincs rájuk szüksége a szükségtelen költségek elkerülése érdekében.