Delen via


Azure OpenAI met RAG implementeren met behulp van vectorzoekopdrachten in een .NET-app

In deze zelfstudie wordt de integratie van het RAG-patroon verkend met behulp van Open AI-modellen en vectorzoekmogelijkheden in een .NET-app. De voorbeeldtoepassing voert vectorzoekopdrachten uit op aangepaste gegevens die zijn opgeslagen in Azure Cosmos DB voor MongoDB en verfijnt de antwoorden verder met behulp van generatieve AI-modellen, zoals GPT-35 en GPT-4. In de volgende secties gaat u een voorbeeldtoepassing instellen en belangrijke codevoorbeelden verkennen die deze concepten demonstreren.

Vereiste voorwaarden

App-overzicht

Met de Cosmos Recipe Guide-app kunt u vector- en AI-zoekopdrachten uitvoeren op basis van een set receptgegevens. U kunt rechtstreeks zoeken naar beschikbare recepten of de app vragen om namen van ingrediënten om gerelateerde recepten te vinden. De app en de volgende secties begeleiden u door de volgende werkstroom om dit type functionaliteit te demonstreren:

  1. Voorbeeldgegevens uploaden naar een Azure Cosmos DB voor MongoDB-database.

  2. Maak insluitingen en een vectorindex voor de geüploade voorbeeldgegevens met behulp van het Azure OpenAI-model text-embedding-ada-002 .

  3. Voer vector-overeenkomsten zoeken op basis van de gebruikersprompts.

  4. Gebruik het azure OpenAI-voltooiingsmodel gpt-35-turbo om zinvollere antwoorden op te stellen op basis van de gegevens van de zoekresultaten.

    Een schermopname van de actieve voorbeeld-app.

Get started

  1. Kloon de volgende GitHub-opslagplaats:

    git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git
    
  2. Open in de map C#/CosmosDB-MongoDBvCore het CosmosRecipeGuide.sln bestand.

  3. Vervang in het bestandappsettings.json de volgende configuratiewaarden door uw Azure OpenAI- en Azure CosmosDB voor MongoDb-waarden:

    "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. Start de app door boven aan Visual Studio op de knop Start te drukken.

De app verkennen

Wanneer u de app voor het eerst uitvoert, maakt deze verbinding met Azure Cosmos DB en rapporteert dat er nog geen recepten beschikbaar zijn. Volg de stappen die door de app worden weergegeven om de kernwerkstroom te starten.

  1. Selecteer Recept(en) uploaden naar Cosmos DB en druk op Enter. Met deze opdracht worden voorbeeld-JSON-bestanden uit het lokale project gelezen en geüpload naar het Cosmos DB-account.

    De code uit de Utility.cs klasse parseert de lokale JSON-bestanden.

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

    Met UpsertVectorAsync de methode in het VCoreMongoService.cs-bestand worden de documenten geüpload naar Azure Cosmos DB voor MongoDB.

    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. Selecteer Vectoriseer de recepten en sla deze op in Cosmos DB.

    De JSON-items die zijn geüpload naar Cosmos DB bevatten geen insluitingen en zijn daarom niet geoptimaliseerd voor RAG via vectorzoekopdrachten. Een insluiting is een informatiedichte, numerieke weergave van de semantische betekenis van een stuk tekst. Vectorzoekopdrachten kunnen items vinden met contextuele vergelijkbare insluitingen.

    De GetEmbeddingsAsync methode in het bestand OpenAIService.cs maakt een insluiting voor elk item in de database.

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

    Het CreateVectorIndexIfNotExistsbestand in het VCoreMongoService.cs maakt een vectorindex, waarmee u vector-overeenkomsten kunt uitvoeren.

    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. Selecteer de AI-assistent Vragen (zoek naar een recept op naam of beschrijving, of stel een vraag) in de toepassing om een vraag van een gebruiker uit te voeren.

    De gebruikersquery wordt geconverteerd naar een insluiting met behulp van de Open AI-service en het insluitmodel. Het insluiten wordt vervolgens verzonden naar Azure Cosmos DB voor MongoDB en wordt gebruikt om een vectorzoekopdracht uit te voeren. De VectorSearchAsync methode in het bestand VCoreMongoService.cs voert een vectorzoekactie uit om vectoren te vinden die zich dicht bij de opgegeven vector bevinden en retourneert een lijst met documenten van Azure Cosmos DB voor MongoDB vCore.

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

    De GetChatCompletionAsync methode genereert een verbeterd antwoord op het voltooien van chats op basis van de gebruikersprompt en de bijbehorende vectorzoekresultaten.

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

    De app maakt ook gebruik van prompt-engineering om ervoor te zorgen dat de limieten van de Open AI-service worden nageleefd en dat het antwoord op opgegeven recepten correct wordt opgemaakt.

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