completion-kit 0.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +192 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/config/completion_kit_manifest.js +1 -0
  6. data/app/assets/config/manifest.js +3 -0
  7. data/app/assets/images/completion_kit/logo.svg +6 -0
  8. data/app/assets/javascripts/completion_kit/evaluation_steps_controller.js +25 -0
  9. data/app/assets/stylesheets/completion_kit/application.css +2214 -0
  10. data/app/controllers/completion_kit/api/v1/base_controller.rb +29 -0
  11. data/app/controllers/completion_kit/api/v1/criteria_controller.rb +62 -0
  12. data/app/controllers/completion_kit/api/v1/datasets_controller.rb +51 -0
  13. data/app/controllers/completion_kit/api/v1/metrics_controller.rb +51 -0
  14. data/app/controllers/completion_kit/api/v1/prompts_controller.rb +64 -0
  15. data/app/controllers/completion_kit/api/v1/provider_credentials_controller.rb +51 -0
  16. data/app/controllers/completion_kit/api/v1/responses_controller.rb +32 -0
  17. data/app/controllers/completion_kit/api/v1/runs_controller.rb +71 -0
  18. data/app/controllers/completion_kit/api_reference_controller.rb +9 -0
  19. data/app/controllers/completion_kit/application_controller.rb +31 -0
  20. data/app/controllers/completion_kit/criteria_controller.rb +67 -0
  21. data/app/controllers/completion_kit/datasets_controller.rb +53 -0
  22. data/app/controllers/completion_kit/mcp_controller.rb +57 -0
  23. data/app/controllers/completion_kit/metrics_controller.rb +52 -0
  24. data/app/controllers/completion_kit/prompts_controller.rb +69 -0
  25. data/app/controllers/completion_kit/provider_credentials_controller.rb +63 -0
  26. data/app/controllers/completion_kit/responses_controller.rb +44 -0
  27. data/app/controllers/completion_kit/runs_controller.rb +131 -0
  28. data/app/helpers/completion_kit/application_helper.rb +193 -0
  29. data/app/jobs/completion_kit/application_job.rb +4 -0
  30. data/app/jobs/completion_kit/generate_job.rb +12 -0
  31. data/app/jobs/completion_kit/judge_job.rb +12 -0
  32. data/app/jobs/completion_kit/model_discovery_job.rb +29 -0
  33. data/app/mailers/completion_kit/application_mailer.rb +6 -0
  34. data/app/models/completion_kit/application_record.rb +5 -0
  35. data/app/models/completion_kit/criteria.rb +22 -0
  36. data/app/models/completion_kit/criteria_membership.rb +20 -0
  37. data/app/models/completion_kit/dataset.rb +24 -0
  38. data/app/models/completion_kit/metric.rb +97 -0
  39. data/app/models/completion_kit/model.rb +13 -0
  40. data/app/models/completion_kit/prompt.rb +99 -0
  41. data/app/models/completion_kit/provider_credential.rb +114 -0
  42. data/app/models/completion_kit/response.rb +30 -0
  43. data/app/models/completion_kit/review.rb +28 -0
  44. data/app/models/completion_kit/run.rb +253 -0
  45. data/app/models/completion_kit/run_metric.rb +6 -0
  46. data/app/models/completion_kit/suggestion.rb +8 -0
  47. data/app/services/completion_kit/anthropic_client.rb +86 -0
  48. data/app/services/completion_kit/api_config.rb +80 -0
  49. data/app/services/completion_kit/csv_processor.rb +65 -0
  50. data/app/services/completion_kit/judge_service.rb +87 -0
  51. data/app/services/completion_kit/llm_client.rb +45 -0
  52. data/app/services/completion_kit/mcp_dispatcher.rb +53 -0
  53. data/app/services/completion_kit/mcp_tools/criteria.rb +106 -0
  54. data/app/services/completion_kit/mcp_tools/datasets.rb +90 -0
  55. data/app/services/completion_kit/mcp_tools/metrics.rb +98 -0
  56. data/app/services/completion_kit/mcp_tools/prompts.rb +112 -0
  57. data/app/services/completion_kit/mcp_tools/provider_credentials.rb +97 -0
  58. data/app/services/completion_kit/mcp_tools/responses.rb +45 -0
  59. data/app/services/completion_kit/mcp_tools/runs.rb +130 -0
  60. data/app/services/completion_kit/model_discovery_service.rb +223 -0
  61. data/app/services/completion_kit/ollama_client.rb +80 -0
  62. data/app/services/completion_kit/open_ai_client.rb +71 -0
  63. data/app/services/completion_kit/open_router_client.rb +69 -0
  64. data/app/services/completion_kit/prompt_improvement_service.rb +81 -0
  65. data/app/views/completion_kit/api_reference/_example.html.erb +6 -0
  66. data/app/views/completion_kit/api_reference/index.html.erb +308 -0
  67. data/app/views/completion_kit/criteria/_form.html.erb +46 -0
  68. data/app/views/completion_kit/criteria/edit.html.erb +14 -0
  69. data/app/views/completion_kit/criteria/index.html.erb +37 -0
  70. data/app/views/completion_kit/criteria/new.html.erb +13 -0
  71. data/app/views/completion_kit/criteria/show.html.erb +37 -0
  72. data/app/views/completion_kit/datasets/_form.html.erb +29 -0
  73. data/app/views/completion_kit/datasets/edit.html.erb +13 -0
  74. data/app/views/completion_kit/datasets/index.html.erb +38 -0
  75. data/app/views/completion_kit/datasets/new.html.erb +12 -0
  76. data/app/views/completion_kit/datasets/show.html.erb +45 -0
  77. data/app/views/completion_kit/metrics/_form.html.erb +72 -0
  78. data/app/views/completion_kit/metrics/edit.html.erb +13 -0
  79. data/app/views/completion_kit/metrics/index.html.erb +34 -0
  80. data/app/views/completion_kit/metrics/new.html.erb +12 -0
  81. data/app/views/completion_kit/metrics/show.html.erb +49 -0
  82. data/app/views/completion_kit/prompts/_form.html.erb +52 -0
  83. data/app/views/completion_kit/prompts/edit.html.erb +13 -0
  84. data/app/views/completion_kit/prompts/index.html.erb +46 -0
  85. data/app/views/completion_kit/prompts/new.html.erb +12 -0
  86. data/app/views/completion_kit/prompts/show.html.erb +156 -0
  87. data/app/views/completion_kit/provider_credentials/_discovery_status.html.erb +30 -0
  88. data/app/views/completion_kit/provider_credentials/_form.html.erb +71 -0
  89. data/app/views/completion_kit/provider_credentials/edit.html.erb +12 -0
  90. data/app/views/completion_kit/provider_credentials/index.html.erb +41 -0
  91. data/app/views/completion_kit/provider_credentials/new.html.erb +12 -0
  92. data/app/views/completion_kit/responses/show.html.erb +87 -0
  93. data/app/views/completion_kit/runs/_actions.html.erb +14 -0
  94. data/app/views/completion_kit/runs/_form.html.erb +159 -0
  95. data/app/views/completion_kit/runs/_progress.html.erb +18 -0
  96. data/app/views/completion_kit/runs/_response_row.html.erb +13 -0
  97. data/app/views/completion_kit/runs/_sort_toolbar.html.erb +8 -0
  98. data/app/views/completion_kit/runs/_status_header.html.erb +15 -0
  99. data/app/views/completion_kit/runs/edit.html.erb +14 -0
  100. data/app/views/completion_kit/runs/index.html.erb +43 -0
  101. data/app/views/completion_kit/runs/new.html.erb +12 -0
  102. data/app/views/completion_kit/runs/show.html.erb +79 -0
  103. data/app/views/completion_kit/runs/suggestion.html.erb +47 -0
  104. data/app/views/layouts/completion_kit/application.html.erb +77 -0
  105. data/config/routes.rb +55 -0
  106. data/db/migrate/20260311000001_create_completion_kit_tables.rb +87 -0
  107. data/db/migrate/20260326000001_rename_criteria_to_instruction_on_metrics_and_reviews.rb +6 -0
  108. data/db/migrate/20260327000001_add_progress_to_runs.rb +6 -0
  109. data/db/migrate/20260327100001_replace_criteria_with_direct_metrics_on_runs.rb +12 -0
  110. data/db/migrate/20260328000001_add_error_message_to_runs.rb +5 -0
  111. data/db/migrate/20260329000001_create_completion_kit_models.rb +20 -0
  112. data/db/migrate/20260401170001_add_discovery_columns_to_completion_kit_provider_credentials.rb +7 -0
  113. data/db/migrate/20260403000001_add_temperature_to_completion_kit_runs.rb +5 -0
  114. data/db/migrate/20260403000002_create_completion_kit_suggestions.rb +13 -0
  115. data/db/migrate/20260403000003_add_applied_at_to_completion_kit_suggestions.rb +5 -0
  116. data/lib/completion-kit.rb +1 -0
  117. data/lib/completion_kit/engine.rb +35 -0
  118. data/lib/completion_kit/version.rb +3 -0
  119. data/lib/completion_kit.rb +55 -0
  120. data/lib/generators/completion_kit/install_generator.rb +21 -0
  121. data/lib/generators/completion_kit/templates/README +20 -0
  122. data/lib/generators/completion_kit/templates/initializer.rb +43 -0
  123. metadata +361 -0
@@ -0,0 +1,29 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class BaseController < ActionController::API
5
+ before_action :authenticate_api!
6
+
7
+ private
8
+
9
+ def authenticate_api!
10
+ token = CompletionKit.config.api_token
11
+ unless token
12
+ render json: {error: "API token not configured"}, status: :unauthorized
13
+ return
14
+ end
15
+
16
+ provided = request.headers["Authorization"]&.match(/\ABearer (.+)\z/)&.[](1)
17
+ unless provided && ActiveSupport::SecurityUtils.secure_compare(provided, token)
18
+ render json: {error: "Unauthorized"}, status: :unauthorized
19
+ end
20
+ end
21
+
22
+ def not_found
23
+ render json: {error: "Record not found"}, status: :not_found
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,62 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class CriteriaController < BaseController
5
+ before_action :set_criteria, only: [:show, :update, :destroy]
6
+
7
+ def index
8
+ render json: Criteria.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @criteria
13
+ end
14
+
15
+ def create
16
+ criteria = Criteria.new(criteria_params.except(:metric_ids))
17
+ if criteria.save
18
+ replace_metric_memberships(criteria, params[:metric_ids]) if params.key?(:metric_ids)
19
+ render json: criteria.reload, status: :created
20
+ else
21
+ render json: {errors: criteria.errors}, status: :unprocessable_entity
22
+ end
23
+ end
24
+
25
+ def update
26
+ if @criteria.update(criteria_params.except(:metric_ids))
27
+ replace_metric_memberships(@criteria, params[:metric_ids]) if params.key?(:metric_ids)
28
+ render json: @criteria.reload
29
+ else
30
+ render json: {errors: @criteria.errors}, status: :unprocessable_entity
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ @criteria.destroy!
36
+ head :no_content
37
+ end
38
+
39
+ private
40
+
41
+ def set_criteria
42
+ @criteria = Criteria.find(params[:id])
43
+ rescue ActiveRecord::RecordNotFound
44
+ not_found
45
+ end
46
+
47
+ def criteria_params
48
+ params.permit(:name, :description, metric_ids: [])
49
+ end
50
+
51
+ def replace_metric_memberships(criteria, metric_ids)
52
+ return unless metric_ids
53
+
54
+ criteria.criteria_memberships.delete_all
55
+ Array(metric_ids).reject(&:blank?).each_with_index do |metric_id, index|
56
+ criteria.criteria_memberships.create!(metric_id: metric_id, position: index + 1)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,51 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class DatasetsController < BaseController
5
+ before_action :set_dataset, only: [:show, :update, :destroy]
6
+
7
+ def index
8
+ render json: Dataset.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @dataset
13
+ end
14
+
15
+ def create
16
+ dataset = Dataset.new(dataset_params)
17
+ if dataset.save
18
+ render json: dataset, status: :created
19
+ else
20
+ render json: {errors: dataset.errors}, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def update
25
+ if @dataset.update(dataset_params)
26
+ render json: @dataset
27
+ else
28
+ render json: {errors: @dataset.errors}, status: :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ @dataset.destroy!
34
+ head :no_content
35
+ end
36
+
37
+ private
38
+
39
+ def set_dataset
40
+ @dataset = Dataset.find(params[:id])
41
+ rescue ActiveRecord::RecordNotFound
42
+ not_found
43
+ end
44
+
45
+ def dataset_params
46
+ params.permit(:name, :csv_data)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class MetricsController < BaseController
5
+ before_action :set_metric, only: [:show, :update, :destroy]
6
+
7
+ def index
8
+ render json: Metric.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @metric
13
+ end
14
+
15
+ def create
16
+ metric = Metric.new(metric_params)
17
+ if metric.save
18
+ render json: metric, status: :created
19
+ else
20
+ render json: {errors: metric.errors}, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def update
25
+ if @metric.update(metric_params)
26
+ render json: @metric
27
+ else
28
+ render json: {errors: @metric.errors}, status: :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ @metric.destroy!
34
+ head :no_content
35
+ end
36
+
37
+ private
38
+
39
+ def set_metric
40
+ @metric = Metric.find(params[:id])
41
+ rescue ActiveRecord::RecordNotFound
42
+ not_found
43
+ end
44
+
45
+ def metric_params
46
+ params.permit(:name, :instruction, evaluation_steps: [], rubric_bands: [:stars, :description])
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class PromptsController < BaseController
5
+ before_action :set_prompt, only: [:show, :update, :destroy, :publish]
6
+
7
+ def index
8
+ render json: Prompt.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @prompt
13
+ end
14
+
15
+ def create
16
+ prompt = Prompt.new(prompt_params)
17
+ if prompt.save
18
+ render json: prompt, status: :created
19
+ else
20
+ render json: {errors: prompt.errors}, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def update
25
+ if @prompt.runs.exists?
26
+ new_prompt = @prompt.clone_as_new_version(prompt_params.to_h)
27
+ new_prompt.publish!
28
+ render json: new_prompt
29
+ elsif @prompt.update(prompt_params)
30
+ render json: @prompt
31
+ else
32
+ render json: {errors: @prompt.errors}, status: :unprocessable_entity
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ @prompt.destroy!
38
+ head :no_content
39
+ end
40
+
41
+ def publish
42
+ @prompt.publish!
43
+ render json: @prompt.reload
44
+ end
45
+
46
+ private
47
+
48
+ def set_prompt
49
+ @prompt = if params[:id].to_s.match?(/\A\d+\z/)
50
+ Prompt.find(params[:id])
51
+ else
52
+ Prompt.current_for(params[:id])
53
+ end
54
+ rescue ActiveRecord::RecordNotFound
55
+ not_found
56
+ end
57
+
58
+ def prompt_params
59
+ params.permit(:name, :description, :template, :llm_model)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,51 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class ProviderCredentialsController < BaseController
5
+ before_action :set_credential, only: [:show, :update, :destroy]
6
+
7
+ def index
8
+ render json: ProviderCredential.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @credential
13
+ end
14
+
15
+ def create
16
+ credential = ProviderCredential.new(credential_params)
17
+ if credential.save
18
+ render json: credential, status: :created
19
+ else
20
+ render json: {errors: credential.errors}, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def update
25
+ if @credential.update(credential_params)
26
+ render json: @credential
27
+ else
28
+ render json: {errors: @credential.errors}, status: :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ @credential.destroy!
34
+ head :no_content
35
+ end
36
+
37
+ private
38
+
39
+ def set_credential
40
+ @credential = ProviderCredential.find(params[:id])
41
+ rescue ActiveRecord::RecordNotFound
42
+ not_found
43
+ end
44
+
45
+ def credential_params
46
+ params.permit(:provider, :api_key, :api_endpoint)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class ResponsesController < BaseController
5
+ before_action :set_run
6
+ before_action :set_response, only: [:show]
7
+
8
+ def index
9
+ render json: @run.responses.includes(:reviews)
10
+ end
11
+
12
+ def show
13
+ render json: @response
14
+ end
15
+
16
+ private
17
+
18
+ def set_run
19
+ @run = Run.find(params[:run_id])
20
+ rescue ActiveRecord::RecordNotFound
21
+ not_found
22
+ end
23
+
24
+ def set_response
25
+ @response = @run.responses.find(params[:id])
26
+ rescue ActiveRecord::RecordNotFound
27
+ not_found
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,71 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class RunsController < BaseController
5
+ before_action :set_run, only: [:show, :update, :destroy, :generate, :judge]
6
+
7
+ def index
8
+ render json: Run.order(created_at: :desc)
9
+ end
10
+
11
+ def show
12
+ render json: @run
13
+ end
14
+
15
+ def create
16
+ run = Run.new(run_params.except(:metric_ids))
17
+ if run.save
18
+ replace_run_metrics(run, params[:metric_ids])
19
+ render json: run.reload, status: :created
20
+ else
21
+ render json: {errors: run.errors}, status: :unprocessable_entity
22
+ end
23
+ end
24
+
25
+ def update
26
+ if @run.update(run_params.except(:metric_ids))
27
+ replace_run_metrics(@run, params[:metric_ids]) if params.key?(:metric_ids)
28
+ render json: @run.reload
29
+ else
30
+ render json: {errors: @run.errors}, status: :unprocessable_entity
31
+ end
32
+ end
33
+
34
+ def destroy
35
+ @run.destroy!
36
+ head :no_content
37
+ end
38
+
39
+ def generate
40
+ GenerateJob.perform_later(@run.id)
41
+ render json: @run.reload, status: :accepted
42
+ end
43
+
44
+ def judge
45
+ JudgeJob.perform_later(@run.id)
46
+ render json: @run.reload, status: :accepted
47
+ end
48
+
49
+ private
50
+
51
+ def set_run
52
+ @run = Run.find(params[:id])
53
+ rescue ActiveRecord::RecordNotFound
54
+ not_found
55
+ end
56
+
57
+ def run_params
58
+ params.permit(:name, :prompt_id, :dataset_id, :judge_model, :temperature, metric_ids: [])
59
+ end
60
+
61
+ def replace_run_metrics(run, metric_ids)
62
+ return unless metric_ids
63
+ run.run_metrics.delete_all
64
+ Array(metric_ids).reject(&:blank?).each_with_index do |metric_id, index|
65
+ run.run_metrics.create!(metric_id: metric_id, position: index + 1)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ module CompletionKit
2
+ class ApiReferenceController < ApplicationController
3
+ def index
4
+ @published_prompts = Prompt.current_versions.order(name: :asc)
5
+ @token = CompletionKit.config.api_token
6
+ @base_url = request.base_url + request.script_name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,31 @@
1
+ module CompletionKit
2
+ class ApplicationController < ActionController::Base
3
+ helper Heroicons::IconsHelper
4
+ layout "completion_kit/application"
5
+
6
+ before_action :authenticate_completion_kit!
7
+
8
+ private
9
+
10
+ def authenticate_completion_kit!
11
+ cfg = CompletionKit.config
12
+
13
+ if (cfg.username && !cfg.password) || (cfg.password && !cfg.username)
14
+ raise CompletionKit::ConfigurationError,
15
+ "Both username and password are required for built-in auth."
16
+ end
17
+
18
+ if cfg.auth_strategy
19
+ cfg.auth_strategy.call(self)
20
+ elsif cfg.username && cfg.password
21
+ authenticate_or_request_with_http_basic("CompletionKit") do |u, p|
22
+ ActiveSupport::SecurityUtils.secure_compare(u, cfg.username) &
23
+ ActiveSupport::SecurityUtils.secure_compare(p, cfg.password)
24
+ end
25
+ elsif Rails.env.production?
26
+ render plain: "CompletionKit authentication not configured. See README for setup instructions.",
27
+ status: :forbidden
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,67 @@
1
+ module CompletionKit
2
+ class CriteriaController < ApplicationController
3
+ before_action :set_criteria, only: [:show, :edit, :update, :destroy]
4
+
5
+ def index
6
+ @criterias = Criteria.includes(:metrics).order(:name)
7
+ end
8
+
9
+ def show
10
+ end
11
+
12
+ def new
13
+ @criteria = Criteria.new
14
+ @metrics = Metric.order(:name)
15
+ end
16
+
17
+ def edit
18
+ @metrics = Metric.order(:name)
19
+ end
20
+
21
+ def create
22
+ @criteria = Criteria.new(criteria_params.except(:metric_ids))
23
+ @metrics = Metric.order(:name)
24
+
25
+ if @criteria.save
26
+ replace_metric_memberships
27
+ redirect_to criterion_path(@criteria), notice: "Criteria was successfully created."
28
+ else
29
+ render :new, status: :unprocessable_entity
30
+ end
31
+ end
32
+
33
+ def update
34
+ @metrics = Metric.order(:name)
35
+
36
+ if @criteria.update(criteria_params.except(:metric_ids))
37
+ replace_metric_memberships
38
+ redirect_to criterion_path(@criteria), notice: "Criteria was successfully updated."
39
+ else
40
+ render :edit, status: :unprocessable_entity
41
+ end
42
+ end
43
+
44
+ def destroy
45
+ @criteria.destroy
46
+ redirect_to criteria_path, notice: "Criteria was successfully destroyed."
47
+ end
48
+
49
+ private
50
+
51
+ def set_criteria
52
+ @criteria = Criteria.find(params[:id])
53
+ end
54
+
55
+ def criteria_params
56
+ params.require(:criteria).permit(:name, :description, metric_ids: [])
57
+ end
58
+
59
+ def replace_metric_memberships
60
+ metric_ids = Array(criteria_params[:metric_ids]).reject(&:blank?)
61
+ @criteria.criteria_memberships.delete_all
62
+ metric_ids.each_with_index do |metric_id, index|
63
+ @criteria.criteria_memberships.create!(metric_id: metric_id, position: index + 1)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,53 @@
1
+ module CompletionKit
2
+ class DatasetsController < ApplicationController
3
+ before_action :set_dataset, only: [:show, :edit, :update, :destroy]
4
+
5
+ def index
6
+ @datasets = Dataset.includes(:runs).order(created_at: :desc)
7
+ end
8
+
9
+ def show
10
+ @runs = @dataset.runs.includes(:prompt, :responses).order(created_at: :desc)
11
+ end
12
+
13
+ def new
14
+ @dataset = Dataset.new
15
+ end
16
+
17
+ def edit
18
+ end
19
+
20
+ def create
21
+ @dataset = Dataset.new(dataset_params)
22
+
23
+ if @dataset.save
24
+ redirect_to datasets_path, notice: "Dataset was successfully created."
25
+ else
26
+ render :new, status: :unprocessable_entity
27
+ end
28
+ end
29
+
30
+ def update
31
+ if @dataset.update(dataset_params)
32
+ redirect_to @dataset, notice: "Dataset was successfully updated."
33
+ else
34
+ render :edit, status: :unprocessable_entity
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @dataset.destroy
40
+ redirect_to datasets_path, notice: "Dataset was successfully destroyed."
41
+ end
42
+
43
+ private
44
+
45
+ def set_dataset
46
+ @dataset = Dataset.find(params[:id])
47
+ end
48
+
49
+ def dataset_params
50
+ params.require(:dataset).permit(:name, :csv_data)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ module CompletionKit
2
+ class McpController < Api::V1::BaseController
3
+ def handle
4
+ request_body = JSON.parse(request.body.read)
5
+
6
+ if request_body["method"] == "initialize"
7
+ result = McpDispatcher.initialize_session
8
+ session_id = result.delete(:session_id)
9
+ response.headers["Mcp-Session-Id"] = session_id
10
+ render json: jsonrpc_response(request_body["id"], result)
11
+ return
12
+ end
13
+
14
+ if request_body["method"] == "notifications/initialized"
15
+ head :ok
16
+ return
17
+ end
18
+
19
+ session_id = request.headers["Mcp-Session-Id"]
20
+ unless session_id && Rails.cache.exist?("mcp_session:#{session_id}")
21
+ render json: jsonrpc_error(request_body["id"], -32000, "Session not initialized. Send initialize first."), status: :bad_request
22
+ return
23
+ end
24
+
25
+ result = McpDispatcher.dispatch(request_body["method"], request_body["params"])
26
+ render json: jsonrpc_response(request_body["id"], result)
27
+ rescue JSON::ParserError
28
+ render json: jsonrpc_error(nil, -32700, "Parse error"), status: :bad_request
29
+ rescue McpDispatcher::MethodNotFound => e
30
+ render json: jsonrpc_error(request_body.dig("id"), -32601, e.message), status: :ok
31
+ rescue McpDispatcher::InvalidParams => e
32
+ render json: jsonrpc_error(request_body.dig("id"), -32602, e.message), status: :ok
33
+ rescue ActiveRecord::RecordNotFound => e
34
+ render json: jsonrpc_error(request_body.dig("id"), -32602, e.message), status: :ok
35
+ rescue ActiveRecord::RecordInvalid, ActiveRecord::InvalidForeignKey => e
36
+ render json: jsonrpc_error(request_body.dig("id"), -32602, e.message), status: :ok
37
+ rescue StandardError => e
38
+ render json: jsonrpc_error(request_body.dig("id"), -32603, e.message), status: :ok
39
+ end
40
+
41
+ def destroy
42
+ session_id = request.headers["Mcp-Session-Id"]
43
+ Rails.cache.delete("mcp_session:#{session_id}") if session_id
44
+ head :ok
45
+ end
46
+
47
+ private
48
+
49
+ def jsonrpc_response(id, result)
50
+ {jsonrpc: "2.0", id: id, result: result}
51
+ end
52
+
53
+ def jsonrpc_error(id, code, message)
54
+ {jsonrpc: "2.0", id: id, error: {code: code, message: message}}
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ module CompletionKit
2
+ class MetricsController < ApplicationController
3
+ before_action :set_metric, only: [:show, :edit, :update, :destroy]
4
+
5
+ def index
6
+ @metrics = Metric.order(:name)
7
+ end
8
+
9
+ def show
10
+ end
11
+
12
+ def new
13
+ @metric = Metric.new
14
+ end
15
+
16
+ def edit
17
+ end
18
+
19
+ def create
20
+ @metric = Metric.new(metric_params)
21
+
22
+ if @metric.save
23
+ redirect_to metric_path(@metric), notice: "Metric was successfully created."
24
+ else
25
+ render :new, status: :unprocessable_entity
26
+ end
27
+ end
28
+
29
+ def update
30
+ if @metric.update(metric_params)
31
+ redirect_to metric_path(@metric), notice: "Metric was successfully updated."
32
+ else
33
+ render :edit, status: :unprocessable_entity
34
+ end
35
+ end
36
+
37
+ def destroy
38
+ @metric.destroy
39
+ redirect_to metrics_path, notice: "Metric was successfully destroyed."
40
+ end
41
+
42
+ private
43
+
44
+ def set_metric
45
+ @metric = Metric.find(params[:id])
46
+ end
47
+
48
+ def metric_params
49
+ params.require(:metric).permit(:name, :instruction, evaluation_steps: [], rubric_bands: [:stars, :description])
50
+ end
51
+ end
52
+ end