@aid-framework/runtime 0.0.0 → 0.1.39
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/LICENSE +201 -0
- package/README.md +53 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/agents/deep_agents.d.ts +78 -0
- package/dist/agents/deep_agents.d.ts.map +1 -0
- package/dist/agents/deep_agents.js +477 -0
- package/dist/agents/deep_agents.js.map +1 -0
- package/dist/agents/engine.d.ts +85 -0
- package/dist/agents/engine.d.ts.map +1 -0
- package/dist/agents/engine.js +129 -0
- package/dist/agents/engine.js.map +1 -0
- package/dist/agents/gateway_chat_model.d.ts +151 -0
- package/dist/agents/gateway_chat_model.d.ts.map +1 -0
- package/dist/agents/gateway_chat_model.js +350 -0
- package/dist/agents/gateway_chat_model.js.map +1 -0
- package/dist/agents/guardrail_middleware.d.ts +57 -0
- package/dist/agents/guardrail_middleware.d.ts.map +1 -0
- package/dist/agents/guardrail_middleware.js +85 -0
- package/dist/agents/guardrail_middleware.js.map +1 -0
- package/dist/agents/guardrails.d.ts +124 -0
- package/dist/agents/guardrails.d.ts.map +1 -0
- package/dist/agents/guardrails.js +336 -0
- package/dist/agents/guardrails.js.map +1 -0
- package/dist/agents/index.d.ts +15 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +15 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/ir.d.ts +144 -0
- package/dist/agents/ir.d.ts.map +1 -0
- package/dist/agents/ir.js +513 -0
- package/dist/agents/ir.js.map +1 -0
- package/dist/agents/loop.d.ts +145 -0
- package/dist/agents/loop.d.ts.map +1 -0
- package/dist/agents/loop.js +1025 -0
- package/dist/agents/loop.js.map +1 -0
- package/dist/agents/memory.d.ts +115 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/memory.js +258 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/state.d.ts +137 -0
- package/dist/agents/state.d.ts.map +1 -0
- package/dist/agents/state.js +255 -0
- package/dist/agents/state.js.map +1 -0
- package/dist/concurrency.d.ts +36 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +86 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/control.d.ts +116 -0
- package/dist/control.d.ts.map +1 -0
- package/dist/control.js +281 -0
- package/dist/control.js.map +1 -0
- package/dist/cost.d.ts +95 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +161 -0
- package/dist/cost.js.map +1 -0
- package/dist/errors.d.ts +213 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +316 -0
- package/dist/errors.js.map +1 -0
- package/dist/evals/dataset.d.ts +45 -0
- package/dist/evals/dataset.d.ts.map +1 -0
- package/dist/evals/dataset.js +147 -0
- package/dist/evals/dataset.js.map +1 -0
- package/dist/evals/gate.d.ts +236 -0
- package/dist/evals/gate.d.ts.map +1 -0
- package/dist/evals/gate.js +572 -0
- package/dist/evals/gate.js.map +1 -0
- package/dist/evals/metrics.d.ts +141 -0
- package/dist/evals/metrics.d.ts.map +1 -0
- package/dist/evals/metrics.js +364 -0
- package/dist/evals/metrics.js.map +1 -0
- package/dist/evals/predicate.d.ts +79 -0
- package/dist/evals/predicate.d.ts.map +1 -0
- package/dist/evals/predicate.js +178 -0
- package/dist/evals/predicate.js.map +1 -0
- package/dist/evals/runner.d.ts +94 -0
- package/dist/evals/runner.d.ts.map +1 -0
- package/dist/evals/runner.js +202 -0
- package/dist/evals/runner.js.map +1 -0
- package/dist/gateway.d.ts +118 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +250 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/json.d.ts +44 -0
- package/dist/internal/json.d.ts.map +1 -0
- package/dist/internal/json.js +90 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/text.d.ts +17 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +22 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/pipeline.d.ts +80 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +159 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/policy.d.ts +52 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +199 -0
- package/dist/policy.js.map +1 -0
- package/dist/prompts.d.ts +86 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +337 -0
- package/dist/prompts.js.map +1 -0
- package/dist/providers/base.d.ts +19 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +13 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/fake.d.ts +98 -0
- package/dist/providers/fake.d.ts.map +1 -0
- package/dist/providers/fake.js +181 -0
- package/dist/providers/fake.js.map +1 -0
- package/dist/providers/http.d.ts +62 -0
- package/dist/providers/http.d.ts.map +1 -0
- package/dist/providers/http.js +245 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/retrieval/chunking.d.ts +88 -0
- package/dist/retrieval/chunking.d.ts.map +1 -0
- package/dist/retrieval/chunking.js +188 -0
- package/dist/retrieval/chunking.js.map +1 -0
- package/dist/retrieval/citations.d.ts +98 -0
- package/dist/retrieval/citations.d.ts.map +1 -0
- package/dist/retrieval/citations.js +134 -0
- package/dist/retrieval/citations.js.map +1 -0
- package/dist/retrieval/connectors.d.ts +74 -0
- package/dist/retrieval/connectors.d.ts.map +1 -0
- package/dist/retrieval/connectors.js +218 -0
- package/dist/retrieval/connectors.js.map +1 -0
- package/dist/retrieval/embedding.d.ts +116 -0
- package/dist/retrieval/embedding.d.ts.map +1 -0
- package/dist/retrieval/embedding.js +282 -0
- package/dist/retrieval/embedding.js.map +1 -0
- package/dist/retrieval/gate.d.ts +52 -0
- package/dist/retrieval/gate.d.ts.map +1 -0
- package/dist/retrieval/gate.js +91 -0
- package/dist/retrieval/gate.js.map +1 -0
- package/dist/retrieval/hybrid.d.ts +76 -0
- package/dist/retrieval/hybrid.d.ts.map +1 -0
- package/dist/retrieval/hybrid.js +150 -0
- package/dist/retrieval/hybrid.js.map +1 -0
- package/dist/retrieval/metrics.d.ts +126 -0
- package/dist/retrieval/metrics.d.ts.map +1 -0
- package/dist/retrieval/metrics.js +200 -0
- package/dist/retrieval/metrics.js.map +1 -0
- package/dist/retrieval/pgvector.d.ts +62 -0
- package/dist/retrieval/pgvector.d.ts.map +1 -0
- package/dist/retrieval/pgvector.js +262 -0
- package/dist/retrieval/pgvector.js.map +1 -0
- package/dist/retrieval/rerank.d.ts +61 -0
- package/dist/retrieval/rerank.d.ts.map +1 -0
- package/dist/retrieval/rerank.js +105 -0
- package/dist/retrieval/rerank.js.map +1 -0
- package/dist/retrieval/retriever.d.ts +125 -0
- package/dist/retrieval/retriever.d.ts.map +1 -0
- package/dist/retrieval/retriever.js +281 -0
- package/dist/retrieval/retriever.js.map +1 -0
- package/dist/retrieval/stores.d.ts +106 -0
- package/dist/retrieval/stores.d.ts.map +1 -0
- package/dist/retrieval/stores.js +142 -0
- package/dist/retrieval/stores.js.map +1 -0
- package/dist/retrieval/types.d.ts +314 -0
- package/dist/retrieval/types.d.ts.map +1 -0
- package/dist/retrieval/types.js +388 -0
- package/dist/retrieval/types.js.map +1 -0
- package/dist/steps.d.ts +156 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +199 -0
- package/dist/steps.js.map +1 -0
- package/dist/structured.d.ts +91 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +155 -0
- package/dist/structured.js.map +1 -0
- package/dist/tools.d.ts +100 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +201 -0
- package/dist/tools.js.map +1 -0
- package/dist/tracing.d.ts +104 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +155 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +357 -0
- package/dist/types.js.map +1 -0
- package/package.json +80 -14
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running the retrieval gate through the shipped statistical gate.
|
|
3
|
+
*
|
|
4
|
+
* No gate logic lives here. {@link RetrievalGate} samples a retriever over retrieval
|
|
5
|
+
* cases, turns each per-run mean into a real {@link MetricRun}, and hands a real
|
|
6
|
+
* {@link EvalRun} to the real {@link BaselineGate}. That matters for the falsification
|
|
7
|
+
* test, which must show the *shipped* gate rejecting a worse retriever; a comparator
|
|
8
|
+
* written for the occasion would prove only that the comparator works.
|
|
9
|
+
*
|
|
10
|
+
* Two deliberate details:
|
|
11
|
+
*
|
|
12
|
+
* **The metrics hand-off needs a cast.** `BaselineGate` annotates `metrics` as
|
|
13
|
+
* `Metric[]`, and the shipped `Metric` requires `score(entry: EvalCase, prediction:
|
|
14
|
+
* string)`, which a retrieval metric cannot implement. What the gate actually consumes
|
|
15
|
+
* from that argument is only `name` and `deterministic` -- that is the entirety of
|
|
16
|
+
* `checkMetricEligibility` -- so a cast is sound here, and is confined to one line
|
|
17
|
+
* with this comment rather than spread through the runtime. The eligibility check it
|
|
18
|
+
* feeds is real: a retrieval metric reporting `deterministic = false` is refused,
|
|
19
|
+
* which a test asserts.
|
|
20
|
+
*
|
|
21
|
+
* **`allowNondeterministic` is not exposed.** The shipped gate has the flag for local
|
|
22
|
+
* exploration, and wiring it through would make it reachable from a gate. There is no
|
|
23
|
+
* retrieval metric in Phase 1 that is non-deterministic; if one ever appears, the
|
|
24
|
+
* metric is what needs fixing.
|
|
25
|
+
*
|
|
26
|
+
* `runs` is a required argument rather than a default, because it is not a tuning
|
|
27
|
+
* knob: at `runs = 1` the run-to-run standard deviation is zero and the statistical
|
|
28
|
+
* half of the gate cannot fire at all, so a gate built on one run silently degrades to
|
|
29
|
+
* a mean comparison.
|
|
30
|
+
*/
|
|
31
|
+
import { type EvalBaseline, EvalRun, type GateResult, type Threshold } from '../evals/gate.js';
|
|
32
|
+
import type { RetrievalCase, RetrievalMetric } from './metrics.js';
|
|
33
|
+
import type { Retriever } from './retriever.js';
|
|
34
|
+
/** Samples a retriever and decides whether its retrieval regressed. */
|
|
35
|
+
export declare class RetrievalGate {
|
|
36
|
+
private readonly _metrics;
|
|
37
|
+
private readonly _gate;
|
|
38
|
+
constructor(thresholds: readonly Threshold[], options?: {
|
|
39
|
+
readonly metrics?: readonly RetrievalMetric[];
|
|
40
|
+
});
|
|
41
|
+
get thresholds(): readonly Threshold[];
|
|
42
|
+
get metrics(): readonly RetrievalMetric[];
|
|
43
|
+
/** Run every case `runs` times, producing one mean per metric per run. */
|
|
44
|
+
sample(cases: readonly RetrievalCase[], retriever: Retriever, init: {
|
|
45
|
+
readonly runs: number;
|
|
46
|
+
readonly dataset: string;
|
|
47
|
+
readonly datasetVersion: string;
|
|
48
|
+
}): Promise<EvalRun>;
|
|
49
|
+
/** Delegate to the shipped gate; there is no verdict logic in this module. */
|
|
50
|
+
evaluate(run: EvalRun, baseline: EvalBaseline): GateResult;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../src/retrieval/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,EAEL,KAAK,YAAY,EACjB,OAAO,EACP,KAAK,UAAU,EAEf,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,uEAAuE;AACvE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IAErC,YACE,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,OAAO,GAAE;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;KAAO,EAQhE;IAED,IAAI,UAAU,IAAI,SAAS,SAAS,EAAE,CAErC;IAED,IAAI,OAAO,IAAI,SAAS,eAAe,EAAE,CAExC;IAED,0EAA0E;IACpE,MAAM,CACV,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GACzF,OAAO,CAAC,OAAO,CAAC,CAsClB;IAED,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,GAAG,UAAU,CAEzD;CACF"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running the retrieval gate through the shipped statistical gate.
|
|
3
|
+
*
|
|
4
|
+
* No gate logic lives here. {@link RetrievalGate} samples a retriever over retrieval
|
|
5
|
+
* cases, turns each per-run mean into a real {@link MetricRun}, and hands a real
|
|
6
|
+
* {@link EvalRun} to the real {@link BaselineGate}. That matters for the falsification
|
|
7
|
+
* test, which must show the *shipped* gate rejecting a worse retriever; a comparator
|
|
8
|
+
* written for the occasion would prove only that the comparator works.
|
|
9
|
+
*
|
|
10
|
+
* Two deliberate details:
|
|
11
|
+
*
|
|
12
|
+
* **The metrics hand-off needs a cast.** `BaselineGate` annotates `metrics` as
|
|
13
|
+
* `Metric[]`, and the shipped `Metric` requires `score(entry: EvalCase, prediction:
|
|
14
|
+
* string)`, which a retrieval metric cannot implement. What the gate actually consumes
|
|
15
|
+
* from that argument is only `name` and `deterministic` -- that is the entirety of
|
|
16
|
+
* `checkMetricEligibility` -- so a cast is sound here, and is confined to one line
|
|
17
|
+
* with this comment rather than spread through the runtime. The eligibility check it
|
|
18
|
+
* feeds is real: a retrieval metric reporting `deterministic = false` is refused,
|
|
19
|
+
* which a test asserts.
|
|
20
|
+
*
|
|
21
|
+
* **`allowNondeterministic` is not exposed.** The shipped gate has the flag for local
|
|
22
|
+
* exploration, and wiring it through would make it reachable from a gate. There is no
|
|
23
|
+
* retrieval metric in Phase 1 that is non-deterministic; if one ever appears, the
|
|
24
|
+
* metric is what needs fixing.
|
|
25
|
+
*
|
|
26
|
+
* `runs` is a required argument rather than a default, because it is not a tuning
|
|
27
|
+
* knob: at `runs = 1` the run-to-run standard deviation is zero and the statistical
|
|
28
|
+
* half of the gate cannot fire at all, so a gate built on one run silently degrades to
|
|
29
|
+
* a mean comparison.
|
|
30
|
+
*/
|
|
31
|
+
// biome-ignore lint/suspicious/noShadowRestrictedNames: the framework's own error name, matching the Python runtime's public surface
|
|
32
|
+
import { EvalError } from '../errors.js';
|
|
33
|
+
import { BaselineGate, EvalRun, MetricRun, } from '../evals/gate.js';
|
|
34
|
+
/** Samples a retriever and decides whether its retrieval regressed. */
|
|
35
|
+
export class RetrievalGate {
|
|
36
|
+
_metrics;
|
|
37
|
+
_gate;
|
|
38
|
+
constructor(thresholds, options = {}) {
|
|
39
|
+
this._metrics = [...(options.metrics ?? [])];
|
|
40
|
+
// See the module docstring: the gate reads only `name` and `deterministic` from
|
|
41
|
+
// these, which is the part of the protocol a retrieval metric shares.
|
|
42
|
+
this._gate = new BaselineGate(thresholds, {
|
|
43
|
+
metrics: this._metrics,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
get thresholds() {
|
|
47
|
+
return this._gate.thresholds;
|
|
48
|
+
}
|
|
49
|
+
get metrics() {
|
|
50
|
+
return this._metrics;
|
|
51
|
+
}
|
|
52
|
+
/** Run every case `runs` times, producing one mean per metric per run. */
|
|
53
|
+
async sample(cases, retriever, init) {
|
|
54
|
+
if (cases.length === 0) {
|
|
55
|
+
throw new EvalError('a retrieval run needs at least one case');
|
|
56
|
+
}
|
|
57
|
+
if (init.runs < 1) {
|
|
58
|
+
throw new EvalError(`a retrieval run needs at least one run, got ${init.runs}`);
|
|
59
|
+
}
|
|
60
|
+
if (this._metrics.length === 0) {
|
|
61
|
+
throw new EvalError('a retrieval run needs at least one metric');
|
|
62
|
+
}
|
|
63
|
+
const samples = new Map(this._metrics.map((metric) => [metric.name, []]));
|
|
64
|
+
for (let run = 0; run < init.runs; run += 1) {
|
|
65
|
+
const totals = new Map(this._metrics.map((metric) => [metric.name, 0.0]));
|
|
66
|
+
for (const entry of cases) {
|
|
67
|
+
// k comes from the case, because a case is what says how many passages count
|
|
68
|
+
// as an answer; a retriever's own topK is a default.
|
|
69
|
+
const retrieved = await retriever.retrieve(entry.query, { topK: entry.k });
|
|
70
|
+
for (const metric of this._metrics) {
|
|
71
|
+
const result = await metric.score(entry, retrieved);
|
|
72
|
+
totals.set(metric.name, (totals.get(metric.name) ?? 0.0) + result.score);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const [name, total] of totals) {
|
|
76
|
+
samples.get(name)?.push(total / cases.length);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return new EvalRun({
|
|
80
|
+
dataset: init.dataset,
|
|
81
|
+
datasetVersion: init.datasetVersion,
|
|
82
|
+
runs: init.runs,
|
|
83
|
+
metrics: [...samples.entries()].map(([name, values]) => new MetricRun({ metric: name, values })),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/** Delegate to the shipped gate; there is no verdict logic in this module. */
|
|
87
|
+
evaluate(run, baseline) {
|
|
88
|
+
return this._gate.evaluate(run, baseline);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/retrieval/gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical scoring and rank fusion.
|
|
3
|
+
*
|
|
4
|
+
* Vector similarity cannot carry retrieval on its own, and the reason is not
|
|
5
|
+
* aesthetic: cosine similarity is dominated by embedding geometry while exact terms
|
|
6
|
+
* -- an identifier, an error code, a flag name -- are what a developer actually
|
|
7
|
+
* greps for. So a retriever fuses a vector ranking with a lexical one.
|
|
8
|
+
*
|
|
9
|
+
* The lexical half is BM25, implemented here rather than delegated to a search engine
|
|
10
|
+
* so that the same scoring applies to both store adapters and to a store that has no
|
|
11
|
+
* database behind it.
|
|
12
|
+
*
|
|
13
|
+
* The fusion is Reciprocal Rank Fusion, and it fuses *ranks* rather than scores. That
|
|
14
|
+
* is the whole reason to pick it: BM25 scores and cosine similarities are unbounded
|
|
15
|
+
* and on incomparable scales, so any weighted score sum would need a per-corpus
|
|
16
|
+
* normalisation constant that nobody can justify. Ranks are already comparable, and
|
|
17
|
+
* RRF's `1 / (k + rank)` says something defensible -- being ranked highly by either
|
|
18
|
+
* retriever is evidence, being ranked highly by both is stronger evidence.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is a pure function of its inputs, so retrieval order is
|
|
21
|
+
* reproducible and a re-ingested corpus ranks identically.
|
|
22
|
+
*/
|
|
23
|
+
/** A ranked chunk id and the score that ranked it. */
|
|
24
|
+
export declare class RankedId {
|
|
25
|
+
readonly chunkId: string;
|
|
26
|
+
readonly score: number;
|
|
27
|
+
constructor(init: {
|
|
28
|
+
readonly chunkId: string;
|
|
29
|
+
readonly score: number;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Lowercase alphanumeric runs.
|
|
34
|
+
*
|
|
35
|
+
* No stemming and no stop-word list: both are language-specific, and a retrieval
|
|
36
|
+
* runtime that silently mangles identifiers by stemming them is worse at its actual
|
|
37
|
+
* job than one that keeps the tokens it was given.
|
|
38
|
+
*/
|
|
39
|
+
export declare function tokenize(text: string): readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* BM25 score of every document for `query`, in the order given.
|
|
42
|
+
*
|
|
43
|
+
* Saturated term frequency (a term repeated ten times is not ten times as
|
|
44
|
+
* informative) and length normalisation (a long chunk should not win merely by
|
|
45
|
+
* containing more words) -- those two properties are the reason to use BM25 rather
|
|
46
|
+
* than a raw term count.
|
|
47
|
+
*/
|
|
48
|
+
export declare function bm25Scores(documents: readonly string[], query: string, options?: {
|
|
49
|
+
readonly k1?: number;
|
|
50
|
+
readonly b?: number;
|
|
51
|
+
}): readonly number[];
|
|
52
|
+
/**
|
|
53
|
+
* The `topK` documents with a non-zero lexical score, best first.
|
|
54
|
+
*
|
|
55
|
+
* Ties break on chunk id so that equal-scoring matches rank identically on every run
|
|
56
|
+
* and in both store adapters.
|
|
57
|
+
*/
|
|
58
|
+
export declare function lexicalRanking(documents: readonly string[], ids: readonly string[], query: string, options: {
|
|
59
|
+
readonly topK: number;
|
|
60
|
+
}): readonly RankedId[];
|
|
61
|
+
/** The reciprocal-rank contribution of a 1-based `rank`. */
|
|
62
|
+
export declare function rrfScore(rank: number, options?: {
|
|
63
|
+
readonly k?: number;
|
|
64
|
+
}): number;
|
|
65
|
+
/**
|
|
66
|
+
* Reciprocal Rank Fusion of rank-ordered id lists.
|
|
67
|
+
*
|
|
68
|
+
* Each ranking contributes `1 / (k + rank)` per id; an id absent from a ranking
|
|
69
|
+
* contributes nothing from it. An id that appears in several rankings therefore
|
|
70
|
+
* outranks one that is top of a single ranking, which is the behaviour that makes
|
|
71
|
+
* fusion worth doing.
|
|
72
|
+
*/
|
|
73
|
+
export declare function fuse(rankings: readonly (readonly string[])[], options?: {
|
|
74
|
+
readonly k?: number;
|
|
75
|
+
}): readonly RankedId[];
|
|
76
|
+
//# sourceMappingURL=hybrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAmBH,sDAAsD;AACtD,qBAAa,QAAQ;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,YAAY,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAMrE;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1D,SAAS,MAAM,EAAE,CAwCnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC,SAAS,QAAQ,EAAE,CAgBrB;AAED,4DAA4D;AAC5D,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,CAKpF;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAClB,QAAQ,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EACxC,OAAO,GAAE;IAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC,SAAS,QAAQ,EAAE,CAarB"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexical scoring and rank fusion.
|
|
3
|
+
*
|
|
4
|
+
* Vector similarity cannot carry retrieval on its own, and the reason is not
|
|
5
|
+
* aesthetic: cosine similarity is dominated by embedding geometry while exact terms
|
|
6
|
+
* -- an identifier, an error code, a flag name -- are what a developer actually
|
|
7
|
+
* greps for. So a retriever fuses a vector ranking with a lexical one.
|
|
8
|
+
*
|
|
9
|
+
* The lexical half is BM25, implemented here rather than delegated to a search engine
|
|
10
|
+
* so that the same scoring applies to both store adapters and to a store that has no
|
|
11
|
+
* database behind it.
|
|
12
|
+
*
|
|
13
|
+
* The fusion is Reciprocal Rank Fusion, and it fuses *ranks* rather than scores. That
|
|
14
|
+
* is the whole reason to pick it: BM25 scores and cosine similarities are unbounded
|
|
15
|
+
* and on incomparable scales, so any weighted score sum would need a per-corpus
|
|
16
|
+
* normalisation constant that nobody can justify. Ranks are already comparable, and
|
|
17
|
+
* RRF's `1 / (k + rank)` says something defensible -- being ranked highly by either
|
|
18
|
+
* retriever is evidence, being ranked highly by both is stronger evidence.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is a pure function of its inputs, so retrieval order is
|
|
21
|
+
* reproducible and a re-ingested corpus ranks identically.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The RRF constant from the original formulation: it damps the influence of the very
|
|
25
|
+
* top ranks so one retriever's confident first hit cannot alone decide the fused
|
|
26
|
+
* order.
|
|
27
|
+
*/
|
|
28
|
+
const RRF_K = 60;
|
|
29
|
+
/**
|
|
30
|
+
* BM25 saturation and length-normalisation. The usual defaults, and defaults are the
|
|
31
|
+
* right call here: they are tuned against judged-relevance corpora, and this runtime
|
|
32
|
+
* has no such corpus to tune against.
|
|
33
|
+
*/
|
|
34
|
+
const BM25_K1 = 1.5;
|
|
35
|
+
const BM25_B = 0.75;
|
|
36
|
+
const TOKEN = /[a-z0-9]+/g;
|
|
37
|
+
/** A ranked chunk id and the score that ranked it. */
|
|
38
|
+
export class RankedId {
|
|
39
|
+
chunkId;
|
|
40
|
+
score;
|
|
41
|
+
constructor(init) {
|
|
42
|
+
if (init.chunkId.length === 0) {
|
|
43
|
+
throw new RangeError('chunkId must not be empty');
|
|
44
|
+
}
|
|
45
|
+
this.chunkId = init.chunkId;
|
|
46
|
+
this.score = init.score;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Lowercase alphanumeric runs.
|
|
51
|
+
*
|
|
52
|
+
* No stemming and no stop-word list: both are language-specific, and a retrieval
|
|
53
|
+
* runtime that silently mangles identifiers by stemming them is worse at its actual
|
|
54
|
+
* job than one that keeps the tokens it was given.
|
|
55
|
+
*/
|
|
56
|
+
export function tokenize(text) {
|
|
57
|
+
return text.toLowerCase().match(TOKEN) ?? [];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* BM25 score of every document for `query`, in the order given.
|
|
61
|
+
*
|
|
62
|
+
* Saturated term frequency (a term repeated ten times is not ten times as
|
|
63
|
+
* informative) and length normalisation (a long chunk should not win merely by
|
|
64
|
+
* containing more words) -- those two properties are the reason to use BM25 rather
|
|
65
|
+
* than a raw term count.
|
|
66
|
+
*/
|
|
67
|
+
export function bm25Scores(documents, query, options = {}) {
|
|
68
|
+
const k1 = options.k1 ?? BM25_K1;
|
|
69
|
+
const b = options.b ?? BM25_B;
|
|
70
|
+
const tokenised = documents.map((document) => tokenize(document));
|
|
71
|
+
const terms = tokenize(query);
|
|
72
|
+
if (tokenised.length === 0 || terms.length === 0) {
|
|
73
|
+
return documents.map(() => 0.0);
|
|
74
|
+
}
|
|
75
|
+
const total = tokenised.length;
|
|
76
|
+
const lengths = tokenised.map((tokens) => tokens.length);
|
|
77
|
+
const averageLength = lengths.reduce((sum, length) => sum + length, 0) / total;
|
|
78
|
+
const counts = tokenised.map((tokens) => {
|
|
79
|
+
const frequencies = new Map();
|
|
80
|
+
for (const token of tokens) {
|
|
81
|
+
frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
|
|
82
|
+
}
|
|
83
|
+
return frequencies;
|
|
84
|
+
});
|
|
85
|
+
const scores = [];
|
|
86
|
+
for (const [index, frequencies] of counts.entries()) {
|
|
87
|
+
const length = lengths[index] ?? 0;
|
|
88
|
+
let score = 0.0;
|
|
89
|
+
for (const term of terms) {
|
|
90
|
+
const frequency = frequencies.get(term) ?? 0;
|
|
91
|
+
if (frequency === 0)
|
|
92
|
+
continue;
|
|
93
|
+
const documentFrequency = counts.reduce((count, other) => (other.has(term) ? count + 1 : count), 0);
|
|
94
|
+
const inverseFrequency = Math.log(1.0 + (total - documentFrequency + 0.5) / (documentFrequency + 0.5));
|
|
95
|
+
const norm = averageLength > 0 ? 1.0 - b + b * (length / averageLength) : 1.0;
|
|
96
|
+
score += (inverseFrequency * (frequency * (k1 + 1.0))) / (frequency + k1 * norm);
|
|
97
|
+
}
|
|
98
|
+
scores.push(score);
|
|
99
|
+
}
|
|
100
|
+
return scores;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The `topK` documents with a non-zero lexical score, best first.
|
|
104
|
+
*
|
|
105
|
+
* Ties break on chunk id so that equal-scoring matches rank identically on every run
|
|
106
|
+
* and in both store adapters.
|
|
107
|
+
*/
|
|
108
|
+
export function lexicalRanking(documents, ids, query, options) {
|
|
109
|
+
if (documents.length !== ids.length) {
|
|
110
|
+
throw new RangeError('documents and ids must be parallel sequences');
|
|
111
|
+
}
|
|
112
|
+
const scores = bm25Scores(documents, query);
|
|
113
|
+
const scored = [];
|
|
114
|
+
for (const [index, chunkId] of ids.entries()) {
|
|
115
|
+
const score = scores[index] ?? 0.0;
|
|
116
|
+
if (score > 0.0) {
|
|
117
|
+
scored.push(new RankedId({ chunkId, score }));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
scored.sort((left, right) => left.score !== right.score ? right.score - left.score : left.chunkId < right.chunkId ? -1 : 1);
|
|
121
|
+
return scored.slice(0, options.topK);
|
|
122
|
+
}
|
|
123
|
+
/** The reciprocal-rank contribution of a 1-based `rank`. */
|
|
124
|
+
export function rrfScore(rank, options = {}) {
|
|
125
|
+
if (rank < 1) {
|
|
126
|
+
throw new RangeError('rank is 1-based');
|
|
127
|
+
}
|
|
128
|
+
return 1.0 / ((options.k ?? RRF_K) + rank);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Reciprocal Rank Fusion of rank-ordered id lists.
|
|
132
|
+
*
|
|
133
|
+
* Each ranking contributes `1 / (k + rank)` per id; an id absent from a ranking
|
|
134
|
+
* contributes nothing from it. An id that appears in several rankings therefore
|
|
135
|
+
* outranks one that is top of a single ranking, which is the behaviour that makes
|
|
136
|
+
* fusion worth doing.
|
|
137
|
+
*/
|
|
138
|
+
export function fuse(rankings, options = {}) {
|
|
139
|
+
const k = options.k ?? RRF_K;
|
|
140
|
+
const totals = new Map();
|
|
141
|
+
for (const ranking of rankings) {
|
|
142
|
+
for (const [offset, chunkId] of ranking.entries()) {
|
|
143
|
+
totals.set(chunkId, (totals.get(chunkId) ?? 0.0) + rrfScore(offset + 1, { k }));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const fused = [...totals.entries()].map(([chunkId, score]) => new RankedId({ chunkId, score }));
|
|
147
|
+
fused.sort((left, right) => left.score !== right.score ? right.score - left.score : left.chunkId < right.chunkId ? -1 : 1);
|
|
148
|
+
return fused;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=hybrid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics over a retrieved set.
|
|
3
|
+
*
|
|
4
|
+
* The shipped `Metric` scores `(case, prediction: string)`. That signature cannot
|
|
5
|
+
* express these metrics: recall@k and MRR score a *ranked set of passages with
|
|
6
|
+
* provenance*, and a citation-grounding score has to look at where each passage came
|
|
7
|
+
* from. Encoding a retrieved set into one string would make every metric re-parse it,
|
|
8
|
+
* and the parse would become the thing under test.
|
|
9
|
+
*
|
|
10
|
+
* So the retrieval metrics get their own protocol here rather than widening the
|
|
11
|
+
* shipped one. Widening `Metric` would change the meaning of an interface the eval
|
|
12
|
+
* gate already threshold-checks, and would do so for every existing implementation.
|
|
13
|
+
* The new protocol is additive and reuses {@link MetricResult}.
|
|
14
|
+
*
|
|
15
|
+
* The two protocols share the part the gate actually consumes -- `name` and
|
|
16
|
+
* `deterministic` -- which is what lets retrieval metrics be handed to the shipped
|
|
17
|
+
* gate's `metrics` option. See `retrieval/gate.ts` for why that hand-off needs a cast
|
|
18
|
+
* and why the cast is sound.
|
|
19
|
+
*
|
|
20
|
+
* `deterministic` is `true` for all three, and that is a claim the gate enforces:
|
|
21
|
+
* none of these metrics calls a model, and every tie is broken by chunk id, so the
|
|
22
|
+
* same retrieved set always scores the same.
|
|
23
|
+
*/
|
|
24
|
+
import { MetricResult } from '../evals/metrics.js';
|
|
25
|
+
import type { RetrievalResult } from './citations.js';
|
|
26
|
+
import type { RetrievalMetricSpec } from './types.js';
|
|
27
|
+
/**
|
|
28
|
+
* One retrieval question and the documents that should answer it.
|
|
29
|
+
*
|
|
30
|
+
* This mirrors the IR's `retrieval` metric node field for field, and it exists
|
|
31
|
+
* because `EvalCase` carries `metadata: Record<string, string>` -- a string map,
|
|
32
|
+
* which cannot hold `expectDocs` without inventing an encoding. The expected
|
|
33
|
+
* documents, and `k`, are what a retrieval metric needs, so they are typed here
|
|
34
|
+
* instead of smuggled through a string.
|
|
35
|
+
*/
|
|
36
|
+
export declare class RetrievalCase {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly query: string;
|
|
39
|
+
readonly expectDocs: readonly string[];
|
|
40
|
+
readonly k: number;
|
|
41
|
+
constructor(init: {
|
|
42
|
+
readonly id: string;
|
|
43
|
+
readonly query: string;
|
|
44
|
+
readonly expectDocs: readonly string[];
|
|
45
|
+
readonly k: number;
|
|
46
|
+
});
|
|
47
|
+
/** Build a case from the IR's `{kind, expectDocs, k}` node. */
|
|
48
|
+
static fromSpec(init: {
|
|
49
|
+
readonly caseId: string;
|
|
50
|
+
readonly query: string;
|
|
51
|
+
readonly spec: RetrievalMetricSpec;
|
|
52
|
+
}): RetrievalCase;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Scores a ranked, cited retrieved set.
|
|
56
|
+
*
|
|
57
|
+
* `name` and `deterministic` are part of the contract because the shipped gate reads
|
|
58
|
+
* exactly those two properties when it checks that a thresholded metric is allowed to
|
|
59
|
+
* gate CI.
|
|
60
|
+
*/
|
|
61
|
+
export interface RetrievalMetric {
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly deterministic: boolean;
|
|
64
|
+
score(entry: RetrievalCase, retrieved: RetrievalResult): Promise<MetricResult>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fraction of expected documents present in the top `k`.
|
|
68
|
+
*
|
|
69
|
+
* Duplicated passages do not inflate the score: {@link RetrievalResult.docIds} is
|
|
70
|
+
* de-duplicated, so a document that contributed three passages counts once. Recall is
|
|
71
|
+
* about which documents were found.
|
|
72
|
+
*/
|
|
73
|
+
export declare class RecallAtK implements RetrievalMetric {
|
|
74
|
+
private readonly _name;
|
|
75
|
+
private readonly _minScore;
|
|
76
|
+
constructor(options?: {
|
|
77
|
+
readonly name?: string;
|
|
78
|
+
readonly minScore?: number;
|
|
79
|
+
});
|
|
80
|
+
get name(): string;
|
|
81
|
+
get deterministic(): boolean;
|
|
82
|
+
score(entry: RetrievalCase, retrieved: RetrievalResult): Promise<MetricResult>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reciprocal rank of the first expected document, averaged over cases.
|
|
86
|
+
*
|
|
87
|
+
* Scored per case, so the "mean" is taken by the gate across cases and runs. One
|
|
88
|
+
* number per case keeps the metric a drop-in for the gate's per-case arithmetic.
|
|
89
|
+
*/
|
|
90
|
+
export declare class MeanReciprocalRank implements RetrievalMetric {
|
|
91
|
+
private readonly _name;
|
|
92
|
+
private readonly _minScore;
|
|
93
|
+
constructor(options?: {
|
|
94
|
+
readonly name?: string;
|
|
95
|
+
readonly minScore?: number;
|
|
96
|
+
});
|
|
97
|
+
get name(): string;
|
|
98
|
+
get deterministic(): boolean;
|
|
99
|
+
score(entry: RetrievalCase, retrieved: RetrievalResult): Promise<MetricResult>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Fraction of expected documents that a returned item cites *correctly*.
|
|
103
|
+
*
|
|
104
|
+
* Distinct from recall because it scores citations rather than document ids, and
|
|
105
|
+
* because it checks integrity first: a citation whose `chunkId` or `docId` disagrees
|
|
106
|
+
* with the passage it is attached to is worse than a missing citation, since it sends
|
|
107
|
+
* a reader to the wrong place while looking authoritative. Any such mismatch scores 0
|
|
108
|
+
* regardless of what was retrieved.
|
|
109
|
+
*
|
|
110
|
+
* This definition is authored here: the IR has no citation metric kind, so there is
|
|
111
|
+
* no upstream semantics to match. It is stated rather than derived.
|
|
112
|
+
*/
|
|
113
|
+
export declare class CitationGrounding implements RetrievalMetric {
|
|
114
|
+
private readonly _name;
|
|
115
|
+
private readonly _minScore;
|
|
116
|
+
constructor(options?: {
|
|
117
|
+
readonly name?: string;
|
|
118
|
+
readonly minScore?: number;
|
|
119
|
+
});
|
|
120
|
+
get name(): string;
|
|
121
|
+
get deterministic(): boolean;
|
|
122
|
+
score(entry: RetrievalCase, retrieved: RetrievalResult): Promise<MetricResult>;
|
|
123
|
+
}
|
|
124
|
+
/** The retrieval metrics this runtime ships, in a stable order. */
|
|
125
|
+
export declare function retrievalMetrics(): readonly RetrievalMetric[];
|
|
126
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/retrieval/metrics.ts"],"names":[],"mappings":"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"}
|