completion-kit 0.21.1 → 0.22.0
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/app/assets/javascripts/completion_kit/application.js +15 -4
- data/app/assets/stylesheets/completion_kit/application.css +28 -1
- data/app/controllers/completion_kit/api/v1/metrics_controller.rb +1 -1
- data/app/controllers/completion_kit/metrics_controller.rb +7 -1
- data/app/helpers/completion_kit/application_helper.rb +13 -0
- data/app/jobs/completion_kit/check_review_job.rb +10 -1
- data/app/models/completion_kit/metric.rb +16 -1
- data/app/services/completion_kit/checks/expected_resolver.rb +17 -0
- data/app/services/completion_kit/checks/registry.rb +6 -0
- data/app/services/completion_kit/mcp_tools/metrics.rb +4 -1
- data/app/services/completion_kit/starter_metrics.rb +8 -0
- data/app/views/completion_kit/metrics/_check_spec.html.erb +7 -1
- data/app/views/completion_kit/metrics/_form.html.erb +19 -0
- data/lib/completion_kit/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa4d0b76c07fb964d0e7db4d26c101653d8ed0e27745e639eabb78d24653dee5
|
|
4
|
+
data.tar.gz: 61b5c7e4b150b4d0fad6e6d54d28fe6a69d27fa1223d9df2388c9229e039d954
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f7b9f2bb3fe037528736c388eff47dd4032edf9c6a5598d851cfb7c36f7a1ee81a818fd5b94866ec6855af87c5e018ef53dd691bcf8d816b316bfe7cd7793a8
|
|
7
|
+
data.tar.gz: 14203d5a281eeb6c43ab266d567f9c68803200654d4c8efcd48457936f19a60812eca6c0e40d863f279d66b3c50b674faf8a1f10d011536cf2f68a6a5f47cca2
|
|
@@ -221,20 +221,31 @@ var CK_CHECK_FIELDS = {
|
|
|
221
221
|
no_refusal: []
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
+
var CK_VALUE_KINDS = ["contains", "not_contains", "equals"];
|
|
225
|
+
|
|
224
226
|
function ckApplyCheckFields(scope) {
|
|
225
227
|
if (!scope) return;
|
|
226
228
|
var kindSelect = scope.querySelector('[name="metric[check_config][check_kind]"]');
|
|
227
229
|
if (!kindSelect) return;
|
|
228
|
-
var
|
|
230
|
+
var kind = kindSelect.value;
|
|
231
|
+
var visible = (CK_CHECK_FIELDS[kind] || []).slice();
|
|
229
232
|
var targetSelect = scope.querySelector('[name="metric[check_config][target]"]');
|
|
230
233
|
var targetIsJsonPath = !!(targetSelect && targetSelect.value === "json_path");
|
|
234
|
+
|
|
235
|
+
if (CK_VALUE_KINDS.indexOf(kind) !== -1) {
|
|
236
|
+
visible.push("compare_to");
|
|
237
|
+
var comparison = scope.querySelector('[name="metric[check_config][compare_to]"]:checked');
|
|
238
|
+
if (comparison && comparison.value === "expected") {
|
|
239
|
+
visible = visible.filter(function(key) { return key !== "value"; });
|
|
240
|
+
visible.push("expected_path");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
231
244
|
scope.querySelectorAll("[data-ck-check-field]").forEach(function(field) {
|
|
232
245
|
var key = field.getAttribute("data-ck-check-field");
|
|
233
246
|
var show;
|
|
234
247
|
if (key === "target_path") {
|
|
235
248
|
show = targetIsJsonPath;
|
|
236
|
-
} else if (!visible) {
|
|
237
|
-
show = true;
|
|
238
249
|
} else {
|
|
239
250
|
show = visible.indexOf(key) !== -1;
|
|
240
251
|
}
|
|
@@ -274,7 +285,7 @@ document.addEventListener("change", function(e) {
|
|
|
274
285
|
ckApplyMetricType(group);
|
|
275
286
|
return;
|
|
276
287
|
}
|
|
277
|
-
if (target.name === "metric[check_config][check_kind]" || target.name === "metric[check_config][target]") {
|
|
288
|
+
if (target.name === "metric[check_config][check_kind]" || target.name === "metric[check_config][target]" || target.name === "metric[check_config][compare_to]") {
|
|
278
289
|
var scope = target.closest('[data-ck-metric-editor="check"]') || target.closest("form");
|
|
279
290
|
ckApplyCheckFields(scope);
|
|
280
291
|
}
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
--ck-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
53
53
|
--ck-radius: 6px;
|
|
54
54
|
--ck-radius-lg: 10px;
|
|
55
|
+
--ck-field-gap: 1.15rem;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
*,
|
|
@@ -1613,10 +1614,32 @@ tr:hover .ck-chip--publish {
|
|
|
1613
1614
|
|
|
1614
1615
|
.ck-form-card {
|
|
1615
1616
|
display: grid;
|
|
1616
|
-
gap:
|
|
1617
|
+
gap: var(--ck-field-gap);
|
|
1617
1618
|
max-width: 36rem;
|
|
1618
1619
|
}
|
|
1619
1620
|
|
|
1621
|
+
.ck-form-card [data-ck-metric-type],
|
|
1622
|
+
.ck-form-card [data-ck-metric-editor] {
|
|
1623
|
+
display: grid;
|
|
1624
|
+
gap: var(--ck-field-gap);
|
|
1625
|
+
align-content: start;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
.ck-form-card [data-ck-metric-type] > .ck-section-title + .ck-hint,
|
|
1629
|
+
.ck-form-card [data-ck-metric-editor] > .ck-section-title + .ck-hint {
|
|
1630
|
+
margin-top: calc(0.35rem - var(--ck-field-gap));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
.ck-form-card [data-ck-metric-type] > .ck-radio,
|
|
1634
|
+
.ck-form-card [data-ck-metric-editor] > .ck-checkbox {
|
|
1635
|
+
margin-top: 0;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.ck-form-card [data-ck-metric-type] > .ck-radio + .ck-radio,
|
|
1639
|
+
.ck-form-card [data-ck-metric-editor] > .ck-checkbox + .ck-checkbox {
|
|
1640
|
+
margin-top: calc(0.4rem - var(--ck-field-gap));
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1620
1643
|
.ck-version-note {
|
|
1621
1644
|
margin: 0 0 1rem;
|
|
1622
1645
|
max-width: 36rem;
|
|
@@ -1922,6 +1945,10 @@ label.ck-checkbox input {
|
|
|
1922
1945
|
cursor: pointer;
|
|
1923
1946
|
}
|
|
1924
1947
|
|
|
1948
|
+
label.ck-checkbox[hidden] {
|
|
1949
|
+
display: none;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1925
1952
|
.ck-field-row {
|
|
1926
1953
|
display: flex;
|
|
1927
1954
|
gap: 1rem;
|
|
@@ -70,7 +70,7 @@ module CompletionKit
|
|
|
70
70
|
def metric_params
|
|
71
71
|
params.permit(:name, :instruction, :metric_type,
|
|
72
72
|
rubric_bands: [:stars, :description],
|
|
73
|
-
check_config: %i[check_kind target target_path value pattern json_path expected min max case_sensitive multiline trim],
|
|
73
|
+
check_config: %i[check_kind target target_path compare_to value pattern json_path expected expected_path min max case_sensitive multiline trim],
|
|
74
74
|
tag_names: [])
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -247,7 +247,7 @@ module CompletionKit
|
|
|
247
247
|
def metric_params
|
|
248
248
|
permitted = params.require(:metric).permit(:name, :instruction, :metric_type,
|
|
249
249
|
rubric_bands: [:stars, :description],
|
|
250
|
-
check_config: %i[check_kind target target_path value pattern json_path expected min max case_sensitive multiline trim],
|
|
250
|
+
check_config: %i[check_kind target target_path compare_to value pattern json_path expected expected_path min max case_sensitive multiline trim],
|
|
251
251
|
tag_names: [])
|
|
252
252
|
permitted[:check_config] = normalize_check_config(permitted[:check_config]) if permitted.key?(:check_config)
|
|
253
253
|
permitted
|
|
@@ -258,6 +258,12 @@ module CompletionKit
|
|
|
258
258
|
%w[min max].each { |key| hash[key] = hash[key].to_i if hash[key].present? }
|
|
259
259
|
%w[case_sensitive multiline trim].each { |key| hash[key] = ActiveModel::Type::Boolean.new.cast(hash[key]) if hash.key?(key) }
|
|
260
260
|
hash["expected"] = coerce_scalar(hash["expected"]) if hash["expected"].present?
|
|
261
|
+
if hash["compare_to"] == "expected"
|
|
262
|
+
hash.delete("value")
|
|
263
|
+
else
|
|
264
|
+
hash.delete("compare_to")
|
|
265
|
+
hash.delete("expected_path")
|
|
266
|
+
end
|
|
261
267
|
hash.reject { |_, value| value.nil? || value == "" }
|
|
262
268
|
end
|
|
263
269
|
|
|
@@ -180,6 +180,11 @@ module CompletionKit
|
|
|
180
180
|
"no_refusal" => "Is not a refusal"
|
|
181
181
|
}.freeze
|
|
182
182
|
|
|
183
|
+
CHECK_COMPARE_TO_LABELS = {
|
|
184
|
+
"constant" => "A value you type",
|
|
185
|
+
"expected" => "Each row's expected value"
|
|
186
|
+
}.freeze
|
|
187
|
+
|
|
183
188
|
CHECK_TARGET_LABELS = {
|
|
184
189
|
"response_text" => "The response text",
|
|
185
190
|
"input_data" => "The input row",
|
|
@@ -191,6 +196,8 @@ module CompletionKit
|
|
|
191
196
|
"pattern" => "Pattern",
|
|
192
197
|
"json_path" => "JSON path",
|
|
193
198
|
"expected" => "Expected value",
|
|
199
|
+
"expected_path" => "Field in the answer key",
|
|
200
|
+
"compare_to" => "Compare against",
|
|
194
201
|
"target_path" => "Path into the JSON",
|
|
195
202
|
"min" => "Shortest allowed",
|
|
196
203
|
"max" => "Longest allowed",
|
|
@@ -211,6 +218,12 @@ module CompletionKit
|
|
|
211
218
|
CHECK_FIELD_LABELS.fetch(key.to_s) { key.to_s.humanize }
|
|
212
219
|
end
|
|
213
220
|
|
|
221
|
+
def ck_check_field_value(key, value)
|
|
222
|
+
return CHECK_COMPARE_TO_LABELS.fetch(value.to_s, value) if key.to_s == "compare_to"
|
|
223
|
+
|
|
224
|
+
value
|
|
225
|
+
end
|
|
226
|
+
|
|
214
227
|
def ck_result_change_badge(change)
|
|
215
228
|
case change
|
|
216
229
|
when "broke"
|
|
@@ -41,7 +41,16 @@ module CompletionKit
|
|
|
41
41
|
return Checks::Result.new(passed: false, detail: "could not resolve target")
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
kind = config["check_kind"]
|
|
45
|
+
if config["compare_to"] == "expected" && Checks::Registry.compares_value?(kind)
|
|
46
|
+
expected_value = Checks::ExpectedResolver.call(response, config)
|
|
47
|
+
if expected_value.equal?(Checks::ExpectedResolver::UNRESOLVED)
|
|
48
|
+
return Checks::Result.new(passed: false, detail: "no expected value for this row")
|
|
49
|
+
end
|
|
50
|
+
config = config.merge("value" => expected_value)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
Checks::Registry.fetch(kind).call(target_value, config)
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
def record_terminal_failure!(error)
|
|
@@ -132,10 +132,23 @@ module CompletionKit
|
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
validate_check_target(config)
|
|
135
|
+
validate_check_compare_to(config, kind)
|
|
135
136
|
validate_check_required_keys(config, kind)
|
|
136
137
|
validate_check_kind_rules(config, kind)
|
|
137
138
|
end
|
|
138
139
|
|
|
140
|
+
def validate_check_compare_to(config, kind)
|
|
141
|
+
return unless config.key?("compare_to")
|
|
142
|
+
|
|
143
|
+
unless %w[constant expected].include?(config["compare_to"])
|
|
144
|
+
errors.add(:check_config, "compare_to must be constant or expected")
|
|
145
|
+
return
|
|
146
|
+
end
|
|
147
|
+
if config["compare_to"] == "expected" && !CompletionKit::Checks::Registry.compares_value?(kind)
|
|
148
|
+
errors.add(:check_config, "compare_to expected only applies to contains, not_contains, or equals")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
139
152
|
def validate_check_target(config)
|
|
140
153
|
target = config["target"].presence || "response_text"
|
|
141
154
|
unless CompletionKit::Checks::TargetResolver::TARGETS.include?(target)
|
|
@@ -147,7 +160,9 @@ module CompletionKit
|
|
|
147
160
|
end
|
|
148
161
|
|
|
149
162
|
def validate_check_required_keys(config, kind)
|
|
150
|
-
CompletionKit::Checks::Registry.required_keys.fetch(kind)
|
|
163
|
+
required = CompletionKit::Checks::Registry.required_keys.fetch(kind)
|
|
164
|
+
required -= %w[value] if config["compare_to"] == "expected" && CompletionKit::Checks::Registry.compares_value?(kind)
|
|
165
|
+
required.each do |required_key|
|
|
151
166
|
if required_key == "expected"
|
|
152
167
|
errors.add(:check_config, "expected is required") unless config.key?("expected")
|
|
153
168
|
elsif config[required_key].to_s.strip.empty?
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module CompletionKit
|
|
2
|
+
module Checks
|
|
3
|
+
module ExpectedResolver
|
|
4
|
+
UNRESOLVED = TargetResolver::UNRESOLVED
|
|
5
|
+
|
|
6
|
+
def self.call(response, config)
|
|
7
|
+
raw = response.expected_output
|
|
8
|
+
return UNRESOLVED if raw.blank?
|
|
9
|
+
|
|
10
|
+
path = config["expected_path"].to_s.strip
|
|
11
|
+
return raw.to_s if path.empty?
|
|
12
|
+
|
|
13
|
+
TargetResolver.resolve_json_path(raw.to_s, path)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -23,6 +23,8 @@ module CompletionKit
|
|
|
23
23
|
"no_refusal" => []
|
|
24
24
|
}.freeze
|
|
25
25
|
|
|
26
|
+
VALUE_COMPARISON_KINDS = %w[contains not_contains equals].freeze
|
|
27
|
+
|
|
26
28
|
KINDS = CHECKS.keys.freeze
|
|
27
29
|
|
|
28
30
|
def self.kinds
|
|
@@ -33,6 +35,10 @@ module CompletionKit
|
|
|
33
35
|
REQUIRED_KEYS
|
|
34
36
|
end
|
|
35
37
|
|
|
38
|
+
def self.compares_value?(kind)
|
|
39
|
+
VALUE_COMPARISON_KINDS.include?(kind)
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
def self.fetch(kind)
|
|
37
43
|
CHECKS.fetch(kind).new
|
|
38
44
|
end
|
|
@@ -13,6 +13,8 @@ module CompletionKit
|
|
|
13
13
|
pattern: {type: "string"},
|
|
14
14
|
json_path: {type: "string"},
|
|
15
15
|
expected: {},
|
|
16
|
+
compare_to: {type: "string", enum: %w[constant expected]},
|
|
17
|
+
expected_path: {type: "string"},
|
|
16
18
|
min: {type: "integer"},
|
|
17
19
|
max: {type: "integer"},
|
|
18
20
|
case_sensitive: {type: "boolean"},
|
|
@@ -23,7 +25,8 @@ module CompletionKit
|
|
|
23
25
|
|
|
24
26
|
CHECK_CONFIG_HINT = "For a deterministic check set metric_type:\"check\" and check_config. Per-kind required keys: " \
|
|
25
27
|
"value (contains/not_contains/equals), pattern (regex), json_path+expected (json_path_equals), " \
|
|
26
|
-
"min and/or max (length_bounds); valid_json and no_refusal take no extra keys. target_path is required when target is json_path."
|
|
28
|
+
"min and/or max (length_bounds); valid_json and no_refusal take no extra keys. target_path is required when target is json_path. " \
|
|
29
|
+
"For contains, not_contains, and equals, set compare_to:\"expected\" to grade against each row's own expected_output (ground truth) instead of a constant value (drop value); add expected_path to dig into the expected value when it is JSON."
|
|
27
30
|
|
|
28
31
|
TOOLS = {
|
|
29
32
|
"metrics_list" => {
|
|
@@ -96,6 +96,14 @@ module CompletionKit
|
|
|
96
96
|
catches: "A required marker, citation, or keyword the output must always include. Set the value to the token you require.",
|
|
97
97
|
metric_type: "check",
|
|
98
98
|
check_config: { "check_kind" => "contains", "target" => "response_text", "value" => "REQUIRED" }
|
|
99
|
+
),
|
|
100
|
+
Starter.new(
|
|
101
|
+
key: "matches_expected",
|
|
102
|
+
name: "Matches the answer key",
|
|
103
|
+
description: "Does the output equal this row's own expected value?",
|
|
104
|
+
catches: "Extraction and classification against ground truth, where every row has its own correct answer in the expected_output column. Exact match, deterministic, no judge call.",
|
|
105
|
+
metric_type: "check",
|
|
106
|
+
check_config: { "check_kind" => "equals", "target" => "response_text", "compare_to" => "expected" }
|
|
99
107
|
)
|
|
100
108
|
].freeze
|
|
101
109
|
|
|
@@ -11,7 +11,13 @@
|
|
|
11
11
|
<% config.except("check_kind", "target").each do |key, value| %>
|
|
12
12
|
<div class="ck-check-spec__row">
|
|
13
13
|
<dt class="ck-check-spec__term"><%= ck_check_field_label(key) %></dt>
|
|
14
|
-
<dd class="ck-check-spec__val"
|
|
14
|
+
<dd class="ck-check-spec__val">
|
|
15
|
+
<% if key == "compare_to" %>
|
|
16
|
+
<%= ck_check_field_value(key, value) %>
|
|
17
|
+
<% else %>
|
|
18
|
+
<code class="ck-check-spec__code"><%= value %></code>
|
|
19
|
+
<% end %>
|
|
20
|
+
</dd>
|
|
15
21
|
</div>
|
|
16
22
|
<% end %>
|
|
17
23
|
</dl>
|
|
@@ -183,6 +183,19 @@
|
|
|
183
183
|
<input type="text" name="metric[check_config][target_path]" id="metric_check_target_path" class="ck-input" value="<%= check["target_path"] %>">
|
|
184
184
|
</div>
|
|
185
185
|
|
|
186
|
+
<div class="ck-field" data-ck-check-field="compare_to">
|
|
187
|
+
<p class="ck-label">Compare against</p>
|
|
188
|
+
<p class="ck-hint">Check every output against one fixed value, or against each row's own expected value (its answer key).</p>
|
|
189
|
+
<label class="ck-radio">
|
|
190
|
+
<input type="radio" name="metric[check_config][compare_to]" value="constant" <%= "checked" unless check["compare_to"] == "expected" %>>
|
|
191
|
+
<span>A value I type</span>
|
|
192
|
+
</label>
|
|
193
|
+
<label class="ck-radio">
|
|
194
|
+
<input type="radio" name="metric[check_config][compare_to]" value="expected" <%= "checked" if check["compare_to"] == "expected" %>>
|
|
195
|
+
<span>Each row's expected value</span>
|
|
196
|
+
</label>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
186
199
|
<div class="ck-field" data-ck-check-field="value">
|
|
187
200
|
<label class="ck-label" for="metric_check_value">Text to look for</label>
|
|
188
201
|
<input type="text" name="metric[check_config][value]" id="metric_check_value" class="ck-input" value="<%= check["value"] %>">
|
|
@@ -206,6 +219,12 @@
|
|
|
206
219
|
<input type="text" name="metric[check_config][expected]" id="metric_check_expected" class="ck-input" value="<%= check["expected"] %>">
|
|
207
220
|
</div>
|
|
208
221
|
|
|
222
|
+
<div class="ck-field" data-ck-check-field="expected_path">
|
|
223
|
+
<label class="ck-label" for="metric_check_expected_path">Field in the answer key</label>
|
|
224
|
+
<p class="ck-hint">Optional. Each row is compared to its own expected value. If that answer key is JSON, dig into it here, like vin. Leave blank to use the whole expected value.</p>
|
|
225
|
+
<input type="text" name="metric[check_config][expected_path]" id="metric_check_expected_path" class="ck-input" value="<%= check["expected_path"] %>">
|
|
226
|
+
</div>
|
|
227
|
+
|
|
209
228
|
<div class="ck-field-row">
|
|
210
229
|
<div class="ck-field" data-ck-check-field="min">
|
|
211
230
|
<label class="ck-label" for="metric_check_min">Shortest allowed</label>
|
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.
|
|
4
|
+
version: 0.22.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Bastin
|
|
@@ -302,6 +302,7 @@ files:
|
|
|
302
302
|
- app/services/completion_kit/api_config.rb
|
|
303
303
|
- app/services/completion_kit/checks/contains.rb
|
|
304
304
|
- app/services/completion_kit/checks/equals.rb
|
|
305
|
+
- app/services/completion_kit/checks/expected_resolver.rb
|
|
305
306
|
- app/services/completion_kit/checks/json_path_equals.rb
|
|
306
307
|
- app/services/completion_kit/checks/length_bounds.rb
|
|
307
308
|
- app/services/completion_kit/checks/no_refusal.rb
|