completion-kit 0.28.43 → 0.28.45
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.
- checksums.yaml +4 -4
- data/README.md +31 -6
- data/app/assets/stylesheets/completion_kit/application.css +5 -0
- data/app/controllers/completion_kit/agreements_controller.rb +1 -5
- data/app/controllers/completion_kit/api/v1/base_controller.rb +5 -0
- data/app/controllers/completion_kit/api/v1/metric_groups_controller.rb +6 -4
- data/app/controllers/completion_kit/api/v1/metrics_controller.rb +1 -1
- data/app/controllers/completion_kit/api/v1/runs_controller.rb +9 -5
- data/app/controllers/completion_kit/application_controller.rb +13 -0
- data/app/controllers/completion_kit/mcp_controller.rb +16 -4
- data/app/controllers/completion_kit/metric_groups_controller.rb +6 -4
- data/app/controllers/completion_kit/provider_credentials_controller.rb +19 -1
- data/app/controllers/completion_kit/runs_controller.rb +13 -8
- data/app/jobs/completion_kit/generate_row_job.rb +2 -1
- data/app/jobs/completion_kit/judge_review_job.rb +1 -0
- data/app/jobs/completion_kit/metric_suggestion_job.rb +1 -2
- data/app/jobs/completion_kit/model_discovery_job.rb +28 -13
- data/app/models/completion_kit/metric_group.rb +4 -2
- data/app/models/completion_kit/metric_group_membership.rb +1 -1
- data/app/models/completion_kit/prompt.rb +7 -6
- data/app/models/completion_kit/provider_credential.rb +19 -2
- data/app/models/completion_kit/run.rb +77 -14
- data/app/models/completion_kit/run_metric.rb +1 -1
- data/app/models/concerns/completion_kit/metric_selection.rb +35 -0
- data/app/services/completion_kit/api_config.rb +23 -7
- data/app/services/completion_kit/azure_foundry_client.rb +4 -2
- data/app/services/completion_kit/csv_processor.rb +4 -6
- data/app/services/completion_kit/json_object.rb +14 -0
- data/app/services/completion_kit/llm_client.rb +1 -1
- data/app/services/completion_kit/mcp_dispatcher.rb +2 -2
- data/app/services/completion_kit/mcp_tools/base.rb +2 -0
- data/app/services/completion_kit/mcp_tools/datasets.rb +1 -1
- data/app/services/completion_kit/mcp_tools/judges.rb +2 -2
- data/app/services/completion_kit/mcp_tools/metric_groups.rb +2 -4
- data/app/services/completion_kit/mcp_tools/provider_credentials.rb +3 -2
- data/app/services/completion_kit/mcp_tools/runs.rb +6 -6
- data/app/services/completion_kit/model_discovery_service.rb +10 -10
- data/app/services/completion_kit/ollama_client.rb +4 -2
- data/app/services/completion_kit/promptfoo_importer.rb +1 -1
- data/app/services/completion_kit/provider_endpoint.rb +18 -9
- data/app/views/completion_kit/agreements/_buttons.html.erb +1 -1
- data/app/views/completion_kit/agreements/_trust_panel.html.erb +1 -2
- data/app/views/completion_kit/api_reference/_body.html.erb +4 -3
- data/app/views/completion_kit/dashboard/show.html.erb +1 -1
- data/app/views/completion_kit/datasets/show.html.erb +5 -0
- data/app/views/completion_kit/metric_groups/_form.html.erb +4 -1
- data/app/views/completion_kit/prompts/show.html.erb +5 -0
- data/app/views/completion_kit/provider_credentials/_form.html.erb +24 -1
- data/app/views/completion_kit/provider_credentials/edit.html.erb +1 -1
- data/app/views/completion_kit/provider_credentials/index.html.erb +1 -2
- data/app/views/completion_kit/responses/show.html.erb +1 -1
- data/app/views/completion_kit/runs/_form.html.erb +1 -1
- data/app/views/completion_kit/runs/index.html.erb +1 -1
- data/config/locales/en.yml +12 -0
- data/lib/completion_kit/errors.rb +30 -0
- data/lib/completion_kit/version.rb +1 -1
- data/lib/completion_kit.rb +1 -1
- data/lib/generators/completion_kit/templates/initializer.rb +9 -0
- data/lib/tasks/completion_kit_runs.rake +2 -2
- metadata +4 -2
- data/app/models/completion_kit/mcp_session.rb +0 -54
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
<% anchor = metric&.name&.parameterize %>
|
|
4
4
|
<% current_metric_version = metric && CompletionKit::MetricVersion.current.find_by(metric_id: metric.id) %>
|
|
5
5
|
<% target_response = if (stats.sample_size.zero? || stats.counter_only?) && metric && current_metric_version
|
|
6
|
-
|
|
7
|
-
verdicted_ids = CompletionKit::Agreement.where(metric_id: metric.id, created_by: created_by, metric_version_id: current_metric_version.id, run_id: CompletionKit::Run.visible_run_ids).pluck(:response_id)
|
|
6
|
+
verdicted_ids = CompletionKit::Agreement.where(metric_id: metric.id, created_by: ck_current_reviewer, metric_version_id: current_metric_version.id, run_id: CompletionKit::Run.visible_run_ids).pluck(:response_id)
|
|
8
7
|
CompletionKit::Response.where(run_id: CompletionKit::Run.visible_run_ids).joins(:reviews)
|
|
9
8
|
.where(reviews: { metric_id: metric.id, metric_version_id: current_metric_version.id })
|
|
10
9
|
.where.not(reviews: { ai_score: nil })
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
} %>
|
|
146
146
|
<div class="ck-api-endpoint">
|
|
147
147
|
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">POST</span> /api/v1/runs/:id/generate</p>
|
|
148
|
-
<p class="ck-meta-copy">Start generating responses. Returns 202 Accepted. Poll the run to check progress.</p>
|
|
148
|
+
<p class="ck-meta-copy">Start generating responses. Returns 202 Accepted. Poll the run to check progress. Returns 422 with the reason when the run cannot start, for example when no configured provider serves the prompt's model or the judge model.</p>
|
|
149
149
|
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X POST #{base_url}/api/v1/runs/1/generate \\\n -H \"Authorization: Bearer #{token}\"" %>
|
|
150
150
|
</div>
|
|
151
151
|
<div class="ck-api-endpoint">
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
</div>
|
|
162
162
|
<div class="ck-api-endpoint">
|
|
163
163
|
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">POST</span> /api/v1/runs/:id/regrade</p>
|
|
164
|
-
<p class="ck-meta-copy">Re-judge the existing successful responses against the current metric versions without regenerating model output. Returns 202 Accepted, or 422 if no responses are eligible.</p>
|
|
164
|
+
<p class="ck-meta-copy">Re-judge the existing successful responses against the current metric versions without regenerating model output. Returns 202 Accepted, or 422 if no responses are eligible or no configured provider serves the judge model.</p>
|
|
165
165
|
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X POST #{base_url}/api/v1/runs/1/regrade \\\n -H \"Authorization: Bearer #{token}\"" %>
|
|
166
166
|
</div>
|
|
167
167
|
<div class="ck-api-endpoint">
|
|
@@ -258,7 +258,7 @@
|
|
|
258
258
|
</div>
|
|
259
259
|
<div class="ck-api-endpoint">
|
|
260
260
|
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">POST</span> /api/v1/metrics/:id/suggest_variants</p>
|
|
261
|
-
<p class="ck-meta-copy">Generate draft metric versions from the current disagreements. Only rubric-style (llm_judge) metrics can suggest variants. Returns 201 with the new draft versions, or 422 if the metric is a check (checks are exact and have no variants to suggest), if no disagreements exist, or if the model produced nothing usable.</p>
|
|
261
|
+
<p class="ck-meta-copy">Generate draft metric versions from the current disagreements. Only rubric-style (llm_judge) metrics can suggest variants. Returns 201 with the new draft versions, or 422 if the metric is a check (checks are exact and have no variants to suggest), if no disagreements exist, if no configured provider serves the model, or if the model produced nothing usable.</p>
|
|
262
262
|
<p class="ck-api-params"><strong>Optional:</strong> <code>count</code>, <code>model</code></p>
|
|
263
263
|
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X POST #{base_url}/api/v1/metrics/1/suggest_variants \\\n -H \"Authorization: Bearer #{token}\"" %>
|
|
264
264
|
</div>
|
|
@@ -395,6 +395,7 @@
|
|
|
395
395
|
<div class="ck-api-endpoint">
|
|
396
396
|
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">GET</span> <span class="ck-chip ck-chip--soft">PATCH</span> <span class="ck-chip" style="color: var(--ck-danger);">DELETE</span> /api/v1/provider_credentials/:id</p>
|
|
397
397
|
<p class="ck-meta-copy">Get, update, or delete a provider credential. DELETE returns 422 (not 204) when the provider is still in use by prompts or judge runs; remove those references first.</p>
|
|
398
|
+
<p class="ck-meta-copy">Changing <code>provider</code> or <code>api_endpoint</code> on a credential with a stored key requires <code>api_key</code> in the same request, otherwise PATCH returns 422 and nothing changes. Send <code>"api_key": null</code> to remove the key instead.</p>
|
|
398
399
|
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X PATCH #{base_url}/api/v1/provider_credentials/1 \\\n -H \"Authorization: Bearer #{token}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"api_key\": \"sk-new...\"}'" %>
|
|
399
400
|
</div>
|
|
400
401
|
<%= render "completion_kit/api_reference/resource_list", title: "Your providers",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
<% if @recent_runs.any? %>
|
|
131
131
|
<%= render "completion_kit/runs/table", runs: @recent_runs %>
|
|
132
132
|
<% elsif CompletionKit::Run.exists? %>
|
|
133
|
-
<div class="ck-empty">
|
|
133
|
+
<div class="ck-empty">No runs to show.</div>
|
|
134
134
|
<% else %>
|
|
135
135
|
<div class="ck-empty">No runs yet. <%= link_to "Create your first run →", new_run_path, class: "ck-link" %></div>
|
|
136
136
|
<% end %>
|
|
@@ -71,6 +71,11 @@
|
|
|
71
71
|
<p class="ck-kicker">Runs</p>
|
|
72
72
|
<%= render "completion_kit/runs/table", runs: @runs %>
|
|
73
73
|
</section>
|
|
74
|
+
<% elsif @dataset.runs.exists? %>
|
|
75
|
+
<section class="ck-card--spaced">
|
|
76
|
+
<p class="ck-kicker">Runs</p>
|
|
77
|
+
<div class="ck-empty">No runs to show.</div>
|
|
78
|
+
</section>
|
|
74
79
|
<% end %>
|
|
75
80
|
|
|
76
81
|
<%= ck_runs_display_footer(@runs) %>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<div class="ck-metric-checkboxes" data-metric-checkboxes role="group" aria-labelledby="mg_metrics_label">
|
|
46
46
|
<% @metrics.each do |metric| %>
|
|
47
47
|
<label class="ck-checkbox-label" data-metric-tags="<%= metric.tag_names.join(",") %>">
|
|
48
|
-
<%= check_box_tag "metric_group[metric_ids][]", metric.id, metric_group.
|
|
48
|
+
<%= check_box_tag "metric_group[metric_ids][]", metric.id, metric_group.selected_metric_ids.include?(metric.id), class: "ck-checkbox", id: "metric_group_metric_#{metric.id}" %>
|
|
49
49
|
<span class="ck-checkbox-label__box" aria-hidden="true"></span>
|
|
50
50
|
<span class="ck-checkbox-label__body">
|
|
51
51
|
<span class="ck-checkbox-label__text"><%= metric.name %></span>
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
</label>
|
|
62
62
|
<% end %>
|
|
63
63
|
</div>
|
|
64
|
+
<% if metric_group.errors[:metric_ids].any? %>
|
|
65
|
+
<p class="ck-field-error" id="<%= ck_field_error_id(form, :metric_ids) %>"><%= metric_group.errors.full_messages_for(:metric_ids).to_sentence %></p>
|
|
66
|
+
<% end %>
|
|
64
67
|
<%= hidden_field_tag "metric_group[metric_ids][]", "" %>
|
|
65
68
|
</div>
|
|
66
69
|
|
|
@@ -162,6 +162,11 @@
|
|
|
162
162
|
<p class="ck-kicker">Runs</p>
|
|
163
163
|
<%= render "completion_kit/runs/table", runs: @runs %>
|
|
164
164
|
</section>
|
|
165
|
+
<% elsif CompletionKit::Run.where(prompt_id: @prompt.family_versions.select(:id)).exists? %>
|
|
166
|
+
<section class="ck-card--spaced">
|
|
167
|
+
<p class="ck-kicker">Runs</p>
|
|
168
|
+
<div class="ck-empty">No runs to show.</div>
|
|
169
|
+
</section>
|
|
165
170
|
<% end %>
|
|
166
171
|
|
|
167
172
|
<%= ck_runs_display_footer(@runs) %>
|
|
@@ -22,10 +22,33 @@
|
|
|
22
22
|
|
|
23
23
|
<div class="ck-field">
|
|
24
24
|
<%= form.label :api_key, "API key", class: "ck-label" %>
|
|
25
|
-
<%= form.text_area :api_key, rows: 3, class: "ck-input ck-input--area ck-input--code", placeholder: "Paste the provider API key", **ck_field_aria(form, :api_key) %>
|
|
25
|
+
<%= form.text_area :api_key, value: (provider_credential.persisted? ? "" : provider_credential.api_key), rows: 3, class: "ck-input ck-input--area ck-input--code", placeholder: "Paste the provider API key", **ck_field_aria(form, :api_key) %>
|
|
26
26
|
<%= ck_field_error(form, :api_key) %>
|
|
27
|
+
<% if provider_credential.api_key_stored? %>
|
|
28
|
+
<p class="ck-field-hint">
|
|
29
|
+
<% if provider_credential.api_key_ending %>
|
|
30
|
+
Leave blank to keep the current key ending in <code><%= provider_credential.api_key_ending %></code>.
|
|
31
|
+
<% else %>
|
|
32
|
+
Leave blank to keep the current key.
|
|
33
|
+
<% end %>
|
|
34
|
+
Enter it again if you change the provider or endpoint.
|
|
35
|
+
</p>
|
|
36
|
+
<% end %>
|
|
27
37
|
</div>
|
|
28
38
|
|
|
39
|
+
<% if provider_credential.api_key_stored? %>
|
|
40
|
+
<div class="ck-field">
|
|
41
|
+
<label class="ck-checkbox-label">
|
|
42
|
+
<%= check_box_tag "provider_credential[remove_api_key]", "1", local_assigns[:remove_api_key], id: "provider_credential_remove_api_key", class: "ck-checkbox" %>
|
|
43
|
+
<span class="ck-checkbox-label__box" aria-hidden="true"></span>
|
|
44
|
+
<span class="ck-checkbox-label__body">
|
|
45
|
+
<span class="ck-checkbox-label__text">Remove the stored key</span>
|
|
46
|
+
<span class="ck-checkbox-label__hint">For endpoints that run without a key, such as a local Ollama server.</span>
|
|
47
|
+
</span>
|
|
48
|
+
</label>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
51
|
+
|
|
29
52
|
<div class="ck-field">
|
|
30
53
|
<%= form.label :api_endpoint, "API endpoint", class: "ck-label" %>
|
|
31
54
|
<%= form.text_field :api_endpoint, class: "ck-input", placeholder: "Needed for Ollama, Azure AI Foundry, or custom OpenAI-compatible endpoints", **ck_field_aria(form, :api_endpoint) %>
|
|
@@ -19,11 +19,10 @@
|
|
|
19
19
|
<% default_endpoints = {"openai" => "https://api.openai.com", "anthropic" => "https://api.anthropic.com"} %>
|
|
20
20
|
<div class="ck-provider-list" data-ck-statuses-url="<%= statuses_provider_credentials_path %>">
|
|
21
21
|
<% @provider_credentials.each do |provider_credential| %>
|
|
22
|
-
<% masked_key = provider_credential.api_key.present? ? "#{provider_credential.api_key[0..6]}#{'•' * 12}#{provider_credential.api_key[-4..]}" : "—" %>
|
|
23
22
|
<a href="<%= edit_provider_credential_path(provider_credential) %>" class="ck-provider-card">
|
|
24
23
|
<div class="ck-provider-card__header">
|
|
25
24
|
<span class="ck-provider-card__name"><%= provider_credential.display_provider %></span>
|
|
26
|
-
<span class="ck-provider-card__key"><%=
|
|
25
|
+
<span class="ck-provider-card__key"><%= provider_credential.api_key_stored? ? "#{'•' * 12}#{provider_credential.api_key_ending}" : "No key" %></span>
|
|
27
26
|
</div>
|
|
28
27
|
|
|
29
28
|
<div class="ck-provider-card__meta">
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
<% if CompletionKit.config.judge_agreement_enabled && review.metric && review.ai_score && review.metric_version&.llm_judge? %>
|
|
140
140
|
<% existing = CompletionKit::Agreement.find_by(
|
|
141
141
|
response_id: @response.id, metric_id: review.metric_id,
|
|
142
|
-
created_by:
|
|
142
|
+
created_by: ck_current_reviewer
|
|
143
143
|
) %>
|
|
144
144
|
<%= render "completion_kit/agreements/buttons",
|
|
145
145
|
review: review, agreement: existing, run: @run,
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
<div class="ck-metric-checkboxes" data-metric-checkboxes role="group" aria-labelledby="run_metrics_label">
|
|
161
161
|
<% @all_metrics.each do |metric| %>
|
|
162
162
|
<label class="ck-checkbox-label" data-metric-tags="<%= metric.tag_names.join(",") %>" data-compare-expected="<%= metric.check? && metric.check_config.to_h["compare_to"] == "expected" ? "1" : "0" %>">
|
|
163
|
-
<%= check_box_tag "run[metric_ids][]", metric.id, run.
|
|
163
|
+
<%= check_box_tag "run[metric_ids][]", metric.id, run.selected_metric_ids.include?(metric.id), class: "ck-checkbox", id: "run_metric_#{metric.id}" %>
|
|
164
164
|
<span class="ck-checkbox-label__box" aria-hidden="true"></span>
|
|
165
165
|
<span class="ck-checkbox-label__body">
|
|
166
166
|
<span class="ck-checkbox-label__text"><%= metric.name %></span>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<p>No runs match these tags. <%= link_to "Clear filters", runs_path, class: "ck-link" %>.</p>
|
|
22
22
|
</div>
|
|
23
23
|
<% elsif CompletionKit::Run.exists? %>
|
|
24
|
-
<div class="ck-empty">
|
|
24
|
+
<div class="ck-empty">No runs to show.</div>
|
|
25
25
|
<% else %>
|
|
26
26
|
<div class="ck-empty">No runs yet. <%= link_to "Create your first run →", new_run_path, class: "ck-link" %></div>
|
|
27
27
|
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
en:
|
|
2
|
+
activerecord:
|
|
3
|
+
attributes:
|
|
4
|
+
completion_kit/prompt:
|
|
5
|
+
llm_model: Model
|
|
6
|
+
completion_kit/run:
|
|
7
|
+
judge_model: Judge model
|
|
8
|
+
metric_ids: Metrics
|
|
9
|
+
completion_kit/metric_group:
|
|
10
|
+
metric_ids: Metrics
|
|
11
|
+
completion_kit/provider_credential:
|
|
12
|
+
api_key: API key
|
|
@@ -1,8 +1,38 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
1
4
|
module CompletionKit
|
|
2
5
|
class Error < StandardError; end
|
|
3
6
|
|
|
4
7
|
class ConfigurationError < Error; end
|
|
5
8
|
|
|
9
|
+
class UnknownModelError < ConfigurationError
|
|
10
|
+
attr_reader :model_name
|
|
11
|
+
|
|
12
|
+
def initialize(model_name = nil)
|
|
13
|
+
@model_name = model_name
|
|
14
|
+
super(%(No configured provider serves model "#{model_name}"))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class EndpointError < Error
|
|
19
|
+
CONNECTION_MESSAGE = "Could not connect to the endpoint."
|
|
20
|
+
TIMEOUT_MESSAGE = "The endpoint did not respond in time."
|
|
21
|
+
NOT_JSON_MESSAGE = "The endpoint did not return JSON."
|
|
22
|
+
|
|
23
|
+
def self.from(error)
|
|
24
|
+
message = case error
|
|
25
|
+
when Faraday::TimeoutError then TIMEOUT_MESSAGE
|
|
26
|
+
when Faraday::Error then CONNECTION_MESSAGE
|
|
27
|
+
when JSON::ParserError then NOT_JSON_MESSAGE
|
|
28
|
+
end
|
|
29
|
+
return error unless message
|
|
30
|
+
|
|
31
|
+
Rails.logger.warn("[CompletionKit] endpoint request failed: #{error.class}: #{error.message}")
|
|
32
|
+
new(message)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
6
36
|
class RateLimitError < Error
|
|
7
37
|
attr_reader :provider, :status, :retry_after
|
|
8
38
|
|
data/lib/completion_kit.rb
CHANGED
|
@@ -7,7 +7,7 @@ module CompletionKit
|
|
|
7
7
|
class Configuration
|
|
8
8
|
attr_accessor :openai_api_key, :anthropic_api_key, :ollama_api_key, :ollama_api_endpoint
|
|
9
9
|
attr_accessor :judge_model, :high_quality_threshold, :medium_quality_threshold
|
|
10
|
-
attr_accessor :username, :password, :auth_strategy, :api_token
|
|
10
|
+
attr_accessor :username, :password, :auth_strategy, :api_token, :current_reviewer
|
|
11
11
|
attr_accessor :tenant_scope, :tenant_scope_columns
|
|
12
12
|
attr_accessor :api_reference_authentication_partial
|
|
13
13
|
attr_accessor :runs_display_scope, :runs_display_footer_partial
|
|
@@ -45,6 +45,15 @@ CompletionKit.configure do |config|
|
|
|
45
45
|
# config.username = "admin"
|
|
46
46
|
# config.password = ENV['COMPLETION_KIT_PASSWORD']
|
|
47
47
|
|
|
48
|
+
# Reviewer name for verdicts recorded in the web UI. The callable receives the
|
|
49
|
+
# controller. Each reviewer keeps one verdict per response and metric, so return
|
|
50
|
+
# a String unique to the signed-in person. Blank or nil falls back to username,
|
|
51
|
+
# then "operator". Any other return type raises CompletionKit::ConfigurationError.
|
|
52
|
+
# config.current_reviewer = ->(controller) { controller.current_user&.email }
|
|
53
|
+
# Behind a proxy that sets X-Remote-User. Only use this when a trusted proxy
|
|
54
|
+
# always overwrites the header, or any client can claim any name:
|
|
55
|
+
# config.current_reviewer = ->(controller) { controller.request.headers["X-Remote-User"] }
|
|
56
|
+
|
|
48
57
|
# Rate limiting (requests per minute, per IP)
|
|
49
58
|
# The REST API and MCP endpoint share api_rate_limit; the web UI uses
|
|
50
59
|
# web_rate_limit. Both are enforced through Rails.cache.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
namespace :completion_kit do
|
|
2
|
-
desc "Mark in-flight runs as failed (for use after the queue adapter cutover)"
|
|
2
|
+
desc "Mark in-flight runs in every tenant as failed (for use after the queue adapter cutover)"
|
|
3
3
|
task mark_interrupted_runs_failed: :environment do
|
|
4
|
-
scope = CompletionKit::Run.where(status: "running")
|
|
4
|
+
scope = CompletionKit::Run.unscoped.where(status: "running")
|
|
5
5
|
count = scope.count
|
|
6
6
|
scope.update_all(
|
|
7
7
|
status: "failed",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: completion-kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.28.
|
|
4
|
+
version: 0.28.45
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Bastin
|
|
@@ -238,7 +238,6 @@ files:
|
|
|
238
238
|
- app/models/completion_kit/application_record.rb
|
|
239
239
|
- app/models/completion_kit/dashboard_dismissal.rb
|
|
240
240
|
- app/models/completion_kit/dataset.rb
|
|
241
|
-
- app/models/completion_kit/mcp_session.rb
|
|
242
241
|
- app/models/completion_kit/metric.rb
|
|
243
242
|
- app/models/completion_kit/metric_group.rb
|
|
244
243
|
- app/models/completion_kit/metric_group_membership.rb
|
|
@@ -256,6 +255,7 @@ files:
|
|
|
256
255
|
- app/models/completion_kit/tag.rb
|
|
257
256
|
- app/models/completion_kit/tagging.rb
|
|
258
257
|
- app/models/concerns/completion_kit/has_job_status.rb
|
|
258
|
+
- app/models/concerns/completion_kit/metric_selection.rb
|
|
259
259
|
- app/models/concerns/completion_kit/taggable.rb
|
|
260
260
|
- app/services/completion_kit/agreement_math.rb
|
|
261
261
|
- app/services/completion_kit/anthropic_client.rb
|
|
@@ -274,6 +274,7 @@ files:
|
|
|
274
274
|
- app/services/completion_kit/checks/valid_json.rb
|
|
275
275
|
- app/services/completion_kit/csv_processor.rb
|
|
276
276
|
- app/services/completion_kit/dashboard_stats.rb
|
|
277
|
+
- app/services/completion_kit/json_object.rb
|
|
277
278
|
- app/services/completion_kit/judge_service.rb
|
|
278
279
|
- app/services/completion_kit/llm_client.rb
|
|
279
280
|
- app/services/completion_kit/mcp_dispatcher.rb
|
|
@@ -398,6 +399,7 @@ files:
|
|
|
398
399
|
- app/views/completion_kit/tags/index.html.erb
|
|
399
400
|
- app/views/completion_kit/tags/new.html.erb
|
|
400
401
|
- app/views/layouts/completion_kit/application.html.erb
|
|
402
|
+
- config/locales/en.yml
|
|
401
403
|
- config/routes.rb
|
|
402
404
|
- db/migrate/20260311000001_create_completion_kit_tables.rb
|
|
403
405
|
- db/migrate/20260326000001_rename_criteria_to_instruction_on_metrics_and_reviews.rb
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
module CompletionKit
|
|
2
|
-
# MCP session marker — one row per active client session, kept in the
|
|
3
|
-
# database so sessions survive Puma restarts, deploys, and Rails.cache
|
|
4
|
-
# eviction.
|
|
5
|
-
#
|
|
6
|
-
# Two things to know about why every query goes through `unscoped`:
|
|
7
|
-
#
|
|
8
|
-
# 1. CompletionKit::ApplicationRecord applies the host app's tenant_scope as
|
|
9
|
-
# a default_scope. MCP sessions are per-CONNECTION, not per-tenant — the
|
|
10
|
-
# table has no organization_id column. Letting the tenant_scope into a
|
|
11
|
-
# session lookup turns "is this session live?" into either a SQL error
|
|
12
|
-
# (no such column) or a false negative (`WHERE 1=0`), which surfaces to
|
|
13
|
-
# the client as a spurious "Session not initialized" right after init.
|
|
14
|
-
# 2. `active?` slides expires_at forward when a session is more than halfway
|
|
15
|
-
# through its TTL, so an MCP connection that keeps making calls stays
|
|
16
|
-
# alive instead of expiring on the original 1-hour wall clock.
|
|
17
|
-
#
|
|
18
|
-
# Expired rows are opportunistically pruned on every new session start, so
|
|
19
|
-
# the table stays bounded by recent activity.
|
|
20
|
-
class McpSession < ApplicationRecord
|
|
21
|
-
self.table_name = "completion_kit_mcp_sessions"
|
|
22
|
-
|
|
23
|
-
SESSION_TTL = 1.hour
|
|
24
|
-
|
|
25
|
-
def self.start!
|
|
26
|
-
prune_expired!
|
|
27
|
-
unscoped.create!(session_id: SecureRandom.uuid, expires_at: SESSION_TTL.from_now).session_id
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.active?(session_id)
|
|
31
|
-
return false if session_id.blank?
|
|
32
|
-
|
|
33
|
-
row = unscoped.where(session_id: session_id).where("expires_at > ?", Time.current).first
|
|
34
|
-
return false unless row
|
|
35
|
-
|
|
36
|
-
slide_expiry(row)
|
|
37
|
-
true
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def self.destroy_session(session_id)
|
|
41
|
-
unscoped.where(session_id: session_id).delete_all
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def self.prune_expired!
|
|
45
|
-
unscoped.where("expires_at < ?", Time.current).delete_all
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def self.slide_expiry(row)
|
|
49
|
-
half_ttl_from_now = (SESSION_TTL / 2).from_now
|
|
50
|
-
return if row.expires_at > half_ttl_from_now
|
|
51
|
-
row.update_column(:expires_at, SESSION_TTL.from_now)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|