From 92729a3344a8fd91039bf30cb1343a62602349e8 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 16 Apr 2024 06:44:20 -0700 Subject: [PATCH 01/68] Encrypt the API keys for OpenAI and Anthropic --- .gitignore | 2 + README.md | 10 +-- app/models/user.rb | 1 + config/application.rb | 2 + config/credentials.yml.sample | 10 +++ config/database.yml.sample | 88 +++++++++++++++++++++++ db/migrate/20240415134849_encrypt_keys.rb | 17 +++++ db/schema.rb | 2 +- test/models/user_test.rb | 20 ++++++ 9 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 config/credentials.yml.sample create mode 100644 config/database.yml.sample create mode 100644 db/migrate/20240415134849_encrypt_keys.rb diff --git a/.gitignore b/.gitignore index 07f8ade5e..499f2fa7b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ # Ignore master key for decrypting credentials and more. /config/master.key +/config/database.yml +/config/credentials.yml.enc /app/assets/builds/* !/app/assets/builds/.keep diff --git a/README.md b/README.md index d22e17318..b6cf525b7 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,9 @@ HostedGPT requires these services to be running: 1. `cd` into your local repository clone 2. `asdf install` to install the correct ruby version -4. `bundle install` to install ruby gems -5. `bin/rails db:setup` < Note: This will load the sample fixture data into your database -6. `bin/dev` < Starts up all the services -5. Open [http://localhost:3000](http://localhost:3000) and register as a new user. +3. `bundle install` to install ruby gems +4. `cp config/database.yml.sample to config/database.yml` +5. `bin/rails credentials:edit` (see config/credentials.yml.sample for names) +6. `bin/rails db:setup` < Optional: This will load the sample fixture data into your database +7. `bin/dev` < Starts up all the services +8. Open [http://localhost:3000](http://localhost:3000) and register as a new user. diff --git a/app/models/user.rb b/app/models/user.rb index e89ecfbc8..a727a8331 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,6 @@ class User < ApplicationRecord include Personable, Registerable + encrypts :openai_key, :anthropic_key has_secure_password has_person_name diff --git a/config/application.rb b/config/application.rb index 2af64fb33..999219380 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,6 +11,8 @@ class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.1 + # To remove in 2025 + config.active_record.encryption.support_unencrypted_data = true # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. # Common ones are `templates`, `generators`, or `middleware`, for example. diff --git a/config/credentials.yml.sample b/config/credentials.yml.sample new file mode 100644 index 000000000..e55216713 --- /dev/null +++ b/config/credentials.yml.sample @@ -0,0 +1,10 @@ +secret_key_base: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 + +database: + username: db_user_name + password: db_user_pwd + +active_record_encryption: + primary_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 + deterministic_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 + key_derivation_salt: ABCDEFGHIJKLMNOPQRSTUVW123456789 diff --git a/config/database.yml.sample b/config/database.yml.sample new file mode 100644 index 000000000..0d091ff5d --- /dev/null +++ b/config/database.yml.sample @@ -0,0 +1,88 @@ +# PostgreSQL. Versions 9.3 and up are supported. +# +# Install the pg driver: +# gem install pg +# On macOS with Homebrew: +# gem install pg -- --with-pg-config=/usr/local/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +# +# Configure Using Gemfile +# gem "pg" +# +default: &default + adapter: postgresql + encoding: unicode + host: localhost + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + username: <%= Rails.application.credentials.database[:username] %> + password: <%= Rails.application.credentials.database[:password] %> +<% if RUBY_PLATFORM =~ /darwin/ %> + gssencmode: disable +<% end %> + +development: + <<: *default + database: hostedgpt_development + + # The specified database role being used to connect to PostgreSQL. + # To create additional roles in PostgreSQL see `$ createuser --help`. + # When left blank, PostgreSQL will use the default role. This is + # the same name as the operating system user running Rails. + #username: hostedgpt + + # The password associated with the PostgreSQL role (username). + #password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + + # The TCP port the server listens on. Defaults to 5432. + # If your server runs on a different port number, change accordingly. + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # Defaults to warning. + #min_messages: notice + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: hostedgpt_test + +# As with config/credentials.yml, you never want to store sensitive information, +# like your database password, in your source code. If your source code is +# ever seen by anyone, they now have access to your database. +# +# Instead, provide the password or a full connection URL as an environment +# variable when you boot the app. For example: +# +# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" +# +# If the connection URL is provided in the special DATABASE_URL environment +# variable, Rails will automatically merge its configuration values on top of +# the values provided in this file. Alternatively, you can specify a connection +# URL environment variable explicitly: +# +# production: +# url: <%= ENV["MY_APP_DATABASE_URL"] %> +# +# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database +# for a full overview on how database connection configuration can be specified. +# +production: + <<: *default + database: hostedgpt_production + username: hostedgpt + password: <%= ENV["HOSTEDGPT_DATABASE_PASSWORD"] %> diff --git a/db/migrate/20240415134849_encrypt_keys.rb b/db/migrate/20240415134849_encrypt_keys.rb new file mode 100644 index 000000000..5cd527b5d --- /dev/null +++ b/db/migrate/20240415134849_encrypt_keys.rb @@ -0,0 +1,17 @@ +class EncryptKeys < ActiveRecord::Migration[7.1] + def up + User.find_each do |user| + Rails.logger.info "Encrypt keys for #{user.id}. Has openai_key: #{user.openai_key.present?}; has anthropic_key: #{user.anthropic_key.present?}" + user.encrypt + if !user.save + Rails.logger.warn "Could not update user #{user.id}: #{user.errors.full_messages.join(',')}" + else + Rails.logger.info "Successfully updated user #{user.id}" + end + end + end + + def down + raise ActiveRecord::IrreversibleMigration.new "Won't decrypt data" + end +end diff --git a/db/schema.rb b/db/schema.rb index 2d6d645b7..0401d93e8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_04_03_015310) do +ActiveRecord::Schema[7.1].define(version: 2024_04_15_134849) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/test/models/user_test.rb b/test/models/user_test.rb index bcdcf8704..e2f951f69 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -11,6 +11,26 @@ class UserTest < ActiveSupport::TestCase refute person.valid? end + test "encrypts openai_key" do + user = users(:keith) + old_openai_key = user.openai_key + old_cipher_text = user.ciphertext_for(:openai_key) + user.update(openai_key: "new one") + assert user.reload + refute_equal old_cipher_text, user.ciphertext_for(:openai_key) + assert_equal "new one", user.openai_key + end + + test "encrypts anthropic_key" do + user = users(:keith) + old_anthropic_key = user.anthropic_key + old_cipher_text = user.ciphertext_for(:anthropic_key) + user.update(anthropic_key: "new one") + assert user.reload + refute_equal old_cipher_text, user.ciphertext_for(:anthropic_key) + assert_equal "new one", user.anthropic_key + end + test "should validate a user with minimum information" do user = User.new(password: "password", password_confirmation: "password", first_name: "John", last_name: "Doe") person = Person.new(email: "example@gmail.com", personable: user) From bfd663e92c398af3e921b895f493f656ee046fdf Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 18 Apr 2024 06:54:29 -0700 Subject: [PATCH 02/68] Review comments: explain config.active_record.encryption.support_unencrypted_data setting and use update! instead of update in tests --- config/application.rb | 3 ++- test/models/user_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config/application.rb b/config/application.rb index 999219380..c7e5b7381 100644 --- a/config/application.rb +++ b/config/application.rb @@ -11,8 +11,9 @@ class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.1 - # To remove in 2025 + # To remove in 2025. This allows migration db/migrate/20240415134849_encrypt_keys.rb to encrypt existing plaintext keys config.active_record.encryption.support_unencrypted_data = true + # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. # Common ones are `templates`, `generators`, or `middleware`, for example. diff --git a/test/models/user_test.rb b/test/models/user_test.rb index e2f951f69..eb3665c6b 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -15,7 +15,7 @@ class UserTest < ActiveSupport::TestCase user = users(:keith) old_openai_key = user.openai_key old_cipher_text = user.ciphertext_for(:openai_key) - user.update(openai_key: "new one") + user.update!(openai_key: "new one") assert user.reload refute_equal old_cipher_text, user.ciphertext_for(:openai_key) assert_equal "new one", user.openai_key @@ -25,7 +25,7 @@ class UserTest < ActiveSupport::TestCase user = users(:keith) old_anthropic_key = user.anthropic_key old_cipher_text = user.ciphertext_for(:anthropic_key) - user.update(anthropic_key: "new one") + user.update!(anthropic_key: "new one") assert user.reload refute_equal old_cipher_text, user.ciphertext_for(:anthropic_key) assert_equal "new one", user.anthropic_key From f91da68606dc4b92abab03d6c96e35334adcd732 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Fri, 19 Apr 2024 06:33:51 -0700 Subject: [PATCH 03/68] Restore original config/database.yml, remove .gitignore entry, and config/database.yml.sample --- .gitignore | 1 - README.md | 9 ++-- config/credentials.yml.sample | 4 -- config/database.yml.sample | 88 ----------------------------------- 4 files changed, 4 insertions(+), 98 deletions(-) delete mode 100644 config/database.yml.sample diff --git a/.gitignore b/.gitignore index 0b799009e..2425e8556 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ # Ignore master key for decrypting credentials and more. /config/master.key -/config/database.yml /config/credentials.yml.enc /app/assets/builds/* diff --git a/README.md b/README.md index b6cf525b7..96824b7c3 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,7 @@ HostedGPT requires these services to be running: 1. `cd` into your local repository clone 2. `asdf install` to install the correct ruby version 3. `bundle install` to install ruby gems -4. `cp config/database.yml.sample to config/database.yml` -5. `bin/rails credentials:edit` (see config/credentials.yml.sample for names) -6. `bin/rails db:setup` < Optional: This will load the sample fixture data into your database -7. `bin/dev` < Starts up all the services -8. Open [http://localhost:3000](http://localhost:3000) and register as a new user. +4. `bin/rails credentials:edit` (see config/credentials.yml.sample for names) +5. `bin/rails db:setup` < Optional: This will load the sample fixture data into your database +6. `bin/dev` < Starts up all the services +7. Open [http://localhost:3000](http://localhost:3000) and register as a new user. diff --git a/config/credentials.yml.sample b/config/credentials.yml.sample index e55216713..d2ad7f0ec 100644 --- a/config/credentials.yml.sample +++ b/config/credentials.yml.sample @@ -1,9 +1,5 @@ secret_key_base: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 -database: - username: db_user_name - password: db_user_pwd - active_record_encryption: primary_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 deterministic_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 diff --git a/config/database.yml.sample b/config/database.yml.sample deleted file mode 100644 index 0d091ff5d..000000000 --- a/config/database.yml.sample +++ /dev/null @@ -1,88 +0,0 @@ -# PostgreSQL. Versions 9.3 and up are supported. -# -# Install the pg driver: -# gem install pg -# On macOS with Homebrew: -# gem install pg -- --with-pg-config=/usr/local/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# -# Configure Using Gemfile -# gem "pg" -# -default: &default - adapter: postgresql - encoding: unicode - host: localhost - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - username: <%= Rails.application.credentials.database[:username] %> - password: <%= Rails.application.credentials.database[:password] %> -<% if RUBY_PLATFORM =~ /darwin/ %> - gssencmode: disable -<% end %> - -development: - <<: *default - database: hostedgpt_development - - # The specified database role being used to connect to PostgreSQL. - # To create additional roles in PostgreSQL see `$ createuser --help`. - # When left blank, PostgreSQL will use the default role. This is - # the same name as the operating system user running Rails. - #username: hostedgpt - - # The password associated with the PostgreSQL role (username). - #password: - - # Connect on a TCP socket. Omitted by default since the client uses a - # domain socket that doesn't need configuration. Windows does not have - # domain sockets, so uncomment these lines. - #host: localhost - - # The TCP port the server listens on. Defaults to 5432. - # If your server runs on a different port number, change accordingly. - #port: 5432 - - # Schema search path. The server defaults to $user,public - #schema_search_path: myapp,sharedapp,public - - # Minimum log levels, in increasing order: - # debug5, debug4, debug3, debug2, debug1, - # log, notice, warning, error, fatal, and panic - # Defaults to warning. - #min_messages: notice - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: hostedgpt_test - -# As with config/credentials.yml, you never want to store sensitive information, -# like your database password, in your source code. If your source code is -# ever seen by anyone, they now have access to your database. -# -# Instead, provide the password or a full connection URL as an environment -# variable when you boot the app. For example: -# -# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" -# -# If the connection URL is provided in the special DATABASE_URL environment -# variable, Rails will automatically merge its configuration values on top of -# the values provided in this file. Alternatively, you can specify a connection -# URL environment variable explicitly: -# -# production: -# url: <%= ENV["MY_APP_DATABASE_URL"] %> -# -# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database -# for a full overview on how database connection configuration can be specified. -# -production: - <<: *default - database: hostedgpt_production - username: hostedgpt - password: <%= ENV["HOSTEDGPT_DATABASE_PASSWORD"] %> From 92024cf6b41b8f39a3cc11af2f52863f7608d649 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Fri, 19 Apr 2024 06:42:25 -0700 Subject: [PATCH 04/68] Update README with respect to credentials.yml.enc, remove the sample file --- README.md | 3 ++- config/credentials.yml.sample | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 config/credentials.yml.sample diff --git a/README.md b/README.md index 96824b7c3..ef75569d0 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,8 @@ HostedGPT requires these services to be running: 1. `cd` into your local repository clone 2. `asdf install` to install the correct ruby version 3. `bundle install` to install ruby gems -4. `bin/rails credentials:edit` (see config/credentials.yml.sample for names) +3. 'bin/rails db:encryption:init' to create random keys for database encryption +4. `bin/rails credentials:edit` (add values from step 3) 5. `bin/rails db:setup` < Optional: This will load the sample fixture data into your database 6. `bin/dev` < Starts up all the services 7. Open [http://localhost:3000](http://localhost:3000) and register as a new user. diff --git a/config/credentials.yml.sample b/config/credentials.yml.sample deleted file mode 100644 index d2ad7f0ec..000000000 --- a/config/credentials.yml.sample +++ /dev/null @@ -1,6 +0,0 @@ -secret_key_base: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 - -active_record_encryption: - primary_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 - deterministic_key: ABCDEFGHIJKLMNOPQRSTUVW123456789 - key_derivation_salt: ABCDEFGHIJKLMNOPQRSTUVW123456789 From 89f80f79e12f9118d9c46f73a77894dc47e084c6 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 24 Apr 2024 11:20:47 -0700 Subject: [PATCH 05/68] Have keys related to active record encryption generated on Render via render.yaml --- render.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/render.yaml b/render.yaml index 66f97b23f..85092282a 100644 --- a/render.yaml +++ b/render.yaml @@ -9,6 +9,12 @@ envVarGroups: envVars: - key: SECRET_KEY_BASE generateValue: true + - key: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY + generateValue: true + - key: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY + generateValue: true + - key: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT + generateValue: true services: - type: web From 51960d078c1a79d5815451f3fc3bdc9179645506 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 24 Apr 2024 11:20:47 -0700 Subject: [PATCH 06/68] Have keys related to active record encryption generated on Render via render.yaml --- config/initializers/active_record_encryption.rb | 10 ++++++++++ render.yaml | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 config/initializers/active_record_encryption.rb diff --git a/config/initializers/active_record_encryption.rb b/config/initializers/active_record_encryption.rb new file mode 100644 index 000000000..b438ab252 --- /dev/null +++ b/config/initializers/active_record_encryption.rb @@ -0,0 +1,10 @@ +# Commonly configured through config/credentials.yml.enc. +# For deployment to Render, we support configuration through ENV variables +if ENV['CONFIGURE_ACTIVE_RECORD_ENCRYPTION_FROM_ENV'] == 'true' + Rails.application.configure do + Rails.logger.info "Configuring active record encryption from environment" + config.active_record.encryption.primary_key = ENV['ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY'] + config.active_record.encryption.deterministic_key = ENV['ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY'] + config.active_record.encryption.key_derivation_salt = ENV['ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT'] + end +end diff --git a/render.yaml b/render.yaml index 66f97b23f..729dd5738 100644 --- a/render.yaml +++ b/render.yaml @@ -9,6 +9,14 @@ envVarGroups: envVars: - key: SECRET_KEY_BASE generateValue: true + - key: CONFIGURE_ACTIVE_RECORD_ENCRYPTION_FROM_ENV + value: true + - key: ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY + generateValue: true + - key: ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY + generateValue: true + - key: ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT + generateValue: true services: - type: web From e26a5b10f38a20bd04f654460dbe2cd384037a4d Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Fri, 26 Apr 2024 08:40:53 -0700 Subject: [PATCH 07/68] Encrypt the email field of Person records also --- app/models/person.rb | 2 ++ config/environments/test.rb | 2 ++ .../20240425173453_encrypt_person_emails.rb | 17 +++++++++++++++ db/schema.rb | 4 ++-- test/models/person_test.rb | 21 +++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20240425173453_encrypt_person_emails.rb diff --git a/app/models/person.rb b/app/models/person.rb index 9fb68d9a0..f2f56236d 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -1,4 +1,6 @@ class Person < ApplicationRecord + encrypts :email, deterministic: true + delegated_type :personable, types: %w[User Tombstone] accepts_nested_attributes_for :personable diff --git a/config/environments/test.rb b/config/environments/test.rb index 83e1a2394..667e26d6c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -23,6 +23,8 @@ "Cache-Control" => "public, max-age=#{1.hour.to_i}" } + config.active_record.encryption.encrypt_fixtures = true + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false diff --git a/db/migrate/20240425173453_encrypt_person_emails.rb b/db/migrate/20240425173453_encrypt_person_emails.rb new file mode 100644 index 000000000..774055dcb --- /dev/null +++ b/db/migrate/20240425173453_encrypt_person_emails.rb @@ -0,0 +1,17 @@ +class EncryptPersonEmails < ActiveRecord::Migration[7.1] + def up + Person.find_each do |person| + Rails.logger.info "Encrypt email for #{person.id}" + person.encrypt + if !person.save(validate: false) + Rails.logger.warn "Could not update person #{person.id}: #{person.errors.full_messages.join(',')}" + else + Rails.logger.info "Successfully updated user #{person.id}" + end + end + end + + def down + raise ActiveRecord::IrreversibleMigration.new "Won't decrypt data" + end +end diff --git a/db/schema.rb b/db/schema.rb index c6e304612..ef0a72ca4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_04_18_162137) do +ActiveRecord::Schema[7.1].define(version: 2024_04_25_173453) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -103,10 +103,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false + t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false - t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" diff --git a/test/models/person_test.rb b/test/models/person_test.rb index 434b74893..6759cb12c 100644 --- a/test/models/person_test.rb +++ b/test/models/person_test.rb @@ -5,6 +5,27 @@ class PersonTest < ActiveSupport::TestCase assert_instance_of User, people(:keith_registered).user end + test "encrypts email" do + person = people(:rob_registered) + old_email = person.email + old_cipher_text = person.ciphertext_for(:email) + person.update!(email: "new@address.net") + assert person.reload + refute_equal old_cipher_text, person.ciphertext_for(:email) + assert_equal "new@address.net", person.email + end + + # Appears length limit for email addresses is 256 + test "encrypts long emails" do + user = User.new password: "password", first_name: "John", last_name: "Doe" + long_email_address = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@xxx.net" + assert_equal 256, long_email_address.length + person = Person.new email: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@xxx.net", personable: user + person.save! + person.reload + assert_equal long_email_address, person.email + end + test "requires email addresses to be unique" do person1 = users(:keith).person person2 = Person.new(email: person1.email) From e5a2af116471bee7d5392e964b7875fda3c38160 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:32:14 -0500 Subject: [PATCH 08/68] cleanup migration --- ...849_encrypt_keys.rb => 20240425173452_encrypt_keys.rb} | 8 +++++++- db/schema.rb | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) rename db/migrate/{20240415134849_encrypt_keys.rb => 20240425173452_encrypt_keys.rb} (68%) diff --git a/db/migrate/20240415134849_encrypt_keys.rb b/db/migrate/20240425173452_encrypt_keys.rb similarity index 68% rename from db/migrate/20240415134849_encrypt_keys.rb rename to db/migrate/20240425173452_encrypt_keys.rb index 5cd527b5d..913f018fd 100644 --- a/db/migrate/20240415134849_encrypt_keys.rb +++ b/db/migrate/20240425173452_encrypt_keys.rb @@ -1,11 +1,17 @@ class EncryptKeys < ActiveRecord::Migration[7.1] def up + puts "" + puts "### ERROR? #################################################" + puts "### YOU SHOULD NOT RUN db:migrate INSTEAD RUN db:prepare ###" + puts "############################################################" + puts "" + User.find_each do |user| Rails.logger.info "Encrypt keys for #{user.id}. Has openai_key: #{user.openai_key.present?}; has anthropic_key: #{user.anthropic_key.present?}" user.encrypt if !user.save Rails.logger.warn "Could not update user #{user.id}: #{user.errors.full_messages.join(',')}" - else + else Rails.logger.info "Successfully updated user #{user.id}" end end diff --git a/db/schema.rb b/db/schema.rb index ef0a72ca4..872ce3b31 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -103,10 +103,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false - t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false + t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" From 02cbed2f16be6ea0c8cac883591ef65513c41898 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:32:37 -0500 Subject: [PATCH 09/68] ensure encryption init happens with db:prepare --- lib/tasks/prepare.rake | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 lib/tasks/prepare.rake diff --git a/lib/tasks/prepare.rake b/lib/tasks/prepare.rake new file mode 100644 index 000000000..fbe292cb1 --- /dev/null +++ b/lib/tasks/prepare.rake @@ -0,0 +1,53 @@ +namespace :db do + desc "Setup database encryption and update credentials" + task setup_encryption: :environment do + old_config = Rails.application.credentials.config + config = old_config.deep_dup + + if config[:secret_key_base].nil? && ENV['SECRET_KEY_BASE'].nil? + config = add_secret_key_base(config) + end + + if config[:active_record_encryption].nil? && ENV['CONFIGURE_ACTIVE_RECORD_ENCRYPTION_FROM_ENV'] != 'true' + config = add_active_record_encryption(config) + end + + if config != old_config + Rails.application.credentials.write(config.to_yaml) + ActiveRecord::Encryption.config.primary_key = config[:active_record_encryption][:primary_key] + ActiveRecord::Encryption.config.deterministic_key = config[:active_record_encryption][:deterministic_key] + ActiveRecord::Encryption.config.key_derivation_salt = config[:active_record_encryption][:key_derivation_salt] + end + end +end + +Rake::Task["db:prepare"].enhance [:setup_encryption] + +def add_secret_key_base(config) + config[:secret_key_base] = SecureRandom.hex(64) + config +end + +def add_active_record_encryption(config) + encryption = encryption_init + + config[:active_record_encryption] = {} + config[:active_record_encryption][:primary_key] = encryption[:primary_key] + config[:active_record_encryption][:deterministic_key] = encryption[:deterministic_key] + config[:active_record_encryption][:key_derivation_salt] = encryption[:key_derivation_salt] + config +end + +def encryption_init + original_stdout = $stdout + $stdout = StringIO.new + Rake::Task["db:encryption:init"].invoke + output = $stdout.string + $stdout = original_stdout + + { + primary_key: output.match(/primary_key: (\S+)/)[1], + deterministic_key: output.match(/deterministic_key: (\S+)/)[1], + key_derivation_salt: output.match(/key_derivation_salt: (\S+)/)[1], + } +end \ No newline at end of file From 849006497c2a6771db30fcea2d87af10f469d025 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:37:13 -0500 Subject: [PATCH 10/68] make docker entrypoint more resilient --- bin/docker-entrypoint | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index 3ce50458e..ff2951fca 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -2,9 +2,13 @@ # If running the rails server then create or migrate existing database if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then + rm -f ./tmp/pids/server.pid 2>/dev/null + echo "Running bundle" + bundle echo "Running db:prepare" ./bin/rails db:prepare else + echo "Skipping bundle" echo "Skipping db:prepare" fi From 5c571b5b4b653392de3e55c4f36bfd6221be82d4 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:49:17 -0500 Subject: [PATCH 11/68] Update readme and startup script --- README.md | 31 ++++++++++++++++--------------- bin/dev | 3 +++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4fb89c60d..75aefe632 100644 --- a/README.md +++ b/README.md @@ -68,32 +68,33 @@ We welcome contributors! After you get your developoment environment setup, revi The easiest way to get up and running is to use the provided docker compose workflow. The only things you need installed on your computer are Docker and Git. -1. Make sure you have [Docker Desktop](https://docs.docker.com/desktop/) installed and running. +1. Make sure you have [Docker Desktop](https://docs.docker.com/desktop/) installed and running 2. Clone your fork `git clone [repository url]` -3. `cd` into your clone. -4. Run `docker compose up` to start the app. -5. Open [http://localhost:3000](http://localhost:3000) and register as a new user. -6. Run tests: `docker compose run base rails test` The app has comprehensive test coverage. +3. `cd` into your clone +4. Run `docker compose up --build` to start the app +5. Open [http://localhost:3000](http://localhost:3000) and register as a new user +6. Run tests: `docker compose run base rails test` The app has comprehensive test coverage but note that system tests currently do not work in docker. 7. Open the rails console: `docker compose run base rails console` 8. Run a psql console: `docker compose run base psql` -Alternatively, you can set up your development environment locally: +Every time you pull new changes down, kill docker (if it's running) and re-run: +`docker compose up --build` This will ensure your local app picks up changes to Gemfile, migrations, and docker config. + +### Alternatively, you can set up your development environment locally: HostedGPT requires these services to be running: - Postgres ([installation instructions](https://www.postgresql.org/download/)) - Redis ([installation instructions](https://redis.io/download)) -- asdf-vm ([installation instructions](https://asdf-vm.com/guide/getting-started.html#_2-download-asdf)) +- rbenv or asdf-vm ([installation instructions](https://asdf-vm.com/guide/getting-started.html#_2-download-asdf)) 1. `cd` into your local repository clone -2. `asdf install` to install the correct ruby version -3. `bundle install` to install ruby gems -4. 'bin/rails db:encryption:init' to create random keys for database encryption -5. `bin/rails credentials:edit` (add values from step 3) -6. `bin/rails db:setup` < Note: This will load the sample fixture data into your database -7. `bin/dev` < Starts up all the services -8. Open [http://localhost:3000](http://localhost:3000) and register as a new user. -9. `bin/rails test` and `bin/rails test:system` to run the comprehensive tests +2. `rbenv install` or `asdf install` to install the correct ruby version +3. `bin/dev` starts up all the services, installs gems, and inits database (don't run **db:setup** as it will not configure encryption properly) +4. Open [http://localhost:3000](http://localhost:3000) and register as a new user +5. `bin/rails test` and `bin/rails test:system` to run the comprehensive tests + +Every time you pull new changes down, kill `bin/dev` and then re-run it. This will ensure your local app picks up changes to Gemfile and migrations. # Changelog diff --git a/bin/dev b/bin/dev index f078a8122..56fc7f6f9 100755 --- a/bin/dev +++ b/bin/dev @@ -9,5 +9,8 @@ else echo "Installing foreman..." gem install foreman fi + + bundle install + bin/rails db:prepare exec foreman start -f Procfile.dev "$@" fi From a52a289924502662b67bea376b6479996b202ebb Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:53:33 -0500 Subject: [PATCH 12/68] fix whitespace --- db/migrate/20240425173453_encrypt_person_emails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20240425173453_encrypt_person_emails.rb b/db/migrate/20240425173453_encrypt_person_emails.rb index 774055dcb..cef303728 100644 --- a/db/migrate/20240425173453_encrypt_person_emails.rb +++ b/db/migrate/20240425173453_encrypt_person_emails.rb @@ -5,7 +5,7 @@ def up person.encrypt if !person.save(validate: false) Rails.logger.warn "Could not update person #{person.id}: #{person.errors.full_messages.join(',')}" - else + else Rails.logger.info "Successfully updated user #{person.id}" end end From d99e387dfafe7cb4797140404d7bef6d7f1c070f Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 29 Apr 2024 16:56:40 -0500 Subject: [PATCH 13/68] update test runner --- .github/workflows/rubyonrails.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml index 751226188..01e7ef113 100644 --- a/.github/workflows/rubyonrails.yml +++ b/.github/workflows/rubyonrails.yml @@ -37,7 +37,7 @@ jobs: bundler-cache: true - name: Set up database schema - run: bin/rails db:schema:load + run: bin/rails db:prepare - name: Build CSS run: bin/rails tailwindcss:build @@ -85,7 +85,7 @@ jobs: bundler-cache: true - name: Set up database schema - run: bin/rails db:schema:load + run: bin/rails db:prepare - name: Build CSS run: bin/rails tailwindcss:build From 776ec26a0e0d66df39f9279c4fb6af7395ce3970 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Tue, 30 Apr 2024 14:00:14 -0500 Subject: [PATCH 14/68] Fix running tests in docker within dev --- bin/docker-entrypoint | 11 +++++++++-- compose.yml => docker-compose.yml | 0 2 files changed, 9 insertions(+), 2 deletions(-) rename compose.yml => docker-compose.yml (100%) diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index ff2951fca..2d42cedf7 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -1,14 +1,21 @@ #!/bin/sh -e # If running the rails server then create or migrate existing database -if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then +if echo "${1}" | grep -q "rails$" && [ "${2}" == "server" ]; then rm -f ./tmp/pids/server.pid 2>/dev/null echo "Running bundle" bundle echo "Running db:prepare" ./bin/rails db:prepare +elif echo "${1}" | grep -q "rails$" && [ "${2}" == "test" ]; then + export RAILS_ENV=test + echo "Running bundle" + bundle + echo "Running db:prepare" + ./bin/rails db:prepare else - echo "Skipping bundle" + echo "Running bundle" + bundle echo "Skipping db:prepare" fi diff --git a/compose.yml b/docker-compose.yml similarity index 100% rename from compose.yml rename to docker-compose.yml From 438bf8c76ddf9fd15ca5a1511d64c92630874aee Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Tue, 30 Apr 2024 14:12:58 -0500 Subject: [PATCH 15/68] Add generation of master.key --- lib/tasks/prepare.rake | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/tasks/prepare.rake b/lib/tasks/prepare.rake index fbe292cb1..1d7e2ad8b 100644 --- a/lib/tasks/prepare.rake +++ b/lib/tasks/prepare.rake @@ -1,6 +1,8 @@ namespace :db do desc "Setup database encryption and update credentials" task setup_encryption: :environment do + ensure_master_key + old_config = Rails.application.credentials.config config = old_config.deep_dup @@ -50,4 +52,12 @@ def encryption_init deterministic_key: output.match(/deterministic_key: (\S+)/)[1], key_derivation_salt: output.match(/key_derivation_salt: (\S+)/)[1], } +end + +def ensure_master_key + master_key_path = Rails.root.join('config', 'master.key') + unless File.exist?(master_key_path) + key = SecureRandom.hex(16) + File.write(master_key_path, key) + end end \ No newline at end of file From 6f2d938d6afb212092cbc364dcfde3e90e13a662 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Tue, 30 Apr 2024 14:13:16 -0500 Subject: [PATCH 16/68] simplify generation of ar encryption keys --- lib/tasks/prepare.rake | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/tasks/prepare.rake b/lib/tasks/prepare.rake index 1d7e2ad8b..a3f54ca3a 100644 --- a/lib/tasks/prepare.rake +++ b/lib/tasks/prepare.rake @@ -31,12 +31,11 @@ def add_secret_key_base(config) end def add_active_record_encryption(config) - encryption = encryption_init - - config[:active_record_encryption] = {} - config[:active_record_encryption][:primary_key] = encryption[:primary_key] - config[:active_record_encryption][:deterministic_key] = encryption[:deterministic_key] - config[:active_record_encryption][:key_derivation_salt] = encryption[:key_derivation_salt] + config[:active_record_encryption] = { + primary_key: SecureRandom.alphanumeric(32), + deterministic_key: SecureRandom.alphanumeric(32), + key_derivation_salt: SecureRandom.alphanumeric(32), + } config end From a17867566945aba6378584b5fc89dc03ac49e465 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 7 May 2024 18:00:11 -0700 Subject: [PATCH 17/68] Allow adding new assistants --- .../settings/application_controller.rb | 2 +- .../settings/assistants_controller.rb | 2 +- app/models/ai_api_model.rb | 4 +++ app/views/layouts/_settings_item.erb | 4 ++- app/views/settings/assistants/_form.html.erb | 6 ++++- .../20240507165222_create_ai_api_models.rb | 25 +++++++++++++++++++ db/schema.rb | 9 ++++++- 7 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 app/models/ai_api_model.rb create mode 100644 db/migrate/20240507165222_create_ai_api_models.rb diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index 6ceccc585..806942441 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -10,7 +10,7 @@ def set_settings_menu assistants: Current.user.assistants.ordered.map { |assistant| [ assistant, edit_settings_assistant_path(assistant) ] }.to_h.merge({ - #'New Assistant': new_settings_assistant_path(assistant) + 'New Assistant': new_settings_assistant_path }), people: { diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index 6a254a243..ad1fb1ebf 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -38,6 +38,6 @@ def set_assistant end def assistant_params - params.require(:assistant).permit(:name, :description, :instructions) + params.require(:assistant).permit(:name, :description, :instructions, :model) end end diff --git a/app/models/ai_api_model.rb b/app/models/ai_api_model.rb new file mode 100644 index 000000000..c40bfa980 --- /dev/null +++ b/app/models/ai_api_model.rb @@ -0,0 +1,4 @@ +class AIAPIModel < ApplicationRecord + def readonly?() = !new_record? + def before_destroy() = raise ActiveRecord::ReadOnlyRecord +end diff --git a/app/views/layouts/_settings_item.erb b/app/views/layouts/_settings_item.erb index d58e107c4..3f906e2c4 100644 --- a/app/views/layouts/_settings_item.erb +++ b/app/views/layouts/_settings_item.erb @@ -1,6 +1,8 @@ <% item, link = settings_item %> <% selected = (controller.to_s == controller_name.to_s) %> -<% selected = selected && params[:id] == item.id.to_s if params[:id] %> +<% compare_id = item.respond_to?(:id) ? item.id.to_s : '' %> +<% selected = selected && params[:id] == compare_id if params[:id] %> +<% selected = selected && request.fullpath == link if params[:id].blank? %>
+ <%= form.label :model %>
+ <%= form.select :model, AIAPIModel.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> +
<%= form.label :name %> <%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> @@ -24,7 +28,7 @@
<%= form.label :instructions %> <%= form.text_area :instructions, - class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black h-18", + class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black h-38", data: { controller: "textarea-autogrow" } %>
diff --git a/db/migrate/20240507165222_create_ai_api_models.rb b/db/migrate/20240507165222_create_ai_api_models.rb new file mode 100644 index 000000000..893dc9129 --- /dev/null +++ b/db/migrate/20240507165222_create_ai_api_models.rb @@ -0,0 +1,25 @@ +class CreateAIAPIModels < ActiveRecord::Migration[7.1] + def change + create_table :ai_api_models do |t| + t.string :name + t.text :description + + t.timestamps + end + + # Current models + up_only do + { + 'open_ai_newest': 'Newest OpenAI Model', + 'claude_newest': 'Newest Anthropic Model', + 'claude-3-sonnet-20240229': 'Claude 3 Sonnet', + 'gpt-4': 'ChatGPT 4', + 'claude-3-opus-20240229': 'Claude 3 Opus', + 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo', + 'gpt-4-turbo-2024-04-09': 'ChatGPT 4 Turbo' + }.each do |name, description| + AIAPIModel.create(name: name, description: description) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 37bdacfeb..674335be5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_04_212345) do +ActiveRecord::Schema[7.1].define(version: 2024_05_07_165222) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -48,6 +48,13 @@ t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end + create_table "ai_api_models", force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "assistants", force: :cascade do |t| t.bigint "user_id", null: false t.string "model" From 06de134b2d6abecdb36b916c39ba5660acfa0e10 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 9 May 2024 18:14:23 -0700 Subject: [PATCH 18/68] Load all assistants when editing, only show 5 initially --- .../settings/application_controller.rb | 24 +++++++-------- app/views/layouts/settings.html.erb | 29 +++++++++++++++---- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index 806942441..a45621acc 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -1,21 +1,21 @@ class Settings::ApplicationController < ApplicationController layout "settings" - before_action :set_settings_menu + before_action :set_settings_assistants private - def set_settings_menu - # controller_name => array of items - @settings_menu = { - assistants: Current.user.assistants.ordered.map { + def set_settings_assistants + settings_assistants = Current.user.assistants.ordered.map { |assistant| [ assistant, edit_settings_assistant_path(assistant) ] - }.to_h.merge({ - 'New Assistant': new_settings_assistant_path - }), - - people: { - 'Account': edit_settings_person_path } - } + @settings_assistants = settings_assistants[0,5] + if settings_assistants.length > 5 + @hide_settings_assistants = settings_assistants[5, settings_assistants.length - 5] + + # If user is editing an "overflow assistant" don't collapse that section, keep it open from the start + @unhide_settings_assistants = params[:controller] =='settings/assistants' && + params[:action] == 'edit' && + @hide_settings_assistants.map(&:first).map(&:id).include?(params[:id].to_i) + end end end diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index d403b8d7e..7a2f0cd2c 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -16,11 +16,30 @@ Settings <% end %> From d919d6ef58a65fd58255a68036a18813522731e3 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 9 May 2024 18:20:41 -0700 Subject: [PATCH 19/68] Rename ai_api_models table to llms --- app/models/{ai_api_model.rb => llm.rb} | 2 +- app/views/settings/assistants/_form.html.erb | 2 +- config/initializers/inflections.rb | 1 + ...i_models.rb => 20240507165222_create_llms.rb} | 6 +++--- db/schema.rb | 16 ++++++++-------- 5 files changed, 14 insertions(+), 13 deletions(-) rename app/models/{ai_api_model.rb => llm.rb} (72%) rename db/migrate/{20240507165222_create_ai_api_models.rb => 20240507165222_create_llms.rb} (77%) diff --git a/app/models/ai_api_model.rb b/app/models/llm.rb similarity index 72% rename from app/models/ai_api_model.rb rename to app/models/llm.rb index c40bfa980..3e7e72301 100644 --- a/app/models/ai_api_model.rb +++ b/app/models/llm.rb @@ -1,4 +1,4 @@ -class AIAPIModel < ApplicationRecord +class LLM < ApplicationRecord def readonly?() = !new_record? def before_destroy() = raise ActiveRecord::ReadOnlyRecord end diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index 575c70316..199c7beff 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -13,7 +13,7 @@
<%= form.label :model %>
- <%= form.select :model, AIAPIModel.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> + <%= form.select :model, LLM.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %>
<%= form.label :name %> diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index a6900ec7f..cfc233b0e 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -15,4 +15,5 @@ # inflect.acronym "RESTful" inflect.acronym "AI" inflect.acronym "API" + inflect.acronym "LLM" end diff --git a/db/migrate/20240507165222_create_ai_api_models.rb b/db/migrate/20240507165222_create_llms.rb similarity index 77% rename from db/migrate/20240507165222_create_ai_api_models.rb rename to db/migrate/20240507165222_create_llms.rb index 893dc9129..0f815dd9a 100644 --- a/db/migrate/20240507165222_create_ai_api_models.rb +++ b/db/migrate/20240507165222_create_llms.rb @@ -1,6 +1,6 @@ -class CreateAIAPIModels < ActiveRecord::Migration[7.1] +class CreateLlms < ActiveRecord::Migration[7.1] def change - create_table :ai_api_models do |t| + create_table :llms do |t| t.string :name t.text :description @@ -18,7 +18,7 @@ def change 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo', 'gpt-4-turbo-2024-04-09': 'ChatGPT 4 Turbo' }.each do |name, description| - AIAPIModel.create(name: name, description: description) + LLM.create(name: name, description: description) end end end diff --git a/db/schema.rb b/db/schema.rb index 674335be5..fc0ac9346 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -48,13 +48,6 @@ t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end - create_table "ai_api_models", force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "assistants", force: :cascade do |t| t.bigint "user_id", null: false t.string "model" @@ -103,6 +96,13 @@ t.index ["user_id"], name: "index_documents_on_user_id" end + create_table "llms", force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "messages", force: :cascade do |t| t.bigint "conversation_id", null: false t.string "role", null: false @@ -112,10 +112,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false + t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false - t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" From dad33d5d57280e829cb60f70b24d6a9b3c9344f9 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 9 May 2024 18:32:21 -0700 Subject: [PATCH 20/68] Use label Custom Instructions in the Assistant form --- app/views/settings/assistants/_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index 199c7beff..699ab0970 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -26,7 +26,7 @@
- <%= form.label :instructions %> + <%= form.label :instructions, 'Custom Instructions' %> <%= form.text_area :instructions, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black h-38", data: { controller: "textarea-autogrow" } From a299cb1c1fc30c04d21ce0e952da676f10b4f2d5 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 9 May 2024 18:46:12 -0700 Subject: [PATCH 21/68] Less clumsy way to keep additional assistants shown when editing one of them --- app/controllers/settings/application_controller.rb | 2 +- app/views/layouts/settings.html.erb | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index a45621acc..0a0a70c9c 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -13,7 +13,7 @@ def set_settings_assistants @hide_settings_assistants = settings_assistants[5, settings_assistants.length - 5] # If user is editing an "overflow assistant" don't collapse that section, keep it open from the start - @unhide_settings_assistants = params[:controller] =='settings/assistants' && + @open_hide_settings_assistants = params[:controller] =='settings/assistants' && params[:action] == 'edit' && @hide_settings_assistants.map(&:first).map(&:id).include?(params[:id].to_i) end diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index 7a2f0cd2c..c3c8ed4b2 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -19,21 +19,12 @@
<%= render partial: "layouts/settings_item", collection: @settings_assistants, locals: { controller: :assistants } %> <% if @hide_settings_assistants.present? %> - <% unless @unhide_settings_assistants %> -
- More +
+
More
<%= render partial: "layouts/settings_item", collection: @hide_settings_assistants, locals: { controller: :assistants } %>
-
- <% else %> -
- More -
-
- <% end %> <% end %>
From b3edb578ab465d631ff04cd6469bd748c81b5a52 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Fri, 10 May 2024 06:42:21 -0700 Subject: [PATCH 22/68] Add back the New Assistant link --- app/views/layouts/settings.html.erb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index c3c8ed4b2..73cd6f01d 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -19,14 +19,18 @@
<%= render partial: "layouts/settings_item", collection: @settings_assistants, locals: { controller: :assistants } %> <% if @hide_settings_assistants.present? %> -
+
bg-base-200">
More
-
- <%= render partial: "layouts/settings_item", collection: @hide_settings_assistants, locals: { controller: :assistants } %> -
+
+ <%= render partial: "layouts/settings_item", collection: @hide_settings_assistants, locals: { controller: :assistants } %> +
<% end %>
+ +
+ <%= render partial: "layouts/settings_item", locals: {settings_item: ['New Assistant', new_settings_assistant_path], controller: 'assistants' } %> +
<%= render partial: "layouts/settings_item", locals: {settings_item: ['Account', edit_settings_person_path], controller: 'people' } %> From f525b96c8c158293174a39cf1e02c17daa84e081 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Fri, 10 May 2024 10:07:00 -0700 Subject: [PATCH 23/68] Fix the collapse mechanism --- .../settings/application_controller.rb | 7 ++++--- app/views/layouts/settings.html.erb | 16 ++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index 0a0a70c9c..a645645d4 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -13,9 +13,10 @@ def set_settings_assistants @hide_settings_assistants = settings_assistants[5, settings_assistants.length - 5] # If user is editing an "overflow assistant" don't collapse that section, keep it open from the start - @open_hide_settings_assistants = params[:controller] =='settings/assistants' && - params[:action] == 'edit' && - @hide_settings_assistants.map(&:first).map(&:id).include?(params[:id].to_i) + if params[:controller] =='settings/assistants' && params[:action] == 'edit' && @hide_settings_assistants.map(&:first).map(&:id).include?(params[:id].to_i) + @settings_assistants.concat(@hide_settings_assistants) + @hide_settings_assistants = [] + end end end end diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index 73cd6f01d..785eb3b51 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -19,12 +19,16 @@
<%= render partial: "layouts/settings_item", collection: @settings_assistants, locals: { controller: :assistants } %> <% if @hide_settings_assistants.present? %> -
bg-base-200"> -
More
-
- <%= render partial: "layouts/settings_item", collection: @hide_settings_assistants, locals: { controller: :assistants } %> -
-
+
+ <%= button_tag type: "button", + class: "p-3", + data: {transition_target: "transitionable", action: "transition#toggleClass", + } do %> + Show Remaining (<%= @hide_settings_assistants.size %>) + <% end %> + <% end %>
From f33f388032b07e6aca9d0f8706ae62ca53b61c5c Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 12 May 2024 15:45:49 -0700 Subject: [PATCH 24/68] Refactor display of Assistant section under Settings --- .../settings/.application_controller.rb.swp | Bin 0 -> 12288 bytes .../settings/application_controller.rb | 16 ++++---- app/helpers/application_helper.rb | 12 ++++++ app/models/assistant.rb | 2 + app/views/layouts/_settings_item.erb | 6 +-- app/views/layouts/settings.html.erb | 36 +++++++----------- app/views/settings/assistants/new.html.erb | 2 +- 7 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 app/controllers/settings/.application_controller.rb.swp diff --git a/app/controllers/settings/.application_controller.rb.swp b/app/controllers/settings/.application_controller.rb.swp new file mode 100644 index 0000000000000000000000000000000000000000..401c8fabbd9ef826591b88f57bfa5e59f0a2a450 GIT binary patch literal 12288 zcmeI2&ubGw6vwBouT zs;+T{Sccq6t%B0QOZWfqE;17VB5;ntz;>3`=GlYQ`z!A0LbU=nZ#+NeKwpUf5g-CY zfCvx)B0vO)01^1#3D{(Yy~Rz=W*fbg%`+F~Y)KOlAOb{y2oM1xKm>>Y5g-CYfCvx) zBJd9q;1OdVu3+JFDE$9F|NZ~<8e^YPXQ+3mW7H9x@@#KMP@ zS~CpwK-roPB9b=9J92{r`*X1wH{rH-dWx}bC~+8YW2CWMSr`0796~dHfGM0qk)iM$ zCs|@H#b#md3pIcZI4RtW4~4OO=vf`~#<6Aj0F}}sa2_%eUY{q6_xz4bjjd-38Pz?h z!gvsgM~ewhdEc~s8SS~OvOcQFRh3(K=sJ-1pt1oqhoc6=Vdk{yUNK>FCeQ~W?Zo;J zHy&^HcTxo#xA6yS4i)c--23 zy4`%WG%3J~h3eWKESLA;_sG3b6|yre!NX|@vR&l&5nxzsXb5d67E2c{b72@=#l>P- zhjB?02q2fOL7EC>J$E)<5o0`^Mj{oFyJB#HB}HPiqe+*8p*czA0k^_^-4`95ieP-r qz{l543<8Mxkses6=7m*R7-I#nn>Eb)ezWYD-zqxlVI3wuXTJc4fuSb= literal 0 HcmV?d00001 diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index a645645d4..8a193dbea 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -8,15 +8,15 @@ def set_settings_assistants settings_assistants = Current.user.assistants.ordered.map { |assistant| [ assistant, edit_settings_assistant_path(assistant) ] } - @settings_assistants = settings_assistants[0,5] - if settings_assistants.length > 5 - @hide_settings_assistants = settings_assistants[5, settings_assistants.length - 5] - + if settings_assistants.length > Assistant::MAX_LIST_DISPLAY + assistants_to_hide = settings_assistants[Assistant::MAX_LIST_DISPLAY, settings_assistants.length - Assistant::MAX_LIST_DISPLAY] # If user is editing an "overflow assistant" don't collapse that section, keep it open from the start - if params[:controller] =='settings/assistants' && params[:action] == 'edit' && @hide_settings_assistants.map(&:first).map(&:id).include?(params[:id].to_i) - @settings_assistants.concat(@hide_settings_assistants) - @hide_settings_assistants = [] - end + @hide_settings_assistants_overflow = params[:controller] !='settings/assistants' || + params[:action] != 'edit' || + !assistants_to_hide.map(&:first).map(&:id).include?(params[:id].to_i) end + @settings_menu = {assistants: settings_assistants.to_h, + new_assistant: {'New Assistant': new_settings_assistant_path}, + people: {'Account': edit_settings_person_path}} end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 4d90946e9..bfb7fd3c2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,4 +1,16 @@ module ApplicationHelper + + def hide_settings_item_class(item, item_counter) + return nil unless item.is_a?(Assistant) + return 'hidden' if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY + nil + end + + def settings_item_data_transition_target(item, item_counter) + return nil unless item.is_a?(Assistant) + return 'data-transition-target="transitionable"'.html_safe if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY + end + def spinner(opts = {}) html = <<~HTML diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 56371f76f..0c7eba0f2 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -9,6 +9,8 @@ class Assistant < ApplicationRecord validates :tools, presence: true, allow_blank: true + MAX_LIST_DISPLAY = 5 + scope :ordered, -> { order(:id) } def initials diff --git a/app/views/layouts/_settings_item.erb b/app/views/layouts/_settings_item.erb index 3f906e2c4..0fc38dee4 100644 --- a/app/views/layouts/_settings_item.erb +++ b/app/views/layouts/_settings_item.erb @@ -11,8 +11,10 @@ group text-sm rounded-s-lg <%= selected ? "bg-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-700 cursor-default" : "cursor-pointer" %> + <%= hide_settings_item_class(item, settings_item_counter) %> " data-role="assistant" + <%= settings_item_data_transition_target(item, settings_item_counter) %> > <%= link_to link, class: %| @@ -29,8 +31,6 @@
<% end %> - "> - <%= item.to_s %> - + <%= content_tag :span, item.to_s, class: !item.is_a?(Assistant) && "ml-1 text-base" %> <% end %>
diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index 785eb3b51..d6b569902 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -16,29 +16,21 @@ Settings <% end %> diff --git a/app/views/settings/assistants/new.html.erb b/app/views/settings/assistants/new.html.erb index 4122ed1f9..7a49677b0 100644 --- a/app/views/settings/assistants/new.html.erb +++ b/app/views/settings/assistants/new.html.erb @@ -1,5 +1,5 @@
-

New assistant

+

New Assistant

<%= render "form", assistant: @assistant %>
From 808285ebb49ada9c6fc697813015a81104f116de Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 12 May 2024 16:04:35 -0700 Subject: [PATCH 25/68] Have assistant list truncated in message view also (but there is no need to expand the list for the "active" assistant like under Settings --- app/controllers/messages_controller.rb | 1 + .../settings/.application_controller.rb.swp | Bin 12288 -> 0 bytes app/helpers/application_helper.rb | 4 ++-- app/views/assistants/_assistant.html.erb | 2 ++ app/views/layouts/_settings_item.erb | 4 ++-- app/views/messages/_nav_column.html.erb | 10 +++++++++- 6 files changed, 16 insertions(+), 5 deletions(-) delete mode 100644 app/controllers/settings/.application_controller.rb.swp diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index b16573ebd..b55f6c7df 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -90,6 +90,7 @@ def set_nav_conversations def set_nav_assistants @nav_assistants = Current.user.assistants.ordered + @hide_settings_assistants_overflow = @nav_assistants.length > Assistant::MAX_LIST_DISPLAY end def message_params diff --git a/app/controllers/settings/.application_controller.rb.swp b/app/controllers/settings/.application_controller.rb.swp deleted file mode 100644 index 401c8fabbd9ef826591b88f57bfa5e59f0a2a450..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI2&ubGw6vwBouT zs;+T{Sccq6t%B0QOZWfqE;17VB5;ntz;>3`=GlYQ`z!A0LbU=nZ#+NeKwpUf5g-CY zfCvx)B0vO)01^1#3D{(Yy~Rz=W*fbg%`+F~Y)KOlAOb{y2oM1xKm>>Y5g-CYfCvx) zBJd9q;1OdVu3+JFDE$9F|NZ~<8e^YPXQ+3mW7H9x@@#KMP@ zS~CpwK-roPB9b=9J92{r`*X1wH{rH-dWx}bC~+8YW2CWMSr`0796~dHfGM0qk)iM$ zCs|@H#b#md3pIcZI4RtW4~4OO=vf`~#<6Aj0F}}sa2_%eUY{q6_xz4bjjd-38Pz?h z!gvsgM~ewhdEc~s8SS~OvOcQFRh3(K=sJ-1pt1oqhoc6=Vdk{yUNK>FCeQ~W?Zo;J zHy&^HcTxo#xA6yS4i)c--23 zy4`%WG%3J~h3eWKESLA;_sG3b6|yre!NX|@vR&l&5nxzsXb5d67E2c{b72@=#l>P- zhjB?02q2fOL7EC>J$E)<5o0`^Mj{oFyJB#HB}HPiqe+*8p*czA0k^_^-4`95ieP-r qz{l543<8Mxkses6=7m*R7-I#nn>Eb)ezWYD-zqxlVI3wuXTJc4fuSb= diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bfb7fd3c2..6dcabbafa 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,12 +1,12 @@ module ApplicationHelper - def hide_settings_item_class(item, item_counter) + def hide_assistant_class(item, item_counter) return nil unless item.is_a?(Assistant) return 'hidden' if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY nil end - def settings_item_data_transition_target(item, item_counter) + def assitant_data_transaction_target(item, item_counter) return nil unless item.is_a?(Assistant) return 'data-transition-target="transitionable"'.html_safe if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY end diff --git a/app/views/assistants/_assistant.html.erb b/app/views/assistants/_assistant.html.erb index 1a06efc6f..f8c95a5e5 100644 --- a/app/views/assistants/_assistant.html.erb +++ b/app/views/assistants/_assistant.html.erb @@ -13,11 +13,13 @@ group cursor-pointer text-sm rounded-lg <%= selected && 'relationship' %> + <%= hide_assistant_class(assistant, assistant_counter) %> " data-role="assistant" data-radio-behavior-target="radio" data-action="radio-changed@window->radio-behavior#select" data-radio-behavior-id-param="<%= assistant.id %>" + <%= assitant_data_transaction_target(assistant, assistant_counter) %> > <%= link_to new_assistant_message_path(assistant), class: "flex-1 flex items-center text-gray-950 dark:text-gray-100 font-medium truncate", data: { role: "name" } do %> <%= render partial: "layouts/assistant_avatar", locals: { assistant: assistant, size: 7, classes: "mr-2" } %> diff --git a/app/views/layouts/_settings_item.erb b/app/views/layouts/_settings_item.erb index 0fc38dee4..3c4057958 100644 --- a/app/views/layouts/_settings_item.erb +++ b/app/views/layouts/_settings_item.erb @@ -11,10 +11,10 @@ group text-sm rounded-s-lg <%= selected ? "bg-gray-200 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-700 cursor-default" : "cursor-pointer" %> - <%= hide_settings_item_class(item, settings_item_counter) %> + <%= hide_assistant_class(item, settings_item_counter) %> " data-role="assistant" - <%= settings_item_data_transition_target(item, settings_item_counter) %> + <%= assitant_data_transaction_target(item, settings_item_counter) %> > <%= link_to link, class: %| diff --git a/app/views/messages/_nav_column.html.erb b/app/views/messages/_nav_column.html.erb index f7058451f..78d9b4c21 100644 --- a/app/views/messages/_nav_column.html.erb +++ b/app/views/messages/_nav_column.html.erb @@ -1,5 +1,13 @@ -
+ <% if @hide_settings_assistants_overflow %> +
+ <%= button_tag type: "button", + class: "p-1 text-sm", + data: {transition_target: "transitionable", action: "transition#toggleClass", + } do %> + Show Remaining + <% end %> + <% end %> <%= render @nav_assistants %>
From 77dcefbc50b4cd2242f3e6d2b8095d64a0510842 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 12 May 2024 16:38:31 -0700 Subject: [PATCH 26/68] Rename the LLM model (all-caps identifiers are usually constants) to LanguageModel, update the list in the migration --- app/models/{llm.rb => language_model.rb} | 3 +- app/views/settings/assistants/_form.html.erb | 2 +- config/initializers/inflections.rb | 1 - db/migrate/20240507165222_create_llms.rb | 25 ----------- .../20240512163300_create_language_models.rb | 42 +++++++++++++++++++ db/schema.rb | 9 +++- 6 files changed, 53 insertions(+), 29 deletions(-) rename app/models/{llm.rb => language_model.rb} (56%) delete mode 100644 db/migrate/20240507165222_create_llms.rb create mode 100644 db/migrate/20240512163300_create_language_models.rb diff --git a/app/models/llm.rb b/app/models/language_model.rb similarity index 56% rename from app/models/llm.rb rename to app/models/language_model.rb index 3e7e72301..0e5fd09d2 100644 --- a/app/models/llm.rb +++ b/app/models/language_model.rb @@ -1,4 +1,5 @@ -class LLM < ApplicationRecord +# We don't care about large or not +class LanguageModel < ApplicationRecord def readonly?() = !new_record? def before_destroy() = raise ActiveRecord::ReadOnlyRecord end diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index 699ab0970..5bfd0287c 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -13,7 +13,7 @@
<%= form.label :model %>
- <%= form.select :model, LLM.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> + <%= form.select :model, LanguageModel.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %>
<%= form.label :name %> diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index cfc233b0e..a6900ec7f 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -15,5 +15,4 @@ # inflect.acronym "RESTful" inflect.acronym "AI" inflect.acronym "API" - inflect.acronym "LLM" end diff --git a/db/migrate/20240507165222_create_llms.rb b/db/migrate/20240507165222_create_llms.rb deleted file mode 100644 index 0f815dd9a..000000000 --- a/db/migrate/20240507165222_create_llms.rb +++ /dev/null @@ -1,25 +0,0 @@ -class CreateLlms < ActiveRecord::Migration[7.1] - def change - create_table :llms do |t| - t.string :name - t.text :description - - t.timestamps - end - - # Current models - up_only do - { - 'open_ai_newest': 'Newest OpenAI Model', - 'claude_newest': 'Newest Anthropic Model', - 'claude-3-sonnet-20240229': 'Claude 3 Sonnet', - 'gpt-4': 'ChatGPT 4', - 'claude-3-opus-20240229': 'Claude 3 Opus', - 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo', - 'gpt-4-turbo-2024-04-09': 'ChatGPT 4 Turbo' - }.each do |name, description| - LLM.create(name: name, description: description) - end - end - end -end diff --git a/db/migrate/20240512163300_create_language_models.rb b/db/migrate/20240512163300_create_language_models.rb new file mode 100644 index 000000000..da198a828 --- /dev/null +++ b/db/migrate/20240512163300_create_language_models.rb @@ -0,0 +1,42 @@ +class CreateLanguageModels < ActiveRecord::Migration[7.1] + def change + create_table :language_models do |t| + t.string :name + t.text :description + + t.timestamps + end + + # Current models + up_only do + { + 'open_ai_best': 'Best OpenAI Model', + 'claude_best': 'Best Anthropic Model', + + 'gpt-4': 'ChatGPT 4', + 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update)', + 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', + 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', + 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', + 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', + 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images', + 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', + 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', + + 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', + 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2023-06-13)', + 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2023-01-25)', + 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', + + 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', + 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', + 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', + 'claude-2.1': 'Claude 2.1', + 'claude-2.0': 'Claude 2.0', + 'claude-instant-1.2': 'Claude Instant 1.2' + }.each do |name, description| + LanguageModel.create(name: name, description: description) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index fc0ac9346..19a517bc1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_07_165222) do +ActiveRecord::Schema[7.1].define(version: 2024_05_12_163300) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -96,6 +96,13 @@ t.index ["user_id"], name: "index_documents_on_user_id" end + create_table "language_models", force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "llms", force: :cascade do |t| t.string "name" t.text "description" From de6c887e797fba1a84807a098d364dc52185c9ca Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 12 May 2024 17:33:32 -0700 Subject: [PATCH 27/68] Update list of language models, Assistant model belongs_to LanguageModel, implement the "best" naming --- app/models/assistant.rb | 2 ++ app/models/language_model.rb | 6 ++++++ app/services/ai_backends/anthropic.rb | 2 +- app/services/ai_backends/open_ai.rb | 2 +- .../20240512163300_create_language_models.rb | 15 ++++++++------- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 0c7eba0f2..94a9be04b 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -7,6 +7,8 @@ class Assistant < ApplicationRecord has_many :steps, dependent: :destroy has_many :messages # TODO: What should happen if an assistant is deleted? + belongs_to :language_model, primary_key: :name, foreign_key: :model + validates :tools, presence: true, allow_blank: true MAX_LIST_DISPLAY = 5 diff --git a/app/models/language_model.rb b/app/models/language_model.rb index 0e5fd09d2..f784d660a 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -2,4 +2,10 @@ class LanguageModel < ApplicationRecord def readonly?() = !new_record? def before_destroy() = raise ActiveRecord::ReadOnlyRecord + + PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', + 'claude-best': 'claude-3-opus-20240229'} + def provider_id + PROVIDER_ID_MAP[self.name.to_sym] || self.name unless self.name =~ /^best/ + end end diff --git a/app/services/ai_backends/anthropic.rb b/app/services/ai_backends/anthropic.rb index 3ff7a5145..5e41cf622 100644 --- a/app/services/ai_backends/anthropic.rb +++ b/app/services/ai_backends/anthropic.rb @@ -49,7 +49,7 @@ def get_next_chat_message(&chunk_received_handler) begin response = @client.messages( - model: @assistant.model, + model: @assistant.model.provider_id, system: @assistant.instructions, messages: preceding_messages, parameters: { diff --git a/app/services/ai_backends/open_ai.rb b/app/services/ai_backends/open_ai.rb index 9201bdc85..ec66497bd 100644 --- a/app/services/ai_backends/open_ai.rb +++ b/app/services/ai_backends/open_ai.rb @@ -49,7 +49,7 @@ def get_next_chat_message(&chunk_received_handler) begin response = @client.chat(parameters: { - model: @assistant.model, + model: @assistant.language_model.provider_id, messages: system_message + preceding_messages, stream: response_handler, max_tokens: 2000, # we should really set this dynamically, based on the model, to the max diff --git a/db/migrate/20240512163300_create_language_models.rb b/db/migrate/20240512163300_create_language_models.rb index da198a828..41ad194c0 100644 --- a/db/migrate/20240512163300_create_language_models.rb +++ b/db/migrate/20240512163300_create_language_models.rb @@ -10,22 +10,23 @@ def change # Current models up_only do { - 'open_ai_best': 'Best OpenAI Model', - 'claude_best': 'Best Anthropic Model', + 'gpt-best': 'Best OpenAI Model', + 'claude-best': 'Best Anthropic Model', 'gpt-4': 'ChatGPT 4', - 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update)', + 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', - 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images', - 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', + 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', + 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', - 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2023-06-13)', - 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2023-01-25)', + 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', + 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', + 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', From 8b98bfaa83f93901a45e034b1bfe9c22db3b01bd Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 12 May 2024 18:12:42 -0700 Subject: [PATCH 28/68] Fix tests until question of adding language_model_id to assistants table is sorted out --- app/models/assistant.rb | 2 +- app/services/ai_backends/anthropic.rb | 2 +- test/fixtures/language_models.yml | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/language_models.yml diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 94a9be04b..dc32bed6b 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -7,7 +7,7 @@ class Assistant < ApplicationRecord has_many :steps, dependent: :destroy has_many :messages # TODO: What should happen if an assistant is deleted? - belongs_to :language_model, primary_key: :name, foreign_key: :model + belongs_to :language_model, primary_key: :name, foreign_key: :model, optional: true validates :tools, presence: true, allow_blank: true diff --git a/app/services/ai_backends/anthropic.rb b/app/services/ai_backends/anthropic.rb index 5e41cf622..27e320556 100644 --- a/app/services/ai_backends/anthropic.rb +++ b/app/services/ai_backends/anthropic.rb @@ -49,7 +49,7 @@ def get_next_chat_message(&chunk_received_handler) begin response = @client.messages( - model: @assistant.model.provider_id, + model: @assistant.language_model.provider_id, system: @assistant.instructions, messages: preceding_messages, parameters: { diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml new file mode 100644 index 000000000..129412633 --- /dev/null +++ b/test/fixtures/language_models.yml @@ -0,0 +1,8 @@ +gpt_4: + name: gpt-4 + description: gpt-4 from fixtures + +claude_3: + name: claude-3-opus-20240229 + description: claude-3 from fixtures + From c7c0bd3abbefffdd4b5b396291ffb09421cd4f0d Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 14 May 2024 20:44:26 -0700 Subject: [PATCH 29/68] Tests are passing, added language_models fixtures which are a bit random --- .../settings/assistants_controller.rb | 2 +- app/models/assistant.rb | 10 ++- app/models/concerns/user/registerable.rb | 4 +- app/views/settings/assistants/_form.html.erb | 4 +- .../20240512163300_create_language_models.rb | 81 +++++++++++-------- db/schema.rb | 6 +- test/fixtures/assistants.yml | 8 +- test/fixtures/language_models.yml | 40 ++++++++- 8 files changed, 108 insertions(+), 47 deletions(-) diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index ad1fb1ebf..985427ecc 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -38,6 +38,6 @@ def set_assistant end def assistant_params - params.require(:assistant).permit(:name, :description, :instructions, :model) + params.require(:assistant).permit(:name, :description, :instructions, :language_model_id) end end diff --git a/app/models/assistant.rb b/app/models/assistant.rb index dc32bed6b..2a9a3fedb 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -7,7 +7,15 @@ class Assistant < ApplicationRecord has_many :steps, dependent: :destroy has_many :messages # TODO: What should happen if an assistant is deleted? - belongs_to :language_model, primary_key: :name, foreign_key: :model, optional: true + belongs_to :language_model, required: false + + def model=(model_name) + self.language_model_id = LanguageModel.where(name: model_name).first.id + end + + def model + self.language_model.name + end validates :tools, presence: true, allow_blank: true diff --git a/app/models/concerns/user/registerable.rb b/app/models/concerns/user/registerable.rb index 328fe541b..7da8983fc 100644 --- a/app/models/concerns/user/registerable.rb +++ b/app/models/concerns/user/registerable.rb @@ -8,9 +8,9 @@ module User::Registerable private def create_initial_assistants - assistants.create! name: "GPT-4", model: "gpt-4-turbo-2024-04-09", images: true + assistants.create! name: "GPT (best)", model: "gpt-best", images: true assistants.create! name: "GPT-3.5", model: "gpt-3.5-turbo-0125", images: false - assistants.create! name: "Claude 3 Opus", model: "claude-3-opus-20240229", images: true + assistants.create! name: "Claude (best)", model: "claude-best", images: true assistants.create! name: "Claude 3 Sonnet", model: "claude-3-sonnet-20240229", images: true end end diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index 5bfd0287c..fd4855b22 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -12,8 +12,8 @@ <% end %>
- <%= form.label :model %>
- <%= form.select :model, LanguageModel.order(:description).all.pluck(:description, :name), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> + <%= form.label :language_model_id %>
+ <%= form.select :language_model_id, LanguageModel.order(:description).all.pluck(:description, :id), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %>
<%= form.label :name %> diff --git a/db/migrate/20240512163300_create_language_models.rb b/db/migrate/20240512163300_create_language_models.rb index 41ad194c0..8bc09296d 100644 --- a/db/migrate/20240512163300_create_language_models.rb +++ b/db/migrate/20240512163300_create_language_models.rb @@ -1,5 +1,5 @@ class CreateLanguageModels < ActiveRecord::Migration[7.1] - def change + def up create_table :language_models do |t| t.string :name t.text :description @@ -7,37 +7,54 @@ def change t.timestamps end - # Current models - up_only do - { - 'gpt-best': 'Best OpenAI Model', - 'claude-best': 'Best Anthropic Model', - - 'gpt-4': 'ChatGPT 4', - 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', - 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', - 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', - 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', - 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', - 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', - 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', - 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', - - 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', - 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', - 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', - 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', - 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', - - 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', - 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', - 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', - 'claude-2.1': 'Claude 2.1', - 'claude-2.0': 'Claude 2.0', - 'claude-instant-1.2': 'Claude Instant 1.2' - }.each do |name, description| - LanguageModel.create(name: name, description: description) - end + # Currently supported models + { + 'gpt-best': 'Best OpenAI Model', + 'claude-best': 'Best Anthropic Model', + + 'gpt-4': 'ChatGPT 4', + 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', + 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', + 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', + 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', + 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', + 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', + 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', + 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', + + 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', + 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', + 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', + 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', + 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', + + 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', + 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', + 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', + 'claude-2.1': 'Claude 2.1', + 'claude-2.0': 'Claude 2.0', + 'claude-instant-1.2': 'Claude Instant 1.2' + }.each do |name, description| + LanguageModel.create(name: name, description: description) end + + # Respect some users who may have added their own model values in the assistants table + (Assistant.all.pluck(:model).uniq - LanguageModel.all.pluck(:name)).each do |model_name| + LanguageModel.create(name: model_name, description: '???') + end + + add_reference :assistants, :language_model, null: true, foreign_key: { to_table: :language_models} + + ActiveRecord::Base.connection.execute 'update assistants a set language_model_id = (select id from language_models lm where lm.name = a.model)' + + remove_column :assistants, :model + end + + def down + add_column :assistants, :model, :string + ActiveRecord::Base.connection.execute 'update assistants a set model = (select name from language_models lm where lm.id = a.language_model_id)' + + remove_column :assistants, :language_model_id + drop_table :language_models end end diff --git a/db/schema.rb b/db/schema.rb index 19a517bc1..a84ddc2f7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -50,7 +50,6 @@ create_table "assistants", force: :cascade do |t| t.bigint "user_id", null: false - t.string "model" t.string "name" t.string "description" t.string "instructions" @@ -58,6 +57,8 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.boolean "images", default: false, null: false + t.bigint "language_model_id" + t.index ["language_model_id"], name: "index_assistants_on_language_model_id" t.index ["user_id"], name: "index_assistants_on_user_id" end @@ -119,10 +120,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false - t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false + t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" @@ -314,6 +315,7 @@ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" + add_foreign_key "assistants", "language_models" add_foreign_key "assistants", "users" add_foreign_key "chats", "users" add_foreign_key "conversations", "assistants" diff --git a/test/fixtures/assistants.yml b/test/fixtures/assistants.yml index 1e1f77d00..ee8115955 100644 --- a/test/fixtures/assistants.yml +++ b/test/fixtures/assistants.yml @@ -1,6 +1,6 @@ samantha: user: keith - model: gpt-4 + language_model_id: 123456 name: Samantha description: My personal assistant instructions: You are a helpful assistant @@ -9,7 +9,7 @@ samantha: keith_gpt4: user: keith - model: gpt-4 + language_model_id: 12345678 name: GPT-4 description: OpenAI's GPT-4 instructions: @@ -18,7 +18,7 @@ keith_gpt4: keith_claude3: user: keith - model: claude-3-opus-20240229 + language_model_id: 1234567 name: Claude 3 Opus description: Claude 3, Opus version instructions: @@ -27,7 +27,7 @@ keith_claude3: rob_gpt4: user: rob - model: gpt-4 + language_model_id: 12345678 name: GPT-4 description: OpenAI's GPT-4 instructions: diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index 129412633..2d7cf5231 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -1,8 +1,42 @@ +jpt_1: + id: 123 + name: jpt-4 + description: jpt-4 from fixtures +klaus_2: + id: 1234 + name: klaus-3-opus-20240229 + description: klaus-3 from fixtures +aiai_3: + id: 12345 + name: aiai-2 + description: aiai-3 from fixtures + +best_gpt: + id: 123456 + name: gpt-best + description: best-gpt from fixtures + +best_claude: + id: 1234567 + name: claude-best + description: best-gpt from fixtures + +gpt_3_5_turbo_0125: + id: 12345678 + name: gpt-3.5-turbo-0125 + description: gpt-3.5-turbo-0125 from fixtures + +claude_3_sonnet_20240229: + id: 123456789 + name: claude-3-sonnet-20240229 + description: claude-3-sonnet-20240229 from fixtures + gpt_4: + id: 1234567890 name: gpt-4 description: gpt-4 from fixtures -claude_3: +claude_3_opus_20240229: + id: 12345678901 name: claude-3-opus-20240229 - description: claude-3 from fixtures - + description: claude-3-opus-20240229 from fixtures From bb32de52b6a85a30dcc0b2daff3787063fb693af Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 15 May 2024 08:41:18 -0700 Subject: [PATCH 30/68] Update assistants/language_models link, add LanguageModel constants according to the ids used in the migration --- app/jobs/get_next_ai_message_job.rb | 6 +-- app/models/assistant.rb | 8 --- app/models/concerns/user/registerable.rb | 8 +-- app/models/language_model.rb | 26 +++++++--- app/models/run.rb | 2 +- app/views/assistants/show.html.erb | 2 +- .../20240515080700_create_language_models.rb | 52 ++++++++++--------- db/schema.rb | 1 - .../controllers/assistants_controller_test.rb | 4 +- test/fixtures/assistants.yml | 8 +-- test/fixtures/language_models.yml | 51 +++++++----------- test/fixtures/runs.yml | 21 -------- test/models/run_test.rb | 1 - 13 files changed, 79 insertions(+), 111 deletions(-) diff --git a/app/jobs/get_next_ai_message_job.rb b/app/jobs/get_next_ai_message_job.rb index dfa817f44..6e6071dd8 100644 --- a/app/jobs/get_next_ai_message_job.rb +++ b/app/jobs/get_next_ai_message_job.rb @@ -5,11 +5,7 @@ class WaitForPrevious < StandardError; end retry_on WaitForPrevious, wait: ->(run) { (2**run - 1).seconds }, attempts: 3 def ai_backend - if @assistant.model.starts_with?('gpt-') - AIBackends::OpenAI - else - AIBackends::Anthropic - end + @assistant.language_model.ai_backend end def perform(user_id, message_id, assistant_id, attempt = 1) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 2a9a3fedb..376b14076 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -9,14 +9,6 @@ class Assistant < ApplicationRecord belongs_to :language_model, required: false - def model=(model_name) - self.language_model_id = LanguageModel.where(name: model_name).first.id - end - - def model - self.language_model.name - end - validates :tools, presence: true, allow_blank: true MAX_LIST_DISPLAY = 5 diff --git a/app/models/concerns/user/registerable.rb b/app/models/concerns/user/registerable.rb index 7da8983fc..383a3401f 100644 --- a/app/models/concerns/user/registerable.rb +++ b/app/models/concerns/user/registerable.rb @@ -8,9 +8,9 @@ module User::Registerable private def create_initial_assistants - assistants.create! name: "GPT (best)", model: "gpt-best", images: true - assistants.create! name: "GPT-3.5", model: "gpt-3.5-turbo-0125", images: false - assistants.create! name: "Claude (best)", model: "claude-best", images: true - assistants.create! name: "Claude 3 Sonnet", model: "claude-3-sonnet-20240229", images: true + assistants.create! name: "GPT (best)", language_model_id: LanguageModel::GPT_BEST_ID, images: true + assistants.create! name: "GPT-3.5", language_model_id: LanguageModel::GPT_3_5_ID, images: false + assistants.create! name: "Claude (best)", language_model_id: LanguageModel::CLAUDE_BEST_ID, images: true + assistants.create! name: "Claude 3 Sonnet", language_model_id: LanguageModel::CLAUDE_3_SONNET_ID, images: true end end diff --git a/app/models/language_model.rb b/app/models/language_model.rb index f784d660a..678ea8543 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -1,11 +1,25 @@ # We don't care about large or not class LanguageModel < ApplicationRecord - def readonly?() = !new_record? - def before_destroy() = raise ActiveRecord::ReadOnlyRecord + def readonly?() = !new_record? + def before_destroy() = raise ActiveRecord::ReadOnlyRecord - PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', + PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', 'claude-best': 'claude-3-opus-20240229'} - def provider_id - PROVIDER_ID_MAP[self.name.to_sym] || self.name unless self.name =~ /^best/ - end + + GPT_BEST_ID = 1 + CLAUDE_BEST_ID = 2 + GPT_3_5_ID = 12 + CLAUDE_3_SONNET_ID = 18 + + def provider_id + PROVIDER_ID_MAP[self.name.to_sym] || self.name unless self.name =~ /^best/ + end + + def ai_backend + if name.starts_with?('gpt-') + AIBackends::OpenAI + else + AIBackends::Anthropic + end + end end diff --git a/app/models/run.rb b/app/models/run.rb index a68d8b6d2..27db8e588 100644 --- a/app/models/run.rb +++ b/app/models/run.rb @@ -7,6 +7,6 @@ class Run < ApplicationRecord enum status: %w[queued in_progress requires_action cancelling cancelled failed completed expired].index_by(&:to_sym) - validates :status, :expired_at, :model, :instructions, presence: true + validates :status, :expired_at, :instructions, presence: true validates :tools, :file_ids, presence: true, allow_blank: true end diff --git a/app/views/assistants/show.html.erb b/app/views/assistants/show.html.erb index 394918308..273cf6d04 100644 --- a/app/views/assistants/show.html.erb +++ b/app/views/assistants/show.html.erb @@ -9,7 +9,7 @@

Model: - <%= @assistant.model %> + <%= @assistant.language_model.description %>

diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index 8bc09296d..3336cc6c7 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -9,31 +9,31 @@ def up # Currently supported models { - 'gpt-best': 'Best OpenAI Model', - 'claude-best': 'Best Anthropic Model', - - 'gpt-4': 'ChatGPT 4', - 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', - 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', - 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', - 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', - 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', - 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', - 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', - 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', - - 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', - 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', - 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', - 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', - 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', - - 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', - 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', - 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', - 'claude-2.1': 'Claude 2.1', - 'claude-2.0': 'Claude 2.0', - 'claude-instant-1.2': 'Claude Instant 1.2' + 1, 'gpt-best': 'Best OpenAI Model', + 2, 'claude-best': 'Best Anthropic Model', + + 3, 'gpt-4': 'ChatGPT 4', + 4, 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', + 5, 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', + 6, 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', + 7, 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', + 8, 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', + 9, 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', + 10, 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', + 11, 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', + + 12, 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', + 13, 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', + 14, 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', + 15, 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', + 16, 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', + + 17, 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', + 18, 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', + 19, 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', + 20, 'claude-2.1': 'Claude 2.1', + 21, 'claude-2.0': 'Claude 2.0', + 22, 'claude-instant-1.2': 'Claude Instant 1.2' }.each do |name, description| LanguageModel.create(name: name, description: description) end @@ -56,5 +56,7 @@ def down remove_column :assistants, :language_model_id drop_table :language_models + + remove_column :runs, :model end end diff --git a/db/schema.rb b/db/schema.rb index b73cc55b9..1015d0bdf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -174,7 +174,6 @@ t.datetime "cancelled_at", precision: nil t.datetime "failed_at", precision: nil t.datetime "completed_at", precision: nil - t.string "model", null: false t.string "instructions" t.string "additional_instructions" t.jsonb "tools", default: [], null: false diff --git a/test/controllers/assistants_controller_test.rb b/test/controllers/assistants_controller_test.rb index 2224e98f6..1d4bbee7a 100644 --- a/test/controllers/assistants_controller_test.rb +++ b/test/controllers/assistants_controller_test.rb @@ -19,7 +19,7 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest test "should create assistant" do assert_difference("Assistant.count") do - post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, model: @assistant.model, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} + post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, alnguage_model_id: 1, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} end assert_redirected_to assistant_url(Assistant.last) @@ -36,7 +36,7 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest end test "should update assistant" do - patch assistant_url(@assistant), params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, model: @assistant.model, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} + patch assistant_url(@assistant), params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, language_model_id: @assistant.language_model_id, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} assert_redirected_to assistant_url(@assistant) end diff --git a/test/fixtures/assistants.yml b/test/fixtures/assistants.yml index ee8115955..c4b5d47d5 100644 --- a/test/fixtures/assistants.yml +++ b/test/fixtures/assistants.yml @@ -1,6 +1,6 @@ samantha: user: keith - language_model_id: 123456 + language_model_id: 3 name: Samantha description: My personal assistant instructions: You are a helpful assistant @@ -9,7 +9,7 @@ samantha: keith_gpt4: user: keith - language_model_id: 12345678 + language_model_id: 3 name: GPT-4 description: OpenAI's GPT-4 instructions: @@ -18,7 +18,7 @@ keith_gpt4: keith_claude3: user: keith - language_model_id: 1234567 + language_model_id: 17 name: Claude 3 Opus description: Claude 3, Opus version instructions: @@ -27,7 +27,7 @@ keith_claude3: rob_gpt4: user: rob - language_model_id: 12345678 + language_model_id: 3 name: GPT-4 description: OpenAI's GPT-4 instructions: diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index 2d7cf5231..5d881d86f 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -1,42 +1,29 @@ -jpt_1: - id: 123 - name: jpt-4 - description: jpt-4 from fixtures -klaus_2: - id: 1234 - name: klaus-3-opus-20240229 - description: klaus-3 from fixtures -aiai_3: - id: 12345 - name: aiai-2 - description: aiai-3 from fixtures - -best_gpt: - id: 123456 +gpt_best: + id: 1 name: gpt-best - description: best-gpt from fixtures + description: gpt-best from fixtures -best_claude: - id: 1234567 - name: claude-best - description: best-gpt from fixtures +claude_best: + id: 2 + name: best-claude + description: claude-best from fixtures -gpt_3_5_turbo_0125: - id: 12345678 - name: gpt-3.5-turbo-0125 - description: gpt-3.5-turbo-0125 from fixtures - -claude_3_sonnet_20240229: - id: 123456789 +claude_3_sonnet: + id: 18 name: claude-3-sonnet-20240229 description: claude-3-sonnet-20240229 from fixtures +claude_3_opus: + id: 17 + name: claude-3-opus-20240229 + description: claude-3-opus-20240229 from fixtures + gpt_4: - id: 1234567890 + id: 3 name: gpt-4 description: gpt-4 from fixtures -claude_3_opus_20240229: - id: 12345678901 - name: claude-3-opus-20240229 - description: claude-3-opus-20240229 from fixtures +gpt_3_5_turbo_0125: + id: 12 + name: gpt-3.5-turbo + description: gpt-3.5-turbo from fixtures diff --git a/test/fixtures/runs.yml b/test/fixtures/runs.yml index b5f9b4e85..d209349a9 100644 --- a/test/fixtures/runs.yml +++ b/test/fixtures/runs.yml @@ -9,7 +9,6 @@ hear_me_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -25,7 +24,6 @@ identify_photo_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -42,7 +40,6 @@ what_day_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -58,7 +55,6 @@ alive_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -74,7 +70,6 @@ your_name_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -90,7 +85,6 @@ examine_this_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -107,7 +101,6 @@ what_month_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -123,7 +116,6 @@ human_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -139,7 +131,6 @@ can_you_hear_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -155,7 +146,6 @@ one_attachment_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -172,7 +162,6 @@ two_attachments_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -189,7 +178,6 @@ popstate_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -205,7 +193,6 @@ ruby_version_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: - model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -221,7 +208,6 @@ filter_map_response: expired_at: 2023-12-27 15:05:41 cancelled_at: failed_at: - model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -237,7 +223,6 @@ message0_v1_response: expired_at: 2023-1-1 1:01:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -253,7 +238,6 @@ message2_v1_response: expired_at: 2023-1-1 1:03:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -269,7 +253,6 @@ message2_v2_response: expired_at: 2023-1-1 1:05:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -285,7 +268,6 @@ message4_v2_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -301,7 +283,6 @@ msg0_v1_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -317,7 +298,6 @@ msg2_v1_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -333,7 +313,6 @@ msg3_v2_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: - model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] diff --git a/test/models/run_test.rb b/test/models/run_test.rb index 82486c226..92cb687e0 100644 --- a/test/models/run_test.rb +++ b/test/models/run_test.rb @@ -22,7 +22,6 @@ class RunTest < ActiveSupport::TestCase Run.create!( assistant: assistants(:samantha), conversation: conversations(:greeting), - model: assistants(:samantha).model, instructions: assistants(:samantha).instructions, status: "queued", expired_at: 1.minute.from_now From abf9cf57a1ceabf04df9106b4409ba66aeb3d8db Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 15 May 2024 21:04:38 -0700 Subject: [PATCH 31/68] Fix system tests --- app/controllers/assistants_controller.rb | 2 +- app/views/assistants/_form.html.erb | 4 ++-- test/system/assistants_test.rb | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/assistants_controller.rb b/app/controllers/assistants_controller.rb index 2f666065c..d53861117 100644 --- a/app/controllers/assistants_controller.rb +++ b/app/controllers/assistants_controller.rb @@ -46,6 +46,6 @@ def set_assistant end def assistant_params - params.require(:assistant).permit(:user_id, :model, :name, :description, :instructions, :tools) + params.require(:assistant).permit(:user_id, :language_model_id, :name, :description, :instructions, :tools) end end diff --git a/app/views/assistants/_form.html.erb b/app/views/assistants/_form.html.erb index 4b057d41c..fc88471dc 100644 --- a/app/views/assistants/_form.html.erb +++ b/app/views/assistants/_form.html.erb @@ -17,8 +17,8 @@

- <%= form.label :model %> - <%= form.text_field :model, value: "gpt-4", class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> + <%= form.label :language_model_id %>
+ <%= form.select :language_model_id, LanguageModel.order(:description).all.pluck(:description, :id), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %>
diff --git a/test/system/assistants_test.rb b/test/system/assistants_test.rb index 2f4de479b..0639dc46f 100644 --- a/test/system/assistants_test.rb +++ b/test/system/assistants_test.rb @@ -11,32 +11,33 @@ class AssistantsTest < ApplicationSystemTestCase # assert_selector "h1", text: "Assistants" # end - test "should create assistant" do + test "should create Assistant" do visit new_assistant_url fill_in "Description", with: @assistant.description fill_in "Instructions", with: @assistant.instructions - fill_in "Model", with: @assistant.model + find('#assistant_language_model_id').find(:xpath, 'option[1]').select_option fill_in "Name", with: @assistant.name fill_in "User", with: @assistant.user_id click_text "Create Assistant" assert_text "Assistant was successfully created" + assert_text "claude-3-opus-20240229 from fixtures" click_text "Back" end test "should update Assistant" do visit assistant_url(@assistant) click_text "Edit this assistant", match: :first - fill_in "Description", with: @assistant.description fill_in "Instructions", with: @assistant.instructions - fill_in "Model", with: @assistant.model + find('#assistant_language_model_id').find(:xpath, 'option[2]').select_option fill_in "Name", with: @assistant.name fill_in "User", with: @assistant.user_id click_text "Update Assistant" assert_text "Assistant was successfully updated" + assert_text "claude-3-sonnet-20240229 from fixtures" click_text "Back" end From 668b94c42573b2e9d63b34ee33ed962873913f81 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 16 May 2024 07:03:33 -0700 Subject: [PATCH 32/68] Fixed migration, also left model column in the runs table --- app/models/assistant.rb | 2 +- .../20240515080700_create_language_models.rb | 74 ++++++++++--------- db/schema.rb | 1 + .../controllers/assistants_controller_test.rb | 2 +- .../settings/assistants_controller_test.rb | 4 +- test/fixtures/runs.yml | 21 ++++++ test/models/assistant_test.rb | 4 +- 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 376b14076..56374aa72 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -7,7 +7,7 @@ class Assistant < ApplicationRecord has_many :steps, dependent: :destroy has_many :messages # TODO: What should happen if an assistant is deleted? - belongs_to :language_model, required: false + belongs_to :language_model validates :tools, presence: true, allow_blank: true diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index 3336cc6c7..dad202565 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -7,56 +7,62 @@ def up t.timestamps end - # Currently supported models - { - 1, 'gpt-best': 'Best OpenAI Model', - 2, 'claude-best': 'Best Anthropic Model', - - 3, 'gpt-4': 'ChatGPT 4', - 4, 'gpt-4-turbo': 'ChatGPT 4 Turbo with Vision (may update in future)', - 5, 'gpt-4-turbo-2024-04-09': 'ChatGPT-4 Turbo with Vision (2024-04-09)', - 6, 'gpt-4-turbo-preview': 'ChatGPT 4 Turbo Preview', - 7, 'gpt-4-0125-preview': 'ChatGPT 4 Turbo Preview (2024-01-25)', - 8, 'gpt-4-1106-preview': 'ChatGPT 4 Turbo Preview (2023-11-06)', - 9, 'gpt-4-vision-preview': 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)', - 10, 'gpt-4-1106-vision-preview': 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)', - 11, 'gpt-4-0613': 'ChatGPT 4 Snapshot improved function calling (2023-06-13)', - - 12, 'gpt-3.5-turbo': 'ChatGPT 3.5 Turbo', - 13, 'gpt-3.5-turbo-16k-0613': 'ChatGPT 3.5 Turbo (2022-06-13)', - 14, 'gpt-3.5-turbo-0125': 'ChatGPT 3.5 Turbo (2022-01-25)', - 15, 'gpt-3.5-turbo-1106': 'ChatGPT 3.5 Turbo (2022-11-06)', - 16, 'gpt-3.5-turbo-instruct': 'ChatGPT 3.5 Turbo Instruct', - - 17, 'claude-3-opus-20240229': 'Claude 3 Opus (2024-02-29)', - 18, 'claude-3-sonnet-20240229': 'Claude 3 Sonnet (2024-02-29)', - 19, 'claude-3-haiku-20240307': 'Claude 3 Haiku (2024-03-07)', - 20, 'claude-2.1': 'Claude 2.1', - 21, 'claude-2.0': 'Claude 2.0', - 22, 'claude-instant-1.2': 'Claude Instant 1.2' - }.each do |name, description| - LanguageModel.create(name: name, description: description) + # Initially supported models + [ + [1, 'gpt-best', 'Best OpenAI Model'], + [2, 'claude-best', 'Best Anthropic Model'], + + [3, 'gpt-4', 'ChatGPT 4'], + [4, 'gpt-4-turbo', 'ChatGPT 4 Turbo with Vision (may update in future)'], + [5, 'gpt-4-turbo-2024-04-09', 'ChatGPT-4 Turbo with Vision (2024-04-09)'], + [6, 'gpt-4-turbo-preview', 'ChatGPT 4 Turbo Preview'], + [7, 'gpt-4-0125-preview', 'ChatGPT 4 Turbo Preview (2024-01-25)'], + [8, 'gpt-4-1106-preview', 'ChatGPT 4 Turbo Preview (2023-11-06)'], + [9, 'gpt-4-vision-preview', 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)'], + [10, 'gpt-4-1106-vision-preview', 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)'], + [11, 'gpt-4-0613', 'ChatGPT 4 Snapshot improved function calling (2023-06-13)'], + + [12, 'gpt-3.5-turbo', 'ChatGPT 3.5 Turbo'], + [13, 'gpt-3.5-turbo-16k-0613', 'ChatGPT 3.5 Turbo (2022-06-13)'], + [14, 'gpt-3.5-turbo-0125', 'ChatGPT 3.5 Turbo (2022-01-25)'], + [15, 'gpt-3.5-turbo-1106', 'ChatGPT 3.5 Turbo (2022-11-06)'], + [16, 'gpt-3.5-turbo-instruct', 'ChatGPT 3.5 Turbo Instruct'], + + [17, 'claude-3-opus-20240229', 'Claude 3 Opus (2024-02-29)'], + [18, 'claude-3-sonnet-20240229', 'Claude 3 Sonnet (2024-02-29)'], + [19, 'claude-3-haiku-20240307', 'Claude 3 Haiku (2024-03-07)'], + [20, 'claude-2.1', 'Claude 2.1'], + [21, 'claude-2.0', 'Claude 2.0'], + [22, 'claude-instant-1.2', 'Claude Instant 1.2'] + ].each do |db_id, name, description| + record = LanguageModel.new(name: name, description: description) + record.id = db_id + record.save! end + ActiveRecord::Base.connection.execute "ALTER SEQUENCE language_models_id_seq RESTART WITH 30;" + # Respect some users who may have added their own model values in the assistants table (Assistant.all.pluck(:model).uniq - LanguageModel.all.pluck(:name)).each do |model_name| - LanguageModel.create(name: model_name, description: '???') + Rails.logger.info "Create language_models record from assistants column value: #{model_name.inspect}" + LanguageModel.create!(name: model_name, description: model_name) end add_reference :assistants, :language_model, null: true, foreign_key: { to_table: :language_models} - ActiveRecord::Base.connection.execute 'update assistants a set language_model_id = (select id from language_models lm where lm.name = a.model)' + Assistant.all.each do |a| + Rails.logger.info "Have assistant #{a.id} with model #{a.model}" + end + ActiveRecord::Base.connection.execute "update assistants a set language_model_id = (select id from language_models lm where lm.name = a.model)" remove_column :assistants, :model end def down add_column :assistants, :model, :string - ActiveRecord::Base.connection.execute 'update assistants a set model = (select name from language_models lm where lm.id = a.language_model_id)' + ActiveRecord::Base.connection.execute "update assistants a set model = (select name from language_models lm where lm.id=a.language_model_id)" remove_column :assistants, :language_model_id drop_table :language_models - - remove_column :runs, :model end end diff --git a/db/schema.rb b/db/schema.rb index 1015d0bdf..961b29581 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -180,6 +180,7 @@ t.jsonb "file_ids", default: [], null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "model" t.index ["assistant_id"], name: "index_runs_on_assistant_id" t.index ["conversation_id"], name: "index_runs_on_conversation_id" end diff --git a/test/controllers/assistants_controller_test.rb b/test/controllers/assistants_controller_test.rb index 1d4bbee7a..cca59e185 100644 --- a/test/controllers/assistants_controller_test.rb +++ b/test/controllers/assistants_controller_test.rb @@ -19,7 +19,7 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest test "should create assistant" do assert_difference("Assistant.count") do - post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, alnguage_model_id: 1, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} + post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, language_model_id: 1, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} end assert_redirected_to assistant_url(Assistant.last) diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index f5eb13e02..282d847d9 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -12,7 +12,7 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest end test "should create assistant" do - params = assistants(:samantha).slice(:name, :description, :instructions) + params = assistants(:samantha).slice(:name, :description, :instructions, :language_model_id) assert_difference("Assistant.count") do post settings_assistants_url, params: { assistant: params } @@ -20,7 +20,7 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to edit_settings_assistant_url(Assistant.last) assert_nil flash[:error] - assert_equal params, Assistant.last.slice(:name, :description, :instructions) + assert_equal params, Assistant.last.slice(:name, :description, :instructions, :language_model_id) end test "should get edit" do diff --git a/test/fixtures/runs.yml b/test/fixtures/runs.yml index d209349a9..b5f9b4e85 100644 --- a/test/fixtures/runs.yml +++ b/test/fixtures/runs.yml @@ -9,6 +9,7 @@ hear_me_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -24,6 +25,7 @@ identify_photo_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -40,6 +42,7 @@ what_day_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -55,6 +58,7 @@ alive_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -70,6 +74,7 @@ your_name_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -85,6 +90,7 @@ examine_this_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -101,6 +107,7 @@ what_month_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -116,6 +123,7 @@ human_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -131,6 +139,7 @@ can_you_hear_response: expired_at: 2023-12-25 15:05:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -146,6 +155,7 @@ one_attachment_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -162,6 +172,7 @@ two_attachments_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -178,6 +189,7 @@ popstate_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -193,6 +205,7 @@ ruby_version_response: expired_at: 2023-12-25 15:08:41 cancelled_at: failed_at: + model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -208,6 +221,7 @@ filter_map_response: expired_at: 2023-12-27 15:05:41 cancelled_at: failed_at: + model: gpt-4 instructions: additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -223,6 +237,7 @@ message0_v1_response: expired_at: 2023-1-1 1:01:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -238,6 +253,7 @@ message2_v1_response: expired_at: 2023-1-1 1:03:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -253,6 +269,7 @@ message2_v2_response: expired_at: 2023-1-1 1:05:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -268,6 +285,7 @@ message4_v2_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -283,6 +301,7 @@ msg0_v1_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -298,6 +317,7 @@ msg2_v1_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] @@ -313,6 +333,7 @@ msg3_v2_response: expired_at: 2023-1-1 1:07:00 cancelled_at: failed_at: + model: gpt-4 instructions: You are a helpful assistant additional_instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 0e999f0ff..d8f6e2248 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -31,12 +31,12 @@ class AssistantTest < ActiveSupport::TestCase test "simple create works" do assert_nothing_raised do - Assistant.create!(user: users(:keith)) + Assistant.create!(user: users(:keith), language_model_id: 1) end end test "tools defaults to empty array on create" do - a = Assistant.create!(user: users(:keith)) + a = Assistant.create!(user: users(:keith), language_model_id: 1) assert_equal [], a.tools end From 6f3fbd060659950f24a2dbbc105132e0d64c27e1 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 16 May 2024 07:17:01 -0700 Subject: [PATCH 33/68] Populate runs.model from the name of the assistant's language_model --- app/models/run.rb | 9 ++++++++- test/models/run_test.rb | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/run.rb b/app/models/run.rb index 27db8e588..16b8a92e1 100644 --- a/app/models/run.rb +++ b/app/models/run.rb @@ -7,6 +7,13 @@ class Run < ApplicationRecord enum status: %w[queued in_progress requires_action cancelling cancelled failed completed expired].index_by(&:to_sym) - validates :status, :expired_at, :instructions, presence: true + before_validation :set_model, on: :create + validates :status, :expired_at, :model, :instructions, presence: true validates :tools, :file_ids, presence: true, allow_blank: true + + private + + def set_model + self.model = assistant&.language_model&.name + end end diff --git a/test/models/run_test.rb b/test/models/run_test.rb index 92cb687e0..96ba7a59c 100644 --- a/test/models/run_test.rb +++ b/test/models/run_test.rb @@ -29,6 +29,17 @@ class RunTest < ActiveSupport::TestCase end end + test "model populated from assistant" do + r = Run.create!( + assistant: assistants(:keith_claude3), + conversation: conversations(:greeting), + instructions: "Some instructions", + status: "queued", + expired_at: 1.minute.from_now + ) + assert_equal 'claude-3-opus-20240229', r.model + end + test "associations are deleted upon destroy" do assert_difference "Step.count", -1 do runs(:hear_me_response).destroy From 3ce43688b380e8efc9182c31483a568ce46f3345 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 16 May 2024 07:41:33 -0700 Subject: [PATCH 34/68] Fix rubocop issues --- app/helpers/application_helper.rb | 2 +- app/models/language_model.rb | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 6dcabbafa..30b87c8fb 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -10,7 +10,7 @@ def assitant_data_transaction_target(item, item_counter) return nil unless item.is_a?(Assistant) return 'data-transition-target="transitionable"'.html_safe if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY end - + def spinner(opts = {}) html = <<~HTML diff --git a/app/models/language_model.rb b/app/models/language_model.rb index 678ea8543..068730304 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -1,10 +1,15 @@ # We don't care about large or not class LanguageModel < ApplicationRecord - def readonly?() = !new_record? - def before_destroy() = raise ActiveRecord::ReadOnlyRecord + def readonly? + !new_record? + end + + def before_destroy + raise ActiveRecord::ReadOnlyRecord + end PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', - 'claude-best': 'claude-3-opus-20240229'} + 'claude-best': 'claude-3-opus-20240229'} GPT_BEST_ID = 1 CLAUDE_BEST_ID = 2 From 56717541c6d47577c8606804c1b37e217cdb4b6b Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Thu, 16 May 2024 12:57:05 -0700 Subject: [PATCH 35/68] Soft-deletion of assistants --- app/controllers/messages_controller.rb | 10 ++++-- .../settings/assistants_controller.rb | 5 ++- app/models/assistant.rb | 12 +++++++ app/models/message.rb | 2 ++ app/models/user.rb | 3 +- app/views/messages/_main_column.html.erb | 8 ++++- app/views/messages/_message.html.erb | 6 ++-- app/views/messages/edit.html.erb | 36 ++++++++++--------- app/views/messages/new.html.erb | 10 ++++++ app/views/settings/assistants/edit.html.erb | 10 +++--- ...0516144314_add_deleted_at_to_assistants.rb | 6 ++++ db/schema.rb | 4 ++- test/models/assistant_test.rb | 14 ++++---- 13 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 db/migrate/20240516144314_add_deleted_at_to_assistants.rb diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index b55f6c7df..d37b30c1b 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -58,7 +58,8 @@ def create def update # Clicking edit beneath a message actually submits to create and not here. This action is only used for next/prev conversation. # In order to force a morph we PATCH to here and redirect. - if @message.update(message_params) + @message.allow_deleted_assistant = true # disable that validation + if @message.update(version_navigate_message_params) redirect_to conversation_messages_path(@message.conversation, version: @version || @message.version) else render :edit, status: :unprocessable_entity @@ -76,7 +77,7 @@ def set_conversation end def set_assistant - @assistant = Current.user.assistants.find_by(id: params[:assistant_id]) + @assistant = Current.user.assistants_including_deleted.find_by(id: params[:assistant_id]) @assistant ||= @conversation.latest_message_for_version(@version).assistant end @@ -111,4 +112,9 @@ def message_params end modified_params end + + def version_navigate_message_params + params.require(:message).permit(:conversation_id, :version) + end end + diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index 985427ecc..db2d7d975 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -34,7 +34,10 @@ def destroy private def set_assistant - @assistant = Current.user.assistants.find(params[:id]) + @assistant = Current.user.assistants.where(id: params[:id]).first + if @assistant.nil? + redirect_to new_settings_assistant_url, notice: 'The assistant was deleted', status: :see_other + end end def assistant_params diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 56374aa72..1390eed05 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -10,11 +10,14 @@ class Assistant < ApplicationRecord belongs_to :language_model validates :tools, presence: true, allow_blank: true + validates :name, presence: true MAX_LIST_DISPLAY = 5 scope :ordered, -> { order(:id) } + scope :not_deleted, -> { where(deleted_at: nil) } + def initials return nil if name.blank? @@ -24,6 +27,15 @@ def initials parts[1]&.try(:[], 0)&.capitalize.to_s end + def destroy + raise "Can't delete user's last assistant" if user.assistants.count < 2 + update!(deleted_at: Time.now) + end + + def deleted? + deleted_at.present? + end + def to_s name end diff --git a/app/models/message.rb b/app/models/message.rb index 1a2b3e349..f47c24338 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -26,6 +26,7 @@ class Message < ApplicationRecord scope :ordered, -> { latest_version_for_conversation } + attr_accessor :allow_deleted_assistant private def create_conversation @@ -38,6 +39,7 @@ def validate_conversation def validate_assistant errors.add(:assistant, 'is invalid') unless assistant.user == Current.user + errors.add(:assistant, 'has been deleted') if assistant.deleted? && !allow_deleted_assistant end def start_assistant_reply diff --git a/app/models/user.rb b/app/models/user.rb index 22b7d21f7..be77f5188 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,7 +9,8 @@ class User < ApplicationRecord validates :first_name, presence: true validates :last_name, presence: true, on: :create - has_many :assistants, dependent: :destroy + has_many :assistants, -> { not_deleted }, dependent: :destroy + has_many :assistants_including_deleted, class_name: "Assistant" has_many :conversations, dependent: :destroy belongs_to :last_cancelled_message, class_name: "Message", optional: true diff --git a/app/views/messages/_main_column.html.erb b/app/views/messages/_main_column.html.erb index 0b3a287c4..bbef2f21b 100644 --- a/app/views/messages/_main_column.html.erb +++ b/app/views/messages/_main_column.html.erb @@ -125,10 +125,15 @@ <% end %>
-
+ <% if @assistant.deleted? %> +
+

This assistant has been deleted, and cannot assist any longer.

+
+ <% else %> <%= button_tag type: "button", id: "attach-button", class: "absolute left-11 bottom-[26px] w-[23px] h-[23px] relationship:hidden", @@ -362,6 +367,7 @@
<% end %> + <% end %>
diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index 52cfb2623..0077171b3 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -162,7 +162,7 @@ end <% end %> <% end %> - <% if message.user? && !message.has_document_image? %> + <% if message.user? && !message.has_document_image? && !message.assistant.deleted? %> <%= link_to edit_assistant_message_path(message.assistant, message), class: "cursor-pointer hover:text-gray-900 dark:hover:text-white flex items-center", data: { @@ -171,7 +171,7 @@ end } do %> <%= icon "pencil", variant: :outline, size: 18, title: "Edit" %> <% end %> - <% elsif message.assistant? %> + <% elsif message.assistant? && !message.assistant.deleted? %> <%= button_tag type: "button", class: "cursor-pointer hover:text-gray-900 dark:hover:text-white", data: { @@ -261,4 +261,4 @@ end <% end %>
-
\ No newline at end of file +
diff --git a/app/views/messages/edit.html.erb b/app/views/messages/edit.html.erb index 618786434..378df70f5 100644 --- a/app/views/messages/edit.html.erb +++ b/app/views/messages/edit.html.erb @@ -34,23 +34,25 @@ } %> -
- <%= form.submit "Save & Submit", - data: { - role: "save", - turbo_submits_with: "Saving..." - }, - class: %| - inline-block cursor-pointer - rounded-lg py-2 px-3 - bg-gray-200 dark:bg-gray-900 - border border-gray-400 - | %> - <%= link_to "Cancel", @message, - data: { role: "cancel" }, - class: "inline-block ml-5" - %> -
+ <% unless @message.assistant.deleted? %> +
+ <%= form.submit "Save & Submit", + data: { + role: "save", + turbo_submits_with: "Saving..." + }, + class: %| + inline-block cursor-pointer + rounded-lg py-2 px-3 + bg-gray-200 dark:bg-gray-900 + border border-gray-400 + | %> + <%= link_to "Cancel", @message, + data: { role: "cancel" }, + class: "inline-block ml-5" + %> +
+ <% end %> <% end %> diff --git a/app/views/messages/new.html.erb b/app/views/messages/new.html.erb index 8c4d285ce..ed40d2ec0 100644 --- a/app/views/messages/new.html.erb +++ b/app/views/messages/new.html.erb @@ -2,7 +2,17 @@ <%= render 'nav_column' %> <% end %> + + <%= content_for :messages do %> + +<% if @message&.errors&.any? %> +

The following errors prevented your message to reach the AI:

+ <% @message.errors.full_messages.each do |msg| %> + <%= msg %> + <% end %> +<% end %> +
diff --git a/app/views/settings/assistants/edit.html.erb b/app/views/settings/assistants/edit.html.erb index 3ee8e807e..4ab76b51f 100644 --- a/app/views/settings/assistants/edit.html.erb +++ b/app/views/settings/assistants/edit.html.erb @@ -3,10 +3,8 @@ <%= render "form", assistant: @assistant %> - <%# TODO: Messages are connected to assistants. When an assistant is deleted, - what should happen to the messages? - - button_to "Delete", + <% unless Current.user.assistants.count < 2 %> + <%= button_to "Delete", settings_assistant_path(@assistant), method: :delete, data: { turbo_confirm: "Are you sure?" }, @@ -18,6 +16,8 @@ rounded-lg font-medium | - %> + %> + <% end %> + <%= link_to "Cancel", root_path, class: "float-right inline-block ml-5 py-3" %>
diff --git a/db/migrate/20240516144314_add_deleted_at_to_assistants.rb b/db/migrate/20240516144314_add_deleted_at_to_assistants.rb new file mode 100644 index 000000000..b267c0eaa --- /dev/null +++ b/db/migrate/20240516144314_add_deleted_at_to_assistants.rb @@ -0,0 +1,6 @@ +class AddDeletedAtToAssistants < ActiveRecord::Migration[7.1] + def change + add_column :assistants, :deleted_at, :timestamp, default: nil + add_index :assistants, [:user_id, :deleted_at] + end +end diff --git a/db/schema.rb b/db/schema.rb index 961b29581..22f007e8c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_15_080700) do +ActiveRecord::Schema[7.1].define(version: 2024_05_16_144314) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -58,7 +58,9 @@ t.datetime "updated_at", null: false t.boolean "images", default: false, null: false t.bigint "language_model_id" + t.datetime "deleted_at", precision: nil t.index ["language_model_id"], name: "index_assistants_on_language_model_id" + t.index ["user_id", "deleted_at"], name: "index_assistants_on_user_id_and_deleted_at" t.index ["user_id"], name: "index_assistants_on_user_id" end diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index d8f6e2248..038cdce24 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -31,26 +31,26 @@ class AssistantTest < ActiveSupport::TestCase test "simple create works" do assert_nothing_raised do - Assistant.create!(user: users(:keith), language_model_id: 1) + Assistant.create!(user: users(:keith), language_model_id: 1, name: 'abc') end end test "tools defaults to empty array on create" do - a = Assistant.create!(user: users(:keith), language_model_id: 1) + a = Assistant.create!(user: users(:keith), language_model_id: 1, name: 'abc') assert_equal [], a.tools end - test "associations are deleted upon destroy" do + test "associations are not deleted upon destroy" do assistant = assistants(:samantha) conversation_count = assistant.conversations.count * -1 document_count = (assistant.documents.count+assistant.conversations.sum { |c| c.messages.sum { |m| m.documents.count }}) * -1 run_count = assistant.runs.count * -1 step_count = assistant.steps.count * -1 - assert_difference "Conversation.count", conversation_count do - assert_difference "Document.count", document_count do - assert_difference "Run.count", run_count do - assert_difference "Step.count", step_count do + assert_difference "Conversation.count", 0 do + assert_difference "Document.count", 0 do + assert_difference "Run.count", 0 do + assert_difference "Step.count", 0 do assistant.destroy end end From c0ab70d65932cb406002c66c8fa454482fe82c1e Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sat, 18 May 2024 20:23:31 -0500 Subject: [PATCH 36/68] Let's have an explicit position column for ordering --- app/models/language_model.rb | 2 + .../20240515080700_create_language_models.rb | 55 ++++++++++--------- db/schema.rb | 12 +--- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/app/models/language_model.rb b/app/models/language_model.rb index 068730304..c315ae772 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -1,5 +1,7 @@ # We don't care about large or not class LanguageModel < ApplicationRecord + scope :ordered, -> { order(:position) } + def readonly? !new_record? end diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index dad202565..cf707e3f7 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -1,6 +1,7 @@ class CreateLanguageModels < ActiveRecord::Migration[7.1] def up create_table :language_models do |t| + t.integer :position t.string :name t.text :description @@ -12,40 +13,42 @@ def up [1, 'gpt-best', 'Best OpenAI Model'], [2, 'claude-best', 'Best Anthropic Model'], - [3, 'gpt-4', 'ChatGPT 4'], - [4, 'gpt-4-turbo', 'ChatGPT 4 Turbo with Vision (may update in future)'], - [5, 'gpt-4-turbo-2024-04-09', 'ChatGPT-4 Turbo with Vision (2024-04-09)'], - [6, 'gpt-4-turbo-preview', 'ChatGPT 4 Turbo Preview'], - [7, 'gpt-4-0125-preview', 'ChatGPT 4 Turbo Preview (2024-01-25)'], - [8, 'gpt-4-1106-preview', 'ChatGPT 4 Turbo Preview (2023-11-06)'], - [9, 'gpt-4-vision-preview', 'ChatGPT 4 Turbo Model preview with the ability to understand images (2023-11-06)'], - [10, 'gpt-4-1106-vision-preview', 'ChatGPT 4 Turbo preview with the ability to understand images (2023-11-06)'], - [11, 'gpt-4-0613', 'ChatGPT 4 Snapshot improved function calling (2023-06-13)'], + [3, 'gpt-4o', 'GPT-4o (latest)'], + [4, 'gpt-4o-2024-05-13', 'GPT-4o Omni Multimodal (2024-05-13)'], - [12, 'gpt-3.5-turbo', 'ChatGPT 3.5 Turbo'], - [13, 'gpt-3.5-turbo-16k-0613', 'ChatGPT 3.5 Turbo (2022-06-13)'], - [14, 'gpt-3.5-turbo-0125', 'ChatGPT 3.5 Turbo (2022-01-25)'], - [15, 'gpt-3.5-turbo-1106', 'ChatGPT 3.5 Turbo (2022-11-06)'], - [16, 'gpt-3.5-turbo-instruct', 'ChatGPT 3.5 Turbo Instruct'], + [5, 'gpt-4-turbo', 'GPT-4 Turbo with Vision (latest)'], + [6, 'gpt-4-turbo-2024-04-09', 'GPT-4 Turbo with Vision (2024-04-09)'], + [7, 'gpt-4-turbo-preview', 'GPT-4 Turbo Preview'], + [8, 'gpt-4-0125-preview', 'GPT-4 Turbo Preview (2024-01-25)'], + [9, 'gpt-4-1106-preview', 'GPT-4 Turbo Preview (2023-11-06)'], + [10, 'gpt-4-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)'], + [11, 'gpt-4-1106-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)'], - [17, 'claude-3-opus-20240229', 'Claude 3 Opus (2024-02-29)'], - [18, 'claude-3-sonnet-20240229', 'Claude 3 Sonnet (2024-02-29)'], - [19, 'claude-3-haiku-20240307', 'Claude 3 Haiku (2024-03-07)'], - [20, 'claude-2.1', 'Claude 2.1'], - [21, 'claude-2.0', 'Claude 2.0'], - [22, 'claude-instant-1.2', 'Claude Instant 1.2'] - ].each do |db_id, name, description| - record = LanguageModel.new(name: name, description: description) - record.id = db_id - record.save! + [12, 'gpt-4', 'GPT-4 (latest)'], + [13, 'gpt-4-0613', 'GPT-4 Snapshot improved function calling (2023-06-13)'], + + [14, 'gpt-3.5-turbo', 'GPT-3.5 Turbo'], + [15, 'gpt-3.5-turbo-16k-0613', 'GPT-3.5 Turbo (2022-06-13)'], + [16, 'gpt-3.5-turbo-0125', 'GPT-3.5 Turbo (2022-01-25)'], + [17, 'gpt-3.5-turbo-1106', 'GPT-3.5 Turbo (2022-11-06)'], + [18, 'gpt-3.5-turbo-instruct', 'GPT-3.5 Turbo Instruct'], + + [19, 'claude-3-opus-20240229', 'Claude 3 Opus (2024-02-29)'], + [20, 'claude-3-sonnet-20240229', 'Claude 3 Sonnet (2024-02-29)'], + [21, 'claude-3-haiku-20240307', 'Claude 3 Haiku (2024-03-07)'], + [22, 'claude-2.1', 'Claude 2.1'], + [23, 'claude-2.0', 'Claude 2.0'], + [24, 'claude-instant-1.2', 'Claude Instant 1.2'] + ].each do |position, name, description| + LanguageModel.create!(position: position, name: name, description: description) end - ActiveRecord::Base.connection.execute "ALTER SEQUENCE language_models_id_seq RESTART WITH 30;" + max_position = 24 # Respect some users who may have added their own model values in the assistants table (Assistant.all.pluck(:model).uniq - LanguageModel.all.pluck(:name)).each do |model_name| Rails.logger.info "Create language_models record from assistants column value: #{model_name.inspect}" - LanguageModel.create!(name: model_name, description: model_name) + LanguageModel.create!(name: model_name, description: model_name, position: max_position += 1) end add_reference :assistants, :language_model, null: true, foreign_key: { to_table: :language_models} diff --git a/db/schema.rb b/db/schema.rb index 22f007e8c..8f547bea8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -100,13 +100,7 @@ end create_table "language_models", force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "llms", force: :cascade do |t| + t.integer "position" t.string "name" t.text "description" t.datetime "created_at", null: false @@ -122,10 +116,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false - t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false + t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" @@ -176,13 +170,13 @@ t.datetime "cancelled_at", precision: nil t.datetime "failed_at", precision: nil t.datetime "completed_at", precision: nil + t.string "model", null: false t.string "instructions" t.string "additional_instructions" t.jsonb "tools", default: [], null: false t.jsonb "file_ids", default: [], null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "model" t.index ["assistant_id"], name: "index_runs_on_assistant_id" t.index ["conversation_id"], name: "index_runs_on_conversation_id" end From 58d5650b8e734bee15dbe279bf0d34069d0f152c Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sat, 18 May 2024 20:27:12 -0500 Subject: [PATCH 37/68] Add association + updates fixtures do ref by name --- app/models/language_model.rb | 2 ++ test/fixtures/assistants.yml | 8 ++++---- test/fixtures/language_models.yml | 12 ++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/models/language_model.rb b/app/models/language_model.rb index c315ae772..fe5ef672a 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -2,6 +2,8 @@ class LanguageModel < ApplicationRecord scope :ordered, -> { order(:position) } + has_many :assistants + def readonly? !new_record? end diff --git a/test/fixtures/assistants.yml b/test/fixtures/assistants.yml index c4b5d47d5..330926bf7 100644 --- a/test/fixtures/assistants.yml +++ b/test/fixtures/assistants.yml @@ -1,6 +1,6 @@ samantha: user: keith - language_model_id: 3 + language_model: gpt_4 name: Samantha description: My personal assistant instructions: You are a helpful assistant @@ -9,7 +9,7 @@ samantha: keith_gpt4: user: keith - language_model_id: 3 + language_model: gpt_4 name: GPT-4 description: OpenAI's GPT-4 instructions: @@ -18,7 +18,7 @@ keith_gpt4: keith_claude3: user: keith - language_model_id: 17 + language_model: claude_3_opus name: Claude 3 Opus description: Claude 3, Opus version instructions: @@ -27,7 +27,7 @@ keith_claude3: rob_gpt4: user: rob - language_model_id: 3 + language_model: gpt_4 name: GPT-4 description: OpenAI's GPT-4 instructions: diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index 5d881d86f..d95d894c4 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -1,29 +1,29 @@ gpt_best: - id: 1 + position: 1 name: gpt-best description: gpt-best from fixtures claude_best: - id: 2 + position: 2 name: best-claude description: claude-best from fixtures claude_3_sonnet: - id: 18 + position: 18 name: claude-3-sonnet-20240229 description: claude-3-sonnet-20240229 from fixtures claude_3_opus: - id: 17 + position: 17 name: claude-3-opus-20240229 description: claude-3-opus-20240229 from fixtures gpt_4: - id: 3 + position: 3 name: gpt-4 description: gpt-4 from fixtures gpt_3_5_turbo_0125: - id: 12 + position: 12 name: gpt-3.5-turbo description: gpt-3.5-turbo from fixtures From 22261882b1d46cfe1b8851ec4d4206f2649fc511 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sat, 18 May 2024 21:20:53 -0500 Subject: [PATCH 38/68] Improve styling of external assistant list --- app/controllers/messages_controller.rb | 4 +-- app/views/assistants/_assistant.html.erb | 2 +- app/views/messages/_nav_column.html.erb | 35 +++++++++++++++++------- app/views/messages/new.html.erb | 4 +-- bin/dev | 2 ++ 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index d37b30c1b..bb558402c 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -91,7 +91,8 @@ def set_nav_conversations def set_nav_assistants @nav_assistants = Current.user.assistants.ordered - @hide_settings_assistants_overflow = @nav_assistants.length > Assistant::MAX_LIST_DISPLAY + @hide_settings_assistants_overflow = @nav_assistants.length > Assistant::MAX_LIST_DISPLAY && + @nav_assistants.index(@assistant) <= Assistant::MAX_LIST_DISPLAY-1 end def message_params @@ -117,4 +118,3 @@ def version_navigate_message_params params.require(:message).permit(:conversation_id, :version) end end - diff --git a/app/views/assistants/_assistant.html.erb b/app/views/assistants/_assistant.html.erb index f8c95a5e5..ba095a313 100644 --- a/app/views/assistants/_assistant.html.erb +++ b/app/views/assistants/_assistant.html.erb @@ -8,7 +8,7 @@ flex justify-between items-center mb-1 p-1 pl-2 pr-2 mr-5 hover:bg-gray-100 dark:hover:bg-gray-700 - bg-gray-50 dark:bg-transparent + bg-gray-50 dark:bg-transparent dark:bg-gray-700 group cursor-pointer text-sm rounded-lg diff --git a/app/views/messages/_nav_column.html.erb b/app/views/messages/_nav_column.html.erb index 78d9b4c21..db800e047 100644 --- a/app/views/messages/_nav_column.html.erb +++ b/app/views/messages/_nav_column.html.erb @@ -1,14 +1,29 @@ -
- <% if @hide_settings_assistants_overflow %> -
- <%= button_tag type: "button", - class: "p-1 text-sm", - data: {transition_target: "transitionable", action: "transition#toggleClass", - } do %> - Show Remaining - <% end %> - <% end %> +
<%= render @nav_assistants %> + + <% if @hide_settings_assistants_overflow %> + <%= button_tag type: "button", + class: %| + flex + text-sm + rounded-lg + mb-1 p-1 pl-2 pr-2 mr-5 + hover:bg-gray-100 dark:hover:bg-gray-700 + pointer + |, + data: { + transition_target: "transitionable", + action: "transition#toggleClass", + } do %> + <%= icon "chevron-down", variant: :mini, class: 'text-gray-500 ml-[2px] mr-[14px]' %> Show All + <% end %> + <% end %>
diff --git a/app/views/messages/new.html.erb b/app/views/messages/new.html.erb index ed40d2ec0..baf2d0821 100644 --- a/app/views/messages/new.html.erb +++ b/app/views/messages/new.html.erb @@ -2,12 +2,10 @@ <%= render 'nav_column' %> <% end %> - - <%= content_for :messages do %> <% if @message&.errors&.any? %> -

The following errors prevented your message to reach the AI:

+

Errors communicating with assistant:

<% @message.errors.full_messages.each do |msg| %> <%= msg %> <% end %> diff --git a/bin/dev b/bin/dev index 56fc7f6f9..e596e5142 100755 --- a/bin/dev +++ b/bin/dev @@ -3,6 +3,8 @@ export PORT="${PORT:-3000}" if command -v overmind > /dev/null 2>&1; then + bundle install + bin/rails db:prepare exec overmind start -f Procfile.dev "$@" else if ! gem list foreman -i --silent; then From 1960c3008018430ce62ea65ec0ea0c958c37baa7 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sat, 18 May 2024 21:42:14 -0500 Subject: [PATCH 39/68] Remove explicit ref to LLM id's to be less brittle --- app/models/concerns/user/registerable.rb | 8 ++++---- app/models/language_model.rb | 5 ----- .../20240515080700_create_language_models.rb | 2 +- .../controllers/assistants_controller_test.rb | 2 +- test/models/assistant_test.rb | 20 +++++++------------ 5 files changed, 13 insertions(+), 24 deletions(-) diff --git a/app/models/concerns/user/registerable.rb b/app/models/concerns/user/registerable.rb index 383a3401f..770ca4d1b 100644 --- a/app/models/concerns/user/registerable.rb +++ b/app/models/concerns/user/registerable.rb @@ -8,9 +8,9 @@ module User::Registerable private def create_initial_assistants - assistants.create! name: "GPT (best)", language_model_id: LanguageModel::GPT_BEST_ID, images: true - assistants.create! name: "GPT-3.5", language_model_id: LanguageModel::GPT_3_5_ID, images: false - assistants.create! name: "Claude (best)", language_model_id: LanguageModel::CLAUDE_BEST_ID, images: true - assistants.create! name: "Claude 3 Sonnet", language_model_id: LanguageModel::CLAUDE_3_SONNET_ID, images: true + assistants.create! name: "GPT-4o", language_model: LanguageModel.find_by(name: 'gpt-4o'), images: true + assistants.create! name: "GPT-3.5", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo'), images: false + assistants.create! name: "Claude 3 Opus", language_model: LanguageModel.find_by(name: 'claude-3-opus-20240229'), images: true + assistants.create! name: "Claude 3 Sonnet", language_model: LanguageModel.find_by(name: 'claude-3-sonnet-20240229'), images: true end end diff --git a/app/models/language_model.rb b/app/models/language_model.rb index fe5ef672a..e3afc600e 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -15,11 +15,6 @@ def before_destroy PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', 'claude-best': 'claude-3-opus-20240229'} - GPT_BEST_ID = 1 - CLAUDE_BEST_ID = 2 - GPT_3_5_ID = 12 - CLAUDE_3_SONNET_ID = 18 - def provider_id PROVIDER_ID_MAP[self.name.to_sym] || self.name unless self.name =~ /^best/ end diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index cf707e3f7..1fcc588d0 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -27,7 +27,7 @@ def up [12, 'gpt-4', 'GPT-4 (latest)'], [13, 'gpt-4-0613', 'GPT-4 Snapshot improved function calling (2023-06-13)'], - [14, 'gpt-3.5-turbo', 'GPT-3.5 Turbo'], + [14, 'gpt-3.5-turbo', 'GPT-3.5 Turbo (latest)'], [15, 'gpt-3.5-turbo-16k-0613', 'GPT-3.5 Turbo (2022-06-13)'], [16, 'gpt-3.5-turbo-0125', 'GPT-3.5 Turbo (2022-01-25)'], [17, 'gpt-3.5-turbo-1106', 'GPT-3.5 Turbo (2022-11-06)'], diff --git a/test/controllers/assistants_controller_test.rb b/test/controllers/assistants_controller_test.rb index cca59e185..5a5e8d88d 100644 --- a/test/controllers/assistants_controller_test.rb +++ b/test/controllers/assistants_controller_test.rb @@ -19,7 +19,7 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest test "should create assistant" do assert_difference("Assistant.count") do - post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, language_model_id: 1, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} + post assistants_url, params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, language_model_id: @assistant.language_model_id, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} end assert_redirected_to assistant_url(Assistant.last) diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 038cdce24..346fdcb18 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -31,27 +31,21 @@ class AssistantTest < ActiveSupport::TestCase test "simple create works" do assert_nothing_raised do - Assistant.create!(user: users(:keith), language_model_id: 1, name: 'abc') + Assistant.create!(user: users(:keith), language_model: language_models(:gpt_4), name: 'abc') end end test "tools defaults to empty array on create" do - a = Assistant.create!(user: users(:keith), language_model_id: 1, name: 'abc') + a = Assistant.create!(user: users(:keith), language_model: language_models(:gpt_4), name: 'abc') assert_equal [], a.tools end test "associations are not deleted upon destroy" do - assistant = assistants(:samantha) - conversation_count = assistant.conversations.count * -1 - document_count = (assistant.documents.count+assistant.conversations.sum { |c| c.messages.sum { |m| m.documents.count }}) * -1 - run_count = assistant.runs.count * -1 - step_count = assistant.steps.count * -1 - - assert_difference "Conversation.count", 0 do - assert_difference "Document.count", 0 do - assert_difference "Run.count", 0 do - assert_difference "Step.count", 0 do - assistant.destroy + assert_no_difference "Conversation.count" do + assert_no_difference "Document.count" do + assert_no_difference "Run.count" do + assert_no_difference "Step.count" do + assistants(:samantha).destroy end end end From 958bebc3846d0ca8573bc2c8b507f4dbf43e1ed2 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 08:32:45 -0500 Subject: [PATCH 40/68] Tweak styling of the assistants edit page --- app/views/settings/assistants/_form.html.erb | 25 +++++++++++++++++--- app/views/settings/assistants/edit.html.erb | 4 ++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index fd4855b22..d57077195 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -13,8 +13,20 @@
<%= form.label :language_model_id %>
- <%= form.select :language_model_id, LanguageModel.order(:description).all.pluck(:description, :id), class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> + <%= form.select :language_model_id, + LanguageModel.ordered.pluck(:description, :id), + {}, + class: %| + block + border border-gray-200 outline-none + shadow rounded-md + px-3 py-2 mt-2 + w-full + dark:text-black + | + %>
+
<%= form.label :name %> <%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> @@ -26,9 +38,16 @@
- <%= form.label :instructions, 'Custom Instructions' %> + <%= form.label :instructions, "Custom Instructions" %> <%= form.text_area :instructions, - class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black h-38", + class: %| + block + border border-gray-200 outline-none + shadow rounded-md + px-3 py-2 mt-2 + w-full + dark:text-black + |, data: { controller: "textarea-autogrow" } %>
diff --git a/app/views/settings/assistants/edit.html.erb b/app/views/settings/assistants/edit.html.erb index 4ab76b51f..f90aba6e8 100644 --- a/app/views/settings/assistants/edit.html.erb +++ b/app/views/settings/assistants/edit.html.erb @@ -3,14 +3,14 @@ <%= render "form", assistant: @assistant %> - <% unless Current.user.assistants.count < 2 %> + <% unless Current.user.assistants.count <= 1 %> <%= button_to "Delete", settings_assistant_path(@assistant), method: :delete, data: { turbo_confirm: "Are you sure?" }, form: { class: "inline-block" }, class: %| - ml-5 mt-2 py-3 px-5 + ml-5 py-3 px-5 bg-white dark:bg-gray-500 border border-gray-300 dark:border-gray-500 rounded-lg From 594bc97fca51affff403445ad078c8793009f4d5 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 08:41:15 -0500 Subject: [PATCH 41/68] Remove assistant overflow on settings since it's not easy to simplify --- .../settings/application_controller.rb | 28 +++++++++---------- .../settings/assistants_controller.rb | 4 +-- app/views/layouts/_settings_item.erb | 11 +++----- app/views/layouts/settings.html.erb | 14 ++-------- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/app/controllers/settings/application_controller.rb b/app/controllers/settings/application_controller.rb index 8a193dbea..087db0c60 100644 --- a/app/controllers/settings/application_controller.rb +++ b/app/controllers/settings/application_controller.rb @@ -1,22 +1,22 @@ class Settings::ApplicationController < ApplicationController layout "settings" - before_action :set_settings_assistants + before_action :set_settings_menu private - def set_settings_assistants - settings_assistants = Current.user.assistants.ordered.map { + def set_settings_menu + # controller_name => array of items + @settings_menu = { + people: { + "Your Account": edit_settings_person_path + }, + + assistants: Current.user.assistants.ordered.map { |assistant| [ assistant, edit_settings_assistant_path(assistant) ] - } - if settings_assistants.length > Assistant::MAX_LIST_DISPLAY - assistants_to_hide = settings_assistants[Assistant::MAX_LIST_DISPLAY, settings_assistants.length - Assistant::MAX_LIST_DISPLAY] - # If user is editing an "overflow assistant" don't collapse that section, keep it open from the start - @hide_settings_assistants_overflow = params[:controller] !='settings/assistants' || - params[:action] != 'edit' || - !assistants_to_hide.map(&:first).map(&:id).include?(params[:id].to_i) - end - @settings_menu = {assistants: settings_assistants.to_h, - new_assistant: {'New Assistant': new_settings_assistant_path}, - people: {'Account': edit_settings_person_path}} + }.to_h.merge({ + "New Assistant": new_settings_assistant_path + }) + + } end end diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index db2d7d975..18c73867f 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -34,9 +34,9 @@ def destroy private def set_assistant - @assistant = Current.user.assistants.where(id: params[:id]).first + @assistant = Current.user.assistants.find_by(id: params[:id]) if @assistant.nil? - redirect_to new_settings_assistant_url, notice: 'The assistant was deleted', status: :see_other + redirect_to new_settings_assistant_url, notice: "The assistant was deleted", status: :see_other end end diff --git a/app/views/layouts/_settings_item.erb b/app/views/layouts/_settings_item.erb index 3c4057958..3e5e68ba3 100644 --- a/app/views/layouts/_settings_item.erb +++ b/app/views/layouts/_settings_item.erb @@ -1,9 +1,6 @@ <% item, link = settings_item %> <% selected = (controller.to_s == controller_name.to_s) %> -<% compare_id = item.respond_to?(:id) ? item.id.to_s : '' %> -<% selected = selected && params[:id] == compare_id if params[:id] %> -<% selected = selected && request.fullpath == link if params[:id].blank? %> - +<% selected = selected && params[:id] == item.try(:id)&.to_s %>
- <%= hide_assistant_class(item, settings_item_counter) %> " data-role="assistant" - <%= assitant_data_transaction_target(item, settings_item_counter) %> > <%= link_to link, class: %| @@ -31,6 +26,8 @@
<% end %> - <%= content_tag :span, item.to_s, class: !item.is_a?(Assistant) && "ml-1 text-base" %> + "> + <%= item.to_s %> + <% end %>
diff --git a/app/views/layouts/settings.html.erb b/app/views/layouts/settings.html.erb index d6b569902..d403b8d7e 100644 --- a/app/views/layouts/settings.html.erb +++ b/app/views/layouts/settings.html.erb @@ -16,19 +16,9 @@ Settings From c5eec770d37d98f719646cd3089cbff5bed4a4ac Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 09:16:03 -0500 Subject: [PATCH 42/68] Simplify conditional logic for convo's with deleted assistants --- app/views/messages/_main_column.html.erb | 12 +++++------- app/views/messages/_message.html.erb | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/views/messages/_main_column.html.erb b/app/views/messages/_main_column.html.erb index bbef2f21b..e50637a32 100644 --- a/app/views/messages/_main_column.html.erb +++ b/app/views/messages/_main_column.html.erb @@ -125,15 +125,14 @@ <% end %>
+
+

<%= @assistant.name %> has been deleted and cannot assist any longer.

+
+
- <% if @assistant.deleted? %> -
-

This assistant has been deleted, and cannot assist any longer.

-
- <% else %> <%= button_tag type: "button", id: "attach-button", class: "absolute left-11 bottom-[26px] w-[23px] h-[23px] relationship:hidden", @@ -367,7 +366,6 @@
<% end %> - <% end %>
diff --git a/app/views/messages/_message.html.erb b/app/views/messages/_message.html.erb index 0077171b3..a97b79f8b 100644 --- a/app/views/messages/_message.html.erb +++ b/app/views/messages/_message.html.erb @@ -162,7 +162,7 @@ end <% end %> <% end %> - <% if message.user? && !message.has_document_image? && !message.assistant.deleted? %> + <% if message.user? && message.assistant.not_deleted? && !message.has_document_image? %> <%= link_to edit_assistant_message_path(message.assistant, message), class: "cursor-pointer hover:text-gray-900 dark:hover:text-white flex items-center", data: { @@ -171,7 +171,7 @@ end } do %> <%= icon "pencil", variant: :outline, size: 18, title: "Edit" %> <% end %> - <% elsif message.assistant? && !message.assistant.deleted? %> + <% elsif message.assistant? && message.assistant.not_deleted? %> <%= button_tag type: "button", class: "cursor-pointer hover:text-gray-900 dark:hover:text-white", data: { @@ -261,4 +261,4 @@ end <% end %>
-
+ \ No newline at end of file From eece44d1ed985925af84970702c3cfd5322219fb Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 09:20:16 -0500 Subject: [PATCH 43/68] Remove helper methods which get are already added automatically --- app/models/assistant.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 1390eed05..58abcdf79 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -1,4 +1,6 @@ class Assistant < ApplicationRecord + MAX_LIST_DISPLAY = 5 + belongs_to :user has_many :conversations, dependent: :destroy @@ -12,12 +14,8 @@ class Assistant < ApplicationRecord validates :tools, presence: true, allow_blank: true validates :name, presence: true - MAX_LIST_DISPLAY = 5 - scope :ordered, -> { order(:id) } - scope :not_deleted, -> { where(deleted_at: nil) } - def initials return nil if name.blank? @@ -28,14 +26,10 @@ def initials end def destroy - raise "Can't delete user's last assistant" if user.assistants.count < 2 + raise "Can't delete user's last assistant" if user.assistants.count <= update!(deleted_at: Time.now) end - def deleted? - deleted_at.present? - end - def to_s name end From 2da5b5a7d123de90be5fb01afb57f63d03ecb461 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 09:26:47 -0500 Subject: [PATCH 44/68] Slightly simplify logic w/ provider_name --- app/models/language_model.rb | 12 +++++++----- app/services/ai_backends/anthropic.rb | 2 +- app/services/ai_backends/open_ai.rb | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/language_model.rb b/app/models/language_model.rb index e3afc600e..2cc772046 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -1,5 +1,10 @@ # We don't care about large or not class LanguageModel < ApplicationRecord + BEST_MODELS = { + 'gpt-best' => 'gpt-4o-2024-05-13', + 'claude-best' => 'claude-3-opus-20240229' + } + scope :ordered, -> { order(:position) } has_many :assistants @@ -12,11 +17,8 @@ def before_destroy raise ActiveRecord::ReadOnlyRecord end - PROVIDER_ID_MAP = {'gpt-best': 'gpt-4-turbo', - 'claude-best': 'claude-3-opus-20240229'} - - def provider_id - PROVIDER_ID_MAP[self.name.to_sym] || self.name unless self.name =~ /^best/ + def provider_name + BEST_MODELS[name] || name end def ai_backend diff --git a/app/services/ai_backends/anthropic.rb b/app/services/ai_backends/anthropic.rb index 27e320556..a4715fb15 100644 --- a/app/services/ai_backends/anthropic.rb +++ b/app/services/ai_backends/anthropic.rb @@ -49,7 +49,7 @@ def get_next_chat_message(&chunk_received_handler) begin response = @client.messages( - model: @assistant.language_model.provider_id, + model: @assistant.language_model.provider_name, system: @assistant.instructions, messages: preceding_messages, parameters: { diff --git a/app/services/ai_backends/open_ai.rb b/app/services/ai_backends/open_ai.rb index ec66497bd..228bf01e6 100644 --- a/app/services/ai_backends/open_ai.rb +++ b/app/services/ai_backends/open_ai.rb @@ -49,7 +49,7 @@ def get_next_chat_message(&chunk_received_handler) begin response = @client.chat(parameters: { - model: @assistant.language_model.provider_id, + model: @assistant.language_model.provider_name, messages: system_message + preceding_messages, stream: response_handler, max_tokens: 2000, # we should really set this dynamically, based on the model, to the max From 1db32480157afbefa993433688338b80ac21d21b Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Sun, 19 May 2024 09:35:47 -0500 Subject: [PATCH 45/68] Ensure all assistants (incl deleted) are destroyed w/ a user --- app/models/user.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index be77f5188..86076f689 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,8 +9,8 @@ class User < ApplicationRecord validates :first_name, presence: true validates :last_name, presence: true, on: :create - has_many :assistants, -> { not_deleted }, dependent: :destroy - has_many :assistants_including_deleted, class_name: "Assistant" + has_many :assistants, -> { not_deleted } + has_many :assistants_including_deleted, class_name: "Assistant", dependent: :destroy has_many :conversations, dependent: :destroy belongs_to :last_cancelled_message, class_name: "Message", optional: true From 9b0a31ca967784c782dd5471237c38a0f228e7b2 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 21 May 2024 11:25:08 -0700 Subject: [PATCH 46/68] Review comments, fixing code + allowing dev to choose development/test database name --- app/helpers/application_helper.rb | 2 +- app/models/assistant.rb | 2 +- app/models/language_model.rb | 4 -- app/views/assistants/_assistant.html.erb | 2 +- app/views/messages/edit.html.erb | 36 ++++++------- config/database.yml | 4 +- .../20240515080700_create_language_models.rb | 53 ++++++++++--------- db/schema.rb | 3 +- test/fixtures/language_models.yml | 16 ++++++ 9 files changed, 67 insertions(+), 55 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 30b87c8fb..ff4e5e7a2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -6,7 +6,7 @@ def hide_assistant_class(item, item_counter) nil end - def assitant_data_transaction_target(item, item_counter) + def assistant_data_transaction_target(item, item_counter) return nil unless item.is_a?(Assistant) return 'data-transition-target="transitionable"'.html_safe if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY end diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 58abcdf79..2e194236e 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -26,7 +26,7 @@ def initials end def destroy - raise "Can't delete user's last assistant" if user.assistants.count <= + raise "Can't delete user's last assistant" if user.assistants.count <= 1 update!(deleted_at: Time.now) end diff --git a/app/models/language_model.rb b/app/models/language_model.rb index 2cc772046..04c92dc28 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -13,10 +13,6 @@ def readonly? !new_record? end - def before_destroy - raise ActiveRecord::ReadOnlyRecord - end - def provider_name BEST_MODELS[name] || name end diff --git a/app/views/assistants/_assistant.html.erb b/app/views/assistants/_assistant.html.erb index ba095a313..76c840411 100644 --- a/app/views/assistants/_assistant.html.erb +++ b/app/views/assistants/_assistant.html.erb @@ -19,7 +19,7 @@ data-radio-behavior-target="radio" data-action="radio-changed@window->radio-behavior#select" data-radio-behavior-id-param="<%= assistant.id %>" - <%= assitant_data_transaction_target(assistant, assistant_counter) %> + <%= assistant_data_transaction_target(assistant, assistant_counter) %> > <%= link_to new_assistant_message_path(assistant), class: "flex-1 flex items-center text-gray-950 dark:text-gray-100 font-medium truncate", data: { role: "name" } do %> <%= render partial: "layouts/assistant_avatar", locals: { assistant: assistant, size: 7, classes: "mr-2" } %> diff --git a/app/views/messages/edit.html.erb b/app/views/messages/edit.html.erb index 378df70f5..618786434 100644 --- a/app/views/messages/edit.html.erb +++ b/app/views/messages/edit.html.erb @@ -34,25 +34,23 @@ } %> - <% unless @message.assistant.deleted? %> -
- <%= form.submit "Save & Submit", - data: { - role: "save", - turbo_submits_with: "Saving..." - }, - class: %| - inline-block cursor-pointer - rounded-lg py-2 px-3 - bg-gray-200 dark:bg-gray-900 - border border-gray-400 - | %> - <%= link_to "Cancel", @message, - data: { role: "cancel" }, - class: "inline-block ml-5" - %> -
- <% end %> +
+ <%= form.submit "Save & Submit", + data: { + role: "save", + turbo_submits_with: "Saving..." + }, + class: %| + inline-block cursor-pointer + rounded-lg py-2 px-3 + bg-gray-200 dark:bg-gray-900 + border border-gray-400 + | %> + <%= link_to "Cancel", @message, + data: { role: "cancel" }, + class: "inline-block ml-5" + %> +
<% end %> diff --git a/config/database.yml b/config/database.yml index 03998056f..d8928152a 100644 --- a/config/database.yml +++ b/config/database.yml @@ -23,7 +23,7 @@ default: &default development: <<: *default - database: hostedgpt_development + database: <%= ENV['HOSTEDGPT_DEV_DB'] || "hostedgpt_development" %> # The specified database role being used to connect to PostgreSQL. # To create additional roles in PostgreSQL see `$ createuser --help`. @@ -57,7 +57,7 @@ development: # Do not set this db to the same as development or production. test: <<: *default - database: hostedgpt_test + database: <%= ENV['HOSTEDGPT_TEST_DB'] || "hostedgpt_test" %> # As with config/credentials.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index 1fcc588d0..cfe660845 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -4,43 +4,44 @@ def up t.integer :position t.string :name t.text :description + t.boolean :supports_images t.timestamps end # Initially supported models [ - [1, 'gpt-best', 'Best OpenAI Model'], - [2, 'claude-best', 'Best Anthropic Model'], + [1, 'gpt-best', 'Best OpenAI Model', true], + [2, 'claude-best', 'Best Anthropic Model', true], - [3, 'gpt-4o', 'GPT-4o (latest)'], - [4, 'gpt-4o-2024-05-13', 'GPT-4o Omni Multimodal (2024-05-13)'], + [3, 'gpt-4o', 'GPT-4o (latest)', true], + [4, 'gpt-4o-2024-05-13', 'GPT-4o Omni Multimodal (2024-05-13)', true], - [5, 'gpt-4-turbo', 'GPT-4 Turbo with Vision (latest)'], - [6, 'gpt-4-turbo-2024-04-09', 'GPT-4 Turbo with Vision (2024-04-09)'], - [7, 'gpt-4-turbo-preview', 'GPT-4 Turbo Preview'], - [8, 'gpt-4-0125-preview', 'GPT-4 Turbo Preview (2024-01-25)'], - [9, 'gpt-4-1106-preview', 'GPT-4 Turbo Preview (2023-11-06)'], - [10, 'gpt-4-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)'], - [11, 'gpt-4-1106-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)'], + [5, 'gpt-4-turbo', 'GPT-4 Turbo with Vision (latest)', true], + [6, 'gpt-4-turbo-2024-04-09', 'GPT-4 Turbo with Vision (2024-04-09)', true], + [7, 'gpt-4-turbo-preview', 'GPT-4 Turbo Preview', false], + [8, 'gpt-4-0125-preview', 'GPT-4 Turbo Preview (2024-01-25)', false], + [9, 'gpt-4-1106-preview', 'GPT-4 Turbo Preview (2023-11-06)', false], + [10, 'gpt-4-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)', true], + [11, 'gpt-4-1106-vision-preview', 'GPT-4 Turbo with Vision Preview (2023-11-06)', true], - [12, 'gpt-4', 'GPT-4 (latest)'], - [13, 'gpt-4-0613', 'GPT-4 Snapshot improved function calling (2023-06-13)'], + [12, 'gpt-4', 'GPT-4 (latest)', false], + [13, 'gpt-4-0613', 'GPT-4 Snapshot improved function calling (2023-06-13)', false], - [14, 'gpt-3.5-turbo', 'GPT-3.5 Turbo (latest)'], - [15, 'gpt-3.5-turbo-16k-0613', 'GPT-3.5 Turbo (2022-06-13)'], - [16, 'gpt-3.5-turbo-0125', 'GPT-3.5 Turbo (2022-01-25)'], - [17, 'gpt-3.5-turbo-1106', 'GPT-3.5 Turbo (2022-11-06)'], - [18, 'gpt-3.5-turbo-instruct', 'GPT-3.5 Turbo Instruct'], + [14, 'gpt-3.5-turbo', 'GPT-3.5 Turbo (latest)', false], + [15, 'gpt-3.5-turbo-16k-0613', 'GPT-3.5 Turbo (2022-06-13)', false], + [16, 'gpt-3.5-turbo-0125', 'GPT-3.5 Turbo (2022-01-25)', false], + [17, 'gpt-3.5-turbo-1106', 'GPT-3.5 Turbo (2022-11-06)', false], + [18, 'gpt-3.5-turbo-instruct', 'GPT-3.5 Turbo Instruct', false], - [19, 'claude-3-opus-20240229', 'Claude 3 Opus (2024-02-29)'], - [20, 'claude-3-sonnet-20240229', 'Claude 3 Sonnet (2024-02-29)'], - [21, 'claude-3-haiku-20240307', 'Claude 3 Haiku (2024-03-07)'], - [22, 'claude-2.1', 'Claude 2.1'], - [23, 'claude-2.0', 'Claude 2.0'], - [24, 'claude-instant-1.2', 'Claude Instant 1.2'] - ].each do |position, name, description| - LanguageModel.create!(position: position, name: name, description: description) + [19, 'claude-3-opus-20240229', 'Claude 3 Opus (2024-02-29)', true], + [20, 'claude-3-sonnet-20240229', 'Claude 3 Sonnet (2024-02-29)', true], + [21, 'claude-3-haiku-20240307', 'Claude 3 Haiku (2024-03-07)', true], + [22, 'claude-2.1', 'Claude 2.1', false], + [23, 'claude-2.0', 'Claude 2.0', false], + [24, 'claude-instant-1.2', 'Claude Instant 1.2', false] + ].each do |position, name, description, supports_images| + LanguageModel.create!(position: position, name: name, description: description, supports_images: supports_images) end max_position = 24 diff --git a/db/schema.rb b/db/schema.rb index 8f547bea8..57379c775 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -103,6 +103,7 @@ t.integer "position" t.string "name" t.text "description" + t.boolean "supports_images" t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -116,10 +117,10 @@ t.bigint "content_document_id" t.bigint "run_id" t.bigint "assistant_id", null: false + t.datetime "cancelled_at" t.datetime "processed_at", precision: nil t.integer "index", null: false t.integer "version", null: false - t.datetime "cancelled_at" t.boolean "branched", default: false, null: false t.integer "branched_from_version" t.index ["assistant_id"], name: "index_messages_on_assistant_id" diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index d95d894c4..4238e9860 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -1,25 +1,41 @@ gpt_best: position: 1 + supports_images: true name: gpt-best description: gpt-best from fixtures claude_best: position: 2 name: best-claude + supports_images: true description: claude-best from fixtures +gpt_4o: + position: 3 + name: gpt-4o + supports_images: true + description: gpt-4ot from fixtures + +gpt_3.5_turbo: + position: 4 + name: gpt-3.5-turbo + description: gpt-3.5-turbo from fixtures + claude_3_sonnet: position: 18 name: claude-3-sonnet-20240229 + supports_images: true description: claude-3-sonnet-20240229 from fixtures claude_3_opus: position: 17 + supports_images: true name: claude-3-opus-20240229 description: claude-3-opus-20240229 from fixtures gpt_4: position: 3 + supports_images: true name: gpt-4 description: gpt-4 from fixtures From ccbc7e4876e5e595d4763314bfd209a087336fcc Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 21 May 2024 11:59:37 -0700 Subject: [PATCH 47/68] Allowing user.destroy to work as previously --- app/models/assistant.rb | 8 ++++++-- app/models/user.rb | 13 +++++++++++++ .../settings/assistants_controller_test.rb | 5 +++-- test/models/assistant_test.rb | 14 ++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 2e194236e..8c9137303 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -26,8 +26,12 @@ def initials end def destroy - raise "Can't delete user's last assistant" if user.assistants.count <= 1 - update!(deleted_at: Time.now) + if user.destroy_in_progress? + super + else + raise "Can't delete user's last assistant" if user.assistants.count <= 1 + update!(deleted_at: Time.now) + end end def to_s diff --git a/app/models/user.rb b/app/models/user.rb index d20bf03b0..a2f63dc4d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -19,4 +19,17 @@ class User < ApplicationRecord def preferences attributes["preferences"].with_defaults(dark_mode: "system") end + + def destroy_in_progress? + @destroy_in_progress + end + + def destroy + @destroy_in_progress = true + begin + super + ensure + @destroy_in_progress = false + end + end end diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index 282d847d9..dfd17c8f6 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -37,11 +37,12 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest assert_equal params, @assistant.reload.slice(:name, :description, :instructions) end - test "should destroy assistant" do - assert_difference("Assistant.count", -1) do + test "destroy should soft-delete assistant" do + assert_difference("Assistant.count", 0) do delete settings_assistant_url(@assistant) end + assert @assistant.reload.deleted? assert_redirected_to new_settings_assistant_url end end diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 346fdcb18..396127113 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -40,6 +40,20 @@ class AssistantTest < ActiveSupport::TestCase assert_equal [], a.tools end + test "associations are deleted when user is being destroyed" do + User.stub_any_instance(:destroy_in_progress?, true) do + assert_difference "Conversation.count", -6 do + assert_difference "Document.count", -5 do + assert_difference "Run.count", -19 do + assert_difference "Step.count", -3 do + assistants(:samantha).destroy + end + end + end + end + end + end + test "associations are not deleted upon destroy" do assert_no_difference "Conversation.count" do assert_no_difference "Document.count" do From 02bd81e7652a68afe6911b17a48ed470066cd853 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Tue, 21 May 2024 21:24:10 -0700 Subject: [PATCH 48/68] Test updates round 1 --- .../controllers/assistants_controller_test.rb | 7 +++- test/controllers/messages_controller_test.rb | 29 ++++++++++++++ .../settings/assistants_controller_test.rb | 27 +++++++++++++ test/fixtures/language_models.yml | 2 +- test/models/assistant_test.rb | 23 +++++++++++ .../models/concerns/user/registerable_test.rb | 4 +- test/models/language_model_test.rb | 39 +++++++++++++++++++ test/models/user_test.rb | 13 +++++++ 8 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 test/models/language_model_test.rb diff --git a/test/controllers/assistants_controller_test.rb b/test/controllers/assistants_controller_test.rb index 5a5e8d88d..f90e1a547 100644 --- a/test/controllers/assistants_controller_test.rb +++ b/test/controllers/assistants_controller_test.rb @@ -36,8 +36,13 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest end test "should update assistant" do - patch assistant_url(@assistant), params: {assistant: {description: @assistant.description, instructions: @assistant.instructions, language_model_id: @assistant.language_model_id, name: @assistant.name, tools: @assistant.tools, user_id: @assistant.user_id}} + patch assistant_url(@assistant), params: {assistant: {description: "new description", instructions: "new instructions", language_model_id: language_models(:claude_3_opus).id, name: 'new name', tools: @assistant.tools, user_id: @assistant.user_id}} assert_redirected_to assistant_url(@assistant) + @assistant.reload + assert_equal "new description", @assistant.description + assert_equal "new instructions", @assistant.instructions + assert_equal "claude-3-opus-20240229", @assistant.language_model.name + assert_equal "new name", @assistant.name end # TODO: Messages are connected to assistants. When an assistant is deleted, diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index 37c0f7c22..4e6e401ff 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -112,4 +112,33 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest patch message_url(message, version: 2), params: { message: { id: message.id } } assert_redirected_to conversation_messages_url(message.conversation, version: 2) end + + test "even when assistant is deleted update succeeds for a new version in the URL" do + assert assistants(:samantha).destroy + message = messages(:message2_v1) + + patch message_url(message, version: 2), params: { message: { id: message.id } } + assert_redirected_to conversation_messages_url(message.conversation, version: 2) + + get conversation_messages_url(message.conversation, version: 2) + assert_match /Where were you born/, response.body + end + + test "when there are many assistants only a few are shown in the nav bar" do + 5.times do |x| + @user.assistants.create! name: "New assistant #{x+1}", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo') + end + get conversation_messages_url(@conversation, version: 1) + @user.assistants.each do |assistant| + assert_select %{div[data-radio-behavior-id-param="#{assistant.id}"] a[data-role="name"]} + end + @user.assistants.each_with_index do |assistant, index| + if index>5 + assert_select %{div.hidden[data-role="assistant"][data-radio-behavior-id-param="#{assistant.id}"] a[data-role="name"]} + else + assert_select %{div[data-role="assistant"][data-radio-behavior-id-param="#{assistant.id}"] a[data-role="name"]} + assert_select %{div.hiden[data-role="assistant"][data-radio-behavior-id-param="#{assistant.id}"] a[data-role="name"]}, false + end + end + end end diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index dfd17c8f6..9183d018b 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -23,9 +23,36 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest assert_equal params, Assistant.last.slice(:name, :description, :instructions, :language_model_id) end + test "should not hide assistants" do + user = @assistant.user + 5.times do |x| + user.assistants.create! name: "New assistant #{x+1}", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo') + end + get edit_settings_assistant_url(@assistant) + assert user.assistants.length > 6 + user.assistants.each do |assistant| + assert_select %{div[data-role="assistant"] a[data-role="name"] span}, assistant.name + assert_select %{div.hidden[data-role="assistant"] a[data-role="name"] span}, false + end + end + test "should get edit" do get edit_settings_assistant_url(@assistant) assert_response :success + assert_select 'section#menu a[href="/settings/person/edit"] span', "Your Account" + assert_select 'section#menu a[href="/settings/assistants/new"] span', "New Assistant" + end + + test "should allow language_model selection" do + get edit_settings_assistant_url(@assistant) + assert_select 'label', 'Language model' + assert_select 'select#assistant_language_model_id' + end + + test "should update language_model" do + params = {language_model_id: language_models(:claude_best).id} + patch settings_assistant_url(@assistant), params: { assistant: params } + assert_equal language_models(:claude_best), @assistant.reload.language_model end test "should update assistant" do diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index 4238e9860..86b59859a 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -16,7 +16,7 @@ gpt_4o: supports_images: true description: gpt-4ot from fixtures -gpt_3.5_turbo: +gpt_3_5_turbo: position: 4 name: gpt-3.5-turbo description: gpt-3.5-turbo from fixtures diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 396127113..ee62ed2d6 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -78,4 +78,27 @@ class AssistantTest < ActiveSupport::TestCase test "initials will split on - and return two characters" do assert_equal "G4", assistants(:keith_gpt4).initials end + + test "language model validated" do + record = Assistant.new + refute record.valid? + assert record.errors[:language_model].present? + end + + test "name validated" do + record = Assistant.new + refute record.valid? + assert record.errors[:name].present? + end + + test "cannot destroy last assistant of a user" do + assert_raise do + users(:rob).assistants.first.destroy + end + end + test "can destroy assistant of a user if they have more than one" do + assert_nothing_raised do + users(:keith).assistants.first.destroy + end + end end diff --git a/test/models/concerns/user/registerable_test.rb b/test/models/concerns/user/registerable_test.rb index 7eea6a3be..45da8c245 100644 --- a/test/models/concerns/user/registerable_test.rb +++ b/test/models/concerns/user/registerable_test.rb @@ -5,7 +5,9 @@ class User::RegisterableTest < ActiveSupport::TestCase person = Person.new(email: "example@gmail.com", personable_attributes: { password: "password", first_name: "John", last_name: "Doe" }, personable_type: "User") user = person.user - assert person.save + assert_difference 'Assistant.count', 4 do + assert person.save + end assert_instance_of Assistant, user.assistants.first end end diff --git a/test/models/language_model_test.rb b/test/models/language_model_test.rb new file mode 100644 index 000000000..ce1d38602 --- /dev/null +++ b/test/models/language_model_test.rb @@ -0,0 +1,39 @@ +require "test_helper" + +class LanguageModelTest < ActiveSupport::TestCase + test "is readonly?" do + assert language_models(:gpt_best).readonly? + assert language_models(:claude_3_sonnet).readonly? + end + + test "ai_backend for best models" do + assert_equal AIBackends::OpenAI, language_models(:gpt_best).ai_backend + assert_equal AIBackends::Anthropic, language_models(:claude_best).ai_backend + end + + test "ai_backend for Anthropic models" do + assert_equal AIBackends::Anthropic, language_models(:claude_3_sonnet).ai_backend + assert_equal AIBackends::Anthropic, language_models(:claude_3_opus).ai_backend + end + + test "provider_name for Anthropic models" do + assert_equal "claude-3-sonnet-20240229", language_models(:claude_3_sonnet).provider_name + assert_equal "claude-3-opus-20240229", language_models(:claude_3_opus).provider_name + end + + test "provider_name for OpenAI models" do + assert_equal "gpt-3.5-turbo", language_models(:gpt_3_5_turbo_0125).provider_name + assert_equal "gpt-4o", language_models(:gpt_4o).provider_name + end + + test "ai_backend for OpenAI models" do + assert_equal AIBackends::OpenAI, language_models(:gpt_4o).ai_backend + assert_equal AIBackends::OpenAI, language_models(:gpt_3_5_turbo).ai_backend + end + + test "has assistants" do + assert_equal 3, language_models(:gpt_4).assistants.size + assert_equal 1, language_models(:claude_3_opus).assistants.size + assert_equal 0, language_models(:claude_3_sonnet).assistants.size + end +end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 838e8d83a..ddf0e79fa 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -145,4 +145,17 @@ class UserTest < ActiveSupport::TestCase new_user.update!(preferences: { dark_mode: "system" }) assert_equal "system", new_user.preferences[:dark_mode] end + + test "has assistants" do + assert_equal 3, users(:keith).assistants.length + assert_equal 1, users(:rob).assistants.length + end + + test "has assistants including deleted" do + assert_equal 3, users(:keith).assistants_including_deleted.length + users(:keith).assistants.first.destroy + users(:keith).reload + assert_equal 3, users(:keith).assistants_including_deleted.length + assert_equal 2, users(:keith).assistants.length + end end From 6c2d44775056a0315230c4752b9d64ee86d6e00b Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 22 May 2024 10:42:09 -0700 Subject: [PATCH 49/68] Tests for what gets sent to the API client libraries --- app/services/test_clients/anthropic.rb | 10 ++++++---- app/services/test_clients/open_ai.rb | 10 +++++++--- .../jobs/get_next_ai_message_job_anthropic_test.rb | 2 +- test/jobs/get_next_ai_message_job_openai_test.rb | 2 +- test/jobs/get_next_ai_message_job_test.rb | 2 +- test/services/ai_backends/anthropic_test.rb | 4 ++-- test/services/ai_backends/open_ai_test.rb | 14 ++++++++++++-- 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/app/services/test_clients/anthropic.rb b/app/services/test_clients/anthropic.rb index ce1e3c118..6a210d261 100644 --- a/app/services/test_clients/anthropic.rb +++ b/app/services/test_clients/anthropic.rb @@ -10,6 +10,8 @@ def self.text # # Stub this method to respond with something more specific if needed. def messages(**args) + model = args.dig(:model) || "no model" + system_message = args.dig(:system) if proc = args.dig(:parameters, :stream) proc.call({ "id"=>"msg_01LtHY4sJVd7WBdPCsYb8kHQ", @@ -17,8 +19,8 @@ def messages(**args) "role"=>"assistant", "delta"=> {"type"=>"text", - "text"=> self.class.text || "Hello! It's nice to meet you. How can I assist you today?"}, - "model"=>"claude-3-opus-20240229", + "text"=> self.class.text || "Hello this is model #{model} with instruction #{system_message.to_s.inspect}! How can I assist you today?"}, + "model"=>model, "stop_reason"=>"end_turn", "stop_sequence"=>nil, "usage"=>{"input_tokens"=>10, "output_tokens"=>19} @@ -30,8 +32,8 @@ def messages(**args) "role"=>"assistant", "content"=> [{"type"=>"text", - "text"=> self.class.text || "Hello! It's nice to meet you. How can I assist you today?"}], - "model"=>"claude-3-opus-20240229", + "text"=> self.class.text || "Hello this is model #{model} with instruction #{system_message.to_s.inspect}! How can I assist you today?"}], + "model"=> model, "stop_reason"=>"end_turn", "stop_sequence"=>nil, "usage"=>{"input_tokens"=>10, "output_tokens"=>19} diff --git a/app/services/test_clients/open_ai.rb b/app/services/test_clients/open_ai.rb index 5a97fa169..f8d2de753 100644 --- a/app/services/test_clients/open_ai.rb +++ b/app/services/test_clients/open_ai.rb @@ -10,18 +10,22 @@ def self.text # # Stub this method to respond with something more specific if needed. def chat(**args) + model = args.dig(:parameters, :model) || "no model" + if system_role = (args.dig(:parameters, :messages) || []).select { |d| d[:role] == "system"}.first + system_message = system_role[:content] + end if proc = args.dig(:parameters, :stream) proc.call({ "id"=> "chatcmpl-abc123abc123abc123abc123abc12", "object"=>"chat.completion", "created"=>1707429030, - "model"=>"gpt-3.5-turbo-0613", + "model"=>model, "choices"=> [ { "index"=>0, "delta"=>{ "role"=>"assistant", - "content"=>self.class.text || "Hello! How can I assist you today?" + "content"=>self.class.text || "Hello this is model #{model} with instruction #{system_message.to_s.inspect}! How can I assist you today?" }, "logprobs"=>nil, "finish_reason"=>"stop" @@ -41,7 +45,7 @@ def chat(**args) "index"=>0, "message"=>{ "role"=>"assistant", - "content"=>self.class.text || "Hello! How can I assist you today?" + "content"=>self.class.text || "Hello this is model #{model} with instruction #{system_message.inspect}! How can I assist you today?" }, "logprobs"=>nil, "finish_reason"=>"stop" diff --git a/test/jobs/get_next_ai_message_job_anthropic_test.rb b/test/jobs/get_next_ai_message_job_anthropic_test.rb index d1dd5bd64..9eaca79f8 100644 --- a/test/jobs/get_next_ai_message_job_anthropic_test.rb +++ b/test/jobs/get_next_ai_message_job_anthropic_test.rb @@ -14,7 +14,7 @@ class GetNextAIMessageJobAnthropicTest < ActiveJob::TestCase assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) end - message_text = @test_client.messages + message_text = @test_client.messages(model: "claude-3-opus-20240229") assert_equal message_text, @conversation.latest_message_for_version(:latest).content_text end diff --git a/test/jobs/get_next_ai_message_job_openai_test.rb b/test/jobs/get_next_ai_message_job_openai_test.rb index 7e1ac093c..4c474a27c 100644 --- a/test/jobs/get_next_ai_message_job_openai_test.rb +++ b/test/jobs/get_next_ai_message_job_openai_test.rb @@ -14,7 +14,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) end - message_text = @test_client.chat + message_text = @test_client.chat(parameters: {model: "gpt-4", messages: [{role: "system", content: "You are a helpful assistant"}]}) assert_equal message_text, @conversation.latest_message_for_version(:latest).content_text end diff --git a/test/jobs/get_next_ai_message_job_test.rb b/test/jobs/get_next_ai_message_job_test.rb index c62db49ba..1eadc5402 100644 --- a/test/jobs/get_next_ai_message_job_test.rb +++ b/test/jobs/get_next_ai_message_job_test.rb @@ -34,7 +34,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase job = GetNextAIMessageJob.new job.stub(:message_cancelled?, -> { false_on_first_run += 1; false_on_first_run != 1 }) do - assert_changes "@message.content_text", from: nil, to: @test_client.chat do + assert_changes "@message.content_text", from: nil, to: @test_client.chat(parameters: {model: "gpt-4", messages: [{role: "system", content: "You are a helpful assistant"}]}) do assert_changes "@message.reload.cancelled_at", from: nil do assert job.perform(@user.id, @message.id, @conversation.assistant.id) end diff --git a/test/services/ai_backends/anthropic_test.rb b/test/services/ai_backends/anthropic_test.rb index 0e511b473..77e3b2c6b 100644 --- a/test/services/ai_backends/anthropic_test.rb +++ b/test/services/ai_backends/anthropic_test.rb @@ -17,7 +17,7 @@ class AnthropicTest < ActiveSupport::TestCase end test "get_next_chat_message works" do - assert_equal @test_client.messages, @anthropic.get_next_chat_message + assert_equal @test_client.messages(model: "gpt-4", system: "You are a helpful assistant"), @anthropic.get_next_chat_message end test "preceding_messages constructs a proper response and pivots on images" do @@ -46,4 +46,4 @@ class AnthropicTest < ActiveSupport::TestCase assert_equal preceding_messages[0][:content], messages(:can_you_hear).content_text end end -end \ No newline at end of file +end diff --git a/test/services/ai_backends/open_ai_test.rb b/test/services/ai_backends/open_ai_test.rb index 0571a5402..375a90bf8 100644 --- a/test/services/ai_backends/open_ai_test.rb +++ b/test/services/ai_backends/open_ai_test.rb @@ -13,12 +13,22 @@ class OpenAITest < ActiveSupport::TestCase @test_client = TestClients::OpenAI.new(access_token: 'abc') end + test "uses model from assistant" do + openai = OpenAI.new( + users(:keith), + assistants(:keith_claude3), + @conversation, + @conversation.latest_message_for_version(:latest) + ) + assert_equal "Hello this is model claude-3-opus-20240229 with instruction nil! How can I assist you today?", openai.get_next_chat_message + end + test "initializing client works" do assert @openai.client.present? end test "get_next_chat_message works" do - assert_equal @test_client.chat, @openai.get_next_chat_message + assert_equal "Hello this is model gpt-4 with instruction \"You are a helpful assistant\"! How can I assist you today?", @openai.get_next_chat_message end test "preceding_messages constructs a proper response and pivots on images" do @@ -47,4 +57,4 @@ class OpenAITest < ActiveSupport::TestCase assert_equal preceding_messages[0][:content], messages(:can_you_hear).content_text end end -end \ No newline at end of file +end From 19b60a6f61fb9a52272e8e4ce261e6be86865e2d Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Wed, 22 May 2024 11:27:29 -0700 Subject: [PATCH 50/68] Test for message display when assistant is deleted --- test/controllers/messages_controller_test.rb | 24 +++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index 4e6e401ff..2dc294e88 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -113,8 +113,30 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to conversation_messages_url(message.conversation, version: 2) end + test "when assistant is deleted still see all the conversations messages" do + assert @assistant.destroy + get conversation_messages_url(@conversation, version: 1) + assert @conversation.messages.count > 0 + assert_select 'div[data-role="message"]', count: @conversation.messages.count + end + + test "when assistant is not deleted deleted-blurb is hidden but the composer is not" do + get conversation_messages_url(@conversation, version: 1) + assert_select "footer div.hidden p.text-center", "Samantha has been deleted and cannot assist any longer." + assert_select "div#composer" + assert_select "div#composer.hidden", false + end + + test "when assistant is deleted deleted-blurb is shown and the composer is hidden" do + @assistant.destroy + get conversation_messages_url(@conversation, version: 1) + assert_select "footer p.text-center", "Samantha has been deleted and cannot assist any longer." + assert_select "footer div.hidden p.text-center", false + assert_select "div#composer.hidden" + end + test "even when assistant is deleted update succeeds for a new version in the URL" do - assert assistants(:samantha).destroy + @assistant.destroy message = messages(:message2_v1) patch message_url(message, version: 2), params: { message: { id: message.id } } From b80b060007540d6903a309a2e95400cffbd89aae Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Fri, 24 May 2024 16:35:41 -0500 Subject: [PATCH 51/68] Change how test stubbing works for OpenAI to detect model --- app/services/test_client/open_ai.rb | 10 +++++-- .../get_next_ai_message_job_openai_test.rb | 4 +-- test/jobs/get_next_ai_message_job_test.rb | 2 +- test/models/conversation_test.rb | 2 +- test/services/ai_backend/open_ai_test.rb | 28 ++++++++++--------- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/services/test_client/open_ai.rb b/app/services/test_client/open_ai.rb index 7ebb48e68..c057c0e67 100644 --- a/app/services/test_client/open_ai.rb +++ b/app/services/test_client/open_ai.rb @@ -10,18 +10,22 @@ def self.text raise "When using the OpenAI test client for api_text_response you need to stub the .text method" end + def self.default_text + "Hello this is model claude-3-opus-20240229 with instruction nil! How can I assist you today?" + end + def self.api_text_response { "id"=> "chatcmpl-abc123abc123abc123abc123abc12", "object"=>"chat.completion", "created"=>1707429030, - "model"=>"gpt-3.5-turbo-0613", + "model"=> @@model, "choices"=> [ { "index"=>0, "delta"=>{ "role"=>"assistant", - "content"=> text + "content"=> text || default_text }, "logprobs"=>nil, "finish_reason"=>"stop" @@ -63,6 +67,8 @@ def self.api_function_response end def chat(**args) + @@model = args.dig(:parameters, :model) || "no model" + proc = args.dig(:parameters, :stream) raise "No stream proc provided. When calling get_next_chat_message in tests be sure to include a block" if proc.nil? proc.call(self.class.api_response) diff --git a/test/jobs/get_next_ai_message_job_openai_test.rb b/test/jobs/get_next_ai_message_job_openai_test.rb index 7ea2c5b69..3cce91163 100644 --- a/test/jobs/get_next_ai_message_job_openai_test.rb +++ b/test/jobs/get_next_ai_message_job_openai_test.rb @@ -12,7 +12,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase test "populates the latest message from the assistant" do assert_no_difference "@conversation.messages.reload.length" do TestClient::OpenAI.stub :text, "Hello" do - TestClient::OpenAI.stub :api_response, TestClient::OpenAI.api_text_response do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) end end @@ -84,7 +84,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase test "when API response key is missing, a nice error message is displayed" do TestClient::OpenAI.stub :text, "" do - TestClient::OpenAI.stub :api_response, TestClient::OpenAI.api_text_response do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) assert_includes @conversation.latest_message_for_version(:latest).content_text, "a blank response" end diff --git a/test/jobs/get_next_ai_message_job_test.rb b/test/jobs/get_next_ai_message_job_test.rb index 6048edc4f..117c3306d 100644 --- a/test/jobs/get_next_ai_message_job_test.rb +++ b/test/jobs/get_next_ai_message_job_test.rb @@ -34,7 +34,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase job = GetNextAIMessageJob.new job.stub(:message_cancelled?, -> { false_on_first_run += 1; false_on_first_run != 1 }) do TestClient::OpenAI.stub :text, "Hello" do - TestClient::OpenAI.stub :api_response, TestClient::OpenAI.api_text_response do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do assert_changes "@message.content_text", from: nil, to: "Hello" do assert_changes "@message.reload.cancelled_at", from: nil do diff --git a/test/models/conversation_test.rb b/test/models/conversation_test.rb index ce3549c66..71366312c 100644 --- a/test/models/conversation_test.rb +++ b/test/models/conversation_test.rb @@ -63,7 +63,7 @@ class ConversationTest < ActiveSupport::TestCase perform_enqueued_jobs do ChatCompletionAPI.stub :get_next_response, {"topic" => "Hear me"} do TestClient::OpenAI.stub :text, "Hello" do - TestClient::OpenAI.stub :api_response, TestClient::OpenAI.api_text_response do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response } do conversation = users(:keith).conversations.create!(assistant: assistants(:samantha)) assert_nil conversation.title diff --git a/test/services/ai_backend/open_ai_test.rb b/test/services/ai_backend/open_ai_test.rb index 3867fe9ab..c97e86f74 100644 --- a/test/services/ai_backend/open_ai_test.rb +++ b/test/services/ai_backend/open_ai_test.rb @@ -3,9 +3,10 @@ class AIBackend::OpenAITest < ActiveSupport::TestCase setup do @conversation = conversations(:attachments) + @assistant = assistants(:keith_claude3) @openai = AIBackend::OpenAI.new( users(:keith), - assistants(:samantha), + @assistant, @conversation, @conversation.latest_message_for_version(:latest) ) @@ -16,6 +17,19 @@ class AIBackend::OpenAITest < ActiveSupport::TestCase assert @openai.client.present? end + test "get_next_chat_message works to stream text and uses model from assistant" do + assert_not_equal @assistant, @conversation.assistant, + "We want to force this next message to use a different assistant so these should not match" + + TestClient::OpenAI.stub :text, nil do # this forces it to fall back to default text + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do + streamed_text = "" + @openai.get_next_chat_message { |chunk| streamed_text += chunk } + assert_equal "Hello this is model claude-3-opus-20240229 with instruction nil! How can I assist you today?", streamed_text + end + end + end + test "get_tool_messages_by_calling properly executes tools" do tool_message = { role: "tool", @@ -47,18 +61,6 @@ class AIBackend::OpenAITest < ActiveSupport::TestCase assert msg[:content].starts_with?('"An unexpected error occurred. You') end - test "get_next_chat_message works to stream text" do - text = "Hello! How can I assist you today?" - - TestClient::OpenAI.stub :text, text do - TestClient::OpenAI.stub :api_response, TestClient::OpenAI.api_text_response do - streamed_text = "" - @openai.get_next_chat_message { |chunk| streamed_text += chunk } - assert_equal text, streamed_text - end - end - end - test "get_next_chat_message works to get a function call" do function = "openmeteo_get_current_and_todays_weather" From 88142ad955bbc69f21f9606db0b57f83994976e2 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Fri, 24 May 2024 16:45:29 -0500 Subject: [PATCH 52/68] Some additional merge cleanup --- app/models/language_model.rb | 4 ++-- test/models/assistant_test.rb | 4 +++- test/models/language_model_test.rb | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/models/language_model.rb b/app/models/language_model.rb index 04c92dc28..8a241c152 100644 --- a/app/models/language_model.rb +++ b/app/models/language_model.rb @@ -19,9 +19,9 @@ def provider_name def ai_backend if name.starts_with?('gpt-') - AIBackends::OpenAI + AIBackend::OpenAI else - AIBackends::Anthropic + AIBackend::Anthropic end end end diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 0212f53cf..3f53e190a 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -41,8 +41,10 @@ class AssistantTest < ActiveSupport::TestCase end test "associations are deleted when user is being destroyed" do + conversations_count = assistants(:samantha).conversations.count + User.stub_any_instance(:destroy_in_progress?, true) do - assert_difference "Conversation.count", -6 do + assert_difference "Conversation.count", -1*conversations_count do assert_difference "Document.count", -5 do assert_difference "Run.count", -19 do assert_difference "Step.count", -3 do diff --git a/test/models/language_model_test.rb b/test/models/language_model_test.rb index ce1d38602..b9e7ac80a 100644 --- a/test/models/language_model_test.rb +++ b/test/models/language_model_test.rb @@ -7,13 +7,13 @@ class LanguageModelTest < ActiveSupport::TestCase end test "ai_backend for best models" do - assert_equal AIBackends::OpenAI, language_models(:gpt_best).ai_backend - assert_equal AIBackends::Anthropic, language_models(:claude_best).ai_backend + assert_equal AIBackend::OpenAI, language_models(:gpt_best).ai_backend + assert_equal AIBackend::Anthropic, language_models(:claude_best).ai_backend end test "ai_backend for Anthropic models" do - assert_equal AIBackends::Anthropic, language_models(:claude_3_sonnet).ai_backend - assert_equal AIBackends::Anthropic, language_models(:claude_3_opus).ai_backend + assert_equal AIBackend::Anthropic, language_models(:claude_3_sonnet).ai_backend + assert_equal AIBackend::Anthropic, language_models(:claude_3_opus).ai_backend end test "provider_name for Anthropic models" do @@ -27,8 +27,8 @@ class LanguageModelTest < ActiveSupport::TestCase end test "ai_backend for OpenAI models" do - assert_equal AIBackends::OpenAI, language_models(:gpt_4o).ai_backend - assert_equal AIBackends::OpenAI, language_models(:gpt_3_5_turbo).ai_backend + assert_equal AIBackend::OpenAI, language_models(:gpt_4o).ai_backend + assert_equal AIBackend::OpenAI, language_models(:gpt_3_5_turbo).ai_backend end test "has assistants" do From f146f35eb9ae93289ef3c935fcd9214a163bf16f Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Fri, 24 May 2024 16:58:22 -0500 Subject: [PATCH 53/68] Correct an accidental hardcoding in string --- app/services/test_client/open_ai.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/test_client/open_ai.rb b/app/services/test_client/open_ai.rb index c057c0e67..3086dbe08 100644 --- a/app/services/test_client/open_ai.rb +++ b/app/services/test_client/open_ai.rb @@ -11,7 +11,7 @@ def self.text end def self.default_text - "Hello this is model claude-3-opus-20240229 with instruction nil! How can I assist you today?" + "Hello this is model #{@@model} with instruction nil! How can I assist you today?" end def self.api_text_response From c90adcd5d59c36ba698537cb55692352e8df112f Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sat, 25 May 2024 08:45:05 -0700 Subject: [PATCH 54/68] Fix typos in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d7a71a14a..6a733a2cb 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,11 @@ If you encountered an error while waiting for the services to be deployed on Ren # Deploy the app on Fly -Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free servie so Fly is actually less expensive over the long term. +Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free serve so Fly is actually less expensive over the long term. 1. Click Fork > Create New Fork at the top of this repository. Pull your forked repository down to your computer (the usual git clone ...). 1. Install the Fly command-line tool [view instructions](https://fly.io/docs/hands-on/install-flyctl/) -1. In the root directory of the repoistory you pulled down, run `fly launch --build-only` and say `Yes` to copy the existing fly.toml, but note that it will generate the wrong settings. +1. In the root directory of the repository you pulled down, run `fly launch --build-only` and say `Yes` to copy the existing fly.toml, but note that it will generate the wrong settings. 1. **The settings it shows are INCORRECT** so tell it you want to make changes 1. When it opens your browser, change the Database to `Fly Postgres` with a name such as `hostedgpt-db` and you can set the configuration to `Development`. 1. Click `Confirm Settings` at the bottom of the page and close the browser. @@ -80,7 +80,7 @@ Deploying to Fly.io is another great option. It's not quite one-click like Rende # Deploy the app on Heroku -Heroku is a one-click option that will cost $10/monnth for the compute (dyno) and database. By default, apps use Eco dynos ($5) if you are subscribed to Eco. Otherwise, it defaults to Basic dynos ($7). The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos "sleep" after 30 minutes of inactivity and take a few seconds to wake up. Basic dynos do not sleep. +Heroku is a one-click option that will cost $10/month for the compute (dyno) and database. By default, apps use Eco dynos ($5) if you are subscribed to Eco. Otherwise, it defaults to Basic dynos ($7). The Eco dynos plan is shared across all Eco dynos in your account and is recommended if you plan on deploying many small apps to Heroku. Eco dynos "sleep" after 30 minutes of inactivity and take a few seconds to wake up. Basic dynos do not sleep. Eligible students can apply for Heroku platform credits through [Heroku for GitHub Students program](https://blog.heroku.com/github-student-developer-program). @@ -93,7 +93,7 @@ Eligible students can apply for Heroku platform credits through [Heroku for GitH # Contribute as a developer -We welcome contributors! After you get your developoment environment setup, review the list of Issues. We organize the issues into Milestones and are currently working on v0.7. [View 0.7 Milestone](https://github.com/allyourbot/hostedgpt/milestone/6). Look for any issues tagged with **Good first issue** and add a comment so we know you're working on it. +We welcome contributors! After you get your development environment setup, review the list of Issues. We organize the issues into Milestones and are currently working on v0.7. [View 0.7 Milestone](https://github.com/allyourbot/hostedgpt/milestone/6). Look for any issues tagged with **Good first issue** and add a comment so we know you're working on it. ## Setting up development From f8b6c4d6660b81899c2527856ab061f04e8b7c0e Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sat, 25 May 2024 10:32:23 -0700 Subject: [PATCH 55/68] Assistants delegate supports_images? to their language model --- app/models/assistant.rb | 2 ++ app/models/concerns/user/registerable.rb | 8 ++++---- app/services/ai_backend/anthropic.rb | 2 +- app/services/ai_backend/open_ai.rb | 2 +- app/views/messages/_main_column.html.erb | 2 +- .../20240515080700_create_language_models.rb | 16 ++++++++++------ db/schema.rb | 11 +++++------ test/controllers/messages_controller_test.rb | 11 +++++++++++ test/fixtures/assistants.yml | 12 ++++++++---- test/fixtures/conversations.yml | 6 ++++++ test/fixtures/language_models.yml | 2 ++ test/fixtures/messages.yml | 13 +++++++++++++ test/models/assistant_test.rb | 6 ++++++ test/models/language_model_test.rb | 5 +++++ test/models/user_test.rb | 8 ++++---- 15 files changed, 79 insertions(+), 27 deletions(-) diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 8c9137303..1d7aad1b7 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -9,6 +9,8 @@ class Assistant < ApplicationRecord has_many :steps, dependent: :destroy has_many :messages # TODO: What should happen if an assistant is deleted? + delegate :supports_images?, to: :language_model + belongs_to :language_model validates :tools, presence: true, allow_blank: true diff --git a/app/models/concerns/user/registerable.rb b/app/models/concerns/user/registerable.rb index 770ca4d1b..7e4b1fd86 100644 --- a/app/models/concerns/user/registerable.rb +++ b/app/models/concerns/user/registerable.rb @@ -8,9 +8,9 @@ module User::Registerable private def create_initial_assistants - assistants.create! name: "GPT-4o", language_model: LanguageModel.find_by(name: 'gpt-4o'), images: true - assistants.create! name: "GPT-3.5", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo'), images: false - assistants.create! name: "Claude 3 Opus", language_model: LanguageModel.find_by(name: 'claude-3-opus-20240229'), images: true - assistants.create! name: "Claude 3 Sonnet", language_model: LanguageModel.find_by(name: 'claude-3-sonnet-20240229'), images: true + assistants.create! name: "GPT-4o", language_model: LanguageModel.find_by(name: 'gpt-4o') + assistants.create! name: "GPT-3.5", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo') + assistants.create! name: "Claude 3 Opus", language_model: LanguageModel.find_by(name: 'claude-3-opus-20240229') + assistants.create! name: "Claude 3 Sonnet", language_model: LanguageModel.find_by(name: 'claude-3-sonnet-20240229') end end diff --git a/app/services/ai_backend/anthropic.rb b/app/services/ai_backend/anthropic.rb index 444259510..873c1e8c1 100644 --- a/app/services/ai_backend/anthropic.rb +++ b/app/services/ai_backend/anthropic.rb @@ -76,7 +76,7 @@ def get_next_chat_message(&chunk_received_handler) def preceding_messages @conversation.messages.for_conversation_version(@message.version).where("messages.index < ?", @message.index).collect do |message| - if @assistant.images && message.documents.present? + if @assistant.supports_images? && message.documents.present? content = [{ type: "text", text: message.content_text }] content += message.documents.collect do |document| diff --git a/app/services/ai_backend/open_ai.rb b/app/services/ai_backend/open_ai.rb index 448aeea79..6fbdc43e6 100644 --- a/app/services/ai_backend/open_ai.rb +++ b/app/services/ai_backend/open_ai.rb @@ -124,7 +124,7 @@ def system_message def preceding_messages @conversation.messages.for_conversation_version(@message.version).where("messages.index < ?", @message.index).collect do |message| - if @assistant.images && message.documents.present? + if @assistant.supports_images? && message.documents.present? content_with_images = [{ type: "text", text: message.content_text }] content_with_images += message.documents.collect do |document| diff --git a/app/views/messages/_main_column.html.erb b/app/views/messages/_main_column.html.erb index 5984fb2ba..41be6c185 100644 --- a/app/views/messages/_main_column.html.erb +++ b/app/views/messages/_main_column.html.erb @@ -130,7 +130,7 @@
<%= button_tag type: "button", diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240515080700_create_language_models.rb index cfe660845..59f1c899c 100644 --- a/db/migrate/20240515080700_create_language_models.rb +++ b/db/migrate/20240515080700_create_language_models.rb @@ -1,10 +1,10 @@ class CreateLanguageModels < ActiveRecord::Migration[7.1] def up create_table :language_models do |t| - t.integer :position - t.string :name - t.text :description - t.boolean :supports_images + t.integer :position, null: false + t.string :name, null: false + t.text :description, null: false + t.boolean :supports_images, null: false t.timestamps end @@ -48,8 +48,8 @@ def up # Respect some users who may have added their own model values in the assistants table (Assistant.all.pluck(:model).uniq - LanguageModel.all.pluck(:name)).each do |model_name| - Rails.logger.info "Create language_models record from assistants column value: #{model_name.inspect}" - LanguageModel.create!(name: model_name, description: model_name, position: max_position += 1) + Rails.logger.info "Create language_models record from assistants column value: #{model_name.inspect}. Setting supports_images to false, update manually if it has support" + LanguageModel.create!(name: model_name, description: model_name, position: max_position += 1, supports_images: false) end add_reference :assistants, :language_model, null: true, foreign_key: { to_table: :language_models} @@ -60,10 +60,14 @@ def up ActiveRecord::Base.connection.execute "update assistants a set language_model_id = (select id from language_models lm where lm.name = a.model)" remove_column :assistants, :model + remove_column :assistants, :images end def down + add_column :assistants, :images, :boolean add_column :assistants, :model, :string + + ActiveRecord::Base.connection.execute "update assistants a set images = (select supports_images from language_models lm where lm.id=a.language_model_id)" ActiveRecord::Base.connection.execute "update assistants a set model = (select name from language_models lm where lm.id=a.language_model_id)" remove_column :assistants, :language_model_id diff --git a/db/schema.rb b/db/schema.rb index 8c331da1e..c21d49a38 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -56,9 +56,8 @@ t.jsonb "tools", default: [], null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.boolean "images", default: false, null: false - t.bigint "language_model_id" t.datetime "deleted_at", precision: nil + t.bigint "language_model_id" t.index ["language_model_id"], name: "index_assistants_on_language_model_id" t.index ["user_id", "deleted_at"], name: "index_assistants_on_user_id_and_deleted_at" t.index ["user_id"], name: "index_assistants_on_user_id" @@ -100,10 +99,10 @@ end create_table "language_models", force: :cascade do |t| - t.integer "position" - t.string "name" - t.text "description" - t.boolean "supports_images" + t.integer "position", null: false + t.string "name", null: false + t.text "description", null: false + t.boolean "supports_images", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index 2dc294e88..d7c52dba8 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -127,6 +127,17 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_select "div#composer.hidden", false end + test "when assistant supports images the image upload function is available" do + get conversation_messages_url(@conversation, version: 1) + assert_select "div#composer" + assert_select "div#composer.relationship", false + end + + test "when assistant doesnt support images the image upload function is not available" do + get conversation_messages_url(conversations(:trees), version: 1) + assert_select "div#composer.relationship" + end + test "when assistant is deleted deleted-blurb is shown and the composer is hidden" do @assistant.destroy get conversation_messages_url(@conversation, version: 1) diff --git a/test/fixtures/assistants.yml b/test/fixtures/assistants.yml index 18684af93..5f192f87a 100644 --- a/test/fixtures/assistants.yml +++ b/test/fixtures/assistants.yml @@ -5,7 +5,6 @@ samantha: description: My personal assistant instructions: You are a helpful assistant tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] - images: true keith_gpt4: user: keith @@ -14,7 +13,6 @@ keith_gpt4: description: OpenAI's GPT-4 instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] - images: true keith_claude3: user: keith @@ -23,7 +21,6 @@ keith_claude3: description: Claude 3, Opus version instructions: tools: [] - images: true rob_gpt4: user: rob @@ -32,4 +29,11 @@ rob_gpt4: description: OpenAI's GPT-4 instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] - images: true + +zen: + user: keith + language_model: gpt_3_5_turbo + name: Zen + description: OpenAI's GPT-3 but with Zen + instructions: Include a Zen perspective when possilbe + tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] diff --git a/test/fixtures/conversations.yml b/test/fixtures/conversations.yml index 65b7d2e7c..c5a81175b 100644 --- a/test/fixtures/conversations.yml +++ b/test/fixtures/conversations.yml @@ -75,3 +75,9 @@ weather: assistant: samantha title: Weather last_assistant_message: weather_explained + +trees: + user: keith + assistant: zen + title: Trees + last_assistant_message: trees_explained diff --git a/test/fixtures/language_models.yml b/test/fixtures/language_models.yml index 86b59859a..839d9f206 100644 --- a/test/fixtures/language_models.yml +++ b/test/fixtures/language_models.yml @@ -19,6 +19,7 @@ gpt_4o: gpt_3_5_turbo: position: 4 name: gpt-3.5-turbo + supports_images: false description: gpt-3.5-turbo from fixtures claude_3_sonnet: @@ -43,3 +44,4 @@ gpt_3_5_turbo_0125: position: 12 name: gpt-3.5-turbo description: gpt-3.5-turbo from fixtures + supports_images: false diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml index 97b850d58..a460e6508 100644 --- a/test/fixtures/messages.yml +++ b/test/fixtures/messages.yml @@ -658,3 +658,16 @@ weather_explained: processed_at: 2023-12-30 1:03:00 index: 3 version: 1 + +trees_explained: + assistant: zen + conversation: trees + role: assistant + tool_call_id: + content_text: The trees in Australia are + content_tool_calls: + content_document: + created_at: 2024-05-25 1:03:00 + processed_at: 2024-05-25 1:03:00 + index: 4 + version: 1 diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 3f53e190a..13cadeeeb 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -13,6 +13,11 @@ class AssistantTest < ActiveSupport::TestCase assert_instance_of Message, assistants(:samantha).messages.first end + test "has supports_images?" do + assert assistants(:samantha).supports_images? + refute assistants(:zen).supports_images? + end + test "has associated documents" do assert_instance_of Document, assistants(:samantha).documents.first end @@ -98,6 +103,7 @@ class AssistantTest < ActiveSupport::TestCase users(:rob).assistants.first.destroy end end + test "can destroy assistant of a user if they have more than one" do assert_nothing_raised do users(:keith).assistants.first.destroy diff --git a/test/models/language_model_test.rb b/test/models/language_model_test.rb index b9e7ac80a..9464c1d7d 100644 --- a/test/models/language_model_test.rb +++ b/test/models/language_model_test.rb @@ -6,6 +6,11 @@ class LanguageModelTest < ActiveSupport::TestCase assert language_models(:claude_3_sonnet).readonly? end + test "supports_images?" do + assert language_models(:gpt_best).supports_images? + refute language_models(:gpt_3_5_turbo).supports_images? + end + test "ai_backend for best models" do assert_equal AIBackend::OpenAI, language_models(:gpt_best).ai_backend assert_equal AIBackend::Anthropic, language_models(:claude_best).ai_backend diff --git a/test/models/user_test.rb b/test/models/user_test.rb index ddf0e79fa..f98c80c36 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -147,15 +147,15 @@ class UserTest < ActiveSupport::TestCase end test "has assistants" do - assert_equal 3, users(:keith).assistants.length + assert_equal 4, users(:keith).assistants.length assert_equal 1, users(:rob).assistants.length end test "has assistants including deleted" do - assert_equal 3, users(:keith).assistants_including_deleted.length + assert_equal 4, users(:keith).assistants_including_deleted.length users(:keith).assistants.first.destroy users(:keith).reload - assert_equal 3, users(:keith).assistants_including_deleted.length - assert_equal 2, users(:keith).assistants.length + assert_equal 4, users(:keith).assistants_including_deleted.length # no difference + assert_equal 3, users(:keith).assistants.length end end From e49eef9d745048fd9324cf1bd6f59d12ea72a2a8 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 26 May 2024 08:36:10 -0700 Subject: [PATCH 56/68] Ensured documents have assistant populated when available --- app/models/message.rb | 9 +++++++++ test/controllers/messages_controller_test.rb | 19 +++++++++++++++++++ test/models/message_test.rb | 12 ++++++++++++ 3 files changed, 40 insertions(+) diff --git a/app/models/message.rb b/app/models/message.rb index e1483cd23..6b400b071 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -9,6 +9,7 @@ class Message < ApplicationRecord serialize :content_tool_calls, coder: JsonSerializer + enum role: %w[user assistant tool].index_by(&:to_sym) delegate :user, to: :conversation @@ -16,6 +17,7 @@ class Message < ApplicationRecord attribute :role, default: :user before_validation :create_conversation, on: :create, if: -> { conversation.blank? }, prepend: true + before_create :copy_assistant_id_to_documents_if_empty validates :role, presence: true validates :content_text, presence: true, unless: :assistant? @@ -57,6 +59,13 @@ def create_conversation self.conversation = Conversation.create!(user: Current.user, assistant: assistant) end + def copy_assistant_id_to_documents_if_empty + return if assistant.blank? + documents.each do |document| + document.assistant_id ||= assistant.id + end + end + def validate_conversation errors.add(:conversation, 'is invalid') unless conversation.user == Current.user end diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index d7c52dba8..b2db1871c 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -1,6 +1,8 @@ require "test_helper" class MessagesControllerTest < ActionDispatch::IntegrationTest + include ActionDispatch::TestProcess::FixtureFile + setup do @message = messages(:hear_me) @conversation = @message.conversation @@ -79,6 +81,23 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to conversation_messages_url(@conversation, version: 2) end + #post assistant_messages_url(@assistant), params: { message: { content_text: @message.content_text } } + + test "should create message with image attachment" do + test_file = fixture_file_upload("cat.png", "image/png") + assert_difference "Conversation.count", 1 do + assert_difference "Document.count", 1 do + assert_difference "Message.count", 2 do + post assistant_messages_url(@assistant), params: { message: { documents_attributes: {"0": {file: test_file}}, content_text: @message.content_text } } + end + end + end + + conversation = Conversation.last + document = Document.last + assert_equal @assistant, document.assistant + end + test "should fail to create message when there is no content_text" do post assistant_messages_url(@assistant), params: { message: { content_text: nil } } assert_response :unprocessable_entity diff --git a/test/models/message_test.rb b/test/models/message_test.rb index 1699f4394..7313924ae 100644 --- a/test/models/message_test.rb +++ b/test/models/message_test.rb @@ -1,6 +1,8 @@ require "test_helper" class MessageTest < ActiveSupport::TestCase + include ActionDispatch::TestProcess::FixtureFile + test "has an associated assistant (it's through conversation)" do assert_instance_of Assistant, messages(:hear_me).assistant end @@ -17,6 +19,16 @@ class MessageTest < ActiveSupport::TestCase # assert_instance_of Document, messages(:examine_this).content_document # end + test "handles documents_attributes" do + Current.user = users(:keith) + test_file = fixture_file_upload("cat.png", "image/png") + message = Message.create(assistant: assistants(:samantha), documents_attributes: {"0": {file: test_file}}, content_text: "Nice file") + assert_equal 1, message.documents.length + document = message.documents.first + assert_equal 'cat.png', document.filename + assert_equal assistants(:samantha), document.assistant + end + test "has an associated run" do assert_instance_of Run, messages(:yes_i_do).run end From ea074261d27fc199d0139c1e829cea356af06cf9 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 26 May 2024 09:57:19 -0700 Subject: [PATCH 57/68] Make sure documents.assistant_id is consistent with messages.assistant_id, through a db migration and in test fixtures --- .../20240526164833_populate_documents_assistant_id.rb | 9 +++++++++ db/schema.rb | 2 +- test/fixtures/documents.yml | 8 ++++---- 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20240526164833_populate_documents_assistant_id.rb diff --git a/db/migrate/20240526164833_populate_documents_assistant_id.rb b/db/migrate/20240526164833_populate_documents_assistant_id.rb new file mode 100644 index 000000000..ca68c7470 --- /dev/null +++ b/db/migrate/20240526164833_populate_documents_assistant_id.rb @@ -0,0 +1,9 @@ +class PopulateDocumentsAssistantId < ActiveRecord::Migration[7.1] + def up + ActiveRecord::Base.connection.execute "update documents docs set assistant_id = (select assistant_id from messages msgs where msgs.id = docs.message_id) where docs.assistant_id is null" + end + + def down + say "No change" + end +end diff --git a/db/schema.rb b/db/schema.rb index c21d49a38..4a0769415 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_21_175839) do +ActiveRecord::Schema[7.1].define(version: 2024_05_26_164833) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/test/fixtures/documents.yml b/test/fixtures/documents.yml index 13a5d0e87..2f26980d3 100644 --- a/test/fixtures/documents.yml +++ b/test/fixtures/documents.yml @@ -8,7 +8,7 @@ background: cat_photo: user: keith - assistant: + assistant: samantha message: examine_this filename: cat.png purpose: assistants @@ -16,7 +16,7 @@ cat_photo: cute_cat_photo: user: keith - assistant: + assistant: samantha message: one_attachment filename: cute_cat_photo.png purpose: assistants @@ -32,8 +32,8 @@ single_cat_photo: single_dog_photo: user: keith - assistant: + assistant: samantha message: two_attachments filename: single_dog_photo.png purpose: assistants - bytes: 400 \ No newline at end of file + bytes: 400 From d55ad9d48ad0f9be1420ce3b20717b4d0fd44a31 Mon Sep 17 00:00:00 2001 From: Stephan Wehner Date: Sun, 26 May 2024 10:19:12 -0700 Subject: [PATCH 58/68] Remove trailing whitespace+accidental comment --- test/controllers/messages_controller_test.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index b2db1871c..9524ede59 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -81,8 +81,6 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to conversation_messages_url(@conversation, version: 2) end - #post assistant_messages_url(@assistant), params: { message: { content_text: @message.content_text } } - test "should create message with image attachment" do test_file = fixture_file_upload("cat.png", "image/png") assert_difference "Conversation.count", 1 do @@ -92,7 +90,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest end end end - + conversation = Conversation.last document = Document.last assert_equal @assistant, document.assistant From cd612b8beaeb3e7e8dc5e83318385b1e80551a19 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 12:16:13 -0500 Subject: [PATCH 59/68] Readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 364d366c5..fa952715d 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ If you encountered an error while waiting for the services to be deployed on Ren # Deploy the app on Fly -Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free serve so Fly is actually less expensive over the long term. +Deploying to Fly.io is another great option. It's not quite one-click like Render and it's not 100% free. But we've made the configuration really easy for you and the cost should be about $2 per month, and Render costs $7 per month after 90 days of free service so Fly is actually less expensive over the long term. 1. Click Fork > Create New Fork at the top of this repository. Pull your forked repository down to your computer (the usual git clone ...). 1. Install the Fly command-line tool [view instructions](https://fly.io/docs/hands-on/install-flyctl/) From e03f125ddddc2eab857b23b13f0c8e3e1297e32b Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 13:21:05 -0500 Subject: [PATCH 60/68] Complete an initial pass of tests with some cleanup --- app/controllers/messages_controller.rb | 9 +------ app/models/assistant.rb | 10 +++---- app/models/message.rb | 10 ------- .../controllers/assistants_controller_test.rb | 12 ++------- test/controllers/messages_controller_test.rb | 11 ++++---- .../settings/assistants_controller_test.rb | 13 --------- test/fixtures/assistants.yml | 16 +++++------ test/fixtures/conversations.yml | 2 +- test/fixtures/documents.yml | 8 +++--- test/fixtures/messages.yml | 7 +++-- .../get_next_ai_message_job_openai_test.rb | 4 +-- test/models/assistant_test.rb | 27 +++++++++++-------- .../models/concerns/user/registerable_test.rb | 4 +-- test/models/language_model_test.rb | 10 +++---- test/models/message_test.rb | 1 - test/models/user_test.rb | 18 +++++-------- 16 files changed, 61 insertions(+), 101 deletions(-) diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index bb558402c..20faa6912 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -56,10 +56,7 @@ def create end def update - # Clicking edit beneath a message actually submits to create and not here. This action is only used for next/prev conversation. - # In order to force a morph we PATCH to here and redirect. - @message.allow_deleted_assistant = true # disable that validation - if @message.update(version_navigate_message_params) + if @message.update(message_params) redirect_to conversation_messages_path(@message.conversation, version: @version || @message.version) else render :edit, status: :unprocessable_entity @@ -113,8 +110,4 @@ def message_params end modified_params end - - def version_navigate_message_params - params.require(:message).permit(:conversation_id, :version) - end end diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 1d7aad1b7..3658550f3 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -3,11 +3,11 @@ class Assistant < ApplicationRecord belongs_to :user - has_many :conversations, dependent: :destroy - has_many :documents, dependent: :destroy - has_many :runs, dependent: :destroy - has_many :steps, dependent: :destroy - has_many :messages # TODO: What should happen if an assistant is deleted? + has_many :conversations + has_many :documents + has_many :runs + has_many :steps + has_many :messages delegate :supports_images?, to: :language_model diff --git a/app/models/message.rb b/app/models/message.rb index 6b400b071..ecfb3dd02 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -9,7 +9,6 @@ class Message < ApplicationRecord serialize :content_tool_calls, coder: JsonSerializer - enum role: %w[user assistant tool].index_by(&:to_sym) delegate :user, to: :conversation @@ -17,7 +16,6 @@ class Message < ApplicationRecord attribute :role, default: :user before_validation :create_conversation, on: :create, if: -> { conversation.blank? }, prepend: true - before_create :copy_assistant_id_to_documents_if_empty validates :role, presence: true validates :content_text, presence: true, unless: :assistant? @@ -59,20 +57,12 @@ def create_conversation self.conversation = Conversation.create!(user: Current.user, assistant: assistant) end - def copy_assistant_id_to_documents_if_empty - return if assistant.blank? - documents.each do |document| - document.assistant_id ||= assistant.id - end - end - def validate_conversation errors.add(:conversation, 'is invalid') unless conversation.user == Current.user end def validate_assistant errors.add(:assistant, 'is invalid') unless assistant.user == Current.user - errors.add(:assistant, 'has been deleted') if assistant.deleted? && !allow_deleted_assistant end def start_assistant_reply diff --git a/test/controllers/assistants_controller_test.rb b/test/controllers/assistants_controller_test.rb index f90e1a547..806461069 100644 --- a/test/controllers/assistants_controller_test.rb +++ b/test/controllers/assistants_controller_test.rb @@ -12,6 +12,8 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to new_assistant_message_path(@assistant) end + # TODO: Delete this duplicate functionality since it's been moved to settings. + test "should get new" do get new_assistant_url assert_response :success @@ -45,14 +47,4 @@ class AssistantsControllerTest < ActionDispatch::IntegrationTest assert_equal "new name", @assistant.name end - # TODO: Messages are connected to assistants. When an assistant is deleted, - # what should happen to the messages? - # - # test "should destroy assistant" do - # assert_difference("Assistant.count", -1) do - # delete assistant_url(@assistant) - # end - - # assert_redirected_to assistants_url - # end end diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index 9524ede59..f815cd93e 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -91,9 +91,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest end end - conversation = Conversation.last - document = Document.last - assert_equal @assistant, document.assistant + (user_msg, asst_msg) = Message.last(2) + assert_equal Document.last, user_msg.documents.first end test "should fail to create message when there is no content_text" do @@ -137,7 +136,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_select 'div[data-role="message"]', count: @conversation.messages.count end - test "when assistant is not deleted deleted-blurb is hidden but the composer is not" do + test "when assistant is not deleted the deleted-blurb is hidden but the composer is visible" do get conversation_messages_url(@conversation, version: 1) assert_select "footer div.hidden p.text-center", "Samantha has been deleted and cannot assist any longer." assert_select "div#composer" @@ -155,7 +154,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_select "div#composer.relationship" end - test "when assistant is deleted deleted-blurb is shown and the composer is hidden" do + test "when assistant is deleted the deleted-blurb is shown and the composer is hidden" do @assistant.destroy get conversation_messages_url(@conversation, version: 1) assert_select "footer p.text-center", "Samantha has been deleted and cannot assist any longer." @@ -163,7 +162,7 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_select "div#composer.hidden" end - test "even when assistant is deleted update succeeds for a new version in the URL" do + test "when assistant is deleted an update succeeds for a new version in the URL" do @assistant.destroy message = messages(:message2_v1) diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index 9183d018b..3e038d3ff 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -23,19 +23,6 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest assert_equal params, Assistant.last.slice(:name, :description, :instructions, :language_model_id) end - test "should not hide assistants" do - user = @assistant.user - 5.times do |x| - user.assistants.create! name: "New assistant #{x+1}", language_model: LanguageModel.find_by(name: 'gpt-3.5-turbo') - end - get edit_settings_assistant_url(@assistant) - assert user.assistants.length > 6 - user.assistants.each do |assistant| - assert_select %{div[data-role="assistant"] a[data-role="name"] span}, assistant.name - assert_select %{div.hidden[data-role="assistant"] a[data-role="name"] span}, false - end - end - test "should get edit" do get edit_settings_assistant_url(@assistant) assert_response :success diff --git a/test/fixtures/assistants.yml b/test/fixtures/assistants.yml index 5f192f87a..c60ee050f 100644 --- a/test/fixtures/assistants.yml +++ b/test/fixtures/assistants.yml @@ -22,6 +22,14 @@ keith_claude3: instructions: tools: [] +keith_gpt3: + user: keith + language_model: gpt_3_5_turbo + name: GPT 3.5 + description: OpenAI's GPT-3 but with Zen + instructions: Include a Zen perspective when possilbe + tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] + rob_gpt4: user: rob language_model: gpt_4 @@ -29,11 +37,3 @@ rob_gpt4: description: OpenAI's GPT-4 instructions: tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] - -zen: - user: keith - language_model: gpt_3_5_turbo - name: Zen - description: OpenAI's GPT-3 but with Zen - instructions: Include a Zen perspective when possilbe - tools: [{"type": "code_interpreter"}, {"type": "retrieval"}, {"type": "function"}] diff --git a/test/fixtures/conversations.yml b/test/fixtures/conversations.yml index c5a81175b..b0b77859c 100644 --- a/test/fixtures/conversations.yml +++ b/test/fixtures/conversations.yml @@ -78,6 +78,6 @@ weather: trees: user: keith - assistant: zen + assistant: keith_gpt3 title: Trees last_assistant_message: trees_explained diff --git a/test/fixtures/documents.yml b/test/fixtures/documents.yml index 2f26980d3..13a5d0e87 100644 --- a/test/fixtures/documents.yml +++ b/test/fixtures/documents.yml @@ -8,7 +8,7 @@ background: cat_photo: user: keith - assistant: samantha + assistant: message: examine_this filename: cat.png purpose: assistants @@ -16,7 +16,7 @@ cat_photo: cute_cat_photo: user: keith - assistant: samantha + assistant: message: one_attachment filename: cute_cat_photo.png purpose: assistants @@ -32,8 +32,8 @@ single_cat_photo: single_dog_photo: user: keith - assistant: samantha + assistant: message: two_attachments filename: single_dog_photo.png purpose: assistants - bytes: 400 + bytes: 400 \ No newline at end of file diff --git a/test/fixtures/messages.yml b/test/fixtures/messages.yml index a460e6508..3dda98f7c 100644 --- a/test/fixtures/messages.yml +++ b/test/fixtures/messages.yml @@ -659,8 +659,11 @@ weather_explained: index: 3 version: 1 + +# Next conversation + trees_explained: - assistant: zen + assistant: keith_gpt3 conversation: trees role: assistant tool_call_id: @@ -669,5 +672,5 @@ trees_explained: content_document: created_at: 2024-05-25 1:03:00 processed_at: 2024-05-25 1:03:00 - index: 4 + index: 0 version: 1 diff --git a/test/jobs/get_next_ai_message_job_openai_test.rb b/test/jobs/get_next_ai_message_job_openai_test.rb index 3cce91163..9bd2d205a 100644 --- a/test/jobs/get_next_ai_message_job_openai_test.rb +++ b/test/jobs/get_next_ai_message_job_openai_test.rb @@ -12,7 +12,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase test "populates the latest message from the assistant" do assert_no_difference "@conversation.messages.reload.length" do TestClient::OpenAI.stub :text, "Hello" do - TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response } do assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) end end @@ -84,7 +84,7 @@ class GetNextAIMessageJobOpenaiTest < ActiveJob::TestCase test "when API response key is missing, a nice error message is displayed" do TestClient::OpenAI.stub :text, "" do - TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response }do + TestClient::OpenAI.stub :api_response, -> { TestClient::OpenAI.api_text_response } do assert GetNextAIMessageJob.perform_now(@user.id, @message.id, @conversation.assistant.id) assert_includes @conversation.latest_message_for_version(:latest).content_text, "a blank response" end diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 13cadeeeb..22a0cace4 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -9,13 +9,9 @@ class AssistantTest < ActiveSupport::TestCase assert_instance_of Conversation, assistants(:samantha).conversations.first end - test "has associated messages (through conversations)" do - assert_instance_of Message, assistants(:samantha).messages.first - end - test "has supports_images?" do assert assistants(:samantha).supports_images? - refute assistants(:zen).supports_images? + refute assistants(:keith_gpt3).supports_images? end test "has associated documents" do @@ -30,18 +26,27 @@ class AssistantTest < ActiveSupport::TestCase assert_instance_of Step, assistants(:samantha).steps.first end + test "has associated messages (through conversations)" do + assert_instance_of Message, assistants(:samantha).messages.first + end + + test "has associated language_model" do + assert_instance_of LanguageModel, assistants(:samantha).language_model + end + test "tools is an array of objects" do assert_instance_of Array, assistants(:samantha).tools end - test "simple create works" do + test "simple create works and tool defaults to empty array" do + a = nil assert_nothing_raised do - Assistant.create!(user: users(:keith), language_model: language_models(:gpt_4), name: 'abc') + a = Assistant.create!( + user: users(:keith), + language_model: language_models(:gpt_4), + name: 'abc' + ) end - end - - test "tools defaults to empty array on create" do - a = Assistant.create!(user: users(:keith), language_model: language_models(:gpt_4), name: 'abc') assert_equal [], a.tools end diff --git a/test/models/concerns/user/registerable_test.rb b/test/models/concerns/user/registerable_test.rb index 45da8c245..7eea6a3be 100644 --- a/test/models/concerns/user/registerable_test.rb +++ b/test/models/concerns/user/registerable_test.rb @@ -5,9 +5,7 @@ class User::RegisterableTest < ActiveSupport::TestCase person = Person.new(email: "example@gmail.com", personable_attributes: { password: "password", first_name: "John", last_name: "Doe" }, personable_type: "User") user = person.user - assert_difference 'Assistant.count', 4 do - assert person.save - end + assert person.save assert_instance_of Assistant, user.assistants.first end end diff --git a/test/models/language_model_test.rb b/test/models/language_model_test.rb index 9464c1d7d..5dd5bb519 100644 --- a/test/models/language_model_test.rb +++ b/test/models/language_model_test.rb @@ -1,6 +1,10 @@ require "test_helper" class LanguageModelTest < ActiveSupport::TestCase + test "has associated assistant" do + assert_instance_of Assistant, language_models(:gpt_4).assistants.first + end + test "is readonly?" do assert language_models(:gpt_best).readonly? assert language_models(:claude_3_sonnet).readonly? @@ -35,10 +39,4 @@ class LanguageModelTest < ActiveSupport::TestCase assert_equal AIBackend::OpenAI, language_models(:gpt_4o).ai_backend assert_equal AIBackend::OpenAI, language_models(:gpt_3_5_turbo).ai_backend end - - test "has assistants" do - assert_equal 3, language_models(:gpt_4).assistants.size - assert_equal 1, language_models(:claude_3_opus).assistants.size - assert_equal 0, language_models(:claude_3_sonnet).assistants.size - end end diff --git a/test/models/message_test.rb b/test/models/message_test.rb index 7313924ae..a1780f1c0 100644 --- a/test/models/message_test.rb +++ b/test/models/message_test.rb @@ -26,7 +26,6 @@ class MessageTest < ActiveSupport::TestCase assert_equal 1, message.documents.length document = message.documents.first assert_equal 'cat.png', document.filename - assert_equal assistants(:samantha), document.assistant end test "has an associated run" do diff --git a/test/models/user_test.rb b/test/models/user_test.rb index f7b2dd8b7..4159188ba 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -150,16 +150,12 @@ class UserTest < ActiveSupport::TestCase assert_equal "system", new_user.preferences[:dark_mode] end - test "has assistants" do - assert_equal 4, users(:keith).assistants.length - assert_equal 1, users(:rob).assistants.length - end - - test "has assistants including deleted" do - assert_equal 4, users(:keith).assistants_including_deleted.length - users(:keith).assistants.first.destroy - users(:keith).reload - assert_equal 4, users(:keith).assistants_including_deleted.length # no difference - assert_equal 3, users(:keith).assistants.length + test "assistants scope filters out deleted vs assistants_including_deleted" do + assert_difference "users(:keith).assistants.length", -1 do + assert_no_difference "users(:keith).assistants_including_deleted.length" do + users(:keith).assistants.first.destroy + users(:keith).reload + end + end end end From 5b1a89ce50cd9213d8d4ec4c715709b91fc697d3 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 17:24:26 -0500 Subject: [PATCH 61/68] Finish reviewing tests & ensuring all pass --- app/controllers/assistants_controller.rb | 2 +- .../settings/assistants_controller.rb | 7 ++- app/models/assistant.rb | 25 +++++------ test/controllers/messages_controller_test.rb | 14 +++--- .../settings/assistants_controller_test.rb | 18 +++++++- test/models/assistant_test.rb | 43 +++++++++++-------- test/models/user_test.rb | 2 +- 7 files changed, 69 insertions(+), 42 deletions(-) diff --git a/app/controllers/assistants_controller.rb b/app/controllers/assistants_controller.rb index d53861117..b882e545e 100644 --- a/app/controllers/assistants_controller.rb +++ b/app/controllers/assistants_controller.rb @@ -35,7 +35,7 @@ def update end def destroy - @assistant.destroy! + @assistant.soft_delete redirect_to assistants_url, notice: "Assistant was successfully destroyed.", status: :see_other end diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index 18c73867f..d3b254935 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -27,8 +27,11 @@ def update end def destroy - @assistant.destroy! - redirect_to new_settings_assistant_url, notice: "Deleted", status: :see_other + if @assistant.soft_delete + redirect_to new_settings_assistant_url, notice: "Deleted", status: :see_other + else + redirect_to new_settings_assistant_url, alert: "Cannot delete your last assistant", status: :see_other + end end private diff --git a/app/models/assistant.rb b/app/models/assistant.rb index 3658550f3..3eb4f33de 100644 --- a/app/models/assistant.rb +++ b/app/models/assistant.rb @@ -3,11 +3,11 @@ class Assistant < ApplicationRecord belongs_to :user - has_many :conversations - has_many :documents - has_many :runs - has_many :steps - has_many :messages + has_many :conversations, dependent: :destroy + has_many :documents, dependent: :destroy + has_many :runs, dependent: :destroy + has_many :steps, dependent: :destroy + has_many :messages, dependent: :destroy delegate :supports_images?, to: :language_model @@ -27,13 +27,14 @@ def initials parts[1]&.try(:[], 0)&.capitalize.to_s end - def destroy - if user.destroy_in_progress? - super - else - raise "Can't delete user's last assistant" if user.assistants.count <= 1 - update!(deleted_at: Time.now) - end + def soft_delete + return false if user.assistants.count <= 1 + update!(deleted_at: Time.now) + return true + end + + def soft_delete! + raise "Can't delete user's last assistant" if !soft_delete end def to_s diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index f815cd93e..c9f356497 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -129,8 +129,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to conversation_messages_url(message.conversation, version: 2) end - test "when assistant is deleted still see all the conversations messages" do - assert @assistant.destroy + test "messages can still be viewed when attached to a soft-deleted assistant" do + assert @assistant.soft_delete get conversation_messages_url(@conversation, version: 1) assert @conversation.messages.count > 0 assert_select 'div[data-role="message"]', count: @conversation.messages.count @@ -149,21 +149,21 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_select "div#composer.relationship", false end - test "when assistant doesnt support images the image upload function is not available" do + test "when assistant doesn't support images the image upload function is not available" do get conversation_messages_url(conversations(:trees), version: 1) assert_select "div#composer.relationship" end - test "when assistant is deleted the deleted-blurb is shown and the composer is hidden" do - @assistant.destroy + test "the composer is hidden when viewing a list of messages attached to an assistant that has been soft-deleted" do + @assistant.soft_delete get conversation_messages_url(@conversation, version: 1) assert_select "footer p.text-center", "Samantha has been deleted and cannot assist any longer." assert_select "footer div.hidden p.text-center", false assert_select "div#composer.hidden" end - test "when assistant is deleted an update succeeds for a new version in the URL" do - @assistant.destroy + test "viewing messages in a conversation which has history but the assistant has been soft deleted, the conversation history can still be viewed" do + @assistant.soft_delete message = messages(:message2_v1) patch message_url(message, version: 2), params: { message: { id: message.id } } diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index 3e038d3ff..4d7c341c3 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -52,11 +52,27 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest end test "destroy should soft-delete assistant" do - assert_difference("Assistant.count", 0) do + assert_difference "Assistant.count", 0 do delete settings_assistant_url(@assistant) end assert @assistant.reload.deleted? assert_redirected_to new_settings_assistant_url + assert flash[:notice].present?, "There should have been a success message" + refute flash[:alert].present?, "There should NOT have been an error message" + end + + test "destroy on the last assistant should not delete it" do + user = @assistant.user + user.assistants.where.not(id: @assistant.id).map(&:destroy) + + assert_no_difference "Assistant.count" do + delete settings_assistant_url(@assistant) + end + + refute @assistant.reload.deleted? + assert_redirected_to new_settings_assistant_url + refute flash[:notice].present?, "There should NOT have been a success message" + assert flash[:alert].present?, "There should have been an error message" end end diff --git a/test/models/assistant_test.rb b/test/models/assistant_test.rb index 22a0cace4..b018c4f3d 100644 --- a/test/models/assistant_test.rb +++ b/test/models/assistant_test.rb @@ -50,15 +50,20 @@ class AssistantTest < ActiveSupport::TestCase assert_equal [], a.tools end - test "associations are deleted when user is being destroyed" do - conversations_count = assistants(:samantha).conversations.count - - User.stub_any_instance(:destroy_in_progress?, true) do - assert_difference "Conversation.count", -1*conversations_count do - assert_difference "Document.count", -5 do - assert_difference "Run.count", -19 do - assert_difference "Step.count", -3 do - assistants(:samantha).destroy + test "associations are deleted upon destroy" do + assistant = assistants(:samantha) + conversation_count = assistant.conversations.count * -1 + message_count = assistant.conversations.map { |c| c.messages.length }.sum * -1 + document_count = (assistant.documents.count+assistant.conversations.sum { |c| c.messages.sum { |m| m.documents.count }}) * -1 + run_count = assistant.runs.count * -1 + step_count = assistant.steps.count * -1 + + assert_difference "Message.count", message_count do + assert_difference "Conversation.count", conversation_count do + assert_difference "Document.count", document_count do + assert_difference "Run.count", run_count do + assert_difference "Step.count", step_count do + assistant.destroy end end end @@ -66,12 +71,14 @@ class AssistantTest < ActiveSupport::TestCase end end - test "associations are not deleted upon destroy" do - assert_no_difference "Conversation.count" do - assert_no_difference "Document.count" do - assert_no_difference "Run.count" do - assert_no_difference "Step.count" do - assistants(:samantha).destroy + test "associations are not deleted upon soft delete" do + assert_no_difference "Message.count" do + assert_no_difference "Conversation.count" do + assert_no_difference "Document.count" do + assert_no_difference "Run.count" do + assert_no_difference "Step.count" do + assistants(:samantha).soft_delete + end end end end @@ -103,13 +110,13 @@ class AssistantTest < ActiveSupport::TestCase assert record.errors[:name].present? end - test "cannot destroy last assistant of a user" do + test "cannot soft_delete last assistant of a user" do assert_raise do - users(:rob).assistants.first.destroy + users(:rob).assistants.first.soft_delete! end end - test "can destroy assistant of a user if they have more than one" do + test "can soft_delete assistant of a user if they have more than one" do assert_nothing_raised do users(:keith).assistants.first.destroy end diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 4159188ba..d490bf0ce 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -153,7 +153,7 @@ class UserTest < ActiveSupport::TestCase test "assistants scope filters out deleted vs assistants_including_deleted" do assert_difference "users(:keith).assistants.length", -1 do assert_no_difference "users(:keith).assistants_including_deleted.length" do - users(:keith).assistants.first.destroy + users(:keith).assistants.first.soft_delete users(:keith).reload end end From 27ec521443f9389411f7869b812422e58ca3816e Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 17:28:56 -0500 Subject: [PATCH 62/68] Update migrations --- ...odels.rb => 20240523080700_create_language_models.rb} | 0 ...rb => 20240524144314_add_deleted_at_to_assistants.rb} | 0 .../20240526164833_populate_documents_assistant_id.rb | 9 --------- db/schema.rb | 2 +- 4 files changed, 1 insertion(+), 10 deletions(-) rename db/migrate/{20240515080700_create_language_models.rb => 20240523080700_create_language_models.rb} (100%) rename db/migrate/{20240516144314_add_deleted_at_to_assistants.rb => 20240524144314_add_deleted_at_to_assistants.rb} (100%) delete mode 100644 db/migrate/20240526164833_populate_documents_assistant_id.rb diff --git a/db/migrate/20240515080700_create_language_models.rb b/db/migrate/20240523080700_create_language_models.rb similarity index 100% rename from db/migrate/20240515080700_create_language_models.rb rename to db/migrate/20240523080700_create_language_models.rb diff --git a/db/migrate/20240516144314_add_deleted_at_to_assistants.rb b/db/migrate/20240524144314_add_deleted_at_to_assistants.rb similarity index 100% rename from db/migrate/20240516144314_add_deleted_at_to_assistants.rb rename to db/migrate/20240524144314_add_deleted_at_to_assistants.rb diff --git a/db/migrate/20240526164833_populate_documents_assistant_id.rb b/db/migrate/20240526164833_populate_documents_assistant_id.rb deleted file mode 100644 index ca68c7470..000000000 --- a/db/migrate/20240526164833_populate_documents_assistant_id.rb +++ /dev/null @@ -1,9 +0,0 @@ -class PopulateDocumentsAssistantId < ActiveRecord::Migration[7.1] - def up - ActiveRecord::Base.connection.execute "update documents docs set assistant_id = (select assistant_id from messages msgs where msgs.id = docs.message_id) where docs.assistant_id is null" - end - - def down - say "No change" - end -end diff --git a/db/schema.rb b/db/schema.rb index 08a788e90..8898daf97 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_26_164833) do +ActiveRecord::Schema[7.1].define(version: 2024_05_24_144314) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From 1d9f45c2f9bf6750a73f4842f4896484477c35e1 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 17:44:01 -0500 Subject: [PATCH 63/68] database yml --- config/database.yml | 75 ++++++--------------------------------------- 1 file changed, 10 insertions(+), 65 deletions(-) diff --git a/config/database.yml b/config/database.yml index d8928152a..26fd0760d 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,17 +1,3 @@ -# PostgreSQL. Versions 9.3 and up are supported. -# -# Install the pg driver: -# gem install pg -# On macOS with Homebrew: -# gem install pg -- --with-pg-config=/usr/local/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# -# Configure Using Gemfile -# gem "pg" -# default: &default adapter: postgresql encoding: unicode @@ -23,64 +9,23 @@ default: &default development: <<: *default - database: <%= ENV['HOSTEDGPT_DEV_DB'] || "hostedgpt_development" %> + database: hostedgpt_development + # To customize set: + # DATABASE_URL="postgres://username:password@localhost:5432/custom_database_name" + # Or since local often has no credentials and default port it can be (note the three slashes) + # DATABASE_URL="postgres:///custom_database_name" - # The specified database role being used to connect to PostgreSQL. - # To create additional roles in PostgreSQL see `$ createuser --help`. - # When left blank, PostgreSQL will use the default role. This is - # the same name as the operating system user running Rails. - #username: hostedgpt + # Locally you probably want to put that in your bash profile and then when running tests + # you could have a bash alias which executes: + # DATABASE_URL="postgres:///custom_database_name" bin/rails test - # The password associated with the PostgreSQL role (username). - #password: - # Connect on a TCP socket. Omitted by default since the client uses a - # domain socket that doesn't need configuration. Windows does not have - # domain sockets, so uncomment these lines. - #host: localhost - - # The TCP port the server listens on. Defaults to 5432. - # If your server runs on a different port number, change accordingly. - #port: 5432 - - # Schema search path. The server defaults to $user,public - #schema_search_path: myapp,sharedapp,public - - # Minimum log levels, in increasing order: - # debug5, debug4, debug3, debug2, debug1, - # log, notice, warning, error, fatal, and panic - # Defaults to warning. - #min_messages: notice - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. test: <<: *default - database: <%= ENV['HOSTEDGPT_TEST_DB'] || "hostedgpt_test" %> + database: hostedgpt_test -# As with config/credentials.yml, you never want to store sensitive information, -# like your database password, in your source code. If your source code is -# ever seen by anyone, they now have access to your database. -# -# Instead, provide the password or a full connection URL as an environment -# variable when you boot the app. For example: -# -# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" -# -# If the connection URL is provided in the special DATABASE_URL environment -# variable, Rails will automatically merge its configuration values on top of -# the values provided in this file. Alternatively, you can specify a connection -# URL environment variable explicitly: -# -# production: -# url: <%= ENV["MY_APP_DATABASE_URL"] %> -# -# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database -# for a full overview on how database connection configuration can be specified. -# production: <<: *default database: hostedgpt_production username: hostedgpt - password: <%= ENV["HOSTEDGPT_DATABASE_PASSWORD"] %> + password: <%= ENV["HOSTEDGPT_DATABASE_PASSWORD"] %>= \ No newline at end of file From 3f1595eb073c5795876839ae2c20086c7a771afa Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 18:27:35 -0500 Subject: [PATCH 64/68] Revise some settings view rendering logic --- app/controllers/messages_controller.rb | 2 -- .../settings/assistants_controller.rb | 5 +++ app/helpers/application_helper.rb | 11 ------- app/views/assistants/_assistant.html.erb | 5 +-- app/views/messages/_nav_column.html.erb | 33 +++++++++---------- app/views/settings/assistants/_form.html.erb | 28 ++++++++++++---- app/views/settings/assistants/edit.html.erb | 29 ++++++++-------- .../settings/assistants_controller_test.rb | 22 +++++++++++-- test/support/assert_view_helpers.rb | 10 ++++++ test/test_helper.rb | 1 + 10 files changed, 90 insertions(+), 56 deletions(-) create mode 100644 test/support/assert_view_helpers.rb diff --git a/app/controllers/messages_controller.rb b/app/controllers/messages_controller.rb index 20faa6912..bf0279860 100644 --- a/app/controllers/messages_controller.rb +++ b/app/controllers/messages_controller.rb @@ -88,8 +88,6 @@ def set_nav_conversations def set_nav_assistants @nav_assistants = Current.user.assistants.ordered - @hide_settings_assistants_overflow = @nav_assistants.length > Assistant::MAX_LIST_DISPLAY && - @nav_assistants.index(@assistant) <= Assistant::MAX_LIST_DISPLAY-1 end def message_params diff --git a/app/controllers/settings/assistants_controller.rb b/app/controllers/settings/assistants_controller.rb index d3b254935..4c53a4f5f 100644 --- a/app/controllers/settings/assistants_controller.rb +++ b/app/controllers/settings/assistants_controller.rb @@ -1,5 +1,6 @@ class Settings::AssistantsController < Settings::ApplicationController before_action :set_assistant, only: [:edit, :update, :destroy] + before_action :set_last_assistant, except: [:destroy] def new @assistant = Assistant.new @@ -43,6 +44,10 @@ def set_assistant end end + def set_last_assistant + @last_assistant = Current.user.assistants.count <= 1 + end + def assistant_params params.require(:assistant).permit(:name, :description, :instructions, :language_model_id) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ff4e5e7a2..cc2147fa4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,16 +1,5 @@ module ApplicationHelper - def hide_assistant_class(item, item_counter) - return nil unless item.is_a?(Assistant) - return 'hidden' if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY - nil - end - - def assistant_data_transaction_target(item, item_counter) - return nil unless item.is_a?(Assistant) - return 'data-transition-target="transitionable"'.html_safe if @hide_settings_assistants_overflow && item_counter >= Assistant::MAX_LIST_DISPLAY - end - def spinner(opts = {}) html = <<~HTML diff --git a/app/views/assistants/_assistant.html.erb b/app/views/assistants/_assistant.html.erb index 76c840411..1aa616cdd 100644 --- a/app/views/assistants/_assistant.html.erb +++ b/app/views/assistants/_assistant.html.erb @@ -1,6 +1,7 @@ <%# locals: (assistant:, settings: true, assistant_counter: -1) %> <% selected = assistant == @assistant %> <% first = assistant_counter.zero? %> +<% visible = assistant_counter <= Assistant::MAX_LIST_DISPLAY-1 || selected %>
<%# This extra div ^ is needed because of the absolute positioning. It doesn't lay out properly if added to the div below. %> @@ -13,13 +14,13 @@ group cursor-pointer text-sm rounded-lg <%= selected && 'relationship' %> - <%= hide_assistant_class(assistant, assistant_counter) %> + <%= !visible && 'hidden' %> " data-role="assistant" data-radio-behavior-target="radio" data-action="radio-changed@window->radio-behavior#select" data-radio-behavior-id-param="<%= assistant.id %>" - <%= assistant_data_transaction_target(assistant, assistant_counter) %> + data-transition-target="<%= !visible && 'transitionable' %>" > <%= link_to new_assistant_message_path(assistant), class: "flex-1 flex items-center text-gray-950 dark:text-gray-100 font-medium truncate", data: { role: "name" } do %> <%= render partial: "layouts/assistant_avatar", locals: { assistant: assistant, size: 7, classes: "mr-2" } %> diff --git a/app/views/messages/_nav_column.html.erb b/app/views/messages/_nav_column.html.erb index db800e047..8b359a07e 100644 --- a/app/views/messages/_nav_column.html.erb +++ b/app/views/messages/_nav_column.html.erb @@ -1,28 +1,27 @@
<%= render @nav_assistants %> - <% if @hide_settings_assistants_overflow %> - <%= button_tag type: "button", - class: %| - flex - text-sm - rounded-lg - mb-1 p-1 pl-2 pr-2 mr-5 - hover:bg-gray-100 dark:hover:bg-gray-700 - pointer - |, - data: { - transition_target: "transitionable", - action: "transition#toggleClass", - } do %> - <%= icon "chevron-down", variant: :mini, class: 'text-gray-500 ml-[2px] mr-[14px]' %> Show All - <% end %> + <%= button_tag type: "button", + class: %| + flex + text-sm + rounded-lg + mb-1 p-1 pl-2 pr-2 mr-5 + hover:bg-gray-100 dark:hover:bg-gray-700 + pointer + #{@nav_assistants.length <= Assistant::MAX_LIST_DISPLAY && 'hidden'} + |, + data: { + transition_target: "transitionable", + action: "transition#toggleClass", + } do %> + <%= icon "chevron-down", variant: :mini, class: 'text-gray-500 ml-[2px] mr-[14px]' %> Show All <% end %>
diff --git a/app/views/settings/assistants/_form.html.erb b/app/views/settings/assistants/_form.html.erb index 4318a2c26..1f5c977fe 100644 --- a/app/views/settings/assistants/_form.html.erb +++ b/app/views/settings/assistants/_form.html.erb @@ -11,6 +11,19 @@
<% end %> +
+ <%= form.label :name %> + <%= form.text_field :name, + autofocus: assistant.new_record?, + class: %| + block w-full + border border-gray-200 outline-none + shadow rounded-md + px-3 py-2 mt-2 + dark:text-black + | %> +
+
<%= form.label :language_model_id %>
<%= form.select :language_model_id, @@ -27,11 +40,6 @@ %>
-
- <%= form.label :name %> - <%= form.text_field :name, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> -
-
<%= form.label :description %> <%= form.text_field :description, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black" %> @@ -40,7 +48,15 @@
<%= form.label :instructions, "Custom Instructions" %> <%= form.text_area :instructions, - class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full dark:text-black h-18", + autofocus: assistant.persisted?, + class: %| + block w-full + border border-gray-200 outline-none + shadow rounded-md + px-3 py-2 mt-2 + dark:text-black + h-18 + |, data: { controller: "textarea-autogrow", action: %| diff --git a/app/views/settings/assistants/edit.html.erb b/app/views/settings/assistants/edit.html.erb index f90aba6e8..ba76627b8 100644 --- a/app/views/settings/assistants/edit.html.erb +++ b/app/views/settings/assistants/edit.html.erb @@ -3,21 +3,20 @@ <%= render "form", assistant: @assistant %> - <% unless Current.user.assistants.count <= 1 %> - <%= button_to "Delete", - settings_assistant_path(@assistant), - method: :delete, - data: { turbo_confirm: "Are you sure?" }, - form: { class: "inline-block" }, - class: %| - ml-5 py-3 px-5 - bg-white dark:bg-gray-500 - border border-gray-300 dark:border-gray-500 - rounded-lg - font-medium - | - %> - <% end %> + <%= button_to "Delete", + settings_assistant_path(@assistant), + method: :delete, + data: { turbo_confirm: "Are you sure?" }, + form: { class: "inline-block" }, + class: %| + ml-5 py-3 px-5 + bg-white dark:bg-gray-500 + border border-gray-300 dark:border-gray-500 + rounded-lg + font-medium + #{@last_assistant && 'hidden'} + | + %> <%= link_to "Cancel", root_path, class: "float-right inline-block ml-5 py-3" %>
diff --git a/test/controllers/settings/assistants_controller_test.rb b/test/controllers/settings/assistants_controller_test.rb index 4d7c341c3..62b415399 100644 --- a/test/controllers/settings/assistants_controller_test.rb +++ b/test/controllers/settings/assistants_controller_test.rb @@ -3,7 +3,8 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest setup do @assistant = assistants(:samantha) - login_as @assistant.user + @user = @assistant.user + login_as @user end test "should get new" do @@ -26,8 +27,23 @@ class Settings::AssistantsControllerTest < ActionDispatch::IntegrationTest test "should get edit" do get edit_settings_assistant_url(@assistant) assert_response :success - assert_select 'section#menu a[href="/settings/person/edit"] span', "Your Account" - assert_select 'section#menu a[href="/settings/assistants/new"] span', "New Assistant" + assert_contains_text "div#nav-container", "Your Account" + assert_contains_text "div#nav-container", "New Assistant" + end + + test "form should display a DELETE button if this is not your last assistant" do + assert @user.assistants.length > 1, "User needs to have more than one assistant" + get edit_settings_assistant_url(@assistant) + assert_response :success + assert_contains_text "main", "Delete" + end + + test "form should NOT display a DELETE button if this is your last assistant" do + @user.assistants.where.not(id: @assistant.id).map(&:destroy) + assert @user.assistants.length == 1, "User needs to have more than one assistant" + get edit_settings_assistant_url(@user.assistants.first) + assert_response :success + assert_contains_text "main", "Delete" end test "should allow language_model selection" do diff --git a/test/support/assert_view_helpers.rb b/test/support/assert_view_helpers.rb new file mode 100644 index 000000000..f9ba24570 --- /dev/null +++ b/test/support/assert_view_helpers.rb @@ -0,0 +1,10 @@ +module ViewHelpers + + private + + def assert_contains_text(selector, text) + assert_select selector, 1, "#{selector} was not found" do |element| + assert element.text.include?(text), "Element #{selector} did not contain '#{text}' (#{element.text.remove("\n")})" + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index d5366843b..965052ae8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -48,6 +48,7 @@ class TestCase include ActiveJob::TestHelper include FeatureHelpers include PostgresqlHelper + include ViewHelpers parallelize(workers: :number_of_processors) fixtures :all From efaaf3b44eea58fad127e99c49a91ff1a4d62924 Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 18:29:45 -0500 Subject: [PATCH 65/68] Remove attr --- app/models/message.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/models/message.rb b/app/models/message.rb index ecfb3dd02..d1309fdc4 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -29,8 +29,6 @@ class Message < ApplicationRecord scope :ordered, -> { latest_version_for_conversation } - attr_accessor :allow_deleted_assistant - def name case role when "user" then user.first_name[/\A[a-zA-Z0-9_-]+/] From c23297d629c57b60824340e872362ad6f4a695bc Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 19:06:26 -0500 Subject: [PATCH 66/68] Remove some unused code --- app/helpers/application_helper.rb | 1 - app/views/messages/new.html.erb | 7 ------- 2 files changed, 8 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index cc2147fa4..4d90946e9 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,4 @@ module ApplicationHelper - def spinner(opts = {}) html = <<~HTML diff --git a/app/views/messages/new.html.erb b/app/views/messages/new.html.erb index baf2d0821..69b6dc37e 100644 --- a/app/views/messages/new.html.erb +++ b/app/views/messages/new.html.erb @@ -4,13 +4,6 @@ <%= content_for :messages do %> -<% if @message&.errors&.any? %> -

Errors communicating with assistant:

- <% @message.errors.full_messages.each do |msg| %> - <%= msg %> - <% end %> -<% end %> -
From 5f7a0dbd576ab1ba27ae572dae8a37d8ec2e802b Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Mon, 27 May 2024 19:15:20 -0500 Subject: [PATCH 67/68] Final tweaks --- app/views/messages/_main_column.html.erb | 4 ++-- test/controllers/messages_controller_test.rb | 9 ++++++--- test/support/{assert_view_helpers.rb => view_helpers.rb} | 0 3 files changed, 8 insertions(+), 5 deletions(-) rename test/support/{assert_view_helpers.rb => view_helpers.rb} (100%) diff --git a/app/views/messages/_main_column.html.erb b/app/views/messages/_main_column.html.erb index 41be6c185..0ed3d0025 100644 --- a/app/views/messages/_main_column.html.erb +++ b/app/views/messages/_main_column.html.erb @@ -411,7 +411,7 @@ data-controller="modal" data-action="keydown@document->modal#keydownQuestionOpen" > -
+
+
diff --git a/test/controllers/messages_controller_test.rb b/test/controllers/messages_controller_test.rb index c9f356497..6047d1900 100644 --- a/test/controllers/messages_controller_test.rb +++ b/test/controllers/messages_controller_test.rb @@ -138,7 +138,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest test "when assistant is not deleted the deleted-blurb is hidden but the composer is visible" do get conversation_messages_url(@conversation, version: 1) - assert_select "footer div.hidden p.text-center", "Samantha has been deleted and cannot assist any longer." + assert_response :success + assert_contains_text "main footer", "Samantha has been deleted and cannot assist any longer." assert_select "div#composer" assert_select "div#composer.hidden", false end @@ -157,7 +158,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest test "the composer is hidden when viewing a list of messages attached to an assistant that has been soft-deleted" do @assistant.soft_delete get conversation_messages_url(@conversation, version: 1) - assert_select "footer p.text-center", "Samantha has been deleted and cannot assist any longer." + assert_response :success + assert_contains_text "main footer", "Samantha has been deleted and cannot assist any longer." assert_select "footer div.hidden p.text-center", false assert_select "div#composer.hidden" end @@ -170,7 +172,8 @@ class MessagesControllerTest < ActionDispatch::IntegrationTest assert_redirected_to conversation_messages_url(message.conversation, version: 2) get conversation_messages_url(message.conversation, version: 2) - assert_match /Where were you born/, response.body + assert_response :success + assert_contains_text "main", "Where were you born" end test "when there are many assistants only a few are shown in the nav bar" do diff --git a/test/support/assert_view_helpers.rb b/test/support/view_helpers.rb similarity index 100% rename from test/support/assert_view_helpers.rb rename to test/support/view_helpers.rb From 7c95ee37ccba1fabd9748eced9b7c76ca2f806ce Mon Sep 17 00:00:00 2001 From: Keith Schacht Date: Tue, 28 May 2024 09:09:47 -0500 Subject: [PATCH 68/68] restore database.yml --- config/database.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/config/database.yml b/config/database.yml index 26fd0760d..4abe0cd3f 100644 --- a/config/database.yml +++ b/config/database.yml @@ -9,20 +9,11 @@ default: &default development: <<: *default - database: hostedgpt_development - # To customize set: - # DATABASE_URL="postgres://username:password@localhost:5432/custom_database_name" - # Or since local often has no credentials and default port it can be (note the three slashes) - # DATABASE_URL="postgres:///custom_database_name" - - # Locally you probably want to put that in your bash profile and then when running tests - # you could have a bash alias which executes: - # DATABASE_URL="postgres:///custom_database_name" bin/rails test - + database: <%= ENV['HOSTEDGPT_DEV_DB'] || "hostedgpt_development" %> test: <<: *default - database: hostedgpt_test + database: <%= ENV['HOSTEDGPT_TEST_DB'] || "hostedgpt_test" %> production: <<: *default