Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Idiomaschevron-right
C#chevron-right

Building an AI Agent With Semantic Kernel, C#, OpenAI, and MongoDB Atlas

Luce Carter13 min read • Published Nov 28, 2024 • Updated Dec 02, 2024
.NETIAC#
APLICATIVO COMPLETO
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Gen AI continues to take off and AI agents are no different. More and more developers are being asked to develop solutions that integrate AI agents into the user experience. This helps businesses reduce costs by attempting to solve many user questions or requests without the need for human interaction.
With winter on its way, there is no better time to think about comforting food on those cold nights. For this reason, in this tutorial, we are going to create an AI agent using Microsoft’s Semantic Kernel for C#, OpenAI, and MongoDB Atlas to help you decide whether you have the ingredients to cook, or whether you should just head to a cozy restaurant that is recommended and let someone else do the cooking!
Nosso agente será composto por algumas partes:
  1. A plugin for retrieving a list of available ingredients from a text file
  2. A prompt that uses AI to return what the cuisine of the given meal is
  3. Another plugin for searching a collection in our MongoDB Atlas cluster
Para explicar brevemente,plugin um plugin -in é um código personalizado marcado como disponível para AI a IA e que pode ser chamado para obter alguma funcionalidade a partir do código, como API chamar uma API ou interagir com outros serviços.
Um prompt também é personalizado, mas cria texto, geralmente com entradas dinâmicas, que é então usado como entrada para AI que a IA execute a tarefa com base na entrada.
Microsoft Learn has a fantastic course on Semantic Kernel, if you want to learn more about both.
If you would like to learn more about integrating retrieval-augmented generation (RAG) with Semantic Kernel, you can learn to build a movie recommendation bot that uses MongoDB Atlas to store the data and vector embeddings and uses Atlas Vector Search under the hood via a Semantic Kernel connector to search the data.
Mas antes de nos distraírmos pela faminta, vamos começar, para que tenhamos nosso agente pronto a tempo do restaurante!

Pré-requisitos

Você precisará de algumas coisas para acompanhar este tutorial:
  • Atlas M0 cluster deployed with the full sample dataset loaded
  • OpenAI account and API key
  • .NET 9 SDK
  • The starter code that can be found on the “start” branch on GitHub

Explorando o repositório inicial

To save time with some of the code and files, the starter repo comes with some things already available out of the box.
Lista de arquivos disponíveis no projeto na ramificação inicial do repositório
  • Inside of Data/cupboardinventory.txt is a list of ingredients that you might find in a cupboard or fridge. You can always make changes to this if you wish to add or remove ingredients, and these can be case-insensitive. We will use this to simulate what ingredients are available.
  • Restaurants.cs within Models has properties we care about for a restaurant. As part of the MongoDB connector available for Semantic Kernel (which is already added to the project), we have access to the MongoDB C# Driver under the hood. This means we can take advantage of being able to represent our documents as simple classes.
  • Program.cs has a method already implemented inside it called GenerateEmbeddingsForCuisine(). This is because we want to generate embeddings for the cuisine field for documents available from the sample dataset so they are available to the application later on. We don’t need to create embeddings for every document, though. We just need a good sample size so it is set to fetch 1000 documents.
If you want to understand more about how this method works, the section on adding documents to the memory store in the Semantic Kernel and RAG article goes into it in more detail.

Primeiros passos

