DEV Community

Cover image for Como integrar ASP.NET + ChatGPT
Danilo Silva
Danilo Silva

Posted on

Como integrar ASP.NET + ChatGPT

Olá. Neste artigo, vamos ver como podemos conectar uma API em Asp.NET Core com a API do ChatGPT.

Introdução

Todos os modelos de IA generativas que estão operando de maneira madura, ou seja, possuem algum site ou interface para que as pessoas possam fazer uso de seus modelos, costumar ter algum tipo de driver, SDK ou framework para conexão com diversas linguagens. O C# não foge disso, mas, fazer uso da API desses modelos pode ser ainda mais simples.

Vamos ver como fazer isso, mas não apenas fazer uma conexão simples, e sim com um exemplo prático de interação com a base de dados.

Cenário da Aplicação

Para ilustrar um uso dessa API, vamos criar um cenário de um e-commerce de uma grande loja de variedades. Imagine um site onde você possa pedir sugestões de presentes para um determinado público. Uma conexão direta com o ChatGPT facilmente poderia fazer isso, mas, você não deseja que a resposta encaminhe seu usuário para outro site ou sugira um produto que você não venda. Não seria intuitivo.

Talvez, clonar e trabalhar no seu próprio modelo de IA iria resolver isso, mas vamos buscar uma abordagem mais simples.

Vamos trabalhar com uma API simples com arquitetura em camadas, trabalhando apenas com uma única tabela chamada Categories, para armazenar as categorias de produtos que temos no site. Iremos utilizar o mongodb, por ser mais leve para você clonar o projeto e rodar.

Projeto inicial

Você pode clonar essa versão inicial e subir um container com o comando:

docker compose up -d --build
Enter fullscreen mode Exit fullscreen mode

E então rodar o projeto com o comando:

dotnet run
Enter fullscreen mode Exit fullscreen mode

Você poderá então acessar o endereço na rota GET /categories e conferir as informações do banco. Este projeto possui um arquivo mongo-init.js para carregar o mongodb com algumas categorias de produtos.

rota categories

Portanto, vamos utilizar essas categorias para personalizar a resposta que o ChatGPT pode nos entregar.

Obtendo uma chave de API

Quanto à conexão com ChatGPT, entenda que o mesmo é pago e solicita uma chave de API. Você pode carregar um saldo mínimo para fazer uso ou verificar se a sua conta possui um saldo de demonstração. Para isso:

1 - Acesse o endereço da Plataforma da OpenAI e clique em Log in ou Sign up

Image description

2 - Faça login com a sua forma desejada

3 - Clique no canto superior direito, então em Your Profile. Após isso, você verá uma página similar à imagem. Então clique na aba User API Keys.

tela profile

4 - Atualmente, o ChatGPT está retirando as chaves de APIs associadas a usuários e solicitando a criação de chaves associadas a projetos. Portanto, clique em View project API keys.

Image View project API keys

5 - Nesta tela, clique no canto superior direito em Create new secret key. Uma janela modal irá aparecer, onde você pode dar um nome para sua chave e então clicar em Create secret key.

Image create new key

6 - A janela será atualizada agora mostrando a sua chave de API. Clique no botão Copy e cole a sua chave em algum editor.

Image chave criada

⚠️ Essa é a única vez onde você terá acesso à chave pelo site da OpenAI. Portanto, é extremamente importante salvar em um local seguro.

Funcionamento da API do ChatGPT

Para fazer com que nosso projeto faça chamadas na API do ChatGPT, devemos verificar o seu funcionamento na documentação oficial, mas podemos simplificar a explicação.

Para o uso de modelos de texto, a chamada mais básica deverá ser:

/POST https://api.openai.com/v1/chat/completions

{
    "model": "gpt-4",
    "messages": []
}
Enter fullscreen mode Exit fullscreen mode

Onde model indica o modelo de gpt a ser utilizado e messages é um array de mensagens a serem enviadas. As mensagens seguem o seguinte objeto:

{
   "role": "user",
   "content": "Olá ia. Como vai?"
}
Enter fullscreen mode Exit fullscreen mode

Dentre essas mensagens, o atributo role indica o papel da pessoa autora daquela mensagem, tal como se fosse uma conversa. Dentre deste atributo, iremos preenchê-lo com 03 tipos:

  • user: essa role representa a pessoa que está perguntando ao chatgpt. Logo, quando utilizamos o mesmo, estamos assumindo o papel de user.
  • assistant: essa role representa as mensagens do ChatGPT, ou seja, as suas criações.
  • system: essa role representa a pessoa desenvolvedora que irá dar instruções para o modelo e alterar o tom da conversa.

Portanto, se fizermos, por exemplo, uma requisição com o seguinte corpo:

{
  "model": "gpt-4",
  "messages": [
         {
        "role": "system",
        "content": "Crie respostas curtas"
      },
            {
        "role": "user",
        "content": "Olá ia. Como vai?"
      }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Obteremos uma resposta no seguinte formato:

{
    "id": "chatcmpl-9g...",
    "object": "chat.completion",
    "created": 1719955224,
    "model": "gpt-4-0613",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "Olá! Estou funcionando perfeitamente. Como posso ajudar você hoje?"
            },
            "logprobs": null,
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 25,
        "completion_tokens": 19,
        "total_tokens": 44
    },
    "system_fingerprint": null
}
Enter fullscreen mode Exit fullscreen mode

Se analisarmos o objeto de retorno, temos um json com uma chave chamada choices que receberá um array de mensagens. Esse array, para as chamadas simples que iremos fazer, irão retornar um objeto apenas. Esse objeto terá uma chave message com a role: assistant e um content com a nossa mensagem. Entender o formato desse json é essencial para o código.

Criando DTOs

Iremos criar DTOs para representar a request e a response na API do ChatGPT.

namespace dotnet_gpt.DTO;

public class RequestGPT
{
    public string? model { get; set; }
    public List<RequestGPTMessage>? messages { get; set; }
}

