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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4fc454c51bfbe7917a7b3f248b055d58b6a147fe7e9325dadc196d9aebf7c762
4
- data.tar.gz: 5e96ffb9c4d1e98c4917ec7847a6b734e9153142184d0b540585d8f7c8aba3b6
3
+ metadata.gz: aa4d0b76c07fb964d0e7db4d26c101653d8ed0e27745e639eabb78d24653dee5
4
+ data.tar.gz: 61b5c7e4b150b4d0fad6e6d54d28fe6a69d27fa1223d9df2388c9229e039d954
5
5
  SHA512:
6
- metadata.gz: 11ad299c28fdb4dc4be3ee113c99ebf19ec6cb9d422dbc180cb253292adc7373d2d183aef7fd21c5e7293c1acc017f3c0689f34a5ded2e13b17674a86cacb6e6
7
- data.tar.gz: b995b586a1b2c5422be141b9e94476ea0d32928dc81e8aca4210c54c1b4d8ea6cd0d7729621a0155438a2b7819977e6678e1000c343d9160ec09db7120be47e6
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 visible = CK_CHECK_FIELDS[kindSelect.value];
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: 1rem;
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
- Checks::Registry.fetch(config["check_kind"]).call(target_value, config)
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).each do |required_key|
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"><code class="ck-check-spec__code"><%= value %></code></dd>
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>
@@ -1,3 +1,3 @@
1
1
  module CompletionKit
2
- VERSION = "0.21.1"
2
+ VERSION = "0.22.0"
3
3
  end
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.21.1
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