Com a crescente demanda por soluções inteligentes, integrar assistentes de IA em aplicações tornou-se uma oportunidade valiosa para desenvolvedores. Para isso, a OpenAI ampliou suas capacidades com a API Assistants, permitindo a criação de assistentes personalizados com funcionalidades avançadas. Neste artigo, exploraremos como utilizar essa API, desde a configuração inicial até exemplos práticos.

Quais as diferenças entre a API do ChatGPT e a API Assistants?

A API Assistants e a API tradicional do ChatGPT compartilham a base tecnológica dos modelos avançados da OpenAI, mas possuem abordagens distintas quanto à funcionalidade e ao uso:

Personalização e instruções específicas:

  • ChatGPT: possui um foco mais direto em responder perguntas e participar de conversas com base nas mensagens enviadas.
  • Assistants: permite configurar assistentes com instruções específicas para definir a personalidade e os limites do assistente, ajustando-o para diferentes casos de uso.

Threads persistentes:

  • ChatGPT: não armazena o histórico de conversas diretamente.
  • Assistants: trabalha com Threads que armazenam o histórico de mensagens, possibilitando a continuidade das interações mesmo após múltiplas consultas.

Manipulação de arquivos:

  • ChatGPT: não interage diretamente com arquivos.
  • Assistants: suporta o acesso e manipulação de arquivos em diversos formatos, além de citar arquivos e criar novos durante a execução de tarefas.

Configuração inicial

Para fins didáticos, utilizaremos aqui uma aplicação do tipo Console. Então, para começar, vamos organizar a estrutura básica da nossa aplicação de forma clara:

				
					public class Program
{
    private static readonly string apiKey = ""; // Substitua com sua chave de API
    private static readonly string apiUrl = "https://api.openai.com/v1";
    private static string assistantId;
    private static string threadId;

    public static async Task Main(string[] args)
    {
        try
        {
            //Implementação do nosso código.
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Erro geral: {ex.Message}");
        }
    }

    private static async Task<string> CriarAssistente()
    {
    }

    private static async Task<string> CriarThread()
    {
    } 

    private static async Task EnviarMensagem(string assistantId, string threadId, string Mensagem)
    {
    }

    private static async Task BuscarMensagens(string threadId)
    {
    }
}

				
			

Dentro da classe “Program” nós criamos quatro variáveis. Na variável “apiKey” deve ser armazenada a chave de API da OpenAI. Caso tenha dúvidas de como conseguir essa chave, você pode ver o passo a passo neste tutorial

A URL base (apiUrl) é fixa e aponta para o endpoint de interações com assistentes. A variável “assistantId” vai armazenar o id da nossa assistente e a “threadId” vai armazenar o id da thead que vamos criar.

O método principal será a “Main”, responsável por iniciar a execução do sistema e controlar o fluxo principal da interação com a API da OpenAI.

Além dela, contaremos com quatro métodos que desempenham funções específicas:

  • CriarAssistente: Este método será responsável por enviar uma requisição para a criação de um assistente virtual. Ele configurará parâmetros como instruções iniciais e o modelo que será utilizado.
  • CriarThread: Após a criação do assistente, será necessário criar uma thread associada a ele, onde as mensagens e respostas serão organizadas. Este método realiza essa tarefa.
  • EnviarMensagem: Uma vez que a thread estiver configurada, este método permitirá enviar mensagens para o assistente virtual, incluindo instruções e dados adicionais conforme necessário.
  • BuscarMensagens: Este método será responsável por recuperar as mensagens da thread, exibindo as respostas geradas pelo assistente.

Criando o assistente virtual

A primeira etapa para interagir com a API do OpenAI é criar um assistente. Vamos definir algumas configurações iniciais no método “CriarAssistente”:

				
					private static async Task<string> CriarAssistente()
{
    using var client = new HttpClient();
    var data = new
    {
        instructions = "Você é um professor de matemática que auxilia seus alunos.",
        name = "Professor",
        tools = new List<object>
        {
            new { type = "file_search" },
            new { type = "code_interpreter" }
        },
        model = "gpt-4-turbo"
    };
    //...continua
}
				
			

Aqui nós criamos uma instância de “HttpClient” para realizarmos as requisições. Após isso, montamos o objeto da nossa requisição que vai conter a instrução do que nossa assistente deve fazer, o seu nome, uma lista de ferramentas que o assistente pode usar como pesquisa de arquivos e interpretação de código. E por fim, o modelo que usaremos.

Ainda dentro do método “CriarAssistente”, vamos montar a nossa requisição e envia-la:

				
					var request = new HttpRequestMessage(HttpMethod.Post, $"{apiUrl}/assistants")
{
    Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, 
"application/json")
};
request.Headers.Add("Authorization", $"Bearer {apiKey}");
request.Headers.Add("OpenAI-Beta", "assistants=v2");

var response = await client.SendAsync(request);

				
			

Após realizar o envio da requisição, vamos ler a informação que foi retornada:

				
					var result = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(result);
return doc.RootElement.GetProperty("id").GetString();

				
			

No código acima, após realizarmos a leitura da nossa resposta, vamos armazená-la em “result”. Após isso, vamos transformar o valor recebido em um JSON para permitir o acesso estruturado aos seus dados. O código então vai navegar até a raiz do documento (RootElement) e extrair o valor associado à propriedade “id”, convertendo-o em uma string com “GetString()”.  Com isso, conseguimos capturar o id da nossa assistente.

Criando a thread de conversa

Após criar o assistente, precisamos de uma “thread” (ou conversa) onde as interações acontecerão.

				
					private static async Task<string> CriarThread()
{
    using var client = new HttpClient();
    var data = new { metadata = new { project = "AulasMatematica" } };

    //Aqui o objeto “data”  vai conter as informações do projeto que você está criando.

    var request = new HttpRequestMessage(HttpMethod.Post, $"{apiUrl}/threads")
    {
        Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json")
    };
    request.Headers.Add("Authorization", $"Bearer {apiKey}");
    request.Headers.Add("OpenAI-Beta", "assistants=v2");

    var response = await client.SendAsync(request);

    var result = await response.Content.ReadAsStringAsync();
    using var doc = JsonDocument.Parse(result);
    return doc.RootElement.GetProperty("id").GetString();
}

				
			

O código acima vai seguir o mesmo fluxo da criação da assistente, retornando o id da thread que foi criada.

Enviando mensagens para o assistente

Agora que temos o assistente e a thread criados, podemos enviar mensagens para o assistente da seguinte forma:

				
					private static async Task SendMessageAsync(string assistantId, string threadId, string mensagem)
{
    using var client = new HttpClient();

    var data = new
    {
        assistant_id = assistantId,
        instructions = "Responda apenas às perguntas referentes a matemática. Caso seja falado sobre outros assuntos, responda educadamente para voltar para assuntos relacionados a matemática.",
        additional_messages = new[] { new { role = "user", content = mensagem} }
    };
    //...continua
}
				
			

Aqui, a variável “data” guarda o “assistantId” do assistente que foi criado, junto com as instruções específicas para essa conversa que foi armazenado na propriedade “instructions”. 

Uma dica importante: durante o teste desta API ficou perceptível que apenas essas instruções são levadas em conta para influenciar as respostas. Então a indicação que fica é de adicionar as instruções detalhadas nesta propriedade.

Por fim, é adicionado uma mensagem que contém duas propriedades. A primeira é quem está enviando a pergunta, que neste caso é o “user” e o segundo campo e a mensagem que foi digitada.

Após isso, é seguido o fluxo de envio da requisição.

				
					var request = new HttpRequestMessage(HttpMethod.Post, 
$"{apiUrl}/threads/{threadId}/runs")
    {
        Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json")
    };
    request.Headers.Add("Authorization", $"Bearer {apiKey}");
    request.Headers.Add("OpenAI-Beta", "assistants=v2");

    var response = await client.SendAsync(request);

				
			

Criando um objeto para recebimento da resposta

Logo abaixo, nós vamos implementar a busca das mensagens da nossa thead, incluindo a resposta da pergunta que foi feita. E para podermos manipular esta resposta nós vamos desserializar a resposta recebida para um objeto C#. Então, em um arquivo separado adicione o seguinte código:

				
					public class MessageList
{
    [JsonPropertyName("data")]
    public List<Message> Data { get; set; }
}

public class Message
{
    [JsonPropertyName("role")]
    public string Role { get; set; }

    [JsonPropertyName("content")]
    public List<Content> Content { get; set; }
}

public class Content
{
    [JsonPropertyName("text")]
    public Text Text { get; set; }
}

public class Text
{
    [JsonPropertyName("value")]
    public string Value { get; set; }
}

				
			

O JSON retornado possui diversos outros campos, mas nos objetos acima nós vamos capturar apenas as informações que são necessárias para processarmos a nossa resposta.

Recebendo as respostas do assistente

Agora vamos implementar uma função para receber as mensagens do assistente. Ela recupera a lista de mensagens da thread e as exibe no console.

				
					private static async Task BuscarMensagens(string threadId)
{
    using var client = new HttpClient();

    var request = new HttpRequestMessage(HttpMethod.Get, $"{apiUrl}/threads/{threadId}/messages");
    request.Headers.Add("Authorization", $"Bearer {apiKey}");
    request.Headers.Add("OpenAI-Beta", "assistants=v2");

    var response = await client.SendAsync(request);
    response.EnsureSuccessStatusCode();

    var result = await response.Content.ReadAsStringAsync();

    var messageList = JsonSerializer.Deserialize<MessageList>(result);

    Console.Clear();
    if (messageList != null && messageList.Data != null)
    {
        messageList.Data.Reverse();
        foreach (var message in messageList.Data)
        {
            var contentText = message.Content[0]?.Text?.Value;
            if (!string.IsNullOrEmpty(contentText))
            {
                Console.WriteLine($"[{message.Role.ToUpper()}]: {contentText}");
            }
        }
    }
}

				
			

No código acima, nós vamos realizar a requisição para o endpoint que recebe todas as mensagens da thead. Após isso, a resposta será desserealizada para o objeto C# que criamos anteriormente. 

Em seguida, vamos limpar o nosso console, verificar se o objeto é nulo, reverter a lista para mostrar as mensagem da mais antiga para a mais recente e por fim, percorremos essa lista de mensagens apresentando na tela quem escreveu a mensagem (user ou assistent) e a mensagem.

Criando o fluxo de interações

Agora que implementamos as quatro ações da API, vamos para a “Main” para criarmos o fluxo de interação com o usuário. O usuário pode digitar mensagens que serão enviadas ao assistente, e as respostas serão exibidas na tela.

				
					 public static async Task Main(string[] args)
{
    try
    {
        assistantId = await CriarAssistente();
        threadId = await CriarThread();

        Console.WriteLine("Assistente criado com ID: " + assistantId);
        Console.WriteLine("Thread criada com ID: " + threadId);

				
			

No código acima, quando iniciamos a nossa aplicação, ela primeiro acessa o método “CriarAssistente()” e salva o ID retornado na variável “assistantId”, que criamos logo no início. Esse ID representa o assistente que será utilizado nas próximas operações.

Na sequência, o programa chama o método “CriarThread()” para criar uma nova thread de comunicação. O ID gerado por esse processo é armazenado na variável “threadId”, que também declaramos anteriormente.

Depois disso, exibimos no console os IDs do assistente e da thread para confirmar que eles foram criados com sucesso.

				
					 bool continuar = true;

while (continuar)
{
    Console.Write("\nDigite sua mensagem (ou 'sair' para encerrar): ");
    string userMessage = Console.ReadLine();

    if (userMessage?.ToLower() == "sair")
    {
        continuar = false;
        break;
    }

    await EnviarMensagem(assistantId, threadId, userMessage);

				
			

Com o assistente e a thread criados, o programa entra em um loop contínuo para permitir a interação com o usuário.

Dentro desse laço, o usuário digita uma mensagem. Se o texto digitado for “sair”, o programa altera a variável continuar para false, encerrando a execução. Se a mensagem não for “sair”, o programa a envia para o assistente utilizando o método “EnviarMensagem”.

				
					            
            Console.WriteLine("Processando ...");
    
            await Task.Delay(TimeSpan.FromSeconds(5));

            await BuscarMensagens(threadId);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Erro geral: {ex.Message}");
    }
}

				
			
Em seguida, ele exibe a mensagem “Processando …”, aguarda por 5 segundos (Task.Delay) e busca possíveis respostas da thread usando o método “BuscarMensagens”. Esse tempo é necessário para que a API possa processar a pergunta, criar a resposta e só então ela ficar disponível para quando as mensagens forem buscadas. Agora que a aplicação foi totalmente configurada, podemos iniciá-la e realizar um teste:

Acelere a sua carreira conosco!

Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET StartClique aqui

Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET ExpertClique aqui

Conclusão

Se você chegou até aqui, parabéns por concluir mais esse aprendizado técnico! O próximo passo é continuar testando e ajustando o seu assistente para explorar todo o potencial da inteligência artificial em suas aplicações.