Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/entities/ml/mlflow/experiment.rb20
-rw-r--r--lib/api/entities/ml/mlflow/get_experiment.rb13
-rw-r--r--lib/api/entities/ml/mlflow/list_experiment.rb13
-rw-r--r--lib/api/entities/ml/mlflow/update_run.rb2
-rw-r--r--lib/api/ml/mlflow.rb291
5 files changed, 171 insertions, 168 deletions
diff --git a/lib/api/entities/ml/mlflow/experiment.rb b/lib/api/entities/ml/mlflow/experiment.rb
index cfe366feaab..54e0fe63985 100644
--- a/lib/api/entities/ml/mlflow/experiment.rb
+++ b/lib/api/entities/ml/mlflow/experiment.rb
@@ -5,22 +5,10 @@ module API
module Ml
module Mlflow
class Experiment < Grape::Entity
- expose :experiment do
- expose :experiment_id
- expose :name
- expose :lifecycle_stage
- expose :artifact_location
- end
-
- private
-
- def lifecycle_stage
- object.deleted_on? ? 'deleted' : 'active'
- end
-
- def experiment_id
- object.iid.to_s
- end
+ expose(:experiment_id) { |experiment| experiment.iid.to_s }
+ expose :name
+ expose(:lifecycle_stage) { |experiment| experiment.deleted_on? ? 'deleted' : 'active' }
+ expose(:artifact_location) { |experiment| 'not_implemented' }
end
end
end
diff --git a/lib/api/entities/ml/mlflow/get_experiment.rb b/lib/api/entities/ml/mlflow/get_experiment.rb
new file mode 100644
index 00000000000..f28d2ce76f6
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/get_experiment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class GetExperiment < Grape::Entity
+ expose :itself, using: Experiment, as: :experiment
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/list_experiment.rb b/lib/api/entities/ml/mlflow/list_experiment.rb
new file mode 100644
index 00000000000..515015bf4b7
--- /dev/null
+++ b/lib/api/entities/ml/mlflow/list_experiment.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Ml
+ module Mlflow
+ class ListExperiment < Grape::Entity
+ expose :experiments, with: Experiment
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/ml/mlflow/update_run.rb b/lib/api/entities/ml/mlflow/update_run.rb
index 5acdaab0e33..090d69b8895 100644
--- a/lib/api/entities/ml/mlflow/update_run.rb
+++ b/lib/api/entities/ml/mlflow/update_run.rb
@@ -10,7 +10,7 @@ module API
private
def run_info
- ::API::Entities::Ml::Mlflow::RunInfo.represent object
+ RunInfo.represent object
end
end
end
diff --git a/lib/api/ml/mlflow.rb b/lib/api/ml/mlflow.rb
index d1f8daaa93d..2ffb04ebcbd 100644
--- a/lib/api/ml/mlflow.rb
+++ b/lib/api/ml/mlflow.rb
@@ -9,20 +9,28 @@ module API
include APIGuard
# The first part of the url is the namespace, the second part of the URL is what the MLFlow client calls
- MLFLOW_API_PREFIX = ':id/ml/mflow/api/2.0/mlflow/'
+ MLFLOW_API_PREFIX = ':id/ml/mlflow/api/2.0/mlflow/'
allow_access_with_scope :api
allow_access_with_scope :read_api, if: -> (request) { request.get? || request.head? }
+ feature_category :mlops
+
+ content_type :json, 'application/json'
+ default_format :json
+
before do
+ # MLFlow Client considers any status code different than 200 an error, even 201
+ status 200
+
authenticate!
+
not_found! unless Feature.enabled?(:ml_experiment_tracking, user_project)
end
- feature_category :mlops
-
- content_type :json, 'application/json'
- default_format :json
+ rescue_from ActiveRecord::ActiveRecordError do |e|
+ invalid_parameter!(e.message)
+ end
helpers do
def resource_not_found!
@@ -32,6 +40,34 @@ module API
def resource_already_exists!
render_structured_api_error!({ error_code: 'RESOURCE_ALREADY_EXISTS' }, 400)
end
+
+ def invalid_parameter!(message = nil)
+ render_structured_api_error!({ error_code: 'INVALID_PARAMETER_VALUE', message: message }, 400)
+ end
+
+ def experiment_repository
+ ::Ml::ExperimentTracking::ExperimentRepository.new(user_project, current_user)
+ end
+
+ def candidate_repository
+ ::Ml::ExperimentTracking::CandidateRepository.new(user_project, current_user)
+ end
+
+ def experiment
+ @experiment ||= find_experiment!(params[:experiment_id], params[:experiment_name])
+ end
+
+ def candidate
+ @candidate ||= find_candidate!(params[:run_id])
+ end
+
+ def find_experiment!(iid, name)
+ experiment_repository.by_iid_or_name(iid: iid, name: name) || resource_not_found!
+ end
+
+ def find_candidate!(iid)
+ candidate_repository.by_iid(iid) || resource_not_found!
+ end
end
params do
@@ -44,33 +80,35 @@ module API
namespace MLFLOW_API_PREFIX do
resource :experiments do
desc 'Fetch experiment by experiment_id' do
- success Entities::Ml::Mlflow::Experiment
+ success Entities::Ml::Mlflow::GetExperiment
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment'
end
params do
optional :experiment_id, type: String, default: '', desc: 'Experiment ID, in reference to the project'
end
get 'get', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_iid(user_project.id, params[:experiment_id])
-
- resource_not_found! unless experiment
-
- present experiment, with: Entities::Ml::Mlflow::Experiment
+ present experiment, with: Entities::Ml::Mlflow::GetExperiment
end
desc 'Fetch experiment by experiment_name' do
- success Entities::Ml::Mlflow::Experiment
+ success Entities::Ml::Mlflow::GetExperiment
detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-experiment-by-name'
end
params do
optional :experiment_name, type: String, default: '', desc: 'Experiment name'
end
get 'get-by-name', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_name(user_project, params[:experiment_name])
+ present experiment, with: Entities::Ml::Mlflow::GetExperiment
+ end
- resource_not_found! unless experiment
+ desc 'List experiments' do
+ success Entities::Ml::Mlflow::ListExperiment
+ detail 'https://www.mlflow.org/docs/latest/rest-api.html#list-experiments'
+ end
+ get 'list', urgency: :low do
+ response = { experiments: experiment_repository.all }
- present experiment, with: Entities::Ml::Mlflow::Experiment
+ present response, with: Entities::Ml::Mlflow::ListExperiment
end
desc 'Create experiment' do
@@ -83,13 +121,9 @@ module API
optional :tags, type: Array, desc: 'This will be ignored'
end
post 'create', urgency: :low do
- resource_already_exists! if ::Ml::Experiment.has_record?(user_project.id, params[:name])
-
- experiment = ::Ml::Experiment.create!(name: params[:name],
- user: current_user,
- project: user_project)
-
- present experiment, with: Entities::Ml::Mlflow::NewExperiment
+ present experiment_repository.create!(params[:name]), with: Entities::Ml::Mlflow::NewExperiment
+ rescue ActiveRecord::RecordInvalid
+ resource_already_exists!
end
end
@@ -109,153 +143,108 @@ module API
optional :tags, type: Array, desc: 'This will be ignored'
end
post 'create', urgency: :low do
- experiment = ::Ml::Experiment.by_project_id_and_iid(user_project.id, params[:experiment_id].to_i)
+ present candidate_repository.create!(experiment, params[:start_time]), with: Entities::Ml::Mlflow::Run
+ end
- resource_not_found! unless experiment
+ desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do
+ success Entities::Ml::Mlflow::Run
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-run'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the candidate.'
+ optional :run_uuid, type: String, desc: 'This parameter is ignored'
+ end
+ get 'get', urgency: :low do
+ present candidate, with: Entities::Ml::Mlflow::Run
+ end
- candidate = experiment.candidates.create!(
- user: current_user,
- start_time: params[:start_time] || 0
- )
+ desc 'Updates a Run.' do
+ success Entities::Ml::Mlflow::UpdateRun
+ detail ['https://www.mlflow.org/docs/1.28.0/rest-api.html#update-run',
+ 'MLFlow Runs map to GitLab Candidates']
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the candidate.'
+ optional :status, type: String,
+ values: ::Ml::Candidate.statuses.keys.map(&:upcase),
+ desc: "Status of the run. Accepts: " \
+ "#{::Ml::Candidate.statuses.keys.map(&:upcase)}."
+ optional :end_time, type: Integer, desc: 'Ending time of the run'
+ end
+ post 'update', urgency: :low do
+ candidate_repository.update(candidate, params[:status], params[:end_time])
- present candidate, with: Entities::Ml::Mlflow::Run
+ present candidate, with: Entities::Ml::Mlflow::UpdateRun
end
- namespace do
- after_validation do
- @candidate = ::Ml::Candidate.with_project_id_and_iid(
- user_project.id,
- params[:run_id]
- )
+ desc 'Logs a metric to a run.' do
+ summary 'Log a metric for a run. A metric is a key-value pair (string key, float value) with an '\
+ 'associated timestamp. Examples include the various metrics that represent ML model accuracy. '\
+ 'A metric can be logged multiple times.'
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-metric'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ requires :key, type: String, desc: 'Name for the metric.'
+ requires :value, type: Float, desc: 'Value of the metric.'
+ requires :timestamp, type: Integer, desc: 'Unix timestamp in milliseconds when metric was recorded'
+ optional :step, type: Integer, desc: 'Step at which the metric was recorded'
+ end
+ post 'log-metric', urgency: :low do
+ candidate_repository.add_metric!(
+ candidate,
+ params[:key],
+ params[:value],
+ params[:timestamp],
+ params[:step]
+ )
- resource_not_found! unless @candidate
- end
+ {}
+ end
- desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do
- success Entities::Ml::Mlflow::Run
- detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#get-run'
- end
- params do
- requires :run_id, type: String, desc: 'UUID of the candidate.'
- optional :run_uuid, type: String, desc: 'This parameter is ignored'
- end
- get 'get', urgency: :low do
- present @candidate, with: Entities::Ml::Mlflow::Run
- end
+ desc 'Logs a parameter to a run.' do
+ summary 'Log a param used for a run. A param is a key-value pair (string key, string value). '\
+ 'Examples include hyperparameters used for ML model training and constant dates and values '\
+ 'used in an ETL pipeline. A param can be logged only once for a run, duplicate will be .'\
+ 'ignored'
- desc 'Updates a Run.' do
- success Entities::Ml::Mlflow::UpdateRun
- detail ['https://www.mlflow.org/docs/1.28.0/rest-api.html#update-run',
- 'MLFlow Runs map to GitLab Candidates']
- end
- params do
- requires :run_id, type: String, desc: 'UUID of the candidate.'
- optional :status, type: String,
- values: ::Ml::Candidate.statuses.keys.map(&:upcase),
- desc: "Status of the run. Accepts: " \
- "#{::Ml::Candidate.statuses.keys.map(&:upcase)}."
- optional :end_time, type: Integer, desc: 'Ending time of the run'
- end
- post 'update', urgency: :low do
- @candidate.status = params[:status].downcase if params[:status]
- @candidate.end_time = params[:end_time] if params[:end_time]
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ requires :key, type: String, desc: 'Name for the parameter.'
+ requires :value, type: String, desc: 'Value for the parameter.'
+ end
+ post 'log-parameter', urgency: :low do
+ bad_request! unless candidate_repository.add_param!(candidate, params[:key], params[:value])
- @candidate.save
+ {}
+ end
- present @candidate, with: Entities::Ml::Mlflow::UpdateRun
- end
+ desc 'Logs multiple parameters and metrics.' do
+ summary 'Log a batch of metrics and params for a run. Validation errors will block the entire batch, '\
+ 'duplicate errors will be ignored.'
- desc 'Logs a metric to a run.' do
- summary 'Log a metric for a run. A metric is a key-value pair (string key, float value) with an '\
- 'associated timestamp. Examples include the various metrics that represent ML model accuracy. '\
- 'A metric can be logged multiple times.'
- detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-metric'
- end
- params do
- requires :run_id, type: String, desc: 'UUID of the run.'
+ detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
+ end
+ params do
+ requires :run_id, type: String, desc: 'UUID of the run.'
+ optional :metrics, type: Array, default: [] do
requires :key, type: String, desc: 'Name for the metric.'
requires :value, type: Float, desc: 'Value of the metric.'
requires :timestamp, type: Integer, desc: 'Unix timestamp in milliseconds when metric was recorded'
optional :step, type: Integer, desc: 'Step at which the metric was recorded'
end
- post 'log-metric', urgency: :low do
- @candidate.metrics.create!(
- name: params[:key],
- value: params[:value],
- tracked_at: params[:timestamp],
- step: params[:step]
- )
-
- {}
- end
-
- desc 'Logs a parameter to a run.' do
- summary 'Log a param used for a run. A param is a key-value pair (string key, string value). '\
- 'Examples include hyperparameters used for ML model training and constant dates and values '\
- 'used in an ETL pipeline. A param can be logged only once for a run, duplicate will be .'\
- 'ignored'
-
- detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
- end
- params do
- requires :run_id, type: String, desc: 'UUID of the run.'
- requires :key, type: String, desc: 'Name for the parameter.'
- requires :value, type: String, desc: 'Value for the parameter.'
- end
- post 'log-parameter', urgency: :low do
- ::Ml::CandidateParam.create(candidate: @candidate, name: params[:key], value: params[:value])
-
- {}
+ optional :params, type: Array, default: [] do
+ requires :key, type: String, desc: 'Name for the metric.'
+ requires :value, type: String, desc: 'Value of the metric.'
end
+ end
+ post 'log-batch', urgency: :low do
+ candidate_repository.add_metrics(candidate, params[:metrics])
+ candidate_repository.add_params(candidate, params[:params])
- desc 'Logs multiple parameters and metrics.' do
- summary 'Log a batch of metrics and params for a run. Validation errors will block the entire batch, '\
- 'duplicate errors will be ignored.'
-
- detail 'https://www.mlflow.org/docs/1.28.0/rest-api.html#log-param'
- end
- params do
- requires :run_id, type: String, desc: 'UUID of the run.'
- optional :metrics, type: Array, default: [] do
- requires :key, type: String, desc: 'Name for the metric.'
- requires :value, type: Float, desc: 'Value of the metric.'
- requires :timestamp, type: Integer, desc: 'Unix timestamp in milliseconds when metric was recorded'
- optional :step, type: Integer, desc: 'Step at which the metric was recorded'
- end
- optional :params, type: Array, default: [] do
- requires :key, type: String, desc: 'Name for the metric.'
- requires :value, type: String, desc: 'Value of the metric.'
- end
- end
- post 'log-batch', urgency: :low do
- times = { created_at: Time.zone.now, updated_at: Time.zone.now }
-
- metrics = params[:metrics].map do |metric|
- {
- candidate_id: @candidate.id,
- name: metric[:key],
- value: metric[:value],
- tracked_at: metric[:timestamp],
- step: metric[:step],
- **times
- }
- end
-
- ::Ml::CandidateMetric.insert_all(metrics, returning: false) unless metrics.empty?
-
- parameters = params[:params].map do |p|
- {
- candidate_id: @candidate.id,
- name: p[:key],
- value: p[:value],
- **times
- }
- end
-
- ::Ml::CandidateParam.insert_all(parameters, returning: false) unless parameters.empty?
-
- {}
- end
+ {}
end
end
end