Skip to content

Commit e7c2d8b

Browse files
committed
made change to chat gpt request
1 parent 339c912 commit e7c2d8b

File tree

6 files changed

+128
-133
lines changed

6 files changed

+128
-133
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ gem 'tubesock', github: 'openhpi/tubesock'
5656
gem 'turbolinks'
5757
gem 'whenever', require: false
5858
gem 'zxcvbn-ruby', require: 'zxcvbn'
59-
gem 'openai'
59+
gem 'ruby-openai'
6060

6161

6262
# Error Tracing

Gemfile.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ GEM
151151
erubi (1.13.0)
152152
et-orbi (1.2.11)
153153
tzinfo
154+
event_stream_parser (1.0.0)
154155
eventmachine (1.2.7)
155156
excon (1.2.0)
156157
execjs (2.10.0)
@@ -163,6 +164,8 @@ GEM
163164
faraday-net_http (>= 2.0, < 3.4)
164165
json
165166
logger
167+
faraday-multipart (1.0.4)
168+
multipart-post (~> 2)
166169
faraday-net_http (3.3.0)
167170
net-http
168171
faraday-net_http_persistent (2.3.0)
@@ -276,6 +279,7 @@ GEM
276279
multi_json (1.15.0)
277280
multi_xml (0.7.1)
278281
bigdecimal (~> 3.1)
282+
multipart-post (2.4.1)
279283
nested_form (0.3.2)
280284
net-http (0.5.0)
281285
uri
@@ -462,6 +466,10 @@ GEM
462466
rubocop-rspec_rails (2.30.0)
463467
rubocop (~> 1.61)
464468
rubocop-rspec (~> 3, >= 3.0.1)
469+
ruby-openai (7.3.1)
470+
event_stream_parser (>= 0.3.0, < 2.0.0)
471+
faraday (>= 1)
472+
faraday-multipart (>= 1)
465473
ruby-progressbar (1.13.0)
466474
ruby-vips (2.2.2)
467475
ffi (~> 1.12)
@@ -659,6 +667,7 @@ DEPENDENCIES
659667
rubocop-rails
660668
rubocop-rspec
661669
rubocop-rspec_rails
670+
ruby-openai
662671
rubytree
663672
rubyzip
664673
sassc-rails

app/helpers/chat_gpt_helper.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
module ChatGptHelper
3+
4+
5+
def self.construct_prompt_for_rfc(request_for_comment, file)
6+
submission = request_for_comment.submission
7+
test_run_results = Testrun.where(submission_id: submission.id).map(&:log).join("\n")
8+
options = {
9+
learner_solution: file.content,
10+
exercise: submission.exercise.description,
11+
test_results: test_run_results,
12+
question: request_for_comment.question
13+
}
14+
format_prompt(options)
15+
end
16+
17+
def self. format_prompt(options)
18+
if I18n.locale == :en
19+
file_path = Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'prompt_en.xml')
20+
prompt = File.read(file_path)
21+
prompt.gsub!("[Learner's Code]", options[:learner_solution] || "")
22+
prompt.gsub!("[Task]", options[:exercise] || "")
23+
prompt.gsub!("[Error Message]", options[:test_results] || "")
24+
prompt.gsub!("[Student Question]", options[:question] || "")
25+
else
26+
file_path = Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'prompt_de.xml')
27+
prompt = File.read(file_path)
28+
prompt.gsub!("[Code des Lernenden]", options[:learner_solution] || "")
29+
prompt.gsub!("[Aufgabenstellung]", options[:exercise] || "")
30+
prompt.gsub!("[Fehlermeldung]", options[:test_results] || "")
31+
prompt.gsub!("[Frage des Studierenden]", options[:question] || "")
32+
end
33+
34+
prompt
35+
end
36+
37+
def self.format_response(response)
38+
parsed_response = JSON.parse(response)
39+
requirements_comments = ''
40+
if parsed_response['requirements']
41+
requirements_comments = parsed_response['requirements'].map { |req| req['comment'] }.join("\n")
42+
end
43+
44+
line_specific_comments = []
45+
if parsed_response['line_specific_comments']
46+
line_specific_comments = parsed_response['line_specific_comments'].map do |line_comment|
47+
{
48+
line_number: line_comment['line_number'],
49+
comment: line_comment['comment']
50+
}
51+
end
52+
end
53+
54+
{
55+
requirements_comments: requirements_comments,
56+
line_specific_comments: line_specific_comments
57+
}
58+
end
59+
60+
end