public class RequestGPTMessage
{
    public string? role { get; set; }
    public string? content { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

A classe RequestGPT terá os atributos de model e messages para o primeiro nível do json. Já o atributo messages será uma coleção de um segundo DTO chamado RequestGPTMessage que terá o formato do objeto de cada mensagem.

Além disso, iremos criar DTOs para representar o futuro funcionamento da nossa API. No corpo de requisição, teremos um json com um único atributo chamado message enquanto a resposta será a mesma message do ChatGPT mais a categoria de produto do nosso banco de dados recomendada.

public record AdviceRequest
{
    public string? message { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
public record AdviceReponse : AdviceRequest
{
    public Category? Category { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Criando o serviço

Iremos agora criar um serviço capaz de realizar chamadas HTTP. Mas para podermos trabalhar com os objetos json com mais facilidade e realizar a requisição, iremos instalar algumas dependências com os comandos:

dotnet add package Newtonsoft.Json
dotnet add package System.Net.Http
Enter fullscreen mode Exit fullscreen mode

Ou qualquer outra forma de instalar dependências pelo Nuget.

Nosso próximo passo será criar uma classe chamada IAService que irá receber a camada repository e o objeto HttpClient (responsável pela chamada na API do ChatGPT)

public class IAService
{
    protected readonly HttpClient _client;
    private readonly ICategoryRepository _categoryRepository;

    public IAService(ICategoryRepository categoryRepository, HttpClient client)
    {
        _categoryRepository = categoryRepository;
        _client = client;
    }
}
Enter fullscreen mode Exit fullscreen mode

Além disso, iremos criar uma variável para armazenar o token.

 private string token = "seu-token-jwt";
Enter fullscreen mode Exit fullscreen mode

Para facilitar, estamos colocando o token em uma string hard-coded mas em sua aplicação, você pode decidir entre variáveis de ambiente ou configurações do appsettings.json.

Iremos criar um método assíncrono para receber a request do corpo de requisição e retornar o corpo de response.

public async Task<AdviceReponse> GetAdvice(AdviceRequest request)
{
}
Enter fullscreen mode Exit fullscreen mode

Neste método, iremos começar buscando os nomes das categorias da camada repository e concatenar em uma mesma string. Usaremos essa string para informar ao ChatGPT, quais são as categorias de produtos existentes em nossa loja.

public async Task<AdviceReponse> GetAdvice(AdviceRequest request)
{
    List<Category> categories = _categoryRepository.GetCategories();
    string categoriesNames = "";
    foreach(var category in categories)
    {
        categoriesNames += category.Name + ", ";
    }
}
Enter fullscreen mode Exit fullscreen mode

Então podemos criar um objeto com o corpo de requisição para a API da OpenAI.

RequestGPT requestGPT = new RequestGPT {
    model =  "gpt-4",
    messages = new List<RequestGPTMessage>()
};
Enter fullscreen mode Exit fullscreen mode

Neste objeto, adicionaremos 03 mensagens. A primeira mensagem será do tipo system e irá indicar o comportamento das respostas a serem geradas.

requestGPT.messages.Add(new RequestGPTMessage {
    role = "system",
    content = "Se comporte como um assistente para sugerir produtos de um e-commerce."
});
Enter fullscreen mode Exit fullscreen mode

A segunda mensagem também será do tipo system e irá indicar quais são as categorias que o ChatGPT irá considerar ao recomendar um presente.

requestGPT.messages.Add(new RequestGPTMessage {
    role = "system",
    content = "Nesse e-commerce, as categorias de produtos são "+ categoriesNames+"sugira uma dessas categorias baseando-se na solicitação. Não sugira produtos, apenas a categoria com um texto breve de 20 palavras"
});
Enter fullscreen mode Exit fullscreen mode

Note que nesta mensagem, estamos concatenando um texto informativo com as categorias existentes no mongodb.

Por último, iremos indicar a mensagem que a pessoa usuária irá digitar ao pedir uma recomendação de presente.

requestGPT.messages.Add(new RequestGPTMessage {
    role = "user",
    content = request.message
});
Enter fullscreen mode Exit fullscreen mode

Podemos então converter esse objeto para json com a biblioteca Newtonsoft.Json e fazer uma request com o objeto HttpClient passando esse corpo de requisição e o token da API.

var requestBody = JsonConvert.SerializeObject(requestGPT);
var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"https://api.openai.com/v1/chat/completions")
{
    Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};

httpRequest.Headers.Add("Accept", "application/json");
httpRequest.Headers.Add("User-Agent", "seu-agent");
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

var response = await _client.SendAsync(httpRequest);
if(!response.IsSuccessStatusCode) return default!;
Enter fullscreen mode Exit fullscreen mode

Com a response, podemos então converter a resposta da requisição para um DTO que consiga interpretar os dados e obter o texto da primeira resposta do ChatGPT

var result = await response.Content.ReadFromJsonAsync<ResponseGPT>();
var resultText = result!.choices!.First().message!.content!.ToString();
Enter fullscreen mode Exit fullscreen mode

Neste ponto, teremos na variável resultText, a mensagem do GPT4. Como o mesmo indica o texto da categoria escolhida, podemos buscar o objeto do banco de dados referente a essa escolha e montar o objeto de retorno.

Category? choiceCategory = null;
foreach(var category in categories)
{
    if (resultText.IndexOf(category.Name) > -1)
    {
        choiceCategory = category;
    }
}


AdviceReponse adviceReponse = new AdviceReponse { message = resultText, Category = choiceCategory };

return adviceReponse;
Enter fullscreen mode Exit fullscreen mode

Ao final dessa construção, teremos a seguinte camada service

public class IAService
{
    protected readonly HttpClient _client;
    private readonly ICategoryRepository _categoryRepository;
    private string token = "sk-proj-6TXNCUChRKjLdPupBYSET3BlbkFJIrrztu24plcHou3YduWv";
    public IAService(ICategoryRepository categoryRepository, HttpClient client)
    {
        _categoryRepository = categoryRepository;
        _client = client;
    }

    public async Task<AdviceReponse> GetAdvice(AdviceRequest request)
    {
        List<Category> categories = _categoryRepository.GetCategories();
        string categoriesNames = "";
        foreach(var category in categories)
        {
            categoriesNames += category.Name + ", ";
        }

        RequestGPT requestGPT = new RequestGPT {
            model =  "gpt-4",
            messages = new List<RequestGPTMessage>()
        };

        requestGPT.messages.Add(new RequestGPTMessage {
            role = "system",
            content = "Se comporte como um assistente para sugerir produtos de um e-commerce."
        });

        requestGPT.messages.Add(new RequestGPTMessage {
            role = "system",
            content = "Nesse e-commerce, as categorias de produtos são "+ categoriesNames+"sugira uma dessas categorias baseando-se na solicitação. Não sugira produtos, apenas a categoria com um texto breve de 20 palavras"
        });

        requestGPT.messages.Add(new RequestGPTMessage {
            role = "user",
            content = request.message
        });

        var requestBody = JsonConvert.SerializeObject(requestGPT);
        var httpRequest = new HttpRequestMessage(HttpMethod.Post, $"https://api.openai.com/v1/chat/completions")
        {
            Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
        };
        httpRequest.Headers.Add("Accept", "application/json");
        httpRequest.Headers.Add("User-Agent", "seu-agent");
        _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        var response = await _client.SendAsync(httpRequest);

        if(!response.IsSuccessStatusCode) return default!;

        var result = await response.Content.ReadFromJsonAsync<ResponseGPT>();
        var resultText = result!.choices!.First().message!.content!.ToString();

        Category? choiceCategory = null;
        foreach(var category in categories)
        {
            if (resultText.IndexOf(category.Name) > -1)
            {
                choiceCategory = category;
            }
        }


        AdviceReponse adviceReponse = new AdviceReponse { message = resultText, Category = choiceCategory };
        return adviceReponse;
    }
}
Enter fullscreen mode Exit fullscreen mode

Criando a controller

Por fim, para criarmos a controller, precisamos receber em sua injeção de dependências, o IAService criado anteriormente, e criar um método que receba do corpo de requisição, o objeto AdviceRequest que iremos enviar em nosso serviço.

[ApiController]
[Route("[controller]")]
public class AdviceController : ControllerBase
{
    private readonly IAService _iaService;
    public AdviceController(IAService iaService)
    {
        _iaService = iaService;
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] AdviceRequest adviceRequest)
    {
        return Ok(await _iaService.GetAdvice(adviceRequest));
    }
}
Enter fullscreen mode Exit fullscreen mode

Após essa etapa, devemos garantir as injeções de dependências com:

builder.Services.AddScoped<IContextConnection, ContextConnection>();
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
builder.Services.AddHttpClient<IAService>();
Enter fullscreen mode Exit fullscreen mode

Testando a aplicação

Para testar a aplicação, podemos chamar a rota POST /advice com um corpo de requisição com uma solicitação.

insomnia

Podemos ver que, enviamos a solicitação ao ChatGPT e graças à mensagem do tipo system, recebemos um texto indicando qual a categoria de produto que o mesmo melhor se enquadra.

Analisando o texto da resposta do ChatGPT, podemos coletar o registro da categoria no banco de dados e retornar para o front-end.

Conclusão

Você aprendeu como podemos utilizar o HttpClient do C# para fazer uma requisição na API do ChatGPT.

Basicamente, se nossa aplicação tivesse nesse momento, uma lista de produtos, poderíamos responder com os produtos mais vendidos nessa categoria. Portanto, criamos uma forma de fazer bom uso da inteligência artificial sem precisar treinar um modelo do zero.

Para facilitar a construção, deixo aqui o repositório completo.

Danilo Silva

Desenvolvedor de software experiente em boas práticas, clean code e no desenvolvimento de software embarcado e de integração com hardwares de controle e telecomunicação.

Linkedin
Github
Twitter
E-mail

Top comments (0)