Skip to content

Instantly share code, notes, and snippets.

@DarrenSem
Last active April 25, 2024 19:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DarrenSem/44a4a09fdbcf06d45e80ac50c0a256fd to your computer and use it in GitHub Desktop.
Save DarrenSem/44a4a09fdbcf06d45e80ac50c0a256fd to your computer and use it in GitHub Desktop.
chatgpt-lite.js equivalent in Csharp is... not nearly as minimal (ChatGPT results, MarkDown) (1st comment = final C# version, 2nd comment = Java version)

SYSTEM:

You are an expert at JavaScript and C#

. You must format indents using 2 spaces, and you must wrap strings using doublequotes " instead of '.

Let's think step by step.

ME:

Make a similarly minimal C# version of this JavaScript:

let OpenAI = function({apiKey} = {}) {
  let openai = {
    create: async ({...options}) => {
      return ( await fetch( "https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer " + apiKey
        },
        body: JSON.stringify( {
          ...options,
        } )
      } ) )
      .json();
    },
  };
  return openai;
};

BOT:

For a C# version that accomplishes a similar functionality, you would typically use HttpClient for web requests. This version also involves asynchronous programming, similar to the JavaScript example.

Here is a minimal C# version:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; // Make sure to include Newtonsoft.Json for JSON operations

public class OpenAI
{
  private readonly string apiKey;

  public OpenAI(string apiKey)
  {
    this.apiKey = apiKey;
  }

  public async Task<string> CreateAsync(object options)
  {
    var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
    httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");

    var jsonOptions = JsonConvert.SerializeObject(options);
    var content = new StringContent(jsonOptions, Encoding.UTF8, "application/json");

    var response = await httpClient.PostAsync("https://api.openai.com/v1/chat/completions", content);
    return await response.Content.ReadAsStringAsync();
  }
}

In this C# version, you instantiate an OpenAI object with an API key. The CreateAsync method allows you to send a POST request similar to the JavaScript example's create method. This method takes an options object, serializes it to JSON, and sends it in the request body. The response is read as a string asynchronously.

Please note:

  • You need to have Newtonsoft.Json package installed to use JsonConvert for serializing the options. Alternatively, if you are using .NET Core 3.0 or later, you might use the System.Text.Json namespace.
  • This example uses HttpClient in a simplified manner. For real-world applications, it's recommended to use HttpClient as a singleton or a single instance for the application's lifetime to avoid socket exhaustion issues.
  • Don't forget to handle exceptions and errors properly in production code, as this example lacks those checks for brevity.

ME:

Here is JavaScript usage example. Show me similar usage for the C# code you generated above.

import OpenAI from "chatgpt-lite"; // ESM.mjs

async function main() {

  const openai = OpenAI({
    // for minimal size it does NOT currently default to setting apiKey = process.env["OPENAI_API_KEY"]
    apiKey: "api_key_can_not_be_omitted"
    // apiKey: process.env["OPENAI_API_KEY"] || await fetchApiKey()
  });

  console.log("openai:", openai);
  // openai: {
  //   apiKey: 'sk-QiyAbcDe1FGhIjk23l4mnOpQ5RstuV67w89x012y2z34Ab7C',
  //   create: [AsyncFunction: create]
  // }

  const completion = await openai.create({
    messages: [{ role: "user", content: "Say this is a test" }],
    model: "gpt-3.5-turbo",
  });

  console.log("completion:", completion);
  // completion: {
  //   id: 'chatcmpl-8xAbCdEFghI4JklmNopQrs7TuvwxYz',
  //   object: 'chat.completion',
  //   created: 1709167986,
  //   model: 'gpt-3.5-turbo-0125',
  //   choices: [
  //     {
  //       index: 0,
  //       message: [Object],
  //       logprobs: null,
  //       finish_reason: 'stop'
  //     }
  //   ],
  //   usage: { prompt_tokens: 12, completion_tokens: 5, total_tokens: 17 },
  //   system_fingerprint: 'fp_86123a45b6'
  // }

  const {usage} = completion;
  console.log("usage:", usage);
  // usage: { prompt_tokens: 12, completion_tokens: 5, total_tokens: 17 }

  const message = completion.error || completion.choices[0].message;
  console.log("message:", message);
  // message: { role: 'assistant', content: 'This is a test.' }

};

main();
// TODO:

BOT:

To create a similar C# example using the OpenAI class I provided, you would first make sure to have your project environment set up for asynchronous main methods, which is supported in C# 7.1 and later. If you're using an older version, you'll have to adjust the structure accordingly (e.g., by calling GetAwaiter().GetResult() on asynchronous calls or using async event handlers in a desktop application).

I'll show you how to use the OpenAI class in a console application scenario:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // Assuming OpenAI class is defined in the same project and Newtonsoft.Json is installed

        var apiKey = "api_key_can_not_be_omitted"; // Consider using Environment.GetEnvironmentVariable("OPENAI_API_KEY") for real applications

        var openai = new OpenAI(apiKey);
        Console.WriteLine("openai: instance created.");

        var options = new
        {
            messages = new List<object>
            {
                new { role = "user", content = "Say this is a test" }
            },
            model = "gpt-3.5-turbo",
        };

        var completionJson = await openai.CreateAsync(options);
        Console.WriteLine($"completion: {completionJson}");

        // Since the completion is a JSON string, parsing it to work with the data similarly to the JS example would require JSON parsing. For simplicity, we'll print the JSON.
        // In a real scenario, you would parse this JSON to dynamically access its properties.

        // Optional: Parse the JSON string to an object to access specific fields like 'usage'
        // var completionObj = JsonConvert.DeserializeObject<dynamic>(completionJson);
        // Console.WriteLine($"usage: {completionObj.usage}");

        // For accessing and displaying a message or error similarly
        // var message = completionObj.error != null ? completionObj.error : completionObj.choices[0].message;
        // Console.WriteLine($"message: {message}");
    }
}

Note:

  • This example demonstrates how to asynchronously make a call to the CreateAsync method of the OpenAI class and print the result.
  • Parsing the JSON string to a dynamic object or a specific class is necessary to access individual properties of the response, such as usage or choices, as you would in the provided JavaScript example. I mentioned this as an optional step because it requires defining a model that fits the JSON structure or using a dynamic object with JsonConvert.
  • Remember to handle exceptions and errors that could occur during the HTTP call or JSON parsing, which are not shown in this minimal example for brevity.
  • Ensure your project either targets .NET Core 3.1/.NET 5+ or has the Newtonsoft.Json NuGet package installed for JSON operations.
@DarrenSem
Copy link
Author

DarrenSem commented Apr 18, 2024

// .cs equivalent of chatgpt-lite.js -- because why not

// See https://aka.ms/new-console-template => https://learn.microsoft.com/en-us/dotnet/core/tutorials/top-level-templates => for more information

using System.Net.Http.Headers;
using Newtonsoft.Json; // dotnet add package Newtonsoft.Json

// Environment.SetEnvironmentVariable("OPENAI_API_KEY", "sess-bP8m50wxsQq7JSHLmL0bGXCItRWl0Z0sCvXtYrHZ");
await test_OpenAI();

// Environment.SetEnvironmentVariable("OPENAI_API_KEY", "XYZsess-bP8m50wxsQq7JSHLmL0bGXCItRWl0Z0sCvXtYrHZ");
await test_OpenAI_min();

// try {
//   Console.ReadKey(intercept: false);
// } catch(Exception) {} // System.InvalidOperationException WHEN launch.json "console": "internalConsole", // (def) routed through the VS Code Debug Console [Console.ReadKey() etc. FAIL]

static async Task test_OpenAI()
{

  try { Console.Clear(); } catch (System.IO.IOException) { }; // debug (F5) throws in System.Console.dll: 'The handle is invalid.'

  // Console.WriteLine("BEGIN\t{0}", DateTime.Now);
  // Console.WriteLine("BEGIN\t{0}", DateTime.Now.ToString("HH:mm:ss yyyy-MM-dd"));
  // Console.WriteLine("BEGIN\t{0}", DateTime.Now.ToString("h:mm:ss tt yyyy MMM dd"));
  // Console.WriteLine("BEGIN\t{0}", DateTime.Now.ToString("h:mm:ss tt"));
  Console.WriteLine("BEGIN\t{0}", DateTime.Now.ToString("h:mm:ss.fff tt"));
  Console.WriteLine();

  var openai = new OpenAI(new Dictionary<string, object?> {
    { "apiKey", Environment.GetEnvironmentVariable("OPENAI_API_KEY") }, // || await fetchApiKey() },
  });

  object options = new
  {
    messages = new[] {
      new { role = "user", content = "Say this is a test" }
    },
    model = "gpt-3.5-turbo",
  };

  Console.WriteLine("+++create()   called @ {0}", DateTime.Now.ToString("h:mm:ss.fff tt"));
  var completion = await openai.create(options);
  Console.WriteLine("===create() returned @ {0}", DateTime.Now.ToString("h:mm:ss.fff tt"));

  Console.WriteLine($"completion: {completion ?? "<null>"}");

  // Assuming completion is a JObject (from Newtonsoft.Json), accessing specific fields:
  var usage = completion?["usage"]; // needs <dynamic> instead of just <object>
  Console.WriteLine($"usage: {usage ?? "<null>"}");

  var message = completion?["choices"]?[0]?["message"]?["content"] ?? null;
  Console.WriteLine($"message: {message ?? "<null>"}");

  // Console.WriteLine("ENDED\t{0}", DateTime.Now);
  // Console.WriteLine("ENDED\t{0}", DateTime.Now.ToString("HH:mm:ss yyyy-MM-dd"));
  // Console.WriteLine("\nENDED\t{0}", DateTime.Now.ToString("h:mm:ss tt yyyy MMM dd"));
  Console.WriteLine("\nENDED\t{0}", DateTime.Now.ToString("h:mm:ss.fff tt"));

}



static async Task test_OpenAI_min()
{
  var openai = new OpenAI_min(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));

  Console.WriteLine("+++create()   called @ {0}", DateTime.Now.ToString("h:mm:ss.fff tt"));
  var completion = await openai.create(new
  {
    messages = new[] {
    new { role = "user", content = "Say only the final word in this sentence" }
  },
    model = "gpt-3.5-turbo",
  });
  Console.WriteLine("===create() returned @ {0}", DateTime.Now.ToString("h:mm:ss.fff tt"));

  Console.WriteLine($"completion: {completion ?? "<null>"}");

  var usage = completion?["usage"];
  Console.WriteLine($"usage: {usage ?? "<null>"}");

  var message = completion?["choices"]?[0]?["message"]?["content"] ?? completion?["error"] ?? null;
  Console.WriteLine($"message: {message ?? "<null>"}");
}



public class OpenAI
{
  private readonly string? apiKey;
  // private readonly Dictionary<string, object> defaultOptions;

  public OpenAI(Dictionary<string, object?> options)
  {
    apiKey = options.TryGetValue("apiKey", out object? value) ? (string?)value : null;
    // options.Remove("apiKey");
    // defaultOptions = options;
  }

  public async Task<dynamic?> create(object options)
  {

    // var httpClient = new HttpClient();
    using (var httpClient = new HttpClient())
    {
      httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

      var jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(options); // Or JsonContent.Create(options) with System.Text.Json

      var response = await httpClient.PostAsync(
        "https://api.openai.com/v1/chat/completions",
        new StringContent(jsonData, System.Text.Encoding.UTF8, "application/json")
      // ^ AKA ~` httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json") `
      );
      // var jsonString = await response.Content.ReadAsStringAsync();
      string jsonString = await response.Content.ReadAsStringAsync();

      return JsonConvert.DeserializeObject<dynamic>(jsonString);
    }
  }

}

public class OpenAI_min(string? apiKey)
{
  public async Task<dynamic?> create(object options)
  {
    using (var httpClient = new HttpClient())
    {
      httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);

      var response = await httpClient.PostAsync(
        "https://api.openai.com/v1/chat/completions",
        new StringContent(
          Newtonsoft.Json.JsonConvert.SerializeObject(options),
          System.Text.Encoding.UTF8,
          "application/json"
        )
      );

      // JObject from Newtonsoft.Json, thus <dynamic> instead of just <object>
      return JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
    }
  }
}

@DarrenSem
Copy link
Author

DarrenSem commented Apr 19, 2024

// .cs equivalent of chatgpt-lite.js -- because why not
// ChatGPTLite.java equivalent of .cs equivalent of chatgpt-lite.js -- because again why not

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject; // ../lib/json.jar (e.g. via Gradle: `implementation 'org.json:json:20210307'`)

public class ChatGPTLite {
  public static void main(String[] args) throws Exception {

    System.setProperty(
      "OPENAI_API_KEY",
      "sess-wVm71VHwgrBEgTbiVhHiwzzbnEryUlZP4kGgTqnP"
    );
    testOpenAIMin();
  }

  static void testOpenAIMin() throws IOException {
    OpenAI_min openai = new OpenAI_min(System.getProperty("OPENAI_API_KEY"));

    System.out.println("+++ create()   called @ " + java.time.LocalTime.now().toString());

    JSONObject options = new JSONObject().put(
        "messages", new JSONObject[] {
          new JSONObject()
          .put("role", "user")
          .put("content", "Say only the final word in this sentence")
        }
      )
      .put("model", "gpt-3.5-turbo");

    JSONObject completion = openai.create(options);

    System.out.println("=== create() returned @ " + java.time.LocalTime.now().toString());

    System.out.println("completion: " + (completion != null ? completion.toString() : "<null>"));

    JSONObject usage = completion != null
      ? completion.optJSONObject("usage")
      : null;
    System.out.println("usage: " + (usage != null ? usage.toString() : "<null>"));

    String message = null;
    if (completion != null
      && completion.optJSONArray("choices") != null
      && completion.optJSONArray("choices").length() > 0
    ) {
      JSONObject messageObject = (
        completion.optJSONArray("choices")
        .getJSONObject(0)
        .optJSONObject("message")
      );
      if (messageObject != null) {
        message = messageObject.optString("content", null);
      }
    }
    System.out.println("message: " + (message != null ? message : "<null>"));
  }
}

class OpenAI_min {
  private String apiKey;

  public OpenAI_min(String apiKey) {
    this.apiKey = apiKey;
  }

  public JSONObject create(JSONObject options) throws IOException {

    URL url = new URL("https://api.openai.com/v1/chat/completions");

    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setRequestMethod("POST");
    con.setRequestProperty("Authorization", "Bearer " + this.apiKey);
    con.setRequestProperty("Content-Type", "application/json");
    con.setDoOutput(true);

    try (OutputStream os = con.getOutputStream()) {
      byte[] input = options.toString().getBytes("utf-8");
      os.write(input, 0, input.length);
    }

    int responseCode = con.getResponseCode();

    // HTTP_OK=200, HTTP_BAD_REQUEST=400 [4XX: client error], HTTP_MULT_CHOICE=300 [3XX: relocation/redirect]
    boolean ok = responseCode < HttpURLConnection.HTTP_BAD_REQUEST && responseCode >= HttpURLConnection.HTTP_OK;

    InputStream inputStream;
    if (ok) {
      inputStream = con.getInputStream();
    } else {
      inputStream = con.getErrorStream(); // returned HTTP error code 400+
    }

    try (BufferedReader br = new BufferedReader(
        new InputStreamReader(inputStream, "utf-8")
    )) {
      StringBuilder response = new StringBuilder();
      String responseLine;
      while ((responseLine = br.readLine()) != null) {
        response.append(responseLine.trim());
      }
      String json = response.toString();
      if (json.isEmpty()) json = "{}";
      JSONObject jsonObject = new JSONObject(json);
      return jsonObject;
    }

  }
}

@DarrenSem
Copy link
Author

DarrenSem commented Apr 20, 2024

// ChatGPT model *ALIAS* info... (as of 20Apr2024)

var model = "gpt-4"; // "gpt-4-0613" (Jun 2023)

var model = "gpt-4-turbo-preview"; // "gpt-4-0125-preview" (Jan 2024)

var model = "gpt-4-turbo"; // "gpt-4-turbo-2024-04-09" (Apr 2024)

OpenAI({apiKey}).create({
  model,
  messages: [
    { role:"user", content:"" }
  ]
}).then(res => console.log(res.model));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment