Introdução ao Semantic Kernel da Microsoft em C# e MongoDB Atlas
Luce Carter10 min read • Published Aug 05, 2024 • Updated Oct 10, 2024
APLICATIVO COMPLETO
Avalie esse Tutorial
O Semantic Kernel se tornou extremamente popular no ecossistema da Microsoft. Na verdade, no Microsoft Build, Semantic Kernel e AI com MongoDB foram os tópicos mais discutidos em nosso estande.
Semantic Kernel é o AI SDK da Microsoft disponível em Java, Python e C#. Ele permite que você crie aplicativos de IA poderosos encadeando plug-ins personalizados prontos para uso, criados pela comunidade. Esses plug-ins trabalham juntos para criar planos que permitem realizar tarefas complexas. Isso pode ser qualquer coisa, desde arrumar a área de trabalho deScott Hanselman até resumir um bloco de texto e enviar o resumo por e-mail. As possibilidades são infinitas!
O Semantic Kernel é uma ferramenta para criar aplicativosde geração aumentada de recuperação (RAG). As partes R e A vêm da recuperação de informações a serem usadas como contexto na entrada do modelo de linguagem grande (LLM). É aqui que entra o MongoDB . O MongoDB é uma opção para armazenar dados, incluindo incorporações que representam esses dados, e até mesmo oferece a capacidade de Atlas Search os dados usando o Atlas Vector Search.
O Semantic Kernel tem suporte para o MongoDB Atlas graças a um connector. Portanto, você não apenas pode armazenar seus dados no MongoDB, incluindo as incorporações, mas também usa automaticamente o MongoDB Atlas Vector Search sob o capô para recuperar os resultados. Você obtém o melhor do Semantic Kernel e o melhor do MongoDB, o banco de dados de documentos mais popular para desenvolvedores C#!
Neste tutorial, você aprenderá como começar a usar o Semantic Kernel e o MongoDB, aproveitando o connector e o plugin-in SemanticTextMemorypara criar um bot que recomenda um filme para assistir, usando a OpenAI para criar incorporações e pesquisando o filme de amostra em nosso conjunto de dados de amostra.
Prefere aprender por meio de conteúdo em vídeo? Então assista à versão em vídeo disponível no Youtube!
Para acompanhar este tutorial, você precisará de algumas coisas:
- Um cluster MongoDB M0
- Os dados de amostra carregados naquele cluster
- Uma conta gratuita da OpenAI e chave de API do projeto
- .NET 8 ou superior
- Um IDE ou editor de texto para acompanhar
Se preferir simplesmente ler o código, você pode encontrá-lo no GitHub. Tem duas ramificações, dependendo se você tem acesso ao Azure OpenAI ou deseja usar o OpenAI. Usaremos o OpenAI para este tutorial, pois ele é gratuito e aberto a todos no momento em que escrevemos.
Agora que você tem os pré-requisitos, é hora de criar o projeto e adicionar os pacotes NuGet necessários para criar o bot.
- Crie um novo projeto de console, usando seu IDE ou por meio da DotNet CLI.
- Adicione os seguintes pacotes NuGet ao seu novo projeto
- Microsoft.SemanticKernel
- Microsoft.SemanticKernel.Connectors.MongoDB (NB: isso está em pré-lançamento)
- Microsoft.SemanticKernel.Connectors.OpenAI
Existem algumas variáveis de que precisaremos ao longo deste tutorial, portanto, começaremos configurando-as em
Program.cs
.Como queremos criar pelo menos um outro método neste tutorial, também mudaremos para a estrutura tradicional da nossa classe de programa. Substitua o conteúdo pelo seguinte:
1 using Microsoft.SemanticKernel.Memory; 2 3 4 public static class Program { 5 static string TextEmbeddingModelName = "text-embedding-ada-002"; 6 static string OpenAIAPIKey = "<YOUR OPENAI PROJECT API KEY>"; 7 8 static string MongoDBAtlasConnectionString = "<YOUR ATLAS CONNECTION STRING>"; 9 static string SearchIndexName = "default"; 10 static string DatabaseName = "semantic-kernel"; 11 static string CollectionName = "movies"; 12 static MemoryBuilder memoryBuilder; 13 14 public static async Task Main(string[] args) { 15 16 } 17 }
A adiçãodopragma warning disable se deve ao fato de que muitos dos recursos são experimentais e isso desativará os erros.
Go em frente e substitua os espaços reservados para OpenAI e Atlas por seus próprios valores.
Você deve ter notado na última seção que adicionou uma variável MemoryBuilder. Esse construtor é o que dá acesso ao plug-in de memória, um plug-in pronto para uso para trabalhar com dados armazenados.
Então agora vamos configurar este plugin, usar este construtor e também conectá-lo ao MongoDB Atlas como nosso armazenamento de memória.
Cole o código abaixo dentro do seu
Main
método:1 memoryBuilder = new MemoryBuilder(); 2 3 memoryBuilder.WithOpenAITextEmbeddingGeneration( 4 TextEmbeddingModelName, 5 OpenAIAPIKey 6 );
O Construtor de Memória vem com alguns métodos auxiliares. Neste caso, estamos usando o
WithOpenAITextEmbeddingGeneration
que ajuda você a configurar o plugin de memória.Como estamos trabalhando com texto neste projeto, precisamos ser capazes de gerar embeddings de texto para que nossos dados sejam usados na pesquisa. É aqui que entra o OpenAI. Ao passar a esse método o nome do modelo que queremos usar e a chave da API OpenAI, o plugin tem tudo de que precisa para lidar automaticamente com o resto para nós — ótimo!
Certifique-se de que as seguintes instruções de uso estão presentes no arquivo:
1 using Microsoft.SemanticKernel; 2 using Microsoft.SemanticKernel.Connectors.MongoDB; 3 using Microsoft.SemanticKernel.Connectors.OpenAI; 4 using Microsoft.SemanticKernel.Memory; 5 using MongoDB.Driver; 6 using Kernel = Microsoft.SemanticKernel.Kernel;
Usar um banco de dados que suporta vetores e pesquisas de vetores, como o MongoDB Atlas, é uma parte fundamental da adição das partes de recuperação e aumento aos seus aplicativos RAG.
O MongoDB Connector do Semantic Kernel adiciona suporte não apenas para o uso do MongoDB como armazenamento de dados para suas incorporações, mas também para os recursos de pesquisa vetorial do MongoDB para realizar a pesquisa.
Cole o código abaixo após o anterior, dentro do seu
Main
método :1 var mongoDBMemoryStore = new MongoDBMemoryStore(MongoDBAtlasConnectionString, DatabaseName, SearchIndexName); 2 memoryBuilder.WithMemoryStore(mongoDBMemoryStore); 3 var memory = memoryBuilder.Build();
Simples assim, com algumas linhas de código, temos o plugin de memória configurado e ele está configurado para usar o MongoDB.
Os dados de amostra do MongoDB vêm com diferentes bancos de dados e collection para uma variedade de casos de uso. Uma das mudanças recentes foi no banco de dados sample_mflix. Esse banco de dados existe nos dados de amostra há muito tempo, mas adicionamos recentemente uma nova collection dentro do banco de dados chamada embedded_movies. Você já deve ter notado isso se tiver navegado em seu novo cluster. Essa collection contém incorporações vetoriais no campo plot a partir de um grande número de documentos da collection de filmes e facilita muito para os desenvolvedores experimentar o Atlas Vector Search do MongoDB em uma variedade de linguagens de programação.
Em um mundo ideal, usaremos esta coleção com o Semantic Kernel. Infelizmente, há uma limitação com o Semantic Kernel no nome do campo que contém o valor de incorporações, bem como na forma dos documentos que ele pode usar. Então, por esse motivo, para fins deste tutorial, vamos importar alguns documentos do nosso banco de dados sample_mflix e salvá-los em uma nova coleção, usando o Semantic Kernel. Isso gerará as incorporações automaticamente usando o OpenAI e as salvará no formato que o Semantic Kernel pode usar posteriormente.
Primeiro, precisamos criar um modelo que represente o documento de filme. Portanto, crie uma nova classe
Movie.cs
em seu projeto e cole o seguinte:1 public class Movie 2 { 3 [ ]4 [ ]5 public string Id { get; set; } 6 7 [ ]8 public string Plot { get; set; } 9 10 [ ]11 public List<string> Genres { get; set; } 12 13 [ ]14 public int Runtime { get; set; } 15 16 [ ]17 public List<string> Cast { get; set; } 18 19 [ ]20 public int NumMflixComments { get; set; } 21 22 [ ]23 public string Poster { get; set; } 24 25 [ ]26 public string Title { get; set; } 27 28 [ ]29 public string Fullplot { get; set; } 30 31 [ ]32 public List<string> Languages { get; set; } 33 34 [ ]35 public DateTime Released { get; set; } 36 37 [ ]38 public List<string> Directors { get; set; } 39 40 [ ]41 public List<string> Writers { get; set; } 42 43 [ ]44 public Awards Awards { get; set; } 45 46 [ ]47 public string? Rated { get; set; } 48 49 [ ]50 public string Lastupdated { get; set; } 51 52 53 [ ]54 public object Year { get; set; } 55 56 [ ]57 public Imdb Imdb { get; set; } 58 59 [ ]60 public List<string> Countries { get; set; } 61 62 [ ]63 public string Type { get; set; } 64 65 [ ]66 public Tomatoes Tomatoes { get; set; } 67 68 [ ]69 public int? Metacritic { get; set; } 70 71 [ ]72 public bool? Awesome { get; set; } 73 } 74 75 public class Awards 76 { 77 [ ]78 public int Wins { get; set; } 79 80 [ ]81 public int Nominations { get; set; } 82 83 [ ]84 public string Text { get; set; } 85 } 86 87 public class Imdb 88 { 89 [ ]90 public object ImdbId { get; set; } 91 92 [ ]93 public object Votes { get; set; } 94 95 [ ]96 public object Rating { get; set; } 97 } 98 99 public class Tomatoes 100 { 101 [ ]102 public Viewer Viewer { get; set; } 103 104 [ ]105 public DateTime LastUpdated { get; set; } 106 107 [ ]108 public DateTime? DVD { get; set; } 109 110 [ ]111 public string? Website { get; set; } 112 113 [ ]114 public string? Production { get; set; } 115 116 [ ]117 public Critic? Critic { get; set; } 118 119 [ ]120 public int? Rotten { get; set; } 121 122 [ ]123 public int? Fresh { get; set; } 124 125 [ ]126 public string? BoxOffice { get; set; } 127 128 [ ]129 public string? Consensus { get; set; } 130 131 } 132 133 public class Viewer 134 { 135 [ ]136 public double Rating { get; set; } 137 138 [ ]139 public int NumReviews { get; set; } 140 141 [ ]142 public int Meter { get; set; } 143 } 144 145 public class Critic 146 { 147 [ ]148 public double Rating { get; set; } 149 150 [ ]151 public int NumReviews { get; set; } 152 153 [ ]154 public int Meter { get; set; } 155 }
Se o seu IDE ou editor de texto não adicionar automaticamente as instruções de uso necessárias, adicione o seguinte na parte superior da classe:
1 using MongoDB.Bson; 2 using MongoDB.Bson.Serialization.Attributes;
Agora que temos o modelo disponível que reflete nosso documento, é hora de usá-lo.
Cole o seguinte código na sua classe
Program.cs
:1 private static async Task FetchAndSaveMovieDocuments(ISemanticTextMemory memory, int limitSize) 2 { 3 MongoClient mongoClient = new MongoClient(MongoDBAtlasConnectionString); 4 var movieDB = mongoClient.GetDatabase("sample_mflix"); 5 var movieCollection = movieDB.GetCollection<Movie>("movies"); 6 List<Movie> movieDocuments; 7 8 Console.WriteLine("Fetching documents from MongoDB..."); 9 10 movieDocuments = movieCollection.Find(m => true).Limit(limitSize).ToList(); 11 12 movieDocuments.ForEach(movie => 13 { 14 if (movie.Plot == null) 15 { 16 movie.Plot = "UNKNOWN"; 17 } 18 }); 19 20 foreach (var movie in movieDocuments) 21 { 22 try 23 { 24 await memory.SaveReferenceAsync( 25 collection: CollectionName, 26 description: movie.Plot, 27 text: movie.Plot, 28 externalId: movie.Title, 29 externalSourceName: "Sample_Mflix_Movies", 30 additionalMetadata: movie.Year.ToString()); 31 } 32 catch (Exception ex) 33 { 34 Console.WriteLine(ex.Message); 35 36 } 37 } 38 }
Vamos dar uma olhada no que está acontecendo:
- Usamos o driver C# do MongoDB, que está disponível para nós no connector, para criar um novo cliente e apontá-lo para nosso banco de dados e coleção existentes.
- Em seguida, criamos uma nova lista de filmes, obtendo o número solicitado de documentos e adicionando-os à lista.
- Para cada filme, fazemos alguma higiene de dados para quaisquer gráficos nulos, pois isso pode causar erros mais tarde, e simplesmente marcá-lo como anulável não funcionará, infelizmente.
- Depois de termos uma lista limpa de filmes, iteramos cada um deles e o salvamos em nossa nova coleção por meio do armazenamento de memória.
- O documento que o Semantic Kernel cria com o plug-in tem alguns campos que queremos preencher, então atribuímos a esses os valores mais sensatos dos campos disponíveis em nosso documento de filme.
Agora, precisamos realmente chamar esse método. Podemos fazer isso simplesmente chamando
await FetchAndSaveMovieDocuments(memory, 1500);
de nosso métodoMain
, após o código existente. Isso preencherá nossa collection vinculada ao armazenamento de memória com documentos 1500 . Você pode escolher um número diferente, se desejar.Execute o aplicativo para preencher nosso novo banco de dados e coleção com dados usando o Kernel Semântico. Depois de exibir "Fetching documents from MongoDB…", aguarde alguns minutos para que ele seja preenchido em segundo plano e feche o aplicativo. Gerar as incorporações de texto em um número tão grande de documentos usando o Kernel Semântico pode demorar um pouco. Este não é um gargalo devido ao soberbo driver C# do MongoDB.
dotnet run
Isso só precisa ser executado uma vez para que tenhamos alguns dados disponíveis. Portanto, se você quiser executar esse aplicativo novamente no futuro, não há problema em comentar a chamada para o método
FetchAndSaveMovieDocuments
ou removê-lo completamente.Isso criará um novo banco de dados em seu cluster chamado semantic-kernel com uma coleção chamada embedded_movies, contendo os dados conforme preenchidos usando o Semantic Kernel.
Você deve ter notado anteriormente que, quando adicionamos nosso armazenamento de memória MongoDB, passamos o nome do índice de pesquisa. Esse índice de pesquisa é usado para identificar quais campos queremos usar em nossa pesquisa. Mas isso ainda não existe em nosso MongoDB database.
Agora que você executou o aplicativo uma vez, os dados estarão disponíveis na coleção para uso no índice de pesquisa.
Já temos uma ótima documentação sobre como criar um índice de pesquisa vetorial para que você possa consultá-la sobre como acessar o assistente na interface do usuário do Atlas para criar o novo índice.
O seguinte JSON pode ser utilizado para definir o índice:
1 { 2 "fields": [ 3 { 4 "numDimensions": 1536, 5 "path": "embedding", 6 "similarity": "dotProduct", 7 "type": "vector" 8 } 9 ] 10 }
Isso usa o campo de incorporação que foi gerado pelo Semantic Kernel. O modelo "text-embedding-ada-002" da OpenAI que estamos usando para a incorporação de texto gera 1536 dimensões. Você verá isso nos documentos gerados, pois a matriz de incorporação contém 1536 elementos.
Você precisará usar o nome do índice "default" para corresponder à variável codificada em seu código. Se você nomear o índice de pesquisa outra coisa, certifique-se de atualizar a variável.
Agora que temos os dados disponíveis e o índice de pesquisa criado, é hora de adicionar a capacidade de realmente fazer perguntas sobre nossos dados.
Cole o código a seguir em seu método
Main
, após o código existente:1 Console.WriteLine("Welcome to the Movie Recommendation System!"); 2 Console.WriteLine("Type 'x' and press Enter to exit."); 3 Console.WriteLine("============================================"); 4 Console.WriteLine(); 5 6 while(true) 7 { 8 Console.WriteLine("Tell me what sort of film you want to watch.."); 9 Console.WriteLine(); 10 11 Console.Write("> "); 12 13 var userInput = Console.ReadLine(); 14 15 if(userInput.ToLower() == "x") 16 { 17 Console.WriteLine("Exiting application.."); 18 break; 19 } 20 21 Console.WriteLine(); 22 23 var memories = memory.SearchAsync(CollectionName, userInput, limit: 3, minRelevanceScore: 0.6); 24 25 Console.WriteLine(String.Format("{0,-20} {1,-50} {2,-10} {3,-15}", "Title", "Plot", "Year", "Relevance (0 - 1)")); 26 Console.WriteLine(new String('-', 95)); // Adjust the length based on your column widths 27 28 await foreach (var mem in memories) 29 { 30 Console.WriteLine(String.Format("{0,-20} {1,-50} {2,-10} {3,-15}", 31 mem.Metadata.Id, 32 mem.Metadata.Description.Length > 47 ? mem.Metadata.Description.Substring(0, 47) + "..." : mem.Metadata.Description, // Truncate long descriptions 33 mem.Metadata.AdditionalMetadata, 34 mem.Relevance.ToString("0.00"))); // Format relevance score to two decimal places 35 } 36 }
Muito desse código é sobre a entrada do usuário e a formatação da saída. Mas vamos dar uma olhada nas linhas de código que importam:
memory.SearchAsync
é como realizamos a pesquisa. Passamos a ele o nome de onde queremos pesquisar, também conhecido como collection, o que queremos pesquisar, quantos resultados receber e qual pontuação de 0 a 1 consideramos um limite para "relevant enough. " await foreach (var mem in memories)
é ligeiramente diferente do forreach que você pode estar usando. A variável de memória que recebeu o resultado da pesquisa é do tipo `''IAsyncEnumerable então temos que realizar um await forreach para iterar por ele.Agora temos tudo pronto para executar o aplicativo e realmente fazer uma pergunta. Por que não tentar pedir um filme sobre tubarões ou outro tema que você adora?
Simples assim, você criou um bot simples de recomendações de chat de filme usando o Microsoft, o MongoDB Atlas e o connector para MongoDB no Microsoft.
Se você quiser saber mais, Escrevi um tutorial sobre como usar o Atlas Vector Search nativamente em um aplicativo .NET!
Há também uma ramificação principal desse repositório que usa o AzureOpenAI para aqueles que têm acesso.
Por que não experimentar hoje e ver qual filme você pode querer assistir esta noite?
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.