diff --git a/.env.example b/.env.example index b22d6e169..2fb4f8121 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,5 @@ SMEE_TUNNEL=https://smee.io/MLq0n9kvAes2vydX # Add the below to bypass token authentication with hyrdra # BYPASS_AUTH=true # AUTH_USER_ID=<> + +OPENAI_ACCESS_TOKEN=changeme diff --git a/Gemfile b/Gemfile index d426ae6ee..421f3b763 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,7 @@ gem 'puma', '~> 5.6' gem 'rack-cors' gem 'rails', '~> 7.0.0' gem 'sentry-rails', '~> 5.5.0' +gem 'ruby-openai' group :development, :test do gem 'dotenv-rails' diff --git a/Gemfile.lock b/Gemfile.lock index c7497da49..5b1f44a40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,77 +1,77 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) + actioncable (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + actionmailbox (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.2) - actionpack (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activesupport (= 7.0.4.2) + actionmailer (7.0.4.3) + actionpack (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activesupport (= 7.0.4.3) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.2) - actionview (= 7.0.4.2) - activesupport (= 7.0.4.2) + actionpack (7.0.4.3) + actionview (= 7.0.4.3) + activesupport (= 7.0.4.3) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.2) - actionpack (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + actiontext (7.0.4.3) + actionpack (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.2) - activesupport (= 7.0.4.2) + actionview (7.0.4.3) + activesupport (= 7.0.4.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.4.2) - activesupport (= 7.0.4.2) + activejob (7.0.4.3) + activesupport (= 7.0.4.3) globalid (>= 0.3.6) - activemodel (7.0.4.2) - activesupport (= 7.0.4.2) - activerecord (7.0.4.2) - activemodel (= 7.0.4.2) - activesupport (= 7.0.4.2) - activestorage (7.0.4.2) - actionpack (= 7.0.4.2) - activejob (= 7.0.4.2) - activerecord (= 7.0.4.2) - activesupport (= 7.0.4.2) + activemodel (7.0.4.3) + activesupport (= 7.0.4.3) + activerecord (7.0.4.3) + activemodel (= 7.0.4.3) + activesupport (= 7.0.4.3) + activestorage (7.0.4.3) + actionpack (= 7.0.4.3) + activejob (= 7.0.4.3) + activerecord (= 7.0.4.3) + activesupport (= 7.0.4.3) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.2) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.718.0) - aws-sdk-core (3.170.0) + aws-partitions (1.745.0) + aws-sdk-core (3.171.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) @@ -79,7 +79,7 @@ GEM aws-sdk-kms (1.63.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) + aws-sdk-s3 (1.120.1) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -89,7 +89,7 @@ GEM msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) - cancancan (3.4.0) + cancancan (3.5.0) climate_control (1.2.0) coderay (1.1.3) concurrent-ruby (1.2.2) @@ -126,7 +126,7 @@ GEM railties (>= 4) globalid (1.1.0) activesupport (>= 5.0) - good_job (3.12.6) + good_job (3.15.0) activejob (>= 6.0.0) activerecord (>= 6.0.0) concurrent-ruby (>= 1.0.2) @@ -134,11 +134,14 @@ GEM railties (>= 6.0.0) thor (>= 0.14.1) webrick (>= 1.3) - graphql (2.0.17) + graphql (2.0.21) graphql-client (0.18.0) activesupport (>= 3.0) graphql hashdiff (1.0.1) + httparty (0.21.0) + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) i18n (1.12.0) concurrent-ruby (~> 1.0) importmap-rails (1.1.5) @@ -161,7 +164,7 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) - loofah (2.19.1) + loofah (2.20.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.8.1) @@ -172,8 +175,9 @@ GEM marcel (1.0.2) method_source (1.0.0) mini_mime (1.1.2) - minitest (5.17.0) - msgpack (1.6.0) + minitest (5.18.0) + msgpack (1.7.0) + multi_xml (0.6.0) net-imap (0.3.4) date net-protocol @@ -183,17 +187,17 @@ GEM timeout net-smtp (0.3.3) net-protocol - nio4r (2.5.8) - nokogiri (1.14.2-aarch64-linux) + nio4r (2.5.9) + nokogiri (1.14.3-aarch64-linux) racc (~> 1.4) - nokogiri (1.14.2-x86_64-linux) + nokogiri (1.14.3-x86_64-linux) racc (~> 1.4) open-uri (0.3.0) stringio time uri parallel (1.22.1) - parser (3.2.1.0) + parser (3.2.2.0) ast (~> 2.4.1) pg (1.4.6) pry (0.14.2) @@ -207,33 +211,33 @@ GEM nio4r (~> 2.0) raabro (1.4.0) racc (1.6.2) - rack (2.2.6.2) - rack-cors (2.0.0) + rack (2.2.6.4) + rack-cors (2.0.1) rack (>= 2.0.0) - rack-test (2.0.2) + rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4.2) - actioncable (= 7.0.4.2) - actionmailbox (= 7.0.4.2) - actionmailer (= 7.0.4.2) - actionpack (= 7.0.4.2) - actiontext (= 7.0.4.2) - actionview (= 7.0.4.2) - activejob (= 7.0.4.2) - activemodel (= 7.0.4.2) - activerecord (= 7.0.4.2) - activestorage (= 7.0.4.2) - activesupport (= 7.0.4.2) + rails (7.0.4.3) + actioncable (= 7.0.4.3) + actionmailbox (= 7.0.4.3) + actionmailer (= 7.0.4.3) + actionpack (= 7.0.4.3) + actiontext (= 7.0.4.3) + actionview (= 7.0.4.3) + activejob (= 7.0.4.3) + activemodel (= 7.0.4.3) + activerecord (= 7.0.4.3) + activestorage (= 7.0.4.3) + activesupport (= 7.0.4.3) bundler (>= 1.15.0) - railties (= 7.0.4.2) + railties (= 7.0.4.3) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.5.0) loofah (~> 2.19, >= 2.19.1) - railties (7.0.4.2) - actionpack (= 7.0.4.2) - activesupport (= 7.0.4.2) + railties (7.0.4.3) + actionpack (= 7.0.4.3) + activesupport (= 7.0.4.3) method_source rake (>= 12.2) thor (~> 1.0) @@ -251,7 +255,7 @@ GEM rspec-expectations (3.12.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-mocks (3.12.3) + rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-rails (6.0.1) @@ -265,30 +269,32 @@ GEM rspec-support (3.12.0) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.47.0) + rubocop (1.50.1) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.26.0, < 2.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.27.0) + rubocop-ast (1.28.0) parser (>= 3.2.1.0) rubocop-capybara (2.17.1) rubocop (~> 1.41) - rubocop-graphql (1.0.0) + rubocop-graphql (1.1.1) rubocop (>= 0.87, < 2) - rubocop-rails (2.18.0) + rubocop-rails (2.19.0) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - rubocop-rspec (2.18.1) + rubocop-rspec (2.19.0) rubocop (~> 1.33) rubocop-capybara (~> 2.17) - ruby-progressbar (1.11.0) + ruby-openai (3.7.0) + httparty (>= 0.18.1) + ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) sentry-rails (5.5.0) railties (>= 5.0) @@ -305,13 +311,13 @@ GEM simplecov_json_formatter (0.1.4) stringio (3.0.5) thor (1.2.1) - time (0.2.1) + time (0.2.2) date timeout (0.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.4.2) - uri (0.12.0) + uri (0.12.1) webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -356,6 +362,7 @@ DEPENDENCIES rubocop-graphql rubocop-rails rubocop-rspec + ruby-openai sentry-rails (~> 5.5.0) shoulda-matchers (~> 5.0) simplecov diff --git a/app/graphql/resolvers/error_explanation_resolver.rb b/app/graphql/resolvers/error_explanation_resolver.rb new file mode 100644 index 000000000..36a808462 --- /dev/null +++ b/app/graphql/resolvers/error_explanation_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require 'openai' + +module Resolvers + class ErrorExplanationResolver + + def self.get_error_explanation(error:, code:, system_prompt:) + client = OpenAI::Client.new + + response = client.chat( + parameters: { + model: "gpt-3.5-turbo", + messages: [ + {role: "system", content: system_prompt}, + {role: "user", content: "Explain the error \"#{error}\" in the following python code: \"#{code}\""} + ], + temperature: 0.5 + }) + + puts response.inspect + + {message: response.dig("choices", 0, "message", "content")} + end + end +end diff --git a/app/graphql/types/error_explanation_type.rb b/app/graphql/types/error_explanation_type.rb new file mode 100644 index 000000000..877e9cbb1 --- /dev/null +++ b/app/graphql/types/error_explanation_type.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Types + class ErrorExplanationType < Types::BaseObject + description 'Explain an error message for given python code using ChatGPT' + field :message, String, null: false, description: 'Generated explanation of the error' + + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index fe9563f40..5906a4de1 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -9,6 +9,12 @@ class QueryType < Types::BaseObject # Add root-level fields here. # They will be entry points for queries on your schema. + field :error_explanation, Types::ErrorExplanationType, 'Get explanation for error message and python code' do + argument :error, String, required: true, description: 'The error message that happened when running the code' + argument :code, String, required: true, description: 'The python source code that caused the error' + argument :system_prompt, String, required: false, description: 'The system prompt used to query ChatGPT' + end + field :project, Types::ProjectType, 'Find a project by identifier' do argument :identifier, String, required: true, description: 'Project identifier' argument :preferred_locales, [String], required: false, @@ -30,5 +36,9 @@ def projects(user_id: nil) results end + + def error_explanation(error: "", code: "", system_prompt: "You are a teacher talking to a 12 year old child.") + Resolvers::ErrorExplanationResolver.get_error_explanation(error:, code:, system_prompt:) + end end end diff --git a/config/initializers/openai.rb b/config/initializers/openai.rb new file mode 100644 index 000000000..656130f01 --- /dev/null +++ b/config/initializers/openai.rb @@ -0,0 +1,4 @@ +OpenAI.configure do |config| + config.access_token = ENV.fetch('OPENAI_ACCESS_TOKEN') + config.request_timeout = 240 +end