Code Comment Generator with Ruby on Rails and OpenAI
Demo
GitHub
In the ever-evolving software development landscape, artificial intelligence is transforming how developers write and understand code. Tools like GitHub Copilot and other AI-driven extensions are becoming essential companions, helping developers write code more efficiently and with fewer errors.
This article aims to describe a recent project that explores the integration of GPTs into development tools by creating a code comment generator.
The project comprises two key components:
- An API built using Ruby on Rails that leverages OpenAI’s GPT-4o model to generate comments
- A separate Ruby application that acts as a client, calling the API to provide users with AI-generated comments for their code snippets.
Create the Rails API
rails new code_comment_generator_api --api
The --api
flag tells Rails to generate an application focused solely on APIs and leaves out unnecessary components like views.
Update Gemfile
This API will leverage OpenAI’s GPT-4o model to generate code comments. We’ll use the ruby-openai gem to accomplish this integration.
First, add ruby-openai
to the project’s Gemfile and install it by running bundle install
Talk to GPT-4o
Let’s focus on the service in a app/services/comment_generator.rb
that’s responsible for connecting to GPT-4o and generating the code comments.
The code below shows how my service originally communicated with ChatGPT:
#app/services/comment_generator.rb
class CommentGenerator
def self.generate(code)
openai_response = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'], log_errors: true).chat(
parameters: {
model: "gpt-4o",
messages: [
{
role: "user",
content: "Write a comment for the following code:\n`\n#{code}\n`"
}
],
max_tokens: 500,
n: 1,
stop: nil,
temperature: 0.5
}
)
puts openai_response.inspect
openai_response['choices'][0]['message']['content'].strip
end
end
This implementation waits for the entire response from ChatGPT before processing it. Unfortunately, this approach can make the service feel slow and unresponsive.
Talk to GPT-4o using Streaming
To make the service feel quick and interactive let’s take advantage of the streaming support that ruby-openai
offers:
#app/services/comment_generator.rb
class CommentGenerator
def self.generate(code)
puts "starting CommentGenerator model"
client = OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY'], log_errors: true)
client.chat(
parameters: {
model: "gpt-4o",
messages: [{ role: "user", content: "Write a comment for the following code:\n`\n#{code}\n`"}],
temperature: 0.7,
stream: proc do |chunk|
puts "Received chunk in CommentGenerator: #{chunk}"
comment_part = chunk.dig("choices", 0, "delta", "content")
puts "In CommentGenerator, comment_part: #{comment_part}"
yield comment_part if block_given?
end
}
)
end
end
This streaming approach provides a smoother user experience by offering real-time feedback and aligns with how users expect to interact with text-based AI models like ChatGPT.
Implement Controller
The code below defines a controller for generating comments:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
include ActionController::Live
def generate
puts "Starting generate method"
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Cache-Control'] = 'no-cache'
begin
code = params[:code]
puts "Received code: #{code}"
CommentGenerator.generate(code) do |comment_part|
puts "Received comment part: #{comment_part}"
response.stream.write comment_part
end
rescue => e
puts "Error ocurred: #{e.message}"
response.stream.write "data: Error occurred: #{e.message}\n\n"
ensure
puts "Closing response stream"
response.stream.close
end
end
end
ActionController::Live
enables streaming responses. The generate
method receives a code as input and uses CommentGenerator.generate
(service implemented in comment_generator.rb
) to process it in chunks. Each chunk is then streamed back to the client as a data event. If any errors occur during processing, an error message is streamed back instead.
Update routes.rb
This code in config/routes.rb
defines a route in a Rails application that maps POST requests to the /comment
endpoint to the generate
action in the CommentsController
.
# config/routes.rb
Rails.application.routes.draw do
post '/comment', to: 'comments#generate'
end
That’s it for the API! Running rails s
will start a local web server. This means we can test the API at http://localhost:3000/comment
(Or whatever port your app is configured to listen on). I tested the API by sending POST requests to the endpoint with Postman as well as printing the streamed response to the console.
2. Build the Web Application
With the advent of powerful client-side frameworks, more developers are choosing to use Rails to build robust back-ends that can be shared across web applications and other native platforms. This approach not only streamlines development but also allows for seamless integration of AI-driven tools, like the API we just developed, into modern software workflows.
To demonstrate the interoperability of the API we just created, let’s develop a separate client application with Ruby. This client application will send requests to the API server and display the server’s responses to the user.
Implement View
The web app will look something like this (styled using Bootstrap)
#app/views/home/index.html.erb
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<title>Comment Generator</title>
</head>
<body>
<br>
<h1 class="title">Code Comments Generator</h1>
<br>
<section id="events"></section>
<% if @error %>
<p style="color: red;"><%= @error %></p>
<% end %>
<div class="container">
<div class="form-container">
<form id="commentForm">
<label for="code_snippet">Enter your code:</label><br>
<textarea class="form-control" id="code_snippet" name="code_snippet" rows="10" cols="50"></textarea><br>
<button type="submit" class="btn btn-dark">Generate</button>
</form>
</div>
<div class="result-container">
<pre id="commentedCode"></pre>
</div>
</div>
<script>
document.getElementById('commentForm').onsubmit = function(event) {
event.preventDefault();
const codeSnippet = document.getElementById('code_snippet').value;
const eventSource = new EventSource(`/comments?code_snippet=${encodeURIComponent(codeSnippet)}`);
const commentedCodeElement = document.getElementById('commentedCode');
commentedCodeElement.textContent = ""
commentedCodeElement.textContent += "[INFO] Streaming started...\n";
eventSource.addEventListener("message", (event) => {
console.log(event)
const events = document.getElementById("events")
commentedCodeElement.textContent += event.data;
})
eventSource.addEventListener("error", (event) => {
console.log(event)
if (event.eventPhase === EventSource.CLOSED) {
eventSource.close()
console.log("Event Source Closed")
}
})
}
</script>
</body>
</html>
The main thing to notice here is that the page allows users to input a code snippet and submit it via a form. The page uses JavaScript and server-sent events to stream and display the generated commented code from the API server in real-time within a designated area on the page.
Implement Client Controller
We’ll need a controller that acts as a client for the code comment generation API we developed:
#app/controllers/comments_controller.rb
require 'net/http'
class CommentsController < ApplicationController
include ActionController::Live
def index
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Cache-Control'] = 'no-cache'
response.headers['Last-Modified'] = Time.now.httpdate
code_snippet = params[:code_snippet]
uri = URI('http://localhost:3000/comments')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
request.body = { code: code_snippet }.to_json
sse = SSE.new(response.stream, event: "message")
http.request(request) do |http_response|
http_response.read_body do |chunk|
sse.write(chunk)
end
end
ensure
sse.close if sse
end
end
In short, the controller retrieves the code snippet submitted by the form and sends it to the API server,http://localhost:3000/comment
, via a POST request, and then streams the response back to the client using Server-Sent Events (SSE).
Closing Thoughts
Even as a minimal viable product, this project was a great exercise to gain a bit more familiarity with integrating GPTs into applications, building APIs, and developing with Ruby on Rails.
The app has plenty of room for improvement. For instance, refining the prompt engineering by experimenting with different formats and adding more context could enhance the relevance and accuracy of the comments. Additionally, fine-tuning the GPT-4o model on a dataset of high-quality, well-commented code would enable the generator to produce more context-specific comments and adhere to formatting rules, making it better suited to meet developers’ specific needs.
The articles linked below by Luigi Rojas and EarthCtzn were helpful guides for this project!