ruby-openai

A breakdown of the popular ruby-openai gem

AI has been all over the headlines the past few months ever since OpenAI dropped GPT-3. Even though the AI craze started recently work on today’s gem started over 3 years ago. Today we’re going to discuss the ruby-openai gem that has been growing in popularity and currently has 1.6k stars on GitHub.

The gem makes it simple to start tinkering with the OpenAI API. It only requires that you setup your API key and then you’re off to the races. You may have used this gem in the past but have you dug into the underlying code? Let’s explore the codebase together while taking a look at the things it got right and other things that could be improved.

Documentation

The documentation is quite good. It shows you examples of how to use most methods and classes provided by the library in detail as well as how to parse the response and things that are useful to know.

It does lack any RDoc or Yard documentation. It could greatly benefit from having class and method documentation specifying what arguments are accepted and the shape of the arguments as well as what the response contains. Luckily if you read the README you can figure most of this out.

Configuration

Configuration

The library uses a configuration object to store the config for the client. It stores things like the access token, api version, organization ID, etc. It does initialize quite a few instance variables in the initialize method. This was flagged by reek as a code smell and I tend to agree. An improvement to the Configuration class would have been using the Singleton module so that there is guaranteed to only ever be one instance. Since it is memoized after initialization in the main OpenAI module.

The Client Class

Something that I am not a fan of is how it overrides the variables on the Configuration object if you initialize an instance of the Client class with new values. It is misleading and a seemingly indirect way of temporarily setting those values.

Helper Methods

Helpers

I like the helper methods that the client class exposes. It makes it very obvious what API interactions you have at your disposal. There are some difference between each of the methods, some are a thin wrapper around the Client class while others initialize a new object. The methods could also be improved a bit by changing the parameters argument. In their current form you have to know a lot about OpenAI’s API and the corresponding parameters. The way the methods are setup make them very flexible but not as intuitive to use. It would be great if things that are required were validated or made into keyword arguments similar to parameters, for example the model parameter is always required.

In my opinion all of the helper methods should be a simple wrapper around a class similar to images. It feels more resourceful and delegates the responsibilities a bit more.

images helper method

Images class

The Images class once again overrides some of the configuration values if they are present. If this is run in a threaded environment it could cause some serious issues with values being reused, for example, between workers.

HTTP Class

http.rb json_post method

While the class achieves the goal of handling HTTP communication with the API it is a bit verbose and in my opinion overly nested. As you can see in the photo above there is a very long call to to_json that is the result of a block. It would have been better to capture the value in a variable and pass that to to_json. The functionality would be the same but the code would be a bit more readable.

I’ve discovered a bug when attempting to use the transcribe method with an ActiveStorage Blob Tempfile. Ultimately it calls the multipart_parameters method which converts File instances to Faraday::UploadIO instances. This isn’t great as it is using is_a? instead of duck typing to allow for all IO like objects.

TL;DR

The ruby-openai library has been in development for over 3 years and recently grew in popularity. The documentation is great and shows you how to use the library and how to interact with various API endpoints. The code isn’t as clean as it could be but it gets the job done. Be careful using the dynamic configuration methods to set things such as the access_token in a threaded environment as you could accidentally leak values between workers.