@tangle-network/agent-eval 0.99.0 → 0.100.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.
@@ -12,12 +12,12 @@ import {
12
12
  emitSkillUsageFindings,
13
13
  runSemanticConceptJudge
14
14
  } from "../chunk-SJISCGWD.js";
15
+ import "../chunk-DJWX3GVS.js";
15
16
  import {
16
17
  behavioralAnalyst,
17
18
  buildDefaultAnalystRegistry,
18
19
  deriveEfficiencyFindings
19
20
  } from "../chunk-3NHEO6ZC.js";
20
- import "../chunk-DJWX3GVS.js";
21
21
  import "../chunk-BUTW4RGG.js";
22
22
  import {
23
23
  ANALYST_SEVERITIES,
@@ -0,0 +1,112 @@
1
+ import { S as Span } from '../schema-m0gsnbt3.js';
2
+
3
+ /**
4
+ * Groundedness — "did the retrieval PROVIDER surface what the task needed?"
5
+ *
6
+ * A search/research provider returns text; the task needed certain facts or
7
+ * symbols to be solvable (the CURRENT API, a version number, a function name).
8
+ * This module scores how much of that required knowledge the provider's results
9
+ * actually surfaced — isolating PROVIDER quality (was the right thing
10
+ * retrievable / returned) from AGENT skill (did the agent then use it). A high
11
+ * groundedness score with a failed run blames the agent; a low score blames the
12
+ * provider. That separation is the whole point — pass/fail alone cannot make it.
13
+ *
14
+ * Structural sibling of `../authenticity`:
15
+ * - authenticity scores the agent's PRODUCED files for realness.
16
+ * - groundedness scores the provider's RETRIEVED text for coverage.
17
+ * Both are pure deterministic scorers whose DOMAIN config is supplied by the
18
+ * consumer (authenticity: `AuthenticitySignals`; groundedness:
19
+ * `requiredKnowledge: string[]`) — neither bakes in a benchmark's vocabulary.
20
+ *
21
+ * Relationship to `keyword-coverage-judge`: that judge scores the agent's
22
+ * SERVED OUTPUT (HTML + assets) for expected concepts — a different input
23
+ * (produced deliverable) answering a different question (deliverable quality).
24
+ * Groundedness reads the RETRIEVAL side (provider results). They are
25
+ * complementary coverage scorers over different stages of the run, not
26
+ * duplicates; do not collapse one into the other.
27
+ *
28
+ * Two seams, neither forked:
29
+ * - PURE SCORER `scoreGroundedness(resultText, requiredKnowledge)` — case-
30
+ * insensitive substring containment over a deduped key set. Fail-open: with
31
+ * no required knowledge there is nothing to ground, so `score = 1`.
32
+ * - TRACE EXTRACTOR `extractRetrievedText(spans, opts?)` — pulls the provider's
33
+ * returned text out of the canonical `TraceSchema` spans (`RetrievalSpan.hits`
34
+ * + provider `ToolSpan.result`) instead of re-parsing bespoke run files. This
35
+ * is the retrieval-side analog of `extractProducedState` (events → produced
36
+ * files): structural span input, no IO, no disk walking.
37
+ */
38
+
39
+ interface GroundednessResult {
40
+ /** 0..1 share of required knowledge surfaced by the provider's results.
41
+ * 1 when there is nothing to ground (`requiredKnowledge` empty) — fail-open. */
42
+ score: number;
43
+ /** The required-knowledge keys the result text surfaced (deduped, original casing). */
44
+ found: string[];
45
+ /** The required-knowledge keys the result text did NOT surface. */
46
+ missing: string[];
47
+ /** Distinct required-knowledge keys after dedup — the denominator of `score`. */
48
+ total: number;
49
+ /** Did the provider return any result text at all? Distinguishes "provider
50
+ * surfaced nothing" (`!hadResults`) from "returned text but missed the facts"
51
+ * (`hadResults && score < 1`) — the same provider-vs-agent split as the score. */
52
+ hadResults: boolean;
53
+ }
54
+ /**
55
+ * Score how much of `requiredKnowledge` the retrieval provider's `resultText`
56
+ * surfaced. Pure — same inputs, same output. No IO, no LLM.
57
+ *
58
+ * Matching is case-insensitive substring containment: each required key is
59
+ * checked against the lower-cased result text. This is intentionally the same
60
+ * cheap, deterministic containment the authenticity scorer uses for its
61
+ * structural signals — a key is "surfaced" if the provider's returned text
62
+ * mentions it. Semantic / paraphrase coverage is a separate (LLM) layer a
63
+ * consumer can stack on top, exactly as authenticity stacks its nuance judge.
64
+ *
65
+ * Fail-open at `total === 0`: a task with no required knowledge has nothing for
66
+ * the provider to ground, so it cannot be penalized (`score = 1`). The benchmark
67
+ * caller decides what `requiredKnowledge` is — the substrate never derives it.
68
+ */
69
+ declare function scoreGroundedness(resultText: string, requiredKnowledge: readonly string[]): GroundednessResult;
70
+ /**
71
+ * Predicate selecting which `ToolSpan`s are retrieval-PROVIDER calls (whose
72
+ * `result` carries returned text), by tool name. A parameter — never a baked-in
73
+ * literal — so the substrate stays free of any one benchmark's tool vocabulary,
74
+ * exactly as `AuthenticitySignals` keeps all domain regexes consumer-supplied.
75
+ */
76
+ type ProviderToolMatcher = (toolName: string) => boolean;
77
+ /**
78
+ * Default provider matcher: tool names that look like search/research but not a
79
+ * plain fetch/read. A sensible starting point for the common "search arm" shape;
80
+ * any consumer with different tool names passes its own matcher. `RetrievalSpan`s
81
+ * are ALWAYS included regardless of this matcher (they are retrieval by kind);
82
+ * the matcher only selects which generic `ToolSpan`s also count as provider calls.
83
+ */
84
+ declare const defaultProviderToolMatcher: ProviderToolMatcher;
85
+ interface ExtractRetrievedTextOptions {
86
+ /** Which `ToolSpan`s count as provider calls. Default: {@link defaultProviderToolMatcher}. */
87
+ isProviderTool?: ProviderToolMatcher;
88
+ }
89
+ /**
90
+ * Extract the retrieval PROVIDER's returned text from a span stream — the
91
+ * retrieval-side analog of `extractProducedState`. Reads the canonical
92
+ * `TraceSchema` carriers, NOT bespoke run files:
93
+ * - every `RetrievalSpan`'s `hits[].content` (kind 'retrieval' — the
94
+ * substrate's first-class search/research result carrier; the same `.hits`
95
+ * the `bad_retrieval` failure detector already reads), and
96
+ * - `ToolSpan.result` for tool spans whose `toolName` the provider matcher
97
+ * accepts (kind 'tool').
98
+ *
99
+ * Pure and total: spans of other kinds, and provider tools with no result, are
100
+ * skipped. Returns one text blob ready for `scoreGroundedness`.
101
+ */
102
+ declare function extractRetrievedText(spans: readonly Span[], opts?: ExtractRetrievedTextOptions): string;
103
+ /**
104
+ * Extract the provider's retrieved text from a run's spans and score it against
105
+ * `requiredKnowledge` in one call — the analog of authenticity's file-in
106
+ * convenience. The primary contract is the standalone `scoreGroundedness`; this
107
+ * is the ergonomic path for a consumer holding a persisted run's `Span[]`
108
+ * (e.g. from `TraceStore.spans(...)`).
109
+ */
110
+ declare function scoreGroundednessForRun(spans: readonly Span[], requiredKnowledge: readonly string[], opts?: ExtractRetrievedTextOptions): GroundednessResult;
111
+
112
+ export { type ExtractRetrievedTextOptions, type GroundednessResult, type ProviderToolMatcher, defaultProviderToolMatcher, extractRetrievedText, scoreGroundedness, scoreGroundednessForRun };
@@ -0,0 +1,77 @@
1
+ import {
2
+ isRetrievalSpan,
3
+ isToolSpan
4
+ } from "../chunk-5BKGXME7.js";
5
+ import "../chunk-PZ5AY32C.js";
6
+
7
+ // src/groundedness/index.ts
8
+ function dedupeKeys(keys) {
9
+ const seen = /* @__PURE__ */ new Set();
10
+ const out = [];
11
+ for (const raw of keys) {
12
+ const k = raw.trim();
13
+ if (!k) continue;
14
+ const lower = k.toLowerCase();
15
+ if (seen.has(lower)) continue;
16
+ seen.add(lower);
17
+ out.push(k);
18
+ }
19
+ return out;
20
+ }
21
+ function scoreGroundedness(resultText, requiredKnowledge) {
22
+ const keys = dedupeKeys(requiredKnowledge);
23
+ const total = keys.length;
24
+ const text = resultText ?? "";
25
+ const hadResults = text.trim().length > 0;
26
+ const haystack = text.toLowerCase();
27
+ if (total === 0) {
28
+ return { score: 1, found: [], missing: [], total: 0, hadResults };
29
+ }
30
+ const found = [];
31
+ const missing = [];
32
+ for (const key of keys) {
33
+ if (haystack.includes(key.toLowerCase())) found.push(key);
34
+ else missing.push(key);
35
+ }
36
+ return { score: found.length / total, found, missing, total, hadResults };
37
+ }
38
+ var defaultProviderToolMatcher = (name) => /search|research/i.test(name) && !/fetch/i.test(name);
39
+ function resultToText(result) {
40
+ if (result == null) return "";
41
+ if (typeof result === "string") return result;
42
+ try {
43
+ return JSON.stringify(result);
44
+ } catch {
45
+ return String(result);
46
+ }
47
+ }
48
+ function retrievalSpanText(span) {
49
+ return span.hits.map((h) => h.content ?? "").filter((c) => c.length > 0).join("\n");
50
+ }
51
+ function extractRetrievedText(spans, opts = {}) {
52
+ const isProviderTool = opts.isProviderTool ?? defaultProviderToolMatcher;
53
+ const parts = [];
54
+ for (const span of spans) {
55
+ if (isRetrievalSpan(span)) {
56
+ const t = retrievalSpanText(span);
57
+ if (t) parts.push(t);
58
+ } else if (isToolSpan(span)) {
59
+ const ts = span;
60
+ if (isProviderTool(ts.toolName)) {
61
+ const t = resultToText(ts.result);
62
+ if (t) parts.push(t);
63
+ }
64
+ }
65
+ }
66
+ return parts.join("\n");
67
+ }
68
+ function scoreGroundednessForRun(spans, requiredKnowledge, opts = {}) {
69
+ return scoreGroundedness(extractRetrievedText(spans, opts), requiredKnowledge);
70
+ }
71
+ export {
72
+ defaultProviderToolMatcher,
73
+ extractRetrievedText,
74
+ scoreGroundedness,
75
+ scoreGroundednessForRun
76
+ };
77
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/groundedness/index.ts"],"sourcesContent":["/**\n * Groundedness — \"did the retrieval PROVIDER surface what the task needed?\"\n *\n * A search/research provider returns text; the task needed certain facts or\n * symbols to be solvable (the CURRENT API, a version number, a function name).\n * This module scores how much of that required knowledge the provider's results\n * actually surfaced — isolating PROVIDER quality (was the right thing\n * retrievable / returned) from AGENT skill (did the agent then use it). A high\n * groundedness score with a failed run blames the agent; a low score blames the\n * provider. That separation is the whole point — pass/fail alone cannot make it.\n *\n * Structural sibling of `../authenticity`:\n * - authenticity scores the agent's PRODUCED files for realness.\n * - groundedness scores the provider's RETRIEVED text for coverage.\n * Both are pure deterministic scorers whose DOMAIN config is supplied by the\n * consumer (authenticity: `AuthenticitySignals`; groundedness:\n * `requiredKnowledge: string[]`) — neither bakes in a benchmark's vocabulary.\n *\n * Relationship to `keyword-coverage-judge`: that judge scores the agent's\n * SERVED OUTPUT (HTML + assets) for expected concepts — a different input\n * (produced deliverable) answering a different question (deliverable quality).\n * Groundedness reads the RETRIEVAL side (provider results). They are\n * complementary coverage scorers over different stages of the run, not\n * duplicates; do not collapse one into the other.\n *\n * Two seams, neither forked:\n * - PURE SCORER `scoreGroundedness(resultText, requiredKnowledge)` — case-\n * insensitive substring containment over a deduped key set. Fail-open: with\n * no required knowledge there is nothing to ground, so `score = 1`.\n * - TRACE EXTRACTOR `extractRetrievedText(spans, opts?)` — pulls the provider's\n * returned text out of the canonical `TraceSchema` spans (`RetrievalSpan.hits`\n * + provider `ToolSpan.result`) instead of re-parsing bespoke run files. This\n * is the retrieval-side analog of `extractProducedState` (events → produced\n * files): structural span input, no IO, no disk walking.\n */\n\nimport type { RetrievalSpan, Span, ToolSpan } from '../trace/schema'\nimport { isRetrievalSpan, isToolSpan } from '../trace/schema'\n\n// ── Pure scorer ──────────────────────────────────────────────────────────────\n\nexport interface GroundednessResult {\n /** 0..1 share of required knowledge surfaced by the provider's results.\n * 1 when there is nothing to ground (`requiredKnowledge` empty) — fail-open. */\n score: number\n /** The required-knowledge keys the result text surfaced (deduped, original casing). */\n found: string[]\n /** The required-knowledge keys the result text did NOT surface. */\n missing: string[]\n /** Distinct required-knowledge keys after dedup — the denominator of `score`. */\n total: number\n /** Did the provider return any result text at all? Distinguishes \"provider\n * surfaced nothing\" (`!hadResults`) from \"returned text but missed the facts\"\n * (`hadResults && score < 1`) — the same provider-vs-agent split as the score. */\n hadResults: boolean\n}\n\n/**\n * Dedup a knowledge-key list, case-insensitively, keeping first-seen casing and\n * dropping blanks. The score denominator is distinct keys, so a config that\n * lists the same symbol twice (or with different casing) can't inflate `total`.\n */\nfunction dedupeKeys(keys: readonly string[]): string[] {\n const seen = new Set<string>()\n const out: string[] = []\n for (const raw of keys) {\n const k = raw.trim()\n if (!k) continue\n const lower = k.toLowerCase()\n if (seen.has(lower)) continue\n seen.add(lower)\n out.push(k)\n }\n return out\n}\n\n/**\n * Score how much of `requiredKnowledge` the retrieval provider's `resultText`\n * surfaced. Pure — same inputs, same output. No IO, no LLM.\n *\n * Matching is case-insensitive substring containment: each required key is\n * checked against the lower-cased result text. This is intentionally the same\n * cheap, deterministic containment the authenticity scorer uses for its\n * structural signals — a key is \"surfaced\" if the provider's returned text\n * mentions it. Semantic / paraphrase coverage is a separate (LLM) layer a\n * consumer can stack on top, exactly as authenticity stacks its nuance judge.\n *\n * Fail-open at `total === 0`: a task with no required knowledge has nothing for\n * the provider to ground, so it cannot be penalized (`score = 1`). The benchmark\n * caller decides what `requiredKnowledge` is — the substrate never derives it.\n */\nexport function scoreGroundedness(\n resultText: string,\n requiredKnowledge: readonly string[],\n): GroundednessResult {\n const keys = dedupeKeys(requiredKnowledge)\n const total = keys.length\n const text = resultText ?? ''\n const hadResults = text.trim().length > 0\n const haystack = text.toLowerCase()\n\n if (total === 0) {\n return { score: 1, found: [], missing: [], total: 0, hadResults }\n }\n\n const found: string[] = []\n const missing: string[] = []\n for (const key of keys) {\n if (haystack.includes(key.toLowerCase())) found.push(key)\n else missing.push(key)\n }\n\n return { score: found.length / total, found, missing, total, hadResults }\n}\n\n// ── Trace extractor ────────────────────────────────────────────────────────\n\n/**\n * Predicate selecting which `ToolSpan`s are retrieval-PROVIDER calls (whose\n * `result` carries returned text), by tool name. A parameter — never a baked-in\n * literal — so the substrate stays free of any one benchmark's tool vocabulary,\n * exactly as `AuthenticitySignals` keeps all domain regexes consumer-supplied.\n */\nexport type ProviderToolMatcher = (toolName: string) => boolean\n\n/**\n * Default provider matcher: tool names that look like search/research but not a\n * plain fetch/read. A sensible starting point for the common \"search arm\" shape;\n * any consumer with different tool names passes its own matcher. `RetrievalSpan`s\n * are ALWAYS included regardless of this matcher (they are retrieval by kind);\n * the matcher only selects which generic `ToolSpan`s also count as provider calls.\n */\nexport const defaultProviderToolMatcher: ProviderToolMatcher = (name) =>\n /search|research/i.test(name) && !/fetch/i.test(name)\n\nexport interface ExtractRetrievedTextOptions {\n /** Which `ToolSpan`s count as provider calls. Default: {@link defaultProviderToolMatcher}. */\n isProviderTool?: ProviderToolMatcher\n}\n\n/** Stringify a `ToolSpan.result` of unknown shape into searchable text. */\nfunction resultToText(result: unknown): string {\n if (result == null) return ''\n if (typeof result === 'string') return result\n try {\n return JSON.stringify(result)\n } catch {\n return String(result)\n }\n}\n\n/** Pull the retrieved text out of a `RetrievalSpan`: every hit's `content`. */\nfunction retrievalSpanText(span: RetrievalSpan): string {\n return span.hits\n .map((h) => h.content ?? '')\n .filter((c) => c.length > 0)\n .join('\\n')\n}\n\n/**\n * Extract the retrieval PROVIDER's returned text from a span stream — the\n * retrieval-side analog of `extractProducedState`. Reads the canonical\n * `TraceSchema` carriers, NOT bespoke run files:\n * - every `RetrievalSpan`'s `hits[].content` (kind 'retrieval' — the\n * substrate's first-class search/research result carrier; the same `.hits`\n * the `bad_retrieval` failure detector already reads), and\n * - `ToolSpan.result` for tool spans whose `toolName` the provider matcher\n * accepts (kind 'tool').\n *\n * Pure and total: spans of other kinds, and provider tools with no result, are\n * skipped. Returns one text blob ready for `scoreGroundedness`.\n */\nexport function extractRetrievedText(\n spans: readonly Span[],\n opts: ExtractRetrievedTextOptions = {},\n): string {\n const isProviderTool = opts.isProviderTool ?? defaultProviderToolMatcher\n const parts: string[] = []\n for (const span of spans) {\n if (isRetrievalSpan(span)) {\n const t = retrievalSpanText(span)\n if (t) parts.push(t)\n } else if (isToolSpan(span)) {\n const ts = span as ToolSpan\n if (isProviderTool(ts.toolName)) {\n const t = resultToText(ts.result)\n if (t) parts.push(t)\n }\n }\n }\n return parts.join('\\n')\n}\n\n// ── Convenience: extract-then-score ───────────────────────────────────────────\n\n/**\n * Extract the provider's retrieved text from a run's spans and score it against\n * `requiredKnowledge` in one call — the analog of authenticity's file-in\n * convenience. The primary contract is the standalone `scoreGroundedness`; this\n * is the ergonomic path for a consumer holding a persisted run's `Span[]`\n * (e.g. from `TraceStore.spans(...)`).\n */\nexport function scoreGroundednessForRun(\n spans: readonly Span[],\n requiredKnowledge: readonly string[],\n opts: ExtractRetrievedTextOptions = {},\n): GroundednessResult {\n return scoreGroundedness(extractRetrievedText(spans, opts), requiredKnowledge)\n}\n"],"mappings":";;;;;;;AA8DA,SAAS,WAAW,MAAmC;AACrD,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,MAAM;AACtB,UAAM,IAAI,IAAI,KAAK;AACnB,QAAI,CAAC,EAAG;AACR,UAAM,QAAQ,EAAE,YAAY;AAC5B,QAAI,KAAK,IAAI,KAAK,EAAG;AACrB,SAAK,IAAI,KAAK;AACd,QAAI,KAAK,CAAC;AAAA,EACZ;AACA,SAAO;AACT;AAiBO,SAAS,kBACd,YACA,mBACoB;AACpB,QAAM,OAAO,WAAW,iBAAiB;AACzC,QAAM,QAAQ,KAAK;AACnB,QAAM,OAAO,cAAc;AAC3B,QAAM,aAAa,KAAK,KAAK,EAAE,SAAS;AACxC,QAAM,WAAW,KAAK,YAAY;AAElC,MAAI,UAAU,GAAG;AACf,WAAO,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,OAAO,GAAG,WAAW;AAAA,EAClE;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,SAAS,IAAI,YAAY,CAAC,EAAG,OAAM,KAAK,GAAG;AAAA,QACnD,SAAQ,KAAK,GAAG;AAAA,EACvB;AAEA,SAAO,EAAE,OAAO,MAAM,SAAS,OAAO,OAAO,SAAS,OAAO,WAAW;AAC1E;AAmBO,IAAM,6BAAkD,CAAC,SAC9D,mBAAmB,KAAK,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI;AAQtD,SAAS,aAAa,QAAyB;AAC7C,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI;AACF,WAAO,KAAK,UAAU,MAAM;AAAA,EAC9B,QAAQ;AACN,WAAO,OAAO,MAAM;AAAA,EACtB;AACF;AAGA,SAAS,kBAAkB,MAA6B;AACtD,SAAO,KAAK,KACT,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,EAC1B,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,KAAK,IAAI;AACd;AAeO,SAAS,qBACd,OACA,OAAoC,CAAC,GAC7B;AACR,QAAM,iBAAiB,KAAK,kBAAkB;AAC9C,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,OAAO;AACxB,QAAI,gBAAgB,IAAI,GAAG;AACzB,YAAM,IAAI,kBAAkB,IAAI;AAChC,UAAI,EAAG,OAAM,KAAK,CAAC;AAAA,IACrB,WAAW,WAAW,IAAI,GAAG;AAC3B,YAAM,KAAK;AACX,UAAI,eAAe,GAAG,QAAQ,GAAG;AAC/B,cAAM,IAAI,aAAa,GAAG,MAAM;AAChC,YAAI,EAAG,OAAM,KAAK,CAAC;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAWO,SAAS,wBACd,OACA,mBACA,OAAoC,CAAC,GACjB;AACpB,SAAO,kBAAkB,qBAAqB,OAAO,IAAI,GAAG,iBAAiB;AAC/E;","names":[]}