O código inicial que estamos usando é um .NET aplicação de console .NET tradicional usando .NET 9.NET. Embora possamos Go ir em frente e adicionarappsettings arquivos “” e configurá-los em nossa Program.cs classe , isso é excessivo para uma demonstração simples. Então, vamos aproveitar as variáveis de ambiente.
Antes de começarmos a usar nossas variáveis de ambiente no código, vamos salvá-las. Execute cada um dos seguintes comandos (dependendo do seu sistema operacional (sistema operacional)), um a um, na linha de comando de sua escolha:
1export OPENAI_API_KEY=”<REPLACE WITH YOUR OPEN AI API KEY>” # MacOS/Linux
2set OPENAI_API_KEY=”<REPLACE WITH YOUR OPEN AI API KEY>” # Windows”
3
4export OPENAI_MODEL_NAME=”<REPLACE WITH YOUR MODEL OF CHOICE>”
5set OPENAI_MODEL_NAME_”<REPLACE WITH YOUR MODEL OF CHOICE>”
6
7export MONGODB_ATLAS_CONNECTION_STRING=”<YOUR MONGODB ATLAS CONNECTION STRING>”
8set MONGODB_ATLAS_CONNECTION_STRING=”<YOUR MONGODB ATLAS CONNECTION STRING>”
Now, we can add the following code within Program.cs, below the using statements but before the method definition, to fetch these environment variables:
1string apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new ArgumentNullException("Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")");
2string modelName = Environment.GetEnvironmentVariable("OPENAI_MODEL_NAME") ?? "gpt-4o-mini";
Isto verifica a presença desses valores e lança uma exceção ou define um padrão. Gpt-4o-mini é um modelo perfeitamente aceitável para o nosso caso de uso.
We also want to call the existing method to generate the embeddings for our sample data. Before we can do that, however, we need to update it to use the API key we now have available as a variable.
Inside the method, update the call to use Open AI for text embedding generation.
1memoryBuilder.WithOpenAITextEmbeddingGeneration(
2 "text-embedding-3-small",
3 "<YOUR OPENAI APIKEY>"
4 );
Replace the string for your OpenAI API key with the variable apiKey.
Now, we can add a call to the method earlier in the file, after we set up the variables from environment variables:
1await GenerateEmbeddingsForCuisine();
Pode levar alguns minutos para gerar as incorporações, então agora é um bom momento para executar o aplicação pela primeira vez:
1dotnet run
You can always continue on with the tutorial while you wait, or make a coffee. Once the new embedded_cuisines collection has between 900 and 1000 documents (or around the number you selected if you chose to change the limit in the method), you can stop the application and delete or comment out the call to the method.

Configurando o Construtor de Kernel Semantic

Agora que temos nossos dados de amostra com cozinhas incorporadas disponíveis, é hora de começar a configurar o Semantic Kernel para que possamos começar a disponibilizar as ferramentas para atingir nosso objetivo relacionado à alimentação ao AI agente de IA nas seções posteriores.
1var builder = Kernel.CreateBuilder();
2
3builder.Services.AddOpenAIChatCompletion(
4 modelName,
5 apiKey);
6
7var kernel = builder.Build();
Now, the kernel instance is configured and ready to go. We can start to build the first tool for our AI to opt to use: the ingredients plugin.

Creating the ingredients plugin

As mentioned earlier in this tutorial, we have a list of ingredients available in a .txt file inside the data folder that we can use to simulate fetching the ingredients from an API (if you have a techy smart fridge, for example).
Portanto, o primeiro plugin que vamos escrever é um que obtém todos os componentes desse arquivo. O agente pode então usar isso para obter todos os componentes disponíveis antes de decidir se estão faltando algum componente necessário para preparar a comida escolhida.
  1. Na raiz do projeto, adicione uma nova pasta chamada Plugins.
  2. Crie uma nova classe dentro dessa pasta chamada IngredientsPlugin.
  3. Cole o seguinte código dentro da classe:
1[KernelFunction, Description("Get a list of available ingredients")]
2 public static string GetIngredientsFromCupboard()
3 {
4 // Ensures that the file path functions across all operating systems.
5 string baseDir = AppContext.BaseDirectory;
6 string projectRoot = Path.Combine(baseDir, "..", "..", "..");
7 string filePath = Path.Combine(projectRoot, "Data", "cupboardinventory.txt");
8 return File.ReadAllText(filePath).ToLower();
9 }
Observação: se o seu editor de texto não adicionar automaticamente as declarações de uso, adicione o seguinte no topo do arquivo:
1using System.ComponentModel;
2using Microsoft.SemanticKernel;
Here we have a simple method defined called GetIngredientsFromCupboard.
It is annotated with this KernelFunction definition with a Description property. This tells the AI that this method is available and also what it is for. This is used to help it decide when and if to call this method to achieve a task.
O código dentro do método é um código C# bastante comum para ler um arquivo. O resultado de Directory.GetCurrentDirectory() é diferente dependendo do sistema operacional e de onde o aplicação está sendo executado. Portanto, a maior parte desse código é apenas para obter o caminho do arquivo de forma independente do sistema operacional.
O que considero inteligentes é que o método retorna o conteúdo do arquivo (em letras minúsculas para consistência) e é assim que a IA AI tem acesso a ele, combinando C# código C# básico com o conhecimento de que a função existe!
Agora precisamos disponibilizar o plugin -in para usarmos mais tarde, quando criarmos um prompt para o que queremos alcançar.
Portanto, após a última chamada para var kernel = builder.Build();, adicione o seguinte para importar o plugin-in:
1kernel.ImportPluginFromType<IngredientsPlugin>();

