Skip to content

How to create OpenAI service in Rails

Introduction

I'm working on integrating OpenAI into my app. This will require my Rails application to make API calls to OpenAI. Usually for these needs of communicating with external services, I create "services" in Rails. In this article, we will go through how to create an OpenAI service in Rails.

Where to put services in Rails?

Rails is a great framework with many decisions taken for us out of the box. Making it opinionated and useful from day zero. We know where to put models, controllers, JS and other stuff. As our application grows, we need to create a few more concepts in the app such as serializers, services, operations etc. For these concepts, Rails doesn't give us a pre-created folder. If we read the Rails guide, it hints about where to put all these new concepts. The go-to place for these will be the app folder. So we will be putting our services in app/services/.

In Rails, anything that is under app will be loaded and available for the rest of our code to use. The direct child of app does not need to have an equivalent module/class. Hence we write User for app/models/user.rb instead of writing Model::User. So with the same pattern, we will have services without namespace prefixes. Our first service will be the one that we will use to communicate with OpenAI APIs. We will create a new file for it under app/services/ and name it openai_service.rb. It will define the class OpenaiService.

# app/services/openai_service.rb

class OpenaiService
  # content will come here
end

Getting ready with API access

We will need to create OpenAI's Access Token so that our app can start communicating with OpenAI servers. First, create an account on OpenAI and then go to https://platform.openai.com/account/api-keys

There click on + Create New Secret Key button. It will ask for a name, it can be anything. I prefer giving a name as per the use case. So let's call it Rails dev token. We can create multiple API access tokens. I recommend creating a new token for production env. Once you create the API token, copy it. We won't be able to see the token again so if we lose access to the token, just create a new one.

That's all on the OpenAI platform. Now back to our Rails app.

Storing secrets in the Rails

In the past, we have all kept our secrets in the ENV. Mostly through the use of .env file. Rails recommends not doing that for various reasons. So here we are going to follow the best practices. Your Rails application likely has credentials.yml.enc file (Rails 6+). If not then you will need to set that up first.

Basics of Rails Credentials

Rails stores encrypted credentials in config/credentials.yml.enc file that is encrypted/decrypted using a key called Rails Master Key. The key is stored in config/master.key file or alternatively in environment variable RAILS_MASTER_KEY. credentials.yml.enc is stored in version control (git) whereas master.key is not stored. Your .gitignore file will be set to ignore master.key file.

If you open credentials.yml.env file, you will see encrypted contents like:

cbV3dZL3BQ50HnYPq4xT7wcsSMjQC6H876w/0c8/lm0LPeqgvw...

In order to open the file for editing, we need to open it with credentials:edit command. Let's open it.

EDITOR="code --wait" bin/rails credentials:edit

This command needs to specify EDITOR. I'm using VS Code so my EDITOR is code. By default, VS Code will detach from the terminal on opening. This will complete the credentials:edit command without giving us a chance to edit the file. So we need to pass --wait argument. If you are using other editors, you need to change EDITOR variable accordingly. For example, with Vim, you need to use EDITOR=vi bin/rails credentials:edit (how to exit vim)

Once opened, it will have some commented sample credentials such as

# aws:
#   access_key_id: 123
#   secret_access_key:

Alternatively, you can create a credentials file per environment. For example,

EDITOR="code --wait" bin/rails credentials:edit --environment development

This will create development.yml.env file. It will use the key from development.key in folder config/credentials/.

Let's update those

Adding OpenAI API access token to credentials

openai:
  access_token: sk-AIEIO

Use the API access token that you copied from the OpenAI platform in place of sk-AIEIO. Now save the file and close. On the terminal, we should see the message File encrypted and saved. 🎉

Gem and config for OpenAI

Let's install the gem for OpenAI. In the Gemfile, add the following line

gem 'ruby-openai'

And run the bundler to install it

bin/bundle

This gem provides us OpenAI. Next is to set up the initializer in the config for OpenAI so that it can use our access token. Let's create a new file config/initializers/openai.rb

# frozen_string_literal: true

OpenAI.configure do |config|
  config.access_token = Rails.application.credentials.openai[:access_token]
  # config.organization_id =  # Optional.
end

To access credentials stored in credentials.yml.enc, Rails provides Rails.application.credentials. In the credentials YAML, we defined openai: section and key access_token:. So here we are accessing those.

OpenaiService implementation

Now we are ready to implement the details of our service. We will use a singleton naïve pattern to implement this. We want the OpenaiService to have a single client instance and all parts of the code calling OpenAI APIs will use this client instance.

# frozen_string_literal: true

class OpenaiService
  @client ||= OpenAI::Client.new
  private_class_method :new

  class << self
    attr_reader :client
  end
end

Here we defined attr_reader :client so that the instance of class OpenaiService will have .client. We also made the constructor private so that no one can call OpenaiService.new outside the class itself. And lastly, we created an instance of OpenAI::Client (provided by the gem) and assigned it to @client.

I'm sure there are simpler or more sophisticated implementations of singleton in Ruby. For now, let's go ahead with this one.

Using the service

Finally, comes the cool part for which we did all this. Before jumping into that, let's list down the steps we took so far.

  1. Create API access token on the OpenAI platform
  2. Install gem ruby-openai
  3. Add API access token to credentials.yml.enc
  4. Initialize OpenAI to use our credentials through Rails.application.credentials.openai
  5. Implement the OpenaiService that provides a single instance of OpenAI::Client

To use our service anywhere in the Rails app, we will call OpenaiService.client. Here is an example

response = OpenaiService.client.chat(
  parameters: {
    model: "gpt-3.5-turbo", # Required.
    messages: [{ role: "user", content: "Hello!"}], # Required.
    temperature: 0.7,
  })
puts response.dig("choices", 0, "message", "content")
# -> "Hello! How may I assist you today?"

Further reading

  1. To explore what is possible with 'ruby-openai', check out their doc
  2. To explore the possibilities of OpenAI API, see API Reference
  3. To look at some implementation details and examples, check out OpenAI Cookbook Examples. Examples are in Python, but a similar can be done in Ruby.
  4. Wonder what is a better way to deal with responses coming from OpenAI? You can make a well-designed Struct for it. Read more about that here.