Write a ChatGPT client

openai wallpaper

In this new post, I explore how to write a ChatGPT client in C# using the OpenAI API. Also, walk through the process of creating a ChatGPT client in C# that can generate human-like responses to user queries. This ChatGPT client will also allow us to retain and send conversation history to the API, making it possible to maintain context and provide more personalized responses. I will also parameterize our ChatGPT client so that we can change whether or not to include the conversation history when calling the API, which language model to use, and the nature of the response.

The ChatGPT client in action - Write a ChatGPT client
The ChatGPT client in action

As artificial intelligence continues to advance, language models such as ChatGPT have become increasingly powerful tools for natural language processing. ChatGPT is a large-scale neural language model that has been trained on massive amounts of text data and is capable of generating human-like responses to a wide variety of natural language prompts. It can be used for a range of applications, from chatbots and virtual assistants to language translation and content generation.

So, to demonstrate the write a ChatGPT client in C#, I will be creating a chatbot in a console application using C# and NET 7 and Spectre Console.

The source code of this post is available on GitHub.

Obtain an API key

First step, I have to obtain an API key from the OpenAI developer site. Go to the OpenAI developer website and register yourself.

OpenAI developer website - Write a ChatGPT client
OpenAI developer website

After the successful registration, in your account on the top right, in the menu, you have View API keys. This option allows you to see the keys you already have or create a new one.

View API keys - Write a ChatGPT client
View API keys

For example, in my API keys I can see the one I have created for this demo. If you want to create a new key, click on the button Create new secret key. A window pop up will appear with a new key. Save it because it is not possible to read it again.

OpenAI API keys view - Write a ChatGPT client
OpenAI API keys view

ChatGPT has to retain the conversation Context

Before creating our client, I will briefly look into the ChatGPT API’s default behaviour and how it influences the design of our ChatGPT client.

Here is what happens if we don’t feed the API with all of the conversation’s previous messages:

Image to show the behaviour of ChatGPT when it is not fed the conversation's previous messages.

By the time we ask our second question, the API has already forgotten what the conversation is about. But here’s what happens when we feed the API with all of the conversation’s previous messages:

Image to show the behaviour of ChatGPT when it is fed the conversation's previous messages.

By doing this, we are allowing the API to “remember” who “he” is and remember the context of the conversation. We can then design our ChatGPT C# client accordingly.

The implementation

First, I have to decide what version of the API I want to use. For this reason, I define an enum with the version available so far.

public enum OpenAIModels
{
    gpt_4,
    gpt_4_0314,
    gpt_4_32k,
    gpt_4_32k_0314,
    gpt_3_5_turbo,
    gpt_3_5_turbo_0301
}

Response

After that, I have to receive the response from ChatGTP and I refer to the documentation to create the classes.

public class ChatResponse
{
    public string? Id { get; set; }
    public string? Object { get; set; }
    public int Created { get; set; }
    public List<Choice>? Choices { get; set; }
    public Usage? Usage { get; set; }
}

public class Choice
{
    public int Index { get; set; }
    public Message? Message { get; set; }
    public string? Finish_Reason { get; set; }
}

public class Message
{
    public string? Role { get; set; }
    public string? Content { get; set; }
}

public class Usage
{
    public int Prompt_Tokens { get; set; }
    public int Completion_Tokens { get; set; }
    public int Total_Tokens { get; set; }
}

The ChatGPTClient

The SendMessage method is what we will call in order to generate our responses. This method calls the Chat method which sends our request to the API. Within the chat method, GetMessageObjects is called which retrieves either the current message only or all of the messages in the conversation. This allows the ChatGPT API to remember the context of the conversation. After the request is made, our message and the subsequent response are then saved to history using the AddMessageToHistory method.

public class ChatGPTClient
{
    #region Variables

    private readonly string chatRequestUri;
    private readonly bool includeHistoryWithChatCompletion;
    private readonly List<Message> messageHistory;
    private readonly OpenAIModels model;
    private readonly string openAIAPIKey;
    private readonly double temperature;
    private readonly double top_p;

    #endregion Variables

    public ChatGPTClient(bool includeHistoryWithChatCompletion = true,
        OpenAIModels model = OpenAIModels.gpt_3_5_turbo,
        double temperature = 1,
        double top_p = 1)
    {
        chatRequestUri = "https://api.openai.com/v1/chat/completions";
        openAIAPIKey = Environment.GetEnvironmentVariable("OpenAIAPIKey")!;
        messageHistory = new List<Message>();

        this.includeHistoryWithChatCompletion = includeHistoryWithChatCompletion;
        this.model = model;
        this.temperature = temperature;
        this.top_p = top_p;
    }

    public async Task<ChatResponse?> SendMessage(string message)
    {
        var chatResponse = await Chat(message);

        if (chatResponse != null)
        {
            AddMessageToHistory(new Message { Role = "user", Content = message });

            foreach (var responseMessage in chatResponse.Choices!.Select(c => c.Message!))
                AddMessageToHistory(responseMessage);
        }

        return chatResponse;
    }

    private void AddMessageToHistory(Message message) =>
        messageHistory.Add(message);

    private async Task<ChatResponse?> Chat(string message)
    {
        using var client = new HttpClient();
        using var request = new HttpRequestMessage(HttpMethod.Post, chatRequestUri);
        request.Headers.Add("Authorization", $"Bearer {openAIAPIKey}");

        var requestBody = new
        {
            model = GetModel(),
            temperature,
            top_p,
            messages = GetMessageObjects(message)
        };

        request.Content = new StringContent(JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");

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

        if (response.IsSuccessStatusCode)
        {
            var chatResponse = await response.Content.ReadFromJsonAsync<ChatResponse>();

            if (chatResponse != null &&
                chatResponse.Choices != null &&
                chatResponse.Choices.Any(c => c.Message != null))
                return chatResponse;
        }

        return null;
    }

    private IEnumerable<object> GetMessageObjects(string message)
    {
        foreach (var historicMessage in includeHistoryWithChatCompletion ? messageHistory : Enumerable.Empty<Message>())
        {
            yield return new { role = historicMessage.Role, content = historicMessage.Content };
        }

        yield return new { role = "user", content = message };
    }
    private string GetModel() =>
        model.ToString().Replace("3_5", "3.5").Replace("_", "-");
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.