Azure Open IA, acelerando nossos caminhos.

W

wdossantos

Azure Open IA, acelerando nossos caminhos.​





Há algum tempo temos ouvido falar sobre o OpenAI, que se tornou um dos temas mais requisitados da atualidade. É comum sentirmos o desejo de testar suas funcionalidades, porém, surge a dúvida de por onde começar e como fazê-lo sem comprometer a confidencialidade de nossos dados.



O caminho que vou apresentar aqui envolve o uso de um acelerador do Azure para o OpenAI azure-search-openai-demo, ele nos auxiliará desde a configuração da infraestrutura até a implementação de aplicações de exemplo. Fiquei impressionado com a facilidade de uso, a estratégia de usar o “azd” facilitou muito as coisas, veja como instalar aqui Install the Azure Developer CLI | Microsoft Learn

Basta executar alguns comandos e pronto, o ambiente estará todo configurado no Azure. Eu apenas criei uma nova pasta sem clonar nenhum repositório e executei esses comandos.




azd init -t azure-search-openai-demo
azd auth login
azd up



1-) azd init -t azure-search-openai-demo



azd init -t azure-search-openai-demoazd init -t azure-search-openai-demo



2-) azd auth login



azd auth loginazd auth login



3-) azd up

azd upazd up





Ao fim da execução de mais ou menos 30 minutos, pude ver esses recursos criados:



Lista de recursos criadosLista de recursos criados



Visualizador de Recursos:





Mapa dos recursosMapa dos recursos



A aplicação nasce pronta:



Aplicação de exemplo do aceleradorAplicação de exemplo do acelerador



Arquivos criados ao rodar o comando azd init -t azure-search-openai-demo:

Arquivos criados ao rodar o comando azd init -t azure-search-openai-demoArquivos criados ao rodar o comando azd init -t azure-search-openai-demo



Basta fazer as perguntas. Inclusive, uma série de documentos de exemplo foram adicionados à solução, os quais podem ser encontrados na pasta local “data” ou no armazenamento do Azure.

wdossantos_33-1729596822623.png



O próximo passo será trabalhar com a ingestão de dados. Existe um guia na própria documentação do acelerador intitulado Indexing documents for the Chat App. Para começar, vou fazer o upload de um artigo meu do blog, chamado “Escalando nodes e escalando e pods no AKS. | by Wilson Santos | Medium”, para isso, basta gerar um PDF e colocar o arquivo dentro da pasta “data” do acelerador, diretamente na sua máquina local.



Após isso, você irá executar um pequeno script em PowerShell chamado “prepdoc”. O comando para executá-lo é o seguinte


.\\scripts\\prepdocs.ps1


Assim que terminar de rodar podemos fazer perguntas sobre esse conteúdo.

wdossantos_34-1729596859679.png



O acelerador traz uma implementação em React e python e podemos estudar como ela funciona usando esse código que fica na pasta app do acelerador

Temos um front em React e um backend em Python, mas caso você esteja acostumado com C# que é o meu caso, nada melhor que explorar os SDKS do Azure.AI.OpenAI e o SemanticKernel que eu estou apresentando a seguir.



Nada melhor que “codar” para entender melhor as coisas!​





Agora, vamos criar um pequeno programa em .NET Core Console que aproveitará toda a infraestrutura criada pelo acelerador. Este programa será capaz de fazer perguntas ao Azure Open AI e, além disso, responderá com base nos documentos que forem atualizados na base de conhecimento. Para isso, utilizaremos o pacote Azure.AI.OpenAI. No meu exemplo, estou utilizando a versão 2.0.0.




install-package Azure.AI.OpenAI


Primeiro passo vamos criar um client da classe AzureOpenAIClient:




AzureOpenAIClient azureClient = new(endpoint, credential);



Para as credenciais e o endpoint, utilizamos uma chave do recurso que pode ser obtida na seção “Keys and Endpoint” da instância do Azure OpenAI.



wdossantos_35-1729596914646.png




var credential = new AzureKeyCredential("...");




Depois, vamos criar uma instancia da classe chamada SearchClient. Essa classe ajudará na conexão com o serviço de busca (Search service).



algo assim:



Code:
var searchEndpoint = new Uri("...");
var searchCredential = new AzureKeyCredential("...");
var indexName = "gptkbindex";
var searchClient = new SearchClient(searchEndpoint, indexName, searchCredential);
var searchOptions = new SearchOptions
{
    Size = 5 // Número de documentos a recuperar
};
var searchResults = searchClient.Search<SearchDocument>(text, searchOptions);
var retrievedDocuments = searchResults.Value.GetResults().Select(result => result.Document["content"].ToString());
var context = string.Join("\n", retrievedDocuments);





A classe SearchClient também requer o uso de uma chave de segurança. Para encontrá-la, acessamos a instância do serviço de busca (Search service) na seção “Keys”



wdossantos_36-1729596964770.png



o endpoint esta em overview

wdossantos_37-1729596995296.png



Agora vamos criar uma instancia da classe ChatClient, que será responsável por fazer perguntas para o OpenAI, e passar o prompt para o Azure openIA.



E aqui que está o grande segredo do RAG, tudo é prompt, pegamos o resultado da busca do SearchClient que está na variável context e juntamos no prompt.





Code:
var prompt = $"Contexto: {context}\nPergunta: {text}";
ChatClient chatClient = await azureClient.CompleteChatAsync("gpt-4o");
ChatCompletion completion = chatClient.CompleteChat(new List<ChatMessage>
{
new SystemChatMessage("Você é um assistente atencioso"),
new UserChatMessage(prompt),

});





Para obter o valor do campo DeploymentName, no meu caso o gpt-4o, você pode encontrá-lo na instância do OpenAI. Basta abrir os “Model deployments”, clicar em “Management deployment”. Isso abrirá o Azure OpenAI Studio, onde você poderá visualizar os modelos implantados.



wdossantos_0-1730489069431.png





os pacotes utilizados foram esses:


<PackageReference Include="Azure.AI.OpenAI" Version="2.0.0" />
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Azure.Search.Documents" Version="11.4.0" />

o Código final ficou assim:


Code:
using Azure;
using Azure.AI.OpenAI;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using OpenAI.Chat;


public class Program
{

    static async Task Main(string[] args)
    {
        while (true)
        {
            Console.WriteLine("Digite uma Pergunta");
            var question = Console.ReadLine();
            if (question != null)
            {
                var result = await AskingChatCompletionWithSearchsAsync(question);
                Console.WriteLine(result);
            }

        }

    }

    static async Task<string> AskingChatCompletionWithSearchsAsync(string text)
    {

        var endpoint = new Uri("https://openiapriv02.openai.azure.com/");
        var credential = new AzureKeyCredential("...");
        AzureOpenAIClient azureClient = new(endpoint, credential);


        var searchEndpoint = new Uri("https://gptkb-kv4atymcdg6pg.search.windows.net");
        var searchCredential = new AzureKeyCredential("...");
        var indexName = "gptkbindex";
        var searchClient = new SearchClient(searchEndpoint, indexName, searchCredential);
        var searchOptions = new SearchOptions
        {
            Size = 5 // Número de documentos a recuperar
        };
        var searchResults = searchClient.Search<SearchDocument>(text, searchOptions);
        var retrievedDocuments = searchResults.Value.GetResults().Select(result => result.Document["content"].ToString());
        var context = string.Join("\n", retrievedDocuments);



        var prompt = $"Contexto: {context}\nPergunta: {text}";
        ChatClient chatClient = azureClient.GetChatClient("gpt-4o");
        ChatCompletion completion = await chatClient.CompleteChatAsync(new List<ChatMessage>
        {
            new SystemChatMessage("Você é um assistente atencioso"),
            new UserChatMessage(prompt),

        });


        var result = $"{completion.Role}: {completion.Content[0].Text}";
        return result;
    }
}



fiz uma pergunta para o assistente com base em um documento que já veio de exemplo no acelerador o Benefit_Options.pdf, perguntei o que é o programa Northwind Health Plus?. Observe que a resposta é dada com base nos documentos indexados no Search service



wdossantos_1-1730489483583.png



Mas o que são essas versões de modelos?​





Os modelos são constantemente atualizados com novas características, as versões de modelo são a maneira de controlar essas mudanças do OpenAI.



Em particular, os modelos GPT-3.5 Turbo e GPT-4 recebem atualizações regulares com novos recursos. Por exemplo, as versões 0613 do GPT-3.5 Turbo e GPT-4 introduziram a chamada de função. A chamada de função é um recurso popular que permite ao modelo criar saídas estruturadas que podem ser usadas para chamar ferramentas externas. Fique atento aos modelos disponíveis para sua região quais modelos seu SDK suporta, saiba mais em Azure OpenAI Service model versions — Azure OpenAI | Microsoft Learn



Semantic Kernel​





O Semantic Kernel é um SDK de código aberto projetado para integrar com o código existente. Ele suporta várias linguagens de programação, incluindo C#, Python e Java. Através de conectores e plugins, o Semantic Kernel permite adicionar inteligencia aos aplicativos. Sua flexibilidade permite aos desenvolvedores orquestrar o código existente sem se prender a um provedor específico de modelos de IA, proporcionando liberdade na escolha e combinação de serviços conforme as necessidades do projeto. Saiba mais



Vamos fazer algumas implementações básicas, a começar por um chat básico sem RAG, é importante lembrar que alguns dos pacotes usados ainda estão em versões alpha não indicados para ambiente produtivo.

vamos começar a instalar o pacote




install-pacakge Microsoft.SemanticKernel


Agora vamos criar uma representação do que o SDK chama de Kernel, essa representação já adiciona a injeção do Chat Completion da nossa instancia do Azure Open IA





Code:
 var builder = Kernel.CreateBuilder();  
  
   builder.AddAzureOpenAIChatCompletion(  
            "gpt-4o",                      // Azure OpenAI Deployment Name  
            "https://wsopenia.openai.azure.com/", // Azure OpenAI Endpoint  
            "...");  // Azure OpenAI Key  
  
  
   var kernel = builder.Build();





O prompt no Semantic Kernel é um template em formato texto que permite valorar variáveis, chamar funções e extrair valores, para isso usamos as chaves {{…}}. no nosso exemplo temos as variáveis history e input em uma estrutura que mostra para a IA quais informações são a entrada de usuário e quais são apenas instruções. para saber mais sobre prompt Using the out-of-the-box prompt template language | Microsoft Learn





var prompt = @"Chat:{{$history}} User:{{$input}}";





Agora usamos o método CreateFunctionFromPrompt especificando as configurações de prompt como MaxTokens, Temperatura etc.




Code:
 var kf = kernel.CreateFunctionFromPrompt(prompt, executionSettings: new OpenAIPromptExecutionSettings
 {
     MaxTokens = 500,
     ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
 });

e usando a classe KernelArguments, passamos os valores das variáveis




Code:
var arguments = new KernelArguments();  
arguments\["input"\] = question;  
arguments\["history"\] = questionHistory;



a etapa final é chamar as funções InvokeAsync passando a instancia de “kf” e passando a instancia dos argumentos





var result = await kernel.InvokeAsync(kf, arguments);





um exemplo de implementação é essa:



Code:
var arguments = new KernelArguments();
var prompt = @"Chat:{{$history}} User:{{$input}}";
var kf = kernel.CreateFunctionFromPrompt(prompt, executionSettings: new OpenAIPromptExecutionSettings
{
    MaxTokens = 500,
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
});


var questionHistory = "";
while (true)
{
    Console.WriteLine("Digite uma Pergunta");

    var question = Console.ReadLine();


    // Add user input                
    arguments["input"] = question;

    var result = await kernel.InvokeAsync(kf, arguments);


    Console.WriteLine("result:" + result);
    //Console.WriteLine("history:" + questionHistory);

    questionHistory += "Chat:" + result + "User:" + question + "\n";
    arguments["history"] = questionHistory;
}





aqui não temos RAG apenas o LLM respondendo a pergunta "o que é o Azure OpenIA"?



wdossantos_41-1729597143650.png


Pacotes utilizados:





<PackageReference Include="Microsoft.SemanticKernel" Version="1.26.0" />

Semantic é extensível​




Com ele podemos usar os plugins para interagir com nosso código existente como por exemplo consultar pedidos em uma base de dados, e podemos usar os conectores para integrar com os serviços de IA, por exemplo o Azure openIA, saiba mais

Vou mostrar um exemplo de plugin, vou criar uma classe order:





Code:
public sealed class Order
{
    [KernelFunction, Description("Show order details for number")]
    public static string Sqrt([Description("The number to order details")] double number1)
    {
        return $"The detail about order {number1} is your current state is closed.";
    }

}





perceba que descrevemos seu comportamento usando um atributo KernelFunction

depois eu vou adicionar esse plugin dessa forma:





Code:
var builder = Kernel.CreateBuilder();  
builder.Plugins.AddFromType<Order>();





feito isso podemos optar pela auto chamada configurando a classe OpenAIPromptExecutionSettings com propriedade ToolCallBehavior setada para o valor AutoInvokeKernelFunctions




Code:
 // Enable auto function calling  
  OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()  
  {  
      ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,  
  };



o restante do código é o mesmo mas vou colocar um exemplo completo aqui:




Code:
var builder = Kernel.CreateBuilder();  
  
builder.AddAzureOpenAIChatCompletion(  
  "gpt-4o",                      // Azure OpenAI Deployment Name  
  "https://cog-nggxeq6fpjnxg.openai.azure.com/", // Azure OpenAI Endpoint  
  "...");  // Azure OpenAI Key  
  
builder.Plugins.AddFromType<Order>();  
  
var arguments = new KernelArguments();  
var prompt = @"Chat:{{$history}} User:{{$input}}";  
var kf = kernel.CreateFunctionFromPrompt(prompt, executionSettings: new OpenAIPromptExecutionSettings  
{  
    MaxTokens = 500,  
    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions  
});  
  
  
var questionHistory = "";  
while (true)  
{  
    Console.WriteLine("Digite uma Pergunta");  
    var question = Console.ReadLine();  
    // Add user input                  
    arguments\["input"\] = question;  
    var result = await kernel.InvokeAsync(kf, arguments);  
    Console.WriteLine("result:" + result);  
  
    questionHistory += "Chat:" + result + "User:" + question + "\\n";  
    arguments\["history"\] = questionHistory;  
}

Observem a mágica deste exemplo: com ele, podemos criar uma classe que controla os mecanismos de busca e, portanto, o conteúdo da resposta. No entanto, é o LLM que decide se a pergunta corresponde à descrição da classe e aciona o mecanismo. Esse recurso é chamado de 'function calling' e está disponível apenas nas versões mais recentes dos modelos, como o GPT-4.

wdossantos_42-1729597207511.png
console e execução respondendo a pergunta “Show order details for number 10”?




pacotes utilizados:


<PackageReference Include="Microsoft.SemanticKernel" Version="1.26.0" />

Memorias usando IA Search​





Aqui temos o uso de um conector e de um plugin, o conector vai adicionar accesso ao IA Search por meio dessa implementação:




Code:
var memoryBuilder = new MemoryBuilder();
memoryBuilder.WithMemoryStore(new AzureAISearchMemoryStore(
   "https://gptkb-nggxeq6fpjnxg.search.windows.net",
   "...")
);

Alem do pacote do Microsoft.SemanticKernel ainda vamos precisar de mais dois pacotes o Microsoft.SemanticKernel.Connectors.AzureAISearch e o Microsoft.SemanticKernel.Plugins.Memory

Mas também precisamos do plugin TextMemoryPlugin para acessar as memorias, mas ates de acessar vamos cria-las

Para isso vamos usar essa implementação:




Code:
var memory = memoryBuilder.Build();  
  
const string MemoryCollectionName = "aboutMe";  
  
await memory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");  
await memory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I currently work as a tourist operator");  
await memory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");  
await memory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");  
await memory.SaveInformationAsync(MemoryCollectionName, id: "info5", text: "My family is from New York");

kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));



Com isso sera gerado um index na IA Search chamado aboutme



wdossantos_43-1729597236226.png



lista de indexs



wdossantos_44-1729597266492.png



Search explorer



wdossantos_45-1729597299034.png

Campos do index



esse index foi gerado usando esse Text Embedding text-embedding-ada-002, que é um tipo de algoritmo de vetorização



wdossantos_0-1730492183235.png





Deployments Azure OpenAI Studio



com essa implementação temos a configuração da instancia do Azure openIA que irá fazer o embending das perguntas usando o modelo text-embedding-ada-002 :



Code:
 memoryBuilder.WithTextEmbeddingGeneration((loggerFactory, httpClient) => {
     return new AzureOpenAITextEmbeddingGenerationService(
         "text-embedding-ada-002", // Embedding generation service name
         "https://openiapriv02.openai.azure.com/",
         "...",
         httpClient: httpClient,
         loggerFactory: loggerFactory
     );
 });





por fim para fazer as perguntas ao chat e receber resposta que vem desse index como se fossem uma lembrança do chat, mas antes precisamos enriquecer o prompt com essas informações algo assim:





Code:
 const string skPrompt = @"  
 ChatBot can have a conversation with you about any topic.  
 It can give explicit instructions or say 'I don't know' if it does not have an answer.  
  
 Information about me, from previous conversations:  
 - {{$fact1}} {{recall $fact1}}  
 - {{$fact2}} {{recall $fact2}}  
 - {{$fact3}} {{recall $fact3}}  
 - {{$fact4}} {{recall $fact4}}  
 - {{$fact5}} {{recall $fact5}}  
  
 Chat:  
 {{$history}}  
 User: {{$userInput}}  
 ChatBot: ";





esses valores serão passados pelo KernelArguments:



Code:
 arguments\["fact1"\] = "What my name?";  
 arguments\["fact2"\] = "where do I live?";  
 arguments\["fact3"\] = "where is my family from?";  
 arguments\["fact4"\] = "where have I travelled?";  
 arguments\["fact5"\] = "what do I do for work?";





veja a implementação completa:



Code:
var builder = Kernel.CreateBuilder();

builder.AddAzureOpenAIChatCompletion(
         "gpt-35-turbo",                      // Azure OpenAI Deployment Name
         "https://cog-nggxeq6fpjnxg.openai.azure.com/", // Azure OpenAI Endpoint
         "...");  // Azure OpenAI Key


var kernel = builder.Build();

#pragma warning disable SKEXP0001, SKEXP0010, SKEXP0050, SKEXP0020

var memoryBuilder = new MemoryBuilder();


memoryBuilder.WithTextEmbeddingGeneration((loggerFactory, httpClient) => {
    return new AzureOpenAITextEmbeddingGenerationService(
        "text-embedding-ada-002", // Embedding generation service name
        "https://openiapriv02.openai.azure.com/",
        "...",
        httpClient: httpClient,
        loggerFactory: loggerFactory
    );
});


memoryBuilder.WithMemoryStore(new AzureAISearchMemoryStore(
   "https://gptkb-nggxeq6fpjnxg.search.windows.net",
   "...")
);

var memory = memoryBuilder.Build();

const string MemoryCollectionName = "aboutMe";

await memory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info5", text: "My family is from New York");

var questions = new[]
{
    "what is my name?",
    "where do I live?",
    "where is my family from?",
    "where have I travelled?",
    "what do I do for work?",
};


#pragma warning disable SKEXP0050

// TextMemoryPlugin provides the "recall" function
kernel.ImportPluginFromObject(new TextMemoryPlugin(memory));


const string skPrompt = @"
ChatBot can have a conversation with you about any topic.
It can give explicit instructions or say 'I don't know' if it does not have an answer.

Information about me, from previous conversations:
- {{$fact1}} {{recall $fact1}}
- {{$fact2}} {{recall $fact2}}
- {{$fact3}} {{recall $fact3}}
- {{$fact4}} {{recall $fact4}}
- {{$fact5}} {{recall $fact5}}

Chat:
{{$history}}
User: {{$userInput}}
ChatBot: ";

var chatFunction = kernel.CreateFunctionFromPrompt(skPrompt, new OpenAIPromptExecutionSettings { MaxTokens = 200, Temperature = 0.8 });

#pragma warning disable SKEXP0050

var arguments = new KernelArguments();

arguments["fact1"] = "What my name?";
arguments["fact2"] = "where do I live?";
arguments["fact3"] = "where is my family from?";
arguments["fact4"] = "where have I travelled?";
arguments["fact5"] = "what do I do for work?";

arguments[TextMemoryPlugin.CollectionParam] = MemoryCollectionName;
arguments[TextMemoryPlugin.LimitParam] = "2";
arguments[TextMemoryPlugin.RelevanceParam] = "0.8";

var history = "";
arguments["history"] = history;
Func<string, Task> Chat = async (string input) => {
    // Save new message in the kernel arguments
    arguments["userInput"] = input;

    // Process the user message and get an answer
    var answer = await chatFunction.InvokeAsync(kernel, arguments);

    // Append the new interaction to the chat history
    var result = $"\nUser: {input}\nChatBot: {answer}\n";

    history += result;
    arguments["history"] = history;

    // Show the bot response
    Console.WriteLine(result);
};


while (true)
{
    Console.WriteLine("Digite uma Pergunta");
    var question = Console.ReadLine();
    if (question != null)
    {
        await Chat(question);
    }

}





wdossantos_47-1729597372637.png

console e execução respondendo a pergunta “What my name”?



Pacotes utilizados:




<PackageReference Include="Microsoft.SemanticKernel" Version="1.26.0" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.AzureAISearch" Version="1.6.3-alpha" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.6.3-alpha" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />

Referências​

  1. azure-search-openai-demo
  2. azure-search-openai-demo-csharp
  3. Azure/Vector-Search-AI-Assistant at cognitive-search-vector (github.com)
  4. Troubleshoot Azure Developer CLI | Microsoft Learn
  5. Usar seus próprios dados com o Serviço OpenAI do Azure — Azure OpenAI | Microsoft Learn
  6. Indexing documents for the Chat App
  7. AzureAIServicesLandingZone
  8. Create AI agents with Semantic Kernel | Microsoft Learn
  9. Using the out-of-the-box prompt template language | Microsoft Learn
  10. Azure OpenAI Service model versions — Azure OpenAI | Microsoft Learn
  11. GUEST POST: Getting Started with Semantic Kernel for LangChain users | Semantic Kernel (microsoft.com)
  12. Introducing API Manifest Plugins for Semantic Kernel | Semantic Kernel (microsoft.com)

Continue reading...
 
Back
Top