mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-07-01 12:02:16 +00:00
is update adds logging for changes made in the AI admin panel. When making configuration changes to Embeddings, LLMs, Personas, Tools, or Spam that aren't site setting related, changes will now be logged in Admin > Logs & Screening. This will help admins debug issues related to AI. In this update a helper lib is created called `AiStaffActionLogger` which can be easily used in the future to add logging support for any other admin config we need logged for AI.
201 lines
5.9 KiB
Ruby
201 lines
5.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module Admin
|
|
class AiEmbeddingsController < ::Admin::AdminController
|
|
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
|
|
def index
|
|
embedding_defs = EmbeddingDefinition.all.order(:display_name)
|
|
|
|
render json: {
|
|
ai_embeddings:
|
|
ActiveModel::ArraySerializer.new(
|
|
embedding_defs,
|
|
each_serializer: AiEmbeddingDefinitionSerializer,
|
|
root: false,
|
|
).as_json,
|
|
meta: {
|
|
provider_params: EmbeddingDefinition.provider_params,
|
|
providers: EmbeddingDefinition.provider_names,
|
|
distance_functions: EmbeddingDefinition.distance_functions,
|
|
tokenizers:
|
|
EmbeddingDefinition.tokenizer_names.map { |tn|
|
|
{ id: tn, name: tn.split("::").last }
|
|
},
|
|
presets: EmbeddingDefinition.presets,
|
|
},
|
|
}
|
|
end
|
|
|
|
def new
|
|
end
|
|
|
|
def edit
|
|
embedding_def = EmbeddingDefinition.find(params[:id])
|
|
render json: AiEmbeddingDefinitionSerializer.new(embedding_def)
|
|
end
|
|
|
|
def create
|
|
embedding_def = EmbeddingDefinition.new(ai_embeddings_params)
|
|
|
|
if embedding_def.save
|
|
log_ai_embedding_creation(embedding_def)
|
|
render json: AiEmbeddingDefinitionSerializer.new(embedding_def), status: :created
|
|
else
|
|
render_json_error embedding_def
|
|
end
|
|
end
|
|
|
|
def update
|
|
embedding_def = EmbeddingDefinition.find(params[:id])
|
|
|
|
if embedding_def.seeded?
|
|
return(
|
|
render_json_error(I18n.t("discourse_ai.embeddings.cannot_edit_builtin"), status: 403)
|
|
)
|
|
end
|
|
|
|
initial_attributes = embedding_def.attributes.dup
|
|
|
|
if embedding_def.update(ai_embeddings_params.except(:dimensions))
|
|
log_ai_embedding_update(embedding_def, initial_attributes)
|
|
render json: AiEmbeddingDefinitionSerializer.new(embedding_def)
|
|
else
|
|
render_json_error embedding_def
|
|
end
|
|
end
|
|
|
|
def destroy
|
|
embedding_def = EmbeddingDefinition.find(params[:id])
|
|
|
|
if embedding_def.seeded?
|
|
return(
|
|
render_json_error(I18n.t("discourse_ai.embeddings.cannot_edit_builtin"), status: 403)
|
|
)
|
|
end
|
|
|
|
if embedding_def.id == SiteSetting.ai_embeddings_selected_model.to_i
|
|
return render_json_error(I18n.t("discourse_ai.embeddings.delete_failed"), status: 409)
|
|
end
|
|
|
|
embedding_details = {
|
|
embedding_id: embedding_def.id,
|
|
display_name: embedding_def.display_name,
|
|
provider: embedding_def.provider,
|
|
dimensions: embedding_def.dimensions,
|
|
subject: embedding_def.display_name,
|
|
}
|
|
|
|
if embedding_def.destroy
|
|
log_ai_embedding_deletion(embedding_details)
|
|
head :no_content
|
|
else
|
|
render_json_error embedding_def
|
|
end
|
|
end
|
|
|
|
def test
|
|
RateLimiter.new(
|
|
current_user,
|
|
"ai_embeddings_test_#{current_user.id}",
|
|
3,
|
|
1.minute,
|
|
).performed!
|
|
|
|
embedding_def = EmbeddingDefinition.new(ai_embeddings_params)
|
|
DiscourseAi::Embeddings::Vector.new(embedding_def).vector_from("this is a test")
|
|
|
|
render json: { success: true }
|
|
rescue Net::HTTPBadResponse => e
|
|
render json: { success: false, error: e.message }
|
|
end
|
|
|
|
private
|
|
|
|
def ai_embeddings_params
|
|
permitted =
|
|
params.require(:ai_embedding).permit(
|
|
:display_name,
|
|
:dimensions,
|
|
:max_sequence_length,
|
|
:pg_function,
|
|
:provider,
|
|
:url,
|
|
:api_key,
|
|
:tokenizer_class,
|
|
:embed_prompt,
|
|
:search_prompt,
|
|
:matryoshka_dimensions,
|
|
)
|
|
|
|
extra_field_names = EmbeddingDefinition.provider_params.dig(permitted[:provider]&.to_sym)
|
|
if extra_field_names.present?
|
|
received_prov_params =
|
|
params.dig(:ai_embedding, :provider_params)&.slice(*extra_field_names.keys)
|
|
|
|
if received_prov_params.present?
|
|
permitted[:provider_params] = received_prov_params.permit!
|
|
end
|
|
end
|
|
|
|
permitted
|
|
end
|
|
|
|
def ai_embeddings_logger_fields
|
|
{
|
|
display_name: {
|
|
},
|
|
provider: {
|
|
},
|
|
dimensions: {
|
|
},
|
|
url: {
|
|
},
|
|
tokenizer_class: {
|
|
},
|
|
max_sequence_length: {
|
|
},
|
|
embed_prompt: {
|
|
type: :large_text,
|
|
},
|
|
search_prompt: {
|
|
type: :large_text,
|
|
},
|
|
matryoshka_dimensions: {
|
|
},
|
|
api_key: {
|
|
type: :sensitive,
|
|
},
|
|
# JSON fields should be tracked as simple changes
|
|
json_fields: [:provider_params],
|
|
}
|
|
end
|
|
|
|
def log_ai_embedding_creation(embedding_def)
|
|
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
|
|
entity_details = { embedding_id: embedding_def.id, subject: embedding_def.display_name }
|
|
logger.log_creation("embedding", embedding_def, ai_embeddings_logger_fields, entity_details)
|
|
end
|
|
|
|
def log_ai_embedding_update(embedding_def, initial_attributes)
|
|
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
|
|
entity_details = { embedding_id: embedding_def.id, subject: embedding_def.display_name }
|
|
|
|
logger.log_update(
|
|
"embedding",
|
|
embedding_def,
|
|
initial_attributes,
|
|
ai_embeddings_logger_fields,
|
|
entity_details,
|
|
)
|
|
end
|
|
|
|
def log_ai_embedding_deletion(embedding_details)
|
|
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
|
|
logger.log_deletion("embedding", embedding_details)
|
|
end
|
|
end
|
|
end
|
|
end
|