Az Azure OpenAI implementálása RAG használatával vektoros kereséssel egy .NET-alkalmazásban

Ez az oktatóanyag a RAG-minta integrálását mutatja be OpenAI-modellek és vektorkeresési képességek használatával egy .NET-alkalmazásban. A mintaalkalmazás vektorkereséseket végez a MongoDB-hez készült Azure Cosmos DB-ben tárolt egyéni adatokon, és tovább finomítja a válaszokat generatív AI-modellek, például a gpt-5 használatával. Az alábbi szakaszokban beállít egy mintaalkalmazást, és megismeri az ezeket a fogalmakat szemléltető kulcskód-példákat.

Előfeltételek

Alkalmazás áttekintése

A Cosmos Receptkalauz alkalmazással vektoros és AI-alapú kereséseket hajthat végre receptadatok halmazán. Kereshet közvetlenül az elérhető receptek között, vagy megadhatja az alkalmazásnak az összetevők nevét, hogy kapcsolódó recepteket találjon. Az alkalmazás és az előtte álló szakaszok végigvezetik az alábbi munkafolyamaton az ilyen típusú funkciók bemutatásához:

  1. Mintaadatok feltöltése egy MongoDB-adatbázishoz készült Azure Cosmos DB-adatbázisba.

  2. Beágyazásokat és vektorindexet hozhat létre a feltöltött mintaadatokhoz az Azure OpenAI-modell text-embedding-3-small használatával.

  3. A felhasználói kérések alapján végezzen vektoros hasonlósági keresést.

  4. Az Azure OpenAI-kiegészítési gpt-35-turbo modell használatával értelmesebb válaszokat írhat a keresési eredmények adatai alapján.

    Képernyőkép a futó mintaalkalmazásról.

Első lépések

  1. Klónozza a következő GitHub-adattárat:

    git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git
    
  2. A C#/CosmosDB-MongoDBvCore mappában nyissa meg a CosmosRecipeGuide.sln fájlt.

  3. A appsettings.json fájlban cserélje le a következő konfigurációs értékeket az Azure OpenAI és az Azure Cosmos DB for MongoDB értékeire:

    "OpenAIEndpoint": "https://<your-service-name>.openai.azure.com/",
    "OpenAIKey": "<your-API-key>",
    "OpenAIEmbeddingDeployment": "<your-ADA-deployment-name>",
    "OpenAIcompletionsDeployment": "<your-GPT-deployment-name>",
    "MongoVcoreConnection": "<your-Mongo-connection-string>"
    
  4. Indítsa el az alkalmazást a Visual Studio tetején található Start gombra kattintva.

Az alkalmazás felfedezése

Amikor első alkalommal futtatja az alkalmazást, az csatlakozik az Azure Cosmos DB-hez, és arról számol be, hogy még nem állnak rendelkezésre receptek. Az alapvető munkafolyamat elindításához kövesse az alkalmazás által megjelenített lépéseket.

  1. Válassza a Recept feltöltése a Cosmos DB-be lehetőséget, és nyomja le az Enter billentyűt. Ez a parancs beolvassa a minta JSON-fájlokat a helyi projektből, és feltölti őket a Cosmos DB-fiókba.

    A Utility.cs osztály kódja elemzi a helyi JSON-fájlokat.

    public static List<Recipe> ParseDocuments(string Folderpath)
    {
        List<Recipe> recipes = new List<Recipe>();
    
        Directory.GetFiles(Folderpath)
            .ToList()
            .ForEach(f =>
            {
                var jsonString= System.IO.File.ReadAllText(f);
                Recipe recipe = JsonConvert.DeserializeObject<Recipe>(jsonString);
                recipe.id = recipe.name.ToLower().Replace(" ", "");
                recipes.Add(recipe);
            }
        );
    
        return recipes;
    }
    

    A UpsertVectorAsyncVCoreMongoService.cs fájl metódusa feltölti a dokumentumokat a MongoDB-hez készült Azure Cosmos DB-be.

    public async Task UpsertVectorAsync(Recipe recipe)
        {
            BsonDocument document = recipe.ToBsonDocument();
    
            if (!document.Contains("_id"))
            {
                Console.WriteLine("UpsertVectorAsync: Document does not contain _id.");
                throw new ArgumentException("UpsertVectorAsync: Document does not contain _id.");
            }
    
            string? _idValue = document["_id"].ToString();
    
            try
            {
                var filter = Builders<BsonDocument>.Filter.Eq("_id", _idValue);
                var options = new ReplaceOptions { IsUpsert = true };
                await _recipeCollection.ReplaceOneAsync(filter, document, options);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: UpsertVectorAsync(): {ex.Message}");
                throw;
            }
        }
    
  2. Válassza a Receptek vektorizálása lehetőséget, és tárolja a Cosmos DB-ben.

    A Cosmos DB-be feltöltött JSON-elemek nem tartalmaznak beágyazásokat, ezért nem optimalizálhatók RAG-ra vektorkereséssel. A beágyazás egy szöveg szemantikai jelentésének információdús, numerikus ábrázolása. A vektorkeresések környezetfüggően hasonló beágyazású elemeket találhatnak.

    A GetEmbeddingsAsyncOpenAIService.cs fájlban lévő metódus beágyazást hoz létre az adatbázis minden eleméhez.

    public async Task<float[]?> GetEmbeddingsAsync(dynamic data)
    {
        try
        {
            EmbeddingsOptions options = new EmbeddingsOptions(data)
            {
                Input = data
            };
    
            var response = await _openAIClient.GetEmbeddingsAsync(openAIEmbeddingDeployment, options);
    
            Embeddings embeddings = response.Value;
            float[] embedding = embeddings.Data[0].Embedding.ToArray();
    
            return embedding;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"GetEmbeddingsAsync Exception: {ex.Message}");
            return null;
        }
    }
    

    A CreateVectorIndexIfNotExistsVCoreMongoService.cs fájlban létrehoz egy vektorindexet, amellyel vektoros hasonlósági kereséseket hajthat végre.

    public void CreateVectorIndexIfNotExists(string vectorIndexName)
    {
        try
        {
            //Find if vector index exists in vectors collection
            using (IAsyncCursor<BsonDocument> indexCursor = _recipeCollection.Indexes.List())
            {
                bool vectorIndexExists = indexCursor.ToList().Any(x => x["name"] == vectorIndexName);
                if (!vectorIndexExists)
                {
                    BsonDocumentCommand<BsonDocument> command = new BsonDocumentCommand<BsonDocument>(
                        BsonDocument.Parse(@"
                            { createIndexes: 'Recipe',
                                indexes: [{
                                name: 'vectorSearchIndex',
                                key: { embedding: 'cosmosSearch' },
                                cosmosSearchOptions: {
                                    kind: 'vector-ivf',
                                    numLists: 5,
                                    similarity: 'COS',
                                    dimensions: 1536 }
                                }]
                            }"));
    
                    BsonDocument result = _database.RunCommand(command);
                    if (result["ok"] != 1)
                    {
                        Console.WriteLine("CreateIndex failed with response: " + result.ToJson());
                    }
                }
            }
        }
        catch (MongoException ex)
        {
            Console.WriteLine("MongoDbService InitializeVectorIndex: " + ex.Message);
            throw;
        }
    }
    
  3. Felhasználói lekérdezés futtatásához válassza az Ask AI Assistant (recept keresése név vagy leírás alapján, vagy kérdés feltevése) lehetőséget az alkalmazásban.

    Az alkalmazás beágyazássá alakítja a felhasználói lekérdezést az OpenAI szolgáltatás és a beágyazási modell használatával, majd elküldi a beágyazást a MongoDB-hez készült Azure Cosmos DB-nek vektorkeresés végrehajtásához. A SearchAsyncVCoreMongoService.cs fájl metódusa vektorkeresést végez a megadott vektorhoz közeli vektorok kereséséhez, és visszaadja a MongoDB virtuális maghoz készült Azure Cosmos DB-ből származó dokumentumok listáját.

    public async Task<List<Recipe>> VectorSearchAsync(float[] queryVector)
        {
            List<string> retDocs = new List<string>();
            string resultDocuments = string.Empty;
    
            try
            {
                //Search Azure Cosmos DB for MongoDB vCore collection for similar embeddings
                //Project the fields that are needed
                BsonDocument[] pipeline = new BsonDocument[]
                {
                    BsonDocument.Parse(
                        @$"{{$search: {{
                                cosmosSearch:
                                    {{ vector: [{string.Join(',', queryVector)}],
                                       path: 'embedding',
                                       k: {_maxVectorSearchResults}}},
                                       returnStoredSource:true
                                    }}
                                }}"),
                    BsonDocument.Parse($"{{$project: {{embedding: 0}}}}"),
                };
    
                var bsonDocuments = await _recipeCollection
                    .Aggregate<BsonDocument>(pipeline).ToListAsync();
    
                var recipes = bsonDocuments
                    .ToList()
                    .ConvertAll(bsonDocument =>
                        BsonSerializer.Deserialize<Recipe>(bsonDocument));
                return recipes;
            }
            catch (MongoException ex)
            {
                Console.WriteLine($"Exception: VectorSearchAsync(): {ex.Message}");
                throw;
            }
        }
    

    A GetChatCompletionAsync metódus továbbfejlesztett csevegés-befejezési választ hoz létre a felhasználói kérés és a kapcsolódó vektorkeresési eredmények alapján.

    public async Task<(string response, int promptTokens, int responseTokens)> GetChatCompletionAsync(string userPrompt, string documents)
    {
        try
        {
            ChatMessage systemMessage = new ChatMessage(
                ChatRole.System, _systemPromptRecipeAssistant + documents);
            ChatMessage userMessage = new ChatMessage(
                ChatRole.User, userPrompt);
    
            ChatCompletionsOptions options = new()
            {
                Messages =
                {
                    systemMessage,
                    userMessage
                },
                MaxTokens = openAIMaxTokens,
                Temperature = 0.5f, //0.3f,
                NucleusSamplingFactor = 0.95f,
                FrequencyPenalty = 0,
                PresencePenalty = 0
            };
    
            Azure.Response<ChatCompletions> completionsResponse =
                await openAIClient.GetChatCompletionsAsync(openAICompletionDeployment, options);
            ChatCompletions completions = completionsResponse.Value;
    
            return (
                response: completions.Choices[0].Message.Content,
                promptTokens: completions.Usage.PromptTokens,
                responseTokens: completions.Usage.CompletionTokens
            );
    
        }
        catch (Exception ex)
        {
            string message = $"OpenAIService.GetChatCompletionAsync(): {ex.Message}";
            Console.WriteLine(message);
            throw;
        }
    }
    

    Az alkalmazás parancssori tervezést is használ az OpenAI szolgáltatás korlátainak biztosítására, és formázja a megadott receptekre adott választ.

    //System prompts to send with user prompts to instruct the model for chat session
    private readonly string _systemPromptRecipeAssistant = @"
        You are an intelligent assistant for Contoso Recipes.
        You are designed to provide helpful answers to user questions about
        recipes, cooking instructions provided in JSON format below.
    
        Instructions:
        - Only answer questions related to the recipe provided below.
        - Don't reference any recipe not provided below.
        - If you're unsure of an answer, say ""I don't know"" and recommend users search themselves.
        - Your response  should be complete.
        - List the Name of the Recipe at the start of your response followed by step by step cooking instructions.
        - Assume the user is not an expert in cooking.
        - Format the content so that it can be printed to the Command Line console.
        - In case there is more than one recipe you find, let the user pick the most appropriate recipe.";