Freigeben über


Implementieren von Azure OpenAI mit RAG mithilfe der Vektorsuche in einer .NET-App

In diesem Lernprogramm wird die Integration des RAG-Musters mithilfe von Open AI-Modellen und Vektorsuchfunktionen in einer .NET-App beschrieben. Die Beispielanwendung führt Vektorsuchen nach benutzerdefinierten Daten aus, die in Azure Cosmos DB für MongoDB gespeichert sind, und verfeinert die Antworten mithilfe von generativen KI-Modellen wie GPT-35 und GPT-4 weiter. In den folgenden Abschnitten richten Sie eine Beispielanwendung ein und erkunden wichtige Codebeispiele, die diese Konzepte veranschaulichen.

Voraussetzungen

Apps-Übersicht

Mit der Cosmos Recipe Guide-App können Sie Vektor- und KI-gesteuerte Suchvorgänge anhand einer Reihe von Rezeptdaten durchführen. Sie können direkt nach verfügbaren Rezepten suchen oder die App mit Zutatennamen auffordern, verwandte Rezepte zu finden. Die App und die nachfolgenden Abschnitte führen Sie durch den folgenden Workflow, um diese Art von Funktionalität zu veranschaulichen:

  1. Laden Sie Beispieldaten in eine Azure Cosmos DB für MongoDB-Datenbank hoch.

  2. Erstellen Sie Einbettungen und einen Vektorindex für die hochgeladenen Beispieldaten mithilfe des Azure OpenAI-Modells text-embedding-ada-002 .

  3. Führen Sie die Vektorgleichheitssuche basierend auf den Eingabeaufforderungen des Benutzers aus.

  4. Verwenden Sie das Azure OpenAI-Abschlussmodell gpt-35-turbo , um aussagekräftigere Antworten basierend auf den Suchergebnissen zu verfassen.

    Screenshot der ausgeführten Beispiel-App.

Get started

  1. Klonen Sie das folgende GitHub-Repository:

    git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git
    
  2. Öffnen Sie im Ordner "C#/CosmosDB-MongoDBvCore " die CosmosRecipeGuide.sln Datei.

  3. Ersetzen Sie in der appsettings.json Datei die folgenden Konfigurationswerte durch Ihre Azure OpenAI- und Azure CosmosDB-Werte für MongoDb:

    "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. Starten Sie die App, indem Sie oben in Visual Studio die Schaltfläche "Start " drücken.

Erkunden der App

Wenn Sie die App zum ersten Mal ausführen, wird eine Verbindung mit Azure Cosmos DB hergestellt und berichtet, dass noch keine Rezepte verfügbar sind. Führen Sie die schritte aus, die von der App angezeigt werden, um den Kernworkflow zu starten.

  1. Wählen Sie "Rezepte hochladen" in Cosmos DB aus, und drücken Sie die EINGABETASTE. Mit diesem Befehl werden JSON-Beispieldateien aus dem lokalen Projekt gelesen und in das Cosmos DB-Konto hochgeladen.

    Der Code aus der Utility.cs-Klasse analysiert die lokalen JSON-Dateien.

    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;
    }
    

    Die UpsertVectorAsync Methode in der VCoreMongoService.cs Datei lädt die Dokumente für MongoDB in Azure Cosmos DB hoch.

    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. Wählen Sie Rezepte vektorisieren und in Cosmos DB speichern aus.

    Die in Cosmos DB hochgeladenen JSON-Elemente enthalten keine Einbettungen und sind daher nicht für RAG über die Vektorsuche optimiert. Eine Einbettung ist eine informationsdichte, numerische Darstellung der semantischen Bedeutung eines Textteils. Vektorsuchen können Elemente mit kontextbezogenen ähnlichen Einbettungen finden.

    Die GetEmbeddingsAsync Methode in der datei OpenAIService.cs erstellt eine Einbettung für jedes Element in der Datenbank.

    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;
        }
    }
    

    Die CreateVectorIndexIfNotExists in der datei VCoreMongoService.cs erstellt einen Vektorindex, mit dem Sie Vektorgleichheitssuchen durchführen können.

    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. Wählen Sie in der Anwendung die Option "KI-Assistent fragen" aus (suchen Sie nach einem Rezept nach Name oder Beschreibung, oder stellen Sie eine Frage), um eine Benutzerabfrage auszuführen.

    Die Benutzerabfrage wird mithilfe des Open AI-Diensts und des Einbettungsmodells in eine Einbettung konvertiert. Die Einbettung wird dann an Azure Cosmos DB für MongoDB gesendet und wird verwendet, um eine Vektorsuche durchzuführen. Die VectorSearchAsync Methode in der VCoreMongoService.cs-Datei führt eine Vektorsuche aus, um Vektoren zu finden, die dem angegebenen Vektor nahe sind, und gibt eine Liste von Dokumenten aus Azure Cosmos DB für MongoDB vCore zurück.

    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;
            }
        }
    

    Die GetChatCompletionAsync Methode generiert eine verbesserte Antwort auf den Chatabschluss basierend auf der Benutzeraufforderung und den zugehörigen Vektorsuchergebnissen.

    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;
        }
    }
    

    Die App verwendet auch Prompt-Engineering, um sicherzustellen, dass die Open-AI-Dienstbeschränkungen eingehalten werden und die Antwort für die bereitgestellten Rezepte formatiert wird.

    //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.";