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.
Você pode clonar essa versão inicial e subir um container com o comando:
docker compose up -d --build
E então rodar o projeto com o comando:
dotnet run
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.
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
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
.
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
.
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
.
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.
⚠️ 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": []
}
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?"
}
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?"
}
]
}
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
}
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; }
}
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; }
}
public record AdviceReponse : AdviceRequest
{
public Category? Category { get; set; }
}
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
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;
}
}
Além disso, iremos criar uma variável para armazenar o token.
private string token = "seu-token-jwt";
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)
{
}
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 + ", ";
}
}
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>()
};
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."
});
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"
});
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
});
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!;
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();
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;
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;
}
}
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));
}
}
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>();
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.
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.
Top comments (0)