Creating the GetCuisine prompt

Agora é hora de fazer o prompt GetCuisine. Precisamos de uma maneira de fazer AI com que a IA nos informe qual é a preparação da comida, então é aqui que entra a criação de uma solicitação.
There are two ways of creating a prompt: via two files (a json config file and a prompt txt file) or with a YAML file. I find YAML easy to get wrong with its indenting approach. So we are going to use the former approach.
  1. Crie uma Prompts pasta chamada na raiz do projeto.
  2. Crie uma pasta dentro dessa pasta chamada GetCuisine.
  3. Crie um novo arquivo chamado config.json e insira o seguinte JSON:
1{
2 "schema": 1,
3 "type": "completion",
4 "description": "Identify the cuisine of the user's requested meal",
5 "execution_settings": {
6 "default": {
7 "max_tokens": 800,
8 "temperature": 0
9 }
10 },
11 "input_variables": [
12 {
13 "name": "cuisine",
14 "description": "Text from the user that contains their requested meal",
15 "required": true
16 }
17 ]
18}
This specifies the configuration for our prompt and specifies what it does—that is for chat completion, it should have 0 creativity (temperature: 0), aka be specific, and there will be an input variable available called cuisine which will contain the requested meal. Because input_variables is an array, you can specify multiple input variables if needed here as well.
Crie outro arquivo na pasta chamado,skprompt.txt que é o que criará dinamicamente o texto para que a IA AI entenda o que está sendo solicitado dela. Em seguida, adicione o seguinte:
1Return a single word that represents the cuisine of the requested meal that is sent: {{$cuisine}}.
2
3For example, if the meal is mac and cheese, return American. Or for pizza it would be Italian.
4Do not return any extra words, just return the single name of the cuisine.
This is an example of generating a prompt statement and making use of good prompt engineering to shape how well the AI understands and responds. The {{$cuisine}} is how you dynamically populate the prompt with values. This has to start with a $ sign, be inside the double curly brackets, and match the name of an input variable declared in the array inside the config.json file.
A maneira de disponibilizar prompts para a IA AI como um plugin plugin -in é ligeiramente diferente em comparação com os plug-ins.
Após a chamada para importar o ResultsPlugin, adicione o seguinte:
1string baseDir = AppContext.BaseDirectory;
2string projectRoot = Path.Combine(baseDir, "..", "..", "..");
3var plugins = kernel.CreatePluginFromPromptDirectory(projectRoot + "/Prompts");
Semantic Kernel is clever and can find all prompts available within the prompts directory. It will then be available later in an array of plugin names (named after the folder, in our case GetCuisine).

Creating the restaurants plugin

Por último, queremos criar outro plugin, desta vez para restaurantes e interagir com nosso cluster MongoDB Atlas .
  1. Crie uma classe dentro da pasta Plugins chamada RestaurantsPlugin.
  2. Adicione as seguintes declarações de uso e a declaração de namespace :
1using FoodAgentDotNet.Models;
2using Microsoft.SemanticKernel;
3using Microsoft.SemanticKernel.Connectors.MongoDB;
4using Microsoft.SemanticKernel.Connectors.OpenAI;
5using Microsoft.SemanticKernel.Memory;
6using MongoDB.Driver;
7using System;
8using System.Collections.Generic;
9using System.ComponentModel;
10using System.Linq;
11using System.Text;
12using System.Threading.Tasks;
13
14namespace FoodAgentDotNet.Plugins;
Em seguida, substitua o restante da classe pelo seguinte:
1#pragma warning disable
2public class RestaurantsPlugin
3{
4 static readonly string mongoDBConnectionString = Environment.GetEnvironmentVariable("MONGODB_ATLAS_CONNECTION_STRING");
5 static readonly string openAIApiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
6
7 [KernelFunction, Description("Find a restaurant to eat at")]
8 public static async Task<List<Restaurant>> GetRecommendedRestaurant(
9 [Description("The cuisine to find a restaurant for")] string cuisine)
10 {
11 var mongoDBMemoryStore = new MongoDBMemoryStore(mongoDBConnectionString, "sample_restaurants", "restaurants_index");
12 var memoryBuilder = new MemoryBuilder();
13 memoryBuilder.WithOpenAITextEmbeddingGeneration(
14 "text-embedding-3-small",
15 openAIApiKey );
16 memoryBuilder.WithMemoryStore(mongoDBMemoryStore);
17 var memory = memoryBuilder.Build();
18
19 var restaurants = memory.SearchAsync(
20 "embedded_cuisines",
21 cuisine,
22 limit: 5,
23 minRelevanceScore: 0.5
24 );
25
26 List<Restaurant> recommendedRestaurants = new();
27
28 await foreach(var restaurant in restaurants)
29 {
30 recommendedRestaurants.Add(new Restaurant
31 {
32 Name = restaurant.Metadata.Description,
33 // We include the cuisine so that the AI has this information available to it
34 Cuisine = restaurant.Metadata.AdditionalMetadata,
35 });
36 }
37 return recommendedRestaurants;
38 }
39}
Há muito aqui, então vamos dar uma olhada.
Some of these features are still considered experimental so warnings are disabled.
Just like with the ingredients plugin from earlier, we add the attribute to the method to mark it as a KernelFunction. However, this time, we also pass in an argument for the cuisine so we add an additional description attribute to describe to the AI what the argument is there for.
Next, we build up the memory store and configure MongoDB as our memory store. We also set up the OpenAI text embedding again as it will need to generate embeddings for the cuisine passed in to use in the vector search.
We then bring those pieces together to explicitly search our embedded_cuisines collection for up to five restaurants that might suit the requested cuisine.
We build up a list of recommended restaurants, assigning the values we care about before returning that list so the AI has it available.
Agora precisamos retornar a Program.cs brevemente para adicionar nosso novo plugin-in. Após a chamada anterior para adicionar o ClusterPlugin, adicione o seguinte para adicionar também o RestaurantsPlugin:
1kernel.ImportPluginFromType<RestaurantsPlugin>();

Adicionar o índice Vector Search

When creating the MongoDBMemoryStore object, we passed it an index called restaurants_index, but that doesn’t exist yet. So let’s change that!
It’s coming very soon (version 3.1 of the C# driver) but for now, there is no neat and readable way to programmatically create a vector search index in C#.
The easiest way to create one is from within the Atlas UI in a browser or via MongoDB Compass.
I won’t go into detail here as we already have lots of content on how to do it. Check out our documentation that shows how to do it, if you need help.
Você pode utilizar o seguinte JSON para definir o índice de pesquisa vetorial:
1{
2 "fields": [
3 {
4 "type": "vector",
5 "path": "embedding",
6 "numDimensions": 1536,
7 "similarity": "cosine"
8 }
9 ]
10}
Recomendamos chamar o índice restaurants_index para corresponder ao código. Se você optar por usar outra coisa, certifique-se de atualizar o código colado anteriormente dentro de RestaurantsPlugin.

Unindo tudo

Agora que temos todas as peças definidas, é hora de unir tudo. Vamos solicitar a entrada do usuário e usar essa resposta para construir o que queremos que AI a IA faça.
Primeiro, vamos dizer à IA AI que queremos que ela atenda às chamadas automaticamente por conta própria. Adicione o seguinte após a chamada para criar a variável plugins:
1OpenAIPromptExecutionSettings settings = new()
2{
3 ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
4};
Next, let’s add the user interaction:
1Console.WriteLine("What would you like to make for dinner?");
2var input = Console.ReadLine();
Agora queremos criar um prompt que especifique o que queremos alcançar e comece a adicionar nosso primeiro plugin-in:
1string ingredientsPrompt = @"This is a list of ingredients available to the user:
2{{IngredientsPlugin.GetIngredientsFromCupboard}}
3
4Based on their requested dish " + input + ", list what ingredients they are missing from their cupboard to make that meal and return just the list of missing ingredients. If they have similar items such as pasta instead of a specific type of pasta, don't consider it missing";
Você pode ver aqui como o prompt é construído. Informamos que temos componentes disponíveis que eles podem obter chamando o método que passamos entre double colchetes duplos. Ele está no formato PluginName.Method, então pode parecer familiar.
Em seguida, damos a ele acesso à resposta do usuário sobre o que ele quer Comer e usamos isso para solicitar que ele veja quais componentes estão faltando para fazer a refação. Mais uma vez, há um pouco de engenharia rápida ocorrendo no final, para evitar que seja muito complicado e ignore componentes perfeitamente válidos.
Podemos então invocar esse prompt:
1var ingredientsResult = await kernel.InvokePromptAsync(ingredientsPrompt, new(settings));
2
3var missing = ingredientsResult.ToString().ToLower()
4 .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
5 .Where(line => line.StartsWith("- "))
6 .ToList();
The AI has a tendency to return quite a lot of extra explainer text alongside the list of ingredients so the missing variable just does some basic formatting to grab only the list of missing ingredients as we need to be very specific.
We now want to have some fun with the user and decide whether they have enough ingredients to make their meal or something similar, or should just not bother and go to a restaurant instead! But as well as suggesting they go to a restaurant, we will use our custom plugin to recommend some restaurants too!
1var cuisineResult = await kernel.InvokeAsync(
2 plugins["GetCuisine"],
3 new() { { "cuisine", input } }
4);
5
6if (missing.Count >= 5)
7{
8 string restaurantPrompt = @"This is the cuisine that the user requested: " + cuisineResult + @". Based on this cuisine, recommend a restaurant for the user to eat at. Include the name and address
9 {{RestaurantsPlugin.GetRecommendedRestaurant}}";
10
11 var kernelArguments = new KernelArguments(settings)
12 {
13 { "cuisine", cuisineResult }
14 };
15
16 var restaurantResult = await kernel.InvokePromptAsync(restaurantPrompt, kernelArguments);
17
18 Console.WriteLine($"You have so many missing ingredients ({missing.Count}!), why bother? {restaurantResult}");
19}
20else if(missing.Count < 5 && missing.Count > 0)
21{
22 Console.WriteLine($"You have most of the ingredients to make {input}. You are missing: ");
23 foreach (var ingredient in missing)
24 {
25 Console.WriteLine(ingredient);
26 }
27 string similarPrompt = @"The user requested to make " + input + @" but is missing some ingredients. Based on what they want to eat, suggest another meal that is similar from the " + cuisineResult + " cuisine they can make and tell them the name of it but do not return a full recipe";
28 var similarResult = await kernel.InvokePromptAsync(similarPrompt, new(settings));
29
30 Console.WriteLine(similarResult);
31}
32else {
33 Console.WriteLine("You have all the ingredients to make " + input + "!");
34 string recipePrompt = @"Find a recipe for making " + input;
35 var recipeResult = await kernel.InvokePromptAsync(recipePrompt, new(settings));
36 Console.WriteLine(recipeResult);
37}
Como buscamos todos os prompts disponíveis no diretório de prompts anteriormente, agora os temos disponíveis na array de plugins que mencionei. Então, começando por descobrir qual é a cozinha.
A chamada usa um objeto KernelArguments que contém valores que queremos disponibilizar, portanto, criamos um novo in-line para a chamada, passando o nome da variável de entrada, correspondendo cuisine ao que definimos anteriormente e ao valor que queremos atribuir a isso.
Em seguida, usamos algumas declarações básicas if/else para lidar com as várias condições, que vão desde a falta de muitos componentes, a falta de apenas alguns e a falta de nenhum!
Dentro de cada um, um prompt ligeiramente diferente para a AI IA é criado e usado, com a resposta então enviada ao usuário.

Teste

Agora que temos todo o código, vamos testá-lo!
Debug from within your IDE or using the .NET CLI.
dotnet run
Em seguida, você verá o prompt perguntando o que quer para o restaurante. Tente inserir sua comida favorita e veja como ela funciona!
Dependendo do modelo, pode levar alguns segundos para ser executado, portanto, se você não obter uma resposta instantânea ao nome da sua comida, não se desespere!
AI Interação com agente de IA recomendando restaurantes alternativos

Resumo

Woo! In this tutorial, we have put together the power of Microsoft’s Semantic Kernel, OpenAI, and MongoDB Atlas to build a powerful AI agent that helps users find out whether to visit the grocery store or go out for dinner!
Why don’t you try playing around with different meals or ingredients in the included text file and see what recommendations you get?
Se você tiver uma cozinha inteligentes que tenha uma API para lhe informar quais componentes você tem, pode até tentar combiná-los para que alguns dos dados sejam genuinamente seus!
Boa tarde!
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.
Iniciar a conversa

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Introdução ao Semantic Kernel da Microsoft em C# e MongoDB Atlas


Oct 10, 2024 | 10 min read
Notícias e Anúncios

Apresentamos o MongoDB Analyzer para .NET


Aug 05, 2024 | 6 min read
exemplo de código

Como usar a criptografia no nível do campo do lado do cliente (CSFLE) do MongoDB com C#


Sep 23, 2022 | 18 min read
Tutorial

Introdução ao MongoDB Atlas e ao Azure Functions usando .NET e C#


Apr 02, 2024 | 8 min read
Sumário