OpenAI

Aprenda como incorporar os recursos de inteligência artificial oferecidos pela OpenAI e pelo Microsoft Azure OpenAI Service nos microsserviços, utilizando a plataforma Devprime através do recurso de extensibilidade no Adapter de Extensions.

Devprime using OpenAI

Introdução

A plataforma Devprime acelera a produtividade do desenvolvedor de software, oferecendo um projeto completo de arquitetura de software, componentes com comportamentos inteligentes, aceleradores para implementação de código e atualizações com novos recursos.

Neste artigo, abordaremos a utilização do Adapter de Extensions presente na arquitetura Devprime, que permite adicionar comportamentos adicionais por meio de componentes externos baseados na tecnologia do NuGet. Isso proporciona a possibilidade de expandir as funcionalidades da plataforma Devprime e aderir aos padrões de arquitetura de software, melhorando a manutenibilidade, reuso e testabilidade.

Durante esse artigo utilizaremos o componente nuget Azure.AI.OpenAI que incopora o acesso a API do Microsoft Azure OpenAI e da OpenAI diretamente e você poderá testar utilizando as credenciais de um desses serviços.

Cheklist e preperação do ambiente inicial:

Exemplo pronto com todos os passos do artigo

Este artigo inclui um projeto completo que demonstra o recurso discutido. Você pode optar por fazer o download seguindo as etapas abaixo ou simplesmente prosseguir para os próximos itens.

  1. Efetue um clone no repo
    git clone https://github.com/devprime/examples.git
  2. Entre na pasta
    cd examples/extensions/openai/basic
  3. Atualize a sua licença Devprime
    dp stack
  4. Atualize as configurações do MongoDB / RabbitMQ no arquivo src/App/appsettings.json
  5. Atualize as credenciais do Azure OpenAI / OpenAI no arquivo src/App/appsettings.json
  • Localize em DevPrime_Custom os itens:
    ai.url / ai.credential / ai.deploymentmodel /
  1. Execute o microsserviço

Criando um microserviço para utilizar no exemplo

O primeiro passo é criar um novo microsserviço para que possamos realizar customizações no Adapter de Extensions, adicionando o componente externo NuGet e preparando-o para interagir com a API da OpenAI. O nome deste microsserviço será definido como “ms-ai”, conforme demonstrado no comando abaixo.
dp new ms-ai --state mongodb --stream rabbitmq

Após a criação do novo microsserviço entre na pasta do projeto “ms-ai” e já poderá visualizar todas as implementações pelo Visual Studio Code conforme demonstrado no artigo relacionado a criação do primeiro microsserviço.

Adicionando uma regra de negócio

As regras de negócio na arquitetura da plataforma Devprime são baseadas em Domain-Driven Design (DDD), e para avançarmos, é necessário adicionar uma classe Aggregate Root dentro do projeto Domain. Para facilitar esse procedimento, utilizaremos o comando abaixo disponível no Devprime CLI.
dp add aggregate AI

Visualize a nova classe criada pelo Visual Studio Code.
code src/Core/Domain/Aggregates/AI/AI.cs

Modifique a classe AI adicionando uma propriedade chamada “Prompt” conforme exemplo abaixo.

1
2
3
4
5
namespace Domain.Aggregates.AI;
public class AI : AggRoot
{
         public string Prompt { get; private set; }
}

Utilizando os aceleradores de código da plataforma Devprime

Agora que você já implementou uma regra de negócio, vamos usar o acelerador do Devprime CLI para gerar o código necessário e iniciar o microsserviço com base na regra de negócio inicial que você forneceu.

Execute o comando a seguir e digite A para avançar nas implementações:

1
dp init

Após a conclusão deste comando, você já terá as implementações básicas do seu microsserviço e utilizaremos como referência para avançar para o próximo passo, que envolve a incorporação do componente OpenAI no Adapter de Extension.

Adicionando a extension Intelligence

Neste ponto, iniciaremos os procedimentos para habilitar uma extensão de terceiros fornecida como um componente NuGet na plataforma Devprime, seguindo padrões de desenvolvimento que garantem a manutenibilidade e desacoplamento.

Para acelerar esse processo, usaremos o comando abaixo, que implementa a nova extensão no serviço de aplicação por meio de uma interface e injeção de dependência, e construirá a implementação inicial necessária no adaptador.

Execute o comando abaixo:
dp add extensions IntelligenceService

1
2
3
4
5
6
7
8
9
# created
/src/Core/Application/Interfaces/Adapters/Extensions/IIntelligenceService.cs
/src/Adapters/Extensions/IntelligenceService/IntelligenceService.cs
# modified
/src/Core/Application/Interfaces/Adapters/Extensions/IExtensions.cs 
/src/Adapters/Extensions/Extensions.cs 
/src/Adapters/Extensions/GlobalUsings.cs 
/src/App/App.cs
/src/App/GlobalUsings.cs

A classe IntelligenceService e a sua interface IIntelligenceService implementam as integrações com a biblioteca OpenAI. Por outro lado, a classe Extensions e sua interface IExtensions atuam como proxies, permitindo que o contexto de execução do Devprime Pipeline acesse todas as extensões disponíveis na aplicação por meio da injeção de dependência.

Adicionando a referência ao componente da OpenAI

Adicione a referência do componente NuGet Azure.AI.OpenAI ao arquivo de projeto do Adapter de Extensions. Certifique-se de verificar o portal NuGet em busca da versão mais atual e ajuste a versão, se necessário:

Execute o comando abaixo em uma única linha:

1
2
dotnet add src/Adapters/Extensions/DevPrime.Extensions.csproj package 
Azure.AI.OpenAI --version 1.0.0-beta.17

Agora que você acabou de adicionar um novo componente aproveite e efetue um build do microsserviço para se certificar
que está tudo funcionando corretamente com essa nova dependência.

Execute o comando:

1
dotnet build

Implementando a integração com o componente da OpenAI

Neste momento, vamos implementar a integração com o componente OpenAI dentro do Adapter de Extensions. O primeiro passo é modificar a interface para incluir um método chamado “Conversation” e, em seguida, implementar o código que realiza a chamada à API do OpenAI e retorna o resultado em um formato que pode ser transportado para o contexto de nossa aplicação.

Abra Abra a interface IIntelligenceService no Visual Studio Code.
code src/Core/Application/Interfaces/Adapters/Extensions/IIntelligenceService.cs

Substitua pelo código abaixo:

1
2
3
4
5
namespace Application.Interfaces.Adapters.Extensions;
public interface IIntelligenceService
{
     string Conversation(string prompt);
}

Abra Abra a classe IntelligenceService no Visual Studio Code.
code src/Adapters/Extensions/IntelligenceService/IntelligenceService.cs

Substitua pelo código abaixo:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using System.Collections.Generic;
using Azure;
using Azure.AI.OpenAI;

namespace DevPrime.Extensions.IntelligenceService
{
    // Main class implementing the IIntelligenceService interface
    public class IntelligenceService : DevPrimeExtensions, IIntelligenceService
    {
        // Private properties to store credentials and intelligence service configurations
        private string Credential { get; set; }
        private string Url { get; set; }
        private string DeploymentModel { get; set; }
        private string Platform { get; set; }


        // Constructor initializing properties based on DpExtensions settings
        public IntelligenceService(IDpExtensions dp) : base(dp)
        {
            Credential = Dp.Settings.Default("ai.credential");
            Url = Dp.Settings.Default("ai.url");
            DeploymentModel = Dp.Settings.Default("ai.deploymentmodel");
            Platform = ValidatePlatformSetting(Dp.Settings.Default("ai.platform") ?? "azure");
        }

        // Method to start a conversation with the AI service
        public string Conversation(string prompt)
        {
            // Using DpExtensions pipeline to execute conversation logic
            return Dp.Pipeline(ExecuteResult: () =>
            {
                // Log to indicate the start of interaction with OpenAI service
                Dp.Observability.Log("Starting OpenAI");

                // Settings for interacting with OpenAI service
                var chatCompletionsOptions = new ChatCompletionsOptions()
                {

                    Temperature = (float)0.7,
                    MaxTokens = 800,
                    NucleusSamplingFactor = (float)0.95,
                    FrequencyPenalty = 0,
                    PresencePenalty = 0,
                    DeploymentName = DeploymentModel
                };

                // Creating OpenAI client with provided credentials
                
                var openAiClient = Platform == "azure" ?
                new OpenAIClient(new System.Uri(Url), new AzureKeyCredential(Credential)) :
                new OpenAIClient(Credential);


                Dp.Observability.Log($"Starting OpenAI using platform {Platform}");

                // Adding user's message to the conversation
                // ChatRequestSystemMessage / ChatRequestUserMessage / ChatRequestAssistantMessage  
                chatCompletionsOptions.Messages.Add(new ChatRequestUserMessage(prompt));

                // Preparation for interaction with OpenAI service
                Response<ChatCompletions> response = openAiClient.GetChatCompletions(chatCompletionsOptions);

                // Processing OpenAI response
                var airesponse = response.GetRawResponse().Content.ToString();
                var root = System.Text.Json.JsonSerializer.Deserialize<Root>(airesponse);
                Message message = root.choices[0].message;
                var airesult = message.content;

                // Log to indicate the end of interaction with OpenAI service
                Dp.Observability.Log("Finalizing OpenAI");

                // Returning OpenAI response
                return airesult;
            });
        }

        // Definition of inner classes to structure OpenAI response
        public class ContentFilterResults
        {
            public bool filtered { get; set; }
            public string severity { get; set; }
        }

        public class PromptFilterResults
        {
            public int prompt_index { get; set; }
            public ContentFilterResults content_filter_results { get; set; }
        }

        public class Message
        {
            public string role { get; set; }
            public string content { get; set; }
            public ContentFilterResults content_filter_results { get; set; }
        }

        public class Choice
        {
            public int index { get; set; }
            public string finish_reason { get; set; }
            public Message message { get; set; }
            public ContentFilterResults content_filter_results { get; set; }
        }

        public class Usage
        {
            public int prompt_tokens { get; set; }
            public int completion_tokens { get; set; }
            public int total_tokens { get; set; }
        }

        public class Root
        {
            public string id { get; set; }
            public string _object { get; set; }
            public int created { get; set; }
            public string model { get; set; }
            public List<PromptFilterResults> prompt_filter_results { get; set; }
            public List<Choice> choices { get; set; }
            public Usage usage { get; set; }
        }

        private string ValidatePlatformSetting(string setting)
        {
            Dp.Observability.Log($"[Extensions]ValidatePlatformSetting:{setting}");
            var platform = setting.ToLower();
            if (platform != "azure" && platform != "openai")
            {
                Dp.Observability.Log($"[Extensions]ERROR: Platform must be either 'azure' or 'openai'.");
                throw new System.Exception("Platform must be either 'azure' or 'openai'.");
            }
            return platform;
        }
    }
}

Adicionando váriaveis de ambiente para uso no OpenAI

Na implementação anterior nós utilizamos o comando Dp.Settings.Default("ai.credential") para obter o parametro de uma variavel de ambiente definida no projeto local no arquivo “src/App/appsettings.json”. Abra o bloco “DevPrime_Custom” seguindo exemplo abaixo adicionando a URL do servíco de OpenAI, a credencial e o modelo de deployment e por últimos nós devemos informar no item “ai.platform” o valor openai ou azure.

Abra e edite pelo Visual Studio Code
code src/App/appsettings.json

1
2
3
4
5
6
7
  "DevPrime_Custom": {
    "stream.processevents": "aievents",
    "ai.url": "put azure url or openai",
    "ai.credential": "put azure key or openai",
    "ai.deploymentmodel": "put deployment name",
    "ai.platform": "change to openai or azure"
  }

Implementado a chamada ao adapter de extension

O primeiro ponto de contato com a nova funcionalidade disponibilizada pela nova Extension do OpenAI é o Handler que nesse contexto utilizaremos o CreateAIEventHandler que tem o papel de interceptar um evento de dominio e mediar a integração com os recursos tecnologicos da plataforma. Para que tenha suporte ao Extensions nós adicionamos a herança para a classe EventHandlerWithExtensions além de adicionarmos a interface IExtensions.

É importante observar que após as implementações anteriores agora nós temos a dispoção dentro da arquitetura Devprime um novo metodo Dp.Extensions.IntelligenceService.Conversation() que executa o código externo incoporado dentro do Adapter de Extensions.

Essa nova implementação executará a chamada ao OpenAI e retornará o resultado. Para avançar abra o arquivo e substitua todo o código.

Abra o Handler pelo Visual Studio Code.
code src/Core/Application/EventHandlers/AI/CreateAIEventHandler.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
namespace Application.EventHandlers.AI;
public class CreateAIEventHandler : 
EventHandlerWithExtensions<CreateAI, IExtensions>
{
    public CreateAIEventHandler(IExtensions extensions, IDp dp) : 
    base(extensions, dp)
    {
    }
    public override dynamic Handle(CreateAI createAI)
    {
        var ai = createAI.Get<Domain.Aggregates.AI.AI>();
        var result = Dp.Extensions.IntelligenceService.Conversation(ai.Prompt);
        return result;
    }
}

Em função dessa modificação no Handler remova o arquivo de testes que não é utilizado nesse exemplo
utilizando o comando abaixo:

Removendo arquivo
rm src/Tests/Core/Application/AI/CreateAIEventHandlerTest.cs

Modificando o método ADD no Aggregate Root

Agora retornaremos à regra de negócio no Aggregate Root para realizar uma customização no método “ADD”, substituindo-o pelo código abaixo para permitir a execução do evento de domínio “CreateAI” e obter esse retorno. Esse evento será processado no Handler “CreateAIEventHandler” responsável pela interação com o Adapter de Extensions."

Abra a classe Aggregate Root no Visual Studio Code
code src/Core/domain/aggregates/AI/AI.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public virtual string Add()
 {
    var result = Dp.Pipeline(ExecuteResult: () =>
    {
        ValidFields();
        ID = Guid.NewGuid();
        IsNew = true;
        var processresult = Dp.ProcessEvent<string>(new CreateAI());
        return processresult;
    });
        return result;
    }

Modificando a Interface e Application Services

Devido à implementação no Adapter de Extension, é necessário obter o retorno do Aggregate Root no Application Services. O primeiro passo é modificar a interface IAIService conforme o exemplo abaixo.

Abra a Interface no Vsiual Studio Code
code src\Core\Application\Interfaces\Services\IAIService.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace Application.Interfaces.Services;
public interface IAIService
{
    string Add(Application.Services.AI.Model.AI command);
    void Update(Application.Services.AI.Model.AI command);
    void Delete(Application.Services.AI.Model.AI command);
    Application.Services.AI.Model.AI 
    Get(Application.Services.AI.Model.AI query);
    PagingResult<IList<Application.Services.AI.Model.AI>>
    GetAll(Application.Services.AI.Model.AI query);
}

Após a modificação da interface no passo anterior, é hora de refletir essa alteração no Application Services. Localize o método Add abaixo e faça a substituição para que o serviço de aplicação retorne o resultado do nosso Aggregate Root.

Abra o Application Services no Visual Studio Code.
code src\Core\Application\Services\AI\AIService.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    public string Add(Model.AI command)
    {
      return Dp.Pipeline(ExecuteResult: () =>
        {
            var process = command.ToDomain();
            Dp.Attach(process);
            var processresult = process.Add();
            return processresult;
        });
    }

Explorando o microsserviço integrado com o OpenAI

Ao final dessa implementação nós já podemos realizar os testes executando o microsserviço customizando com a integração do OpenAI.

Inicie o microsserviço executando um dos scripts abaixo:

1
.\run.ps1 (Windows) or ./run.sh (Linux, macOS)

Acesse o portal do swagger do microsserviço
Devprime using OpenAI

Efetue um post na API pelo Swagger preenchendo o prompt

1
2
3
4
{
  "prompt": "Explain how the Devprime platform enhances developer productivity
   in one sentence?"
}

e confira o resultado no json abaixo:

1
2
3
4
5
6
{
  "response": "The Devprime platform streamlines development workflows
   and automates repetitive tasks,   allowing developers to focus on
   writing high-quality code and delivering projects faster.",
  "success": true
}

Acompanhe o exemplo de registro de log gerado automaticamente pelo Adapter de Observability, que detalha todo o fluxo interno, desde o recebimento do POST até a integração com o OpenAI no Adapter de Extensions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[App]["Powered by DevPrime"][Version]["7.0.59"][License]["Developer"]
[App]["Start"]["ms-ai"][Configuration]["Appsettings"][AppVersion]["1.0.0.0"]
[App][Idempotency]["Disable"][RID 3b8cdd1a-48cc-44ea-9d4d-af36db35e4e4]
[State][Type "MongoDB"][Alias "State1"]["Database"]["Enable"]
[App][Tenancy]["Disable"]
[App][Observability]["Enable"][Log "Enable"][Export "Enable"][Type "seq"]
[Trace "Disable"][Metrics "Disable"]
[Security]["Disable"]
[Web]["https://localhost:5001"]["http://localhost:5000"][Host]["Production"]
[Parameters]["Appsettings"]
[Stream][Type "RabbitMQ"][Alias "Stream1"]["Enable"]
[Web]["HTTP"][AI][POST /v1/ai]
[Origin "https://localhost:5001/swagger/index.html"]
[Application][AIService][Add][RID aa13bca3-76f3-4b51-b35e-c05e24e0cad3]
[Domain][AI][Add]
[Domain][AI][ProcessEvent]["CreateAI"]
[Application][EventHandler]["CreateAIEventHandler"][Event]["CreateAI"]
[Extensions][IntelligenceService][Conversation]
[Custom][Starting OpenAI]
[Custom][Finalizing OpenAI]

Mais artigos sobre extesions na plataforma Devprime

Utilizando o SendGrid em microsserviços
Utilizando o Humanizer em microsserviços

Última modificação June 6, 2024 (224cac9f)