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-demo
2-) azd auth login
azd auth login
3-) azd up
azd up
Ao fim da execução de mais ou menos 30 minutos, pude ver esses recursos criados:
Lista de recursos criados
Visualizador de Recursos:
Mapa dos recursos
A aplicação nasce pronta:
Aplicaçã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-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.
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.
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.
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”
o endpoint esta em overview
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.
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
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"?
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.
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
lista de indexs
Search explorer
Campos do index
esse index foi gerado usando esse Text Embedding text-embedding-ada-002, que é um tipo de algoritmo de vetorização
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);
}
}
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
- azure-search-openai-demo
- azure-search-openai-demo-csharp
- Azure/Vector-Search-AI-Assistant at cognitive-search-vector (github.com)
- Troubleshoot Azure Developer CLI | Microsoft Learn
- Usar seus próprios dados com o Serviço OpenAI do Azure — Azure OpenAI | Microsoft Learn
- Indexing documents for the Chat App
- AzureAIServicesLandingZone
- Create AI agents with Semantic Kernel | Microsoft Learn
- Using the out-of-the-box prompt template language | Microsoft Learn
- Azure OpenAI Service model versions — Azure OpenAI | Microsoft Learn
- GUEST POST: Getting Started with Semantic Kernel for LangChain users | Semantic Kernel (microsoft.com)
- Introducing API Manifest Plugins for Semantic Kernel | Semantic Kernel (microsoft.com)
Continue reading...