split Smart Search from general AI functionality

soon SmartSearch will make use of our forthcoming LLM gem

test plan:
 - smoke test search feature

flag=smart_search
closes ADV-55

Change-Id: I9d837528c28c5acfdb0ded817cf0cc2259dd44b8
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/342407
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Jonathan Featherstone <jfeatherstone@instructure.com>
QA-Review: Jeremy Stanley <jeremy@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
Jeremy Stanley 2024-03-07 16:35:10 -07:00
parent ebb42c1408
commit 9a89ade898
7 changed files with 18 additions and 18 deletions

View File

@ -78,16 +78,16 @@ class SmartSearchController < ApplicationController
# @returns [SearchResult]
def search
return render_unauthorized_action unless @context.grants_right?(@current_user, session, :read)
return render_unauthorized_action unless OpenAi.smart_search_available?(@context)
return render_unauthorized_action unless SmartSearch.smart_search_available?(@context)
return render json: { error: "missing 'q' param" }, status: :bad_request unless params.key?(:q)
OpenAi.with_pgvector do
WikiPageEmbedding.with_pgvector do
response = {
results: []
}
if params[:q].present?
embedding = OpenAi.generate_embedding(params[:q])
embedding = SmartSearch.generate_embedding(params[:q])
# Prototype query using "neighbor". Embedding is now on join table so manual SQL for now
# wiki_pages = WikiPage.nearest_neighbors(:embedding, embedding, distance: "inner_product")
@ -110,7 +110,7 @@ class SmartSearchController < ApplicationController
def show
@context = Course.find(params[:course_id])
render_unauthorized_action unless OpenAi.smart_search_available?(@context)
render_unauthorized_action unless SmartSearch.smart_search_available?(@context)
set_active_tab("search")
@show_left_side = true
add_crumb(t("#crumbs.search", "Search"), named_context_url(@context, :course_search_url)) unless @skip_crumb

View File

@ -3368,7 +3368,7 @@ class Course < ActiveRecord::Base
Course.default_tabs
end
if OpenAi.smart_search_available?(self)
if SmartSearch.smart_search_available?(self)
default_tabs.insert(1,
{
id: TAB_SEARCH,

View File

@ -108,7 +108,7 @@ class WikiPage < ActiveRecord::Base
def should_generate_embeddings?
return false if deleted?
return false unless OpenAi.smart_search_available?(context)
return false unless SmartSearch.smart_search_available?(context)
saved_change_to_body? ||
saved_change_to_title? ||
@ -143,7 +143,7 @@ class WikiPage < ActiveRecord::Base
def generate_embeddings
delete_embeddings
chunk_content do |chunk|
embedding = OpenAi.generate_embedding(chunk)
embedding = SmartSearch.generate_embedding(chunk)
wiki_page_embeddings.create!(embedding:)
end
end

View File

@ -786,6 +786,11 @@ class ActiveRecord::Base
# Just return something that isn't an ar connection object so consoles don't explode
override
end
def self.with_pgvector(&)
vector_schema = connection.extension("vector").schema
connection.add_schema_to_search_path(vector_schema, &)
end
end
module UsefulFindInBatches

View File

@ -111,9 +111,9 @@ module FeatureFlags
def self.smart_search_after_state_change_hook(_user, context, old_state, new_state)
if %w[off allowed].include?(old_state) && %w[on allowed_on].include?(new_state)
if context.is_a?(Account) && !context.site_admin?
OpenAi.delay(priority: Delayed::LOW_PRIORITY).index_account(context)
SmartSearch.delay(priority: Delayed::LOW_PRIORITY).index_account(context)
elsif context.is_a?(Course)
OpenAi.delay(priority: Delayed::LOW_PRIORITY).index_course(context)
SmartSearch.delay(priority: Delayed::LOW_PRIORITY).index_course(context)
end
end
end

View File

@ -16,7 +16,7 @@
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
module OpenAi
module SmartSearch
class << self
def api_key
Rails.application.credentials.dig(:smart_search, :openai_api_token)
@ -62,11 +62,6 @@ module OpenAi
JSON.parse(response.body)["choices"][0]["text"].strip
end
def with_pgvector(&)
vector_schema = ActiveRecord::Base.connection.extension("vector").schema
ActiveRecord::Base.connection.add_schema_to_search_path(vector_schema, &)
end
def index_account(root_account)
# by default, index all courses updated in the last year
date_cutoff = Setting.get("smart_search_index_days_ago", "365").to_i.days.ago

View File

@ -1103,8 +1103,8 @@ describe WikiPage do
before do
skip "not available" unless ActiveRecord::Base.connection.table_exists?("wiki_page_embeddings")
allow(OpenAi).to receive(:generate_embedding).and_return([1] * 1536)
expect(OpenAi).to receive(:api_key).at_least(:once).and_return("fake_api_key")
allow(SmartSearch).to receive(:generate_embedding).and_return([1] * 1536)
expect(SmartSearch).to receive(:api_key).at_least(:once).and_return("fake_api_key")
end
before :once do
@ -1128,7 +1128,7 @@ describe WikiPage do
it "strips HTML from the body before indexing" do
wiki_page_model(title: "test", body: "<ul><li>foo</li></ul>")
expect(OpenAi).to receive(:generate_embedding).with("test\n* foo")
expect(SmartSearch).to receive(:generate_embedding).with("test\n* foo")
run_jobs
end