@malloy-publisher/skills 0.1.19 → 0.1.21
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.
- package/package.json +1 -1
- package/skills/eval-answer/SKILL.md +45 -5
- package/skills/eval-answer/reference/definition-ledger.md +305 -0
- package/skills/eval-answer/reference/ledger-schema.md +36 -7
- package/skills/eval-answer/scripts/check_coverage.py +12 -21
- package/skills/eval-answer/scripts/check_coverage_test.py +22 -0
- package/skills/eval-answer/scripts/check_findable.py +386 -0
- package/skills/eval-answer/scripts/check_findable_test.py +326 -0
- package/skills/eval-answer/scripts/json_scan.py +43 -0
- package/skills/eval-answer/scripts/ledger.py +63 -7
- package/skills/eval-answer/scripts/ledger_test.py +7 -0
- package/skills/eval-answer/scripts/mcp_payload.py +111 -2
- package/skills/eval-answer/scripts/mcp_payload_test.py +73 -2
- package/skills/eval-answer/scripts/publisher_rest.py +10 -4
- package/skills/eval-answer/scripts/publisher_rest_test.py +56 -0
- package/skills/eval-answer/scripts/score_retrieval.py +326 -41
- package/skills/eval-answer/scripts/score_retrieval_test.py +537 -15
- package/skills/eval-answer/scripts/verify_definitions.py +752 -0
- package/skills/eval-answer/scripts/verify_definitions_test.py +820 -0
- package/skills/eval-answer/scripts/verify_goldens.py +336 -22
- package/skills/eval-answer/scripts/verify_goldens_test.py +261 -2
- package/skills/eval-diagnose/SKILL.md +47 -2
- package/skills/eval-diagnose/scripts/cluster_failures.py +57 -14
- package/skills/eval-diagnose/scripts/cluster_failures_test.py +127 -0
- package/skills/eval-diagnose/scripts/diagnose.py +471 -42
- package/skills/eval-diagnose/scripts/diagnose_test.py +421 -0
- package/skills/eval-import/SKILL.md +180 -1
- package/skills/eval-import/reference/case-format.md +1 -1
- package/skills/eval-improve/SKILL.md +1 -1
- package/skills/eval-improve/scripts/improve.py +29 -3
- package/skills/eval-improve/scripts/improve_test.py +51 -0
- package/skills/eval-judge/SKILL.md +1 -1
- package/skills/eval-loop/SKILL.md +112 -3
- package/skills/eval-loop/reference/measurement.md +44 -4
- package/skills/eval-loop/reference/running-a-run.md +5 -1
- package/skills/eval-loop/scripts/agent_harness.py +21 -20
- package/skills/eval-loop/scripts/agent_harness_test.py +136 -0
- package/skills/eval-loop/scripts/build_run_package.py +9 -3
- package/skills/eval-loop/scripts/flip_table.py +107 -2
- package/skills/eval-loop/scripts/flip_table_test.py +165 -0
- package/skills/eval-loop/scripts/run_baseline.py +956 -97
- package/skills/eval-loop/scripts/run_baseline_test.py +799 -1
- package/skills/eval-loop/templates/eval-run-package/README.md +19 -5
- package/skills/eval-loop/templates/eval-run-package/eval_run.malloy +48 -19
- package/skills/eval-loop/templates/eval-run-package/eval_run.malloynb +11 -4
- package/skills/eval-loop/templates/eval-run-package/public/app.js +12 -4
- package/skills/eval-report/SKILL.md +362 -0
- package/skills/malloy/SKILL.md +6 -7
- package/skills/malloy-analysis/SKILL.md +22 -45
- package/skills/malloy-analysis-pitfalls/SKILL.md +21 -6
- package/skills/malloy-analysis-report/SKILL.md +1 -1
- package/skills/malloy-analyze/SKILL.md +1 -1
- package/skills/malloy-charts/SKILL.md +2 -2
- package/skills/malloy-dashboards/SKILL.md +1 -1
- package/skills/malloy-debug/SKILL.md +1 -1
- package/skills/malloy-define/SKILL.md +1 -1
- package/skills/malloy-discover/SKILL.md +1 -1
- package/skills/malloy-document/SKILL.md +1 -1
- package/skills/malloy-getting-started/SKILL.md +1 -1
- package/skills/malloy-gotchas-modeling/SKILL.md +2 -2
- package/skills/malloy-gotchas-queries/SKILL.md +2 -2
- package/skills/malloy-gotchas-rendering/SKILL.md +2 -2
- package/skills/malloy-html-data-app-embedding/SKILL.md +1 -1
- package/skills/malloy-html-data-app-runtime/SKILL.md +2 -2
- package/skills/malloy-html-data-apps/SKILL.md +1 -1
- package/skills/malloy-lookml-review/SKILL.md +1 -1
- package/skills/malloy-materialization/SKILL.md +1 -1
- package/skills/malloy-materialization-tuning/SKILL.md +1 -1
- package/skills/malloy-model/SKILL.md +1 -1
- package/skills/malloy-model-as-you-go/SKILL.md +1 -1
- package/skills/malloy-modeling/SKILL.md +1 -1
- package/skills/malloy-notebook-chat/SKILL.md +1 -1
- package/skills/malloy-notebooks/SKILL.md +1 -1
- package/skills/malloy-patterns/SKILL.md +2 -2
- package/skills/malloy-phrase-detection/SKILL.md +12 -7
- package/skills/malloy-publish/SKILL.md +1 -1
- package/skills/malloy-queries/SKILL.md +2 -2
- package/skills/malloy-review/SKILL.md +1 -1
- package/skills/malloy-scope/SKILL.md +7 -4
|
@@ -6,11 +6,12 @@ regression they exist for: an extractor written against the flat `results`
|
|
|
6
6
|
shape returned [] for the nested one, which scored attempts that had received
|
|
7
7
|
everything at 0% recall and moved three baseline failures off "query
|
|
8
8
|
construction" onto "retrieval ranking" and "model coverage" -- the wrong team,
|
|
9
|
-
for a bug in the eval.
|
|
9
|
+
for a bug in the eval. Those are the labels as they read then; the first two
|
|
10
|
+
are now `delivered, wrong` and `not retrieved`.
|
|
10
11
|
"""
|
|
11
12
|
import unittest
|
|
12
13
|
|
|
13
|
-
from mcp_payload import entity_ids, search_terms
|
|
14
|
+
from mcp_payload import entity_hits, entity_ids, search_terms
|
|
14
15
|
|
|
15
16
|
FLAT = {"results": [
|
|
16
17
|
{"kind": "measure", "name": "total_sales_2022", "source": "order_items",
|
|
@@ -215,5 +216,75 @@ class EntityIds(unittest.TestCase):
|
|
|
215
216
|
["measure:s:m"])
|
|
216
217
|
|
|
217
218
|
|
|
219
|
+
class EntityHits(unittest.TestCase):
|
|
220
|
+
"""The provenance `entity_ids` throws away.
|
|
221
|
+
|
|
222
|
+
Without it, a required entity that never came back is indistinguishable
|
|
223
|
+
from one the agent never asked for -- a retrieval defect from a phrase
|
|
224
|
+
detection defect -- which is the reason retrieval scoring could not
|
|
225
|
+
attribute a miss.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
PAYLOAD = {"sources": [{
|
|
229
|
+
"source_info": {"resource_id": {"source": "flights"}},
|
|
230
|
+
"entities": [
|
|
231
|
+
{"entity_id": "measure:flights:flight_count", "relevance": 0.81,
|
|
232
|
+
"matched_targets": [{"search_text": "flight count",
|
|
233
|
+
"relevance": 0.81}]},
|
|
234
|
+
{"name": "carrier", "entity_type": "dimension", "relevance": 0.44,
|
|
235
|
+
"matched_targets": [{"search_text": "carrier", "relevance": 0.44}]},
|
|
236
|
+
]}]}
|
|
237
|
+
|
|
238
|
+
def test_it_agrees_with_entity_ids_about_identity(self):
|
|
239
|
+
# Two readers of one response that disagree about what an entity is
|
|
240
|
+
# called would make every comparison meaningless.
|
|
241
|
+
self.assertEqual([h["entity_id"] for h in entity_hits(self.PAYLOAD)],
|
|
242
|
+
entity_ids(self.PAYLOAD))
|
|
243
|
+
|
|
244
|
+
def test_it_carries_relevance_and_the_matching_targets(self):
|
|
245
|
+
h = {x["entity_id"]: x for x in entity_hits(self.PAYLOAD)}
|
|
246
|
+
m = h["measure:flights:flight_count"]
|
|
247
|
+
self.assertEqual(m["relevance"], 0.81)
|
|
248
|
+
self.assertEqual(m["matched_targets"],
|
|
249
|
+
[{"search_text": "flight count", "relevance": 0.81}])
|
|
250
|
+
|
|
251
|
+
def test_a_source_hit_has_no_attribution_and_says_null(self):
|
|
252
|
+
# The server discards a source card's target attribution before an
|
|
253
|
+
# entity object exists, so there is none to report. `null` is that
|
|
254
|
+
# claim; `[]` would claim the server attributed it to no target.
|
|
255
|
+
h = {x["entity_id"]: x for x in entity_hits(self.PAYLOAD)}
|
|
256
|
+
self.assertIsNone(h["source:flights:flights"]["matched_targets"])
|
|
257
|
+
|
|
258
|
+
def test_a_lexical_response_reports_no_attribution_not_an_empty_one(self):
|
|
259
|
+
# The lexical path withholds matched_targets by design: a lunr score is
|
|
260
|
+
# relative to its own query. Reading that as "matched no target" would
|
|
261
|
+
# charge the agent for never asking.
|
|
262
|
+
lex = {"sources": [{"source_info": {"resource_id": {"source": "f"}},
|
|
263
|
+
"entities": [{"entity_id": "measure:f:x"}]}]}
|
|
264
|
+
h = entity_hits(lex)[1]
|
|
265
|
+
self.assertIsNone(h["matched_targets"])
|
|
266
|
+
self.assertIsNone(h["relevance"])
|
|
267
|
+
|
|
268
|
+
def test_position_is_not_a_rank(self):
|
|
269
|
+
# Kept because it is occasionally useful for reading a response back,
|
|
270
|
+
# never because it orders by relevance: the response sorts globally
|
|
271
|
+
# and then buckets into source cards.
|
|
272
|
+
hits = entity_hits(self.PAYLOAD)
|
|
273
|
+
self.assertEqual([h["position"] for h in hits], [1, 2, 3])
|
|
274
|
+
self.assertEqual([h["position_in_source"] for h in hits], [0, 1, 2])
|
|
275
|
+
|
|
276
|
+
def test_a_duplicate_entity_is_taken_once(self):
|
|
277
|
+
dup = {"sources": [
|
|
278
|
+
{"source_info": {"resource_id": {"source": "f"}},
|
|
279
|
+
"entities": [{"entity_id": "measure:f:x", "relevance": 0.9}]},
|
|
280
|
+
{"source_info": {"resource_id": {"source": "g"}},
|
|
281
|
+
"entities": [{"entity_id": "measure:f:x", "relevance": 0.1}]}]}
|
|
282
|
+
ids = [h["entity_id"] for h in entity_hits(dup)]
|
|
283
|
+
self.assertEqual(ids.count("measure:f:x"), 1)
|
|
284
|
+
|
|
285
|
+
def test_an_empty_payload_is_no_hits(self):
|
|
286
|
+
self.assertEqual(entity_hits({}), [])
|
|
287
|
+
|
|
288
|
+
|
|
218
289
|
if __name__ == "__main__":
|
|
219
290
|
unittest.main(verbosity=2)
|
|
@@ -24,9 +24,15 @@ to the judge as the re-executed evidence for a verdict, so a nested result was
|
|
|
24
24
|
judged against a golden it could not match.
|
|
25
25
|
|
|
26
26
|
The recursive uncelling is therefore the one kept here, and both callers get it.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
|
|
28
|
+
The request also asks for `compactJson`, and that is what recovers the field
|
|
29
|
+
names INSIDE a nest. The typed envelope names only the top level, so a nested
|
|
30
|
+
record came back keyed `c0`, `c1` -- and a golden written the way its author
|
|
31
|
+
ran the query, with the real column names, then read as DRIFT, which blocks an
|
|
32
|
+
arm. That was called a limit here; it was not. Publisher has answered plain
|
|
33
|
+
rows all along, and the request simply never asked. `rows_from_result` handles
|
|
34
|
+
both shapes (compact answers with `result` as a JSON string), so the envelope
|
|
35
|
+
walk stays as the fallback for a server that ignores the flag.
|
|
30
36
|
|
|
31
37
|
TWO ERROR CONVENTIONS, ON PURPOSE
|
|
32
38
|
|
|
@@ -103,7 +109,7 @@ def query(base: str, environment: str, package: str, model: str, malloy: str,
|
|
|
103
109
|
"""Run a Malloy query. Raises on transport, HTTP or parse failure."""
|
|
104
110
|
req = urllib.request.Request(
|
|
105
111
|
_query_url(base, environment, package, model),
|
|
106
|
-
data=json.dumps({"query": malloy}).encode(),
|
|
112
|
+
data=json.dumps({"query": malloy, "compactJson": True}).encode(),
|
|
107
113
|
headers={"Content-Type": "application/json"})
|
|
108
114
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
109
115
|
return rows_from_result(json.loads(r.read().decode()))
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Tests for publisher_rest. Stdlib only: python3 publisher_rest_test.py"""
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
import unittest
|
|
6
|
+
import unittest.mock
|
|
5
7
|
|
|
8
|
+
import publisher_rest
|
|
6
9
|
from publisher_rest import rows_from_result, uncell
|
|
7
10
|
|
|
8
11
|
|
|
@@ -73,6 +76,59 @@ class RowsFromResultTests(unittest.TestCase):
|
|
|
73
76
|
[{"brand": "Levi's", "by_year": [{"c0": 2022}]}])
|
|
74
77
|
|
|
75
78
|
|
|
79
|
+
class CompactJson(unittest.TestCase):
|
|
80
|
+
"""The request asks for compact rows, which is what recovers the names
|
|
81
|
+
inside a nest.
|
|
82
|
+
|
|
83
|
+
The typed envelope does not repeat field names below the top level, so a
|
|
84
|
+
nested record came back keyed `c0`, `c1`. A golden written the way its
|
|
85
|
+
author ran the query -- with the real column names -- then read as DRIFT,
|
|
86
|
+
and drift blocks an arm. Publisher has answered this all along; the
|
|
87
|
+
request just never asked.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def send(self, body):
|
|
91
|
+
"""Capture the request `query()` builds, and hand back `body`."""
|
|
92
|
+
seen = {}
|
|
93
|
+
|
|
94
|
+
class Resp:
|
|
95
|
+
def read(self_): return json.dumps(body).encode()
|
|
96
|
+
def __enter__(self_): return self_
|
|
97
|
+
def __exit__(self_, *a): return False
|
|
98
|
+
|
|
99
|
+
def fake(req, timeout=None):
|
|
100
|
+
seen["payload"] = json.loads(req.data.decode())
|
|
101
|
+
return Resp()
|
|
102
|
+
|
|
103
|
+
with unittest.mock.patch.object(publisher_rest.urllib.request,
|
|
104
|
+
"urlopen", fake):
|
|
105
|
+
rows = publisher_rest.query("http://x", "e", "p", "m.malloy",
|
|
106
|
+
"run: x -> { ... }")
|
|
107
|
+
return seen["payload"], rows
|
|
108
|
+
|
|
109
|
+
def test_the_request_asks_for_compact_rows(self):
|
|
110
|
+
payload, _ = self.send({"result": "[]"})
|
|
111
|
+
self.assertIs(payload["compactJson"], True)
|
|
112
|
+
self.assertEqual(payload["query"], "run: x -> { ... }")
|
|
113
|
+
|
|
114
|
+
def test_a_nest_keeps_its_field_names(self):
|
|
115
|
+
# What the server returns under compactJson: `result` is a JSON
|
|
116
|
+
# STRING of plain rows, nested names intact.
|
|
117
|
+
body = {"result": json.dumps(
|
|
118
|
+
[{"carrier": "WN", "n": 2,
|
|
119
|
+
"top_dest": [{"destination_code": "PHX", "d": 1}]}])}
|
|
120
|
+
_, rows = self.send(body)
|
|
121
|
+
self.assertEqual(rows[0]["top_dest"], [{"destination_code": "PHX",
|
|
122
|
+
"d": 1}])
|
|
123
|
+
self.assertNotIn("c0", rows[0]["top_dest"][0])
|
|
124
|
+
|
|
125
|
+
def test_the_envelope_path_still_works(self):
|
|
126
|
+
# Kept as a fallback: a server that ignores the flag still parses.
|
|
127
|
+
body = envelope(["n"], [record({"number_value": 7})])
|
|
128
|
+
_, rows = self.send(body)
|
|
129
|
+
self.assertEqual(rows, [{"n": 7}])
|
|
130
|
+
|
|
131
|
+
|
|
76
132
|
if __name__ == "__main__":
|
|
77
133
|
os.chdir(os.path.dirname(__file__))
|
|
78
134
|
unittest.main()
|