app/jobs/generate_automatic_comments_job.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
# app/jobs/generate_automatic_comments_job.rb
22
class GenerateAutomaticCommentsJob < ApplicationJob
3+
34
queue_as :default
45
def perform(request_for_comment, current_user)
56
chat_gpt_user = InternalUser.find_by(email: '[email protected]')
67
chat_gpt_service = ChatGptService::ChatGptRequest.new
78
chat_gpt_disclaimer = I18n.t('exercises.editor.chat_gpt_disclaimer')
89
request_for_comment.submission.files.each do |file|
9-
response_data = chat_gpt_service.get_response(
10-
request_for_comment: request_for_comment,
11-
file: file,
12-
response_format_needed: true
13-
)
14-
Rails.logger.debug "Response data: #{response_data.inspect}"
10+
prompt = ChatGptHelper.construct_prompt_for_rfc(request_for_comment, file)
11+
response = chat_gpt_service.make_chat_gpt_request(prompt, true)
12+
response_data = ChatGptHelper.format_response(response)
1513
next unless response_data.present?
1614

1715
# Create comment for combined 'requirements' comments
@@ -32,7 +30,7 @@ def perform(request_for_comment, current_user)
3230
create_comment(
3331
text: "#{line_comment_data[:comment]}\n\n#{chat_gpt_disclaimer}",
3432
file_id: file.id,
35-
row: line_comment_data[:line_number].to_s,
33+
row: (line_comment_data[:line_number].positive? ? line_comment_data[:line_number] - 1 : line_comment_data[:line_number]).to_s,
3634
column: '0',
3735
user: chat_gpt_user
3836
)
@@ -55,6 +53,7 @@ def create_comment(attributes)
5553
def send_emails(comment, request_for_comment, current_user, chat_gpt_user)
5654
send_mail_to_author(comment, request_for_comment, chat_gpt_user)
5755
send_mail_to_subscribers(comment, request_for_comment, current_user)
56+
5857
end
5958

6059
def send_mail_to_author(comment, request_for_comment, chat_gpt_user)

app/models/testrun.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,16 @@ def generate_ai_feedback
3838
raise 'Automatic feedback is not enabled for this exercise.'
3939
end
4040

41-
# Generate feedback without storing it
42-
main_file = submission.main_file
43-
exercise_description = submission.exercise.description
44-
test_results = output
45-
learner_solution = main_file.content
46-
test_passed = passed
47-
4841
chatgpt_request = ChatGptService::ChatGptRequest.new
49-
feedback_message = chatgpt_request.get_response(
50-
learner_solution: learner_solution,
51-
exercise: exercise_description,
52-
test_results: test_results,
53-
test_passed: test_passed
42+
43+
prompt = ChatGptHelper.format_prompt(
44+
learner_solution: submission.main_file.content,
45+
exercise: submission.exercise.description,
46+
test_results: output,
5447
)
5548

49+
feedback_message = chatgpt_request.make_chat_gpt_request(prompt, false)
50+
5651
# Format and sanitize the feedback message
5752
formatted_feedback = Kramdown::Document.new(feedback_message).to_html
5853
sanitized_feedback = ActionController::Base.helpers.sanitize(
Lines changed: 45 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,64 @@
1-
require 'net/http'
2-
require 'uri'
3-
require 'json'
41

52
module ChatGptService
63
class ChatGptRequest
7-
API_URL = 'https://api.openai.com/v1/chat/completions'.freeze
84
MODEL_NAME = 'gpt-4o'.freeze
9-
def initialize
10-
@api_key = Rails.application.credentials.openai[:api_key]
11-
unless @api_key
12-
raise "OpenAI API key is missing. Please set it in environment variables or Rails credentials."
13-
end
14-
end
15-
16-
def get_response(options = {})
17-
prompt = construct_prompt(options)
18-
response = make_chat_gpt_request(prompt, options[:response_format_needed])
19-
if options[:response_format_needed]
20-
format_response(response)
21-
else
22-
response
23-
end
24-
end
25-
26-
private
27-
28-
def construct_prompt(options)
29-
if options[:request_for_comment] && options[:file]
30-
construct_prompt_for_rfc(options[:request_for_comment], options[:file])
31-
else
32-
format_prompt(options)
33-
end
34-
end
35-
36-
def construct_prompt_for_rfc(request_for_comment, file)
37-
submission = request_for_comment.submission
38-
test_run_results = Testrun.where(submission_id: submission.id).map(&:log).join("\n")
39-
options = {
40-
learner_solution: file.content,
41-
exercise: submission.exercise.description,
42-
test_results: test_run_results,
43-
question: request_for_comment.question
44-
}
45-
format_prompt(options)
46-
end
475

48-
def format_prompt(options)
49-
if I18n.locale == :en
50-
file_path = Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'prompt_en.xml')
51-
prompt = File.read(file_path)
52-
prompt.gsub!("[Learner's Code]", options[:learner_solution] || "")
53-
prompt.gsub!("[Task]", options[:exercise] || "")
54-
prompt.gsub!("[Error Message]", options[:test_results] || "")
55-
prompt.gsub!("[Student Question]", options[:question] || "")
56-
else
57-
file_path = Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'prompt_de.xml')
58-
prompt = File.read(file_path)
59-
prompt.gsub!("[Code des Lernenden]", options[:learner_solution] || "")
60-
prompt.gsub!("[Aufgabenstellung]", options[:exercise] || "")
61-
prompt.gsub!("[Fehlermeldung]", options[:test_results] || "")
62-
prompt.gsub!("[Frage des Studierenden]", options[:question] || "")
63-
end
64-
65-
prompt
6+
def initialize
7+
raise Gpt::Error::InvalidApiKey if fetch_api_key.blank?
8+
@client = OpenAI::Client.new(access_token: fetch_api_key)
669
end
6710

6811
def make_chat_gpt_request(prompt, response_format_needed = false)
69-
url = URI.parse(API_URL)
70-
data = {
71-
model: MODEL_NAME,
72-
messages: [{ role: 'user', content: prompt }],
73-
max_tokens: 2048
74-
}
75-
if response_format_needed
76-
response_format = JSON.parse(File.read(Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'response_format.json')))
77-
data[:response_format] = response_format
78-
end
79-
80-
http = Net::HTTP.new(url.host, url.port)
81-
http.use_ssl = true
12+
wrap_api_error! do
13+
data = {
14+
model: MODEL_NAME,
15+
messages: [{ role: 'user', content: prompt }],
16+
temperature: 0.7 # Lower values insure reproducibility
17+
}
8218

83-
request = Net::HTTP::Post.new(url.path, {
84-
'Content-Type' => 'application/json',
85-
'Authorization' => "Bearer #{@api_key}"
86-
})
87-
request.body = data.to_json
19+
if response_format_needed
20+
response_format = JSON.parse(File.read(Rails.root.join('app', 'services/chat_gpt_service/chat_gpt_prompts', 'response_format.json')))
21+
data[:response_format] = response_format
22+
end
8823

89-
begin
90-
response = http.request(request)
91-
json_response = JSON.parse(response.body)
24+
begin
25+
response = @client.chat(parameters: data)
26+
json_response = response.dig('choices', 0, 'message', 'content')
9227

93-
if response.is_a?(Net::HTTPSuccess)
94-
json_response.dig('choices', 0, 'message', 'content')
95-
else
96-
error_message = json_response.dig('error', 'message') || 'Unknown error'
97-
Rails.logger.error "ChatGPT API Error: #{error_message}"
98-
raise "ChatGPT API Error: #{error_message}"
28+
if json_response
29+
json_response
30+
else
31+
error_message = response.dig('error', 'message') || 'Unknown error'
32+
Rails.logger.error "ChatGPT API Error: #{error_message}"
33+
raise "ChatGPT API Error: #{error_message}"
34+
end
35+
rescue JSON::ParserError => e
36+
Rails.logger.error "Failed to parse ChatGPT response: #{e.message}"
37+
raise "Failed to parse ChatGPT response: #{e.message}"
38+
rescue StandardError => e
39+
Rails.logger.error "Error while making request to ChatGPT: #{e.message}"
40+
raise e
9941
end
100-
rescue JSON::ParserError => e
101-
Rails.logger.error "Failed to parse ChatGPT response: #{e.message}"
102-
raise "Failed to parse ChatGPT response: #{e.message}"
103-
rescue StandardError => e
104-
Rails.logger.error "Error while making request to ChatGPT: #{e.message}"
105-
raise e
10642
end
10743
end
10844

109-
def format_response(response)
110-
parsed_response = JSON.parse(response)
111-
requirements_comments = ''
112-
if parsed_response['requirements']
113-
requirements_comments = parsed_response['requirements'].map { |req| req['comment'] }.join("\n")
114-
end
45+
private
11546

116-
line_specific_comments = []
117-
if parsed_response['line_specific_comments']
118-
line_specific_comments = parsed_response['line_specific_comments'].map do |line_comment|
119-
{
120-
line_number: line_comment['line_number'],
121-
comment: line_comment['comment']
122-
}
123-
end
124-
end
47+
def fetch_api_key
48+
api_key = Rails.application.credentials.openai[:api_key]
49+
raise "OpenAI API key is missing. Please set it in environment variables or Rails credentials." unless api_key
50+
api_key
51+
end
12552

126-
{
127-
requirements_comments: requirements_comments,
128-
line_specific_comments: line_specific_comments
129-
}
53+
def wrap_api_error!
54+
yield
55+
rescue Faraday::UnauthorizedError, OpenAI::Error => e
56+
raise Gpt::Error::InvalidApiKey.new("Could not authenticate with OpenAI: #{e.message}")
57+
rescue Faraday::Error => e
58+
raise Gpt::Error::InternalServerError.new("Could not communicate with OpenAI: #{e.inspect}")
59+
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNRESET, SocketError, EOFError => e
60+
raise Gpt::Error.new(e)
13061
end
62+
13163
end
132-
end
64+
end

0 commit comments

Comments
 (0)