Partage via


Implémenter Azure OpenAI avec RAG à l’aide de la recherche vectorielle dans une application .NET

Ce tutoriel explore l’intégration du modèle RAG à l’aide de modèles Open AI et de fonctionnalités de recherche vectorielle dans une application .NET. L’exemple d’application effectue des recherches vectorielles sur des données personnalisées stockées dans Azure Cosmos DB pour MongoDB et affine davantage les réponses à l’aide de modèles IA génératifs, tels que GPT-35 et GPT-4. Dans les sections qui suivent, vous allez configurer un exemple d’application et explorer des exemples de code clés qui illustrent ces concepts.

Prerequisites

  • .NET 8.0
  • Un compte Azure
  • Un service Azure Cosmos DB pour MongoDB vCore
  • Un service Azure Open AI
    • Déployer le text-embedding-ada-002 modèle pour les incorporations
    • Déployer un gpt-35-turbo modèle pour les achèvements de conversation

Vue d’ensemble de l’application

L’application Cosmos Recipe Guide vous permet d’effectuer des recherches vectorielles et pilotées par l’IA sur un ensemble de données de recette. Vous pouvez rechercher directement des recettes disponibles ou inviter l’application avec des noms d’ingrédients pour trouver des recettes connexes. L’application et les sections à l’avance vous guident tout au long du flux de travail suivant pour illustrer ce type de fonctionnalité :

  1. Chargez des exemples de données dans une base de données Azure Cosmos DB pour MongoDB.

  2. Créez des incorporations et un index vectoriel pour les exemples de données chargés à l’aide du modèle Azure OpenAI text-embedding-ada-002 .

  3. Effectuez une recherche de similarité vectorielle en fonction des invites de l’utilisateur.

  4. Utilisez le modèle d’achèvement Azure OpenAI gpt-35-turbo pour composer des réponses plus significatives en fonction des données des résultats de la recherche.

    Capture d’écran montrant l’exemple d’application en cours d’exécution.

Get started

  1. Clonez le référentiel GitHub suivant :

    git clone https://github.com/microsoft/AzureDataRetrievalAugmentedGenerationSamples.git
    
  2. Dans le dossier C#/CosmosDB-MongoDBvCore , ouvrez le fichier CosmosRecipeGuide.sln .

  3. Dans le fichier appsettings.json , remplacez les valeurs de configuration suivantes par vos valeurs Azure OpenAI et Azure CosmosDB pour 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. Lancez l’application en appuyant sur le bouton Démarrer en haut de Visual Studio.

Explorer l’application

Lorsque vous exécutez l’application pour la première fois, elle se connecte à Azure Cosmos DB et signale qu’aucune recette n’est encore disponible. Suivez les étapes affichées par l’application pour commencer le flux de travail principal.

  1. Sélectionnez Charger des recettes dans Cosmos DB , puis appuyez sur Entrée. Cette commande lit des exemples de fichiers JSON à partir du projet local et les charge dans le compte Cosmos DB.

    Le code de la classe Utility.cs analyse les fichiers JSON locaux.

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

    La UpsertVectorAsync méthode du fichier VCoreMongoService.cs charge les documents dans Azure Cosmos DB pour 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. Sélectionnez Vectoriser les recettes et les stocker dans Cosmos DB.

    Les éléments JSON chargés dans Cosmos DB ne contiennent pas d’incorporations et ne sont donc pas optimisés pour RAG via la recherche vectorielle. Une incorporation est une représentation numérique dense d’informations de la signification sémantique d’un morceau de texte. Les recherches vectorielles sont en mesure de rechercher des éléments avec des incorporations contextuellement similaires.

    La GetEmbeddingsAsync méthode dans le fichier OpenAIService.cs crée une incorporation pour chaque élément de la base de données.

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

    Le CreateVectorIndexIfNotExists fichier VCoreMongoService.cs crée un index vectoriel, qui vous permet d’effectuer des recherches de similarité vectorielle.

    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. Sélectionnez l’option Assistant IA (recherchez une recette par nom ou description, ou posez une question) dans l’application pour exécuter une requête utilisateur.

    La requête utilisateur est convertie en incorporation à l’aide du service Open AI et du modèle d’incorporation. L’incorporation est ensuite envoyée à Azure Cosmos DB pour MongoDB et est utilisée pour effectuer une recherche vectorielle. La VectorSearchAsync méthode du fichier VCoreMongoService.cs effectue une recherche vectorielle pour rechercher des vecteurs proches du vecteur fourni et retourne une liste de documents d’Azure Cosmos DB pour 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;
            }
        }
    

    La méthode GetChatCompletionAsync génère une réponse de complétion de chat améliorée en fonction de la sollicitation de l'utilisateur et des résultats de recherche vectorielle associés.

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

    L’application utilise également l’ingénierie rapide pour garantir que les limites du service Open AI et met en forme la réponse pour les recettes fournies.

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