@hikae/jev-algorithms 0.1.0-rc.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.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/adapters/memory.d.ts +10 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +12 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/typesafe.d.ts +15 -0
- package/dist/adapters/typesafe.d.ts.map +1 -0
- package/dist/adapters/typesafe.js +27 -0
- package/dist/adapters/typesafe.js.map +1 -0
- package/dist/adapters/workers-ai.d.ts +8 -0
- package/dist/adapters/workers-ai.d.ts.map +1 -0
- package/dist/adapters/workers-ai.js +11 -0
- package/dist/adapters/workers-ai.js.map +1 -0
- package/dist/algorithms/aggregate.d.ts +39 -0
- package/dist/algorithms/aggregate.d.ts.map +1 -0
- package/dist/algorithms/aggregate.js +100 -0
- package/dist/algorithms/aggregate.js.map +1 -0
- package/dist/algorithms/classify.d.ts +59 -0
- package/dist/algorithms/classify.d.ts.map +1 -0
- package/dist/algorithms/classify.js +65 -0
- package/dist/algorithms/classify.js.map +1 -0
- package/dist/algorithms/cluster.d.ts +30 -0
- package/dist/algorithms/cluster.d.ts.map +1 -0
- package/dist/algorithms/cluster.js +87 -0
- package/dist/algorithms/cluster.js.map +1 -0
- package/dist/algorithms/match.d.ts +16 -0
- package/dist/algorithms/match.d.ts.map +1 -0
- package/dist/algorithms/match.js +63 -0
- package/dist/algorithms/match.js.map +1 -0
- package/dist/algorithms/pairwise.d.ts +45 -0
- package/dist/algorithms/pairwise.d.ts.map +1 -0
- package/dist/algorithms/pairwise.js +165 -0
- package/dist/algorithms/pairwise.js.map +1 -0
- package/dist/algorithms/search.d.ts +25 -0
- package/dist/algorithms/search.d.ts.map +1 -0
- package/dist/algorithms/search.js +32 -0
- package/dist/algorithms/search.js.map +1 -0
- package/dist/client.d.ts +75 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +35 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/chunk.d.ts +3 -0
- package/dist/lib/chunk.d.ts.map +1 -0
- package/dist/lib/chunk.js +12 -0
- package/dist/lib/chunk.js.map +1 -0
- package/dist/lib/unionFind.d.ts +12 -0
- package/dist/lib/unionFind.d.ts.map +1 -0
- package/dist/lib/unionFind.js +58 -0
- package/dist/lib/unionFind.js.map +1 -0
- package/docs/animations/cluster-by-relation.gif +0 -0
- package/docs/animations/find-first-true.gif +0 -0
- package/docs/animations/select-top-k.gif +0 -0
- package/docs/animations/sort-by-pairwise.gif +0 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HikaruEgashira
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# jev-algorithms
|
|
2
|
+
|
|
3
|
+
## Gallery
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @hikae/jev-algorithms
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createTypeSafeClient, sortByPairwise } from "@hikae/jev-algorithms";
|
|
20
|
+
|
|
21
|
+
const jev = createTypeSafeClient({ apiKey: process.env.TYPESAFE_API_KEY! });
|
|
22
|
+
|
|
23
|
+
const inbox = [
|
|
24
|
+
{ id: "1", from: "ceo@acme.com", subject: "Contract", unread: true, body: "Please sign by Friday." },
|
|
25
|
+
{ id: "2", from: "news@acme.com", subject: "Weekly digest", unread: false, body: "This week in..." },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const ordered = await sortByPairwise(jev, inbox, {
|
|
29
|
+
task: "for replying first",
|
|
30
|
+
stateOf: (mail) => ({
|
|
31
|
+
from: mail.from,
|
|
32
|
+
subject: mail.subject,
|
|
33
|
+
unread: mail.unread,
|
|
34
|
+
preview: mail.body.slice(0, 300),
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
// [Contract mail, Weekly digest]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## The client contract
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
interface JevClient {
|
|
44
|
+
evaluate(input: { state: JevState; questions: JevQuestions }): Promise<JevAnswers>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Adapter | Factory | Notes |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| TypeSafe API | `createTypeSafeClient({ apiKey, model?, baseUrl?, fetch? })` | Runtime-agnostic default. Keep the key on the server. |
|
|
51
|
+
| Workers AI | `createWorkersAiClient(binding, model?)` | Pass `env.AI`. Structural, no Cloudflare types required. |
|
|
52
|
+
| In memory | `createMemoryClient(responder)` | Test double; records every `calls` evaluation. |
|
|
53
|
+
|
|
54
|
+
Question builders (`noul`, `choice`, `score`) and answer readers
|
|
55
|
+
(`readNoul`, `readChoice`, `readScore`) are exported for building your own
|
|
56
|
+
algorithms on the same contract.
|
|
57
|
+
|
|
58
|
+
## Algorithms
|
|
59
|
+
|
|
60
|
+
Every algorithm states its request cost in terms of `n` items. A request
|
|
61
|
+
carries many questions, so "one request" is rarely one comparison.
|
|
62
|
+
|
|
63
|
+
### Sorting and selection
|
|
64
|
+
|
|
65
|
+
| Function | What it does | Requests |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `sortByPairwise(client, items, options)` | Total order from pairwise "which ranks higher?" answers | `O(log n)` |
|
|
68
|
+
| `sortByPairwiseWith(items, compare)` | Same, with an injected comparator (no client) | `O(log n)` |
|
|
69
|
+
| `selectTopK(client, items, k, options)` | Top-k, ordered, via quickselect | `O(log n)` |
|
|
70
|
+
| `selectTopKWith(items, k, compare)` | Same, with an injected comparator | `O(log n)` |
|
|
71
|
+
| `createPairComparator(client, items, options)` | Build the comparator to plug Jev into any sort you own | — |
|
|
72
|
+
|
|
73
|
+
The sort is randomized quicksort whose recursion runs level by level. Nodes on
|
|
74
|
+
a level are disjoint, so all their pivot comparisons fit in one request — the
|
|
75
|
+
whole order costs `~log n` round trips instead of `n log n`. `selectTopK` skips
|
|
76
|
+
the side that cannot contain the k-th item.
|
|
77
|
+
|
|
78
|
+
### Threshold search
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const cutoff = await findFirstTrue(jev, emailsByRecency, {
|
|
82
|
+
stateOf: (mail) => ({ ageDays: mail.ageDays }),
|
|
83
|
+
instruction: "Is `candidate` older than 30 days?",
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`findFirstTrue` binary-searches a monotone predicate in `O(log n)` calls.
|
|
88
|
+
|
|
89
|
+
### Ranking from noisy comparisons
|
|
90
|
+
|
|
91
|
+
Pairwise answers can cycle (A > B > C > A). `elo` and `bradleyTerry` turn a
|
|
92
|
+
list of `Comparison`s into one score per id; `rankByElo` and
|
|
93
|
+
`rankByBradleyTerry` order them. `winner` is `"first" | "second" | "draw"`,
|
|
94
|
+
named by position so a player called `"a"` or `"b"` is never ambiguous.
|
|
95
|
+
|
|
96
|
+
### Clustering
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
const groups = await clusterByRelation(jev, tickets, {
|
|
100
|
+
relation: "duplicate support ticket",
|
|
101
|
+
stateOf: (t) => ({ subject: t.subject, body: t.body.slice(0, 300) }),
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Union-find over pairwise equivalence. Every unordered pair is compared once;
|
|
106
|
+
`threshold` controls how confident Jev must be to merge, and `maxComparisons`
|
|
107
|
+
caps sampling for large inputs.
|
|
108
|
+
|
|
109
|
+
### Classification and scoring
|
|
110
|
+
|
|
111
|
+
- `classifyChoice(client, state, { instruction, labels, abstainBelow })` —
|
|
112
|
+
Choice with an abstain band, so uncertain cases can be escalated instead of
|
|
113
|
+
guessed.
|
|
114
|
+
- `booleanDecision(client, state, { instruction, threshold })` — Noul plus a
|
|
115
|
+
thresholded verdict.
|
|
116
|
+
- `rubricScore(client, state, { instruction, levels })` — Score normalized to
|
|
117
|
+
`0..1`, with the nearest level label.
|
|
118
|
+
|
|
119
|
+
### Matching
|
|
120
|
+
|
|
121
|
+
`stableMatching(proposers, receivers, proposerPrefs, receiverPrefs)` is a pure
|
|
122
|
+
Gale-Shapley implementation. `buildPreferences(client, choosers, candidates, options)`
|
|
123
|
+
builds each side's preference order with pairwise comparisons first.
|
|
124
|
+
|
|
125
|
+
## Design notes
|
|
126
|
+
|
|
127
|
+
- **Batch by default.** Every algorithm packs many questions into one
|
|
128
|
+
`evaluate` call and references items through a chunk-local index so a shared
|
|
129
|
+
item travels once.
|
|
130
|
+
- **Fail loud, not wrong.** If Jev omits an answer, algorithms throw. They
|
|
131
|
+
never persist a fabricated order. The one exception is up to the caller:
|
|
132
|
+
`classifyChoice` can abstain by design.
|
|
133
|
+
- **Persistence is yours.** Algorithms return orders and scores; storing them
|
|
134
|
+
(for example, an integer priority column) is a schema decision, not a library
|
|
135
|
+
one.
|
|
136
|
+
|
|
137
|
+
## Testing and mutation testing
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
npm test # build + node:test
|
|
141
|
+
npm run typecheck
|
|
142
|
+
npm run mutation # build + Stryker
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Tests are judged by mutation coverage, not line coverage: a test only earns its
|
|
146
|
+
place if it kills a mutant. Stryker runs with the `command` test runner against
|
|
147
|
+
the built `dist` and a break threshold of 80; the current score is ~85%.
|
|
148
|
+
|
|
149
|
+
Two policies keep the signal honest and the suite lean:
|
|
150
|
+
|
|
151
|
+
- `StringLiteral` mutants are excluded. Prompts and error wording are content,
|
|
152
|
+
not logic; asserting exact prompt text would make the suite brittle without
|
|
153
|
+
catching real bugs.
|
|
154
|
+
- Remaining survivors are equivalent mutants — union-by-size size bookkeeping,
|
|
155
|
+
path compression, and randomized pivot selection that produces the same order
|
|
156
|
+
either way. They are documented rather than chased with tests that assert
|
|
157
|
+
internals.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JevAnswers, JevClient, JevEvaluation } from "../client.js";
|
|
2
|
+
/** Produce the answers for one evaluation. May be async. */
|
|
3
|
+
export type MemoryResponder = (evaluation: JevEvaluation) => JevAnswers | Promise<JevAnswers>;
|
|
4
|
+
export interface MemoryClient extends JevClient {
|
|
5
|
+
/** Every evaluation this client saw, in order. Handy in assertions. */
|
|
6
|
+
readonly calls: JevEvaluation[];
|
|
7
|
+
}
|
|
8
|
+
/** In-memory Jev client driven by a responder, for tests and deterministic oracles. */
|
|
9
|
+
export declare function createMemoryClient(responder: MemoryResponder): MemoryClient;
|
|
10
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEzE,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,CAC7B,UAAU,EAAE,aAAa,KACrB,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC;CAChC;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,eAAe,GAAG,YAAY,CAS3E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** In-memory Jev client driven by a responder, for tests and deterministic oracles. */
|
|
2
|
+
export function createMemoryClient(responder) {
|
|
3
|
+
const calls = [];
|
|
4
|
+
return {
|
|
5
|
+
calls,
|
|
6
|
+
async evaluate(input) {
|
|
7
|
+
calls.push(input);
|
|
8
|
+
return responder(input);
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/adapters/memory.ts"],"names":[],"mappings":"AAYA,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAAC,SAA0B;IAC5D,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,OAAO;QACN,KAAK;QACL,KAAK,CAAC,QAAQ,CAAC,KAAoB;YAClC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JevClient } from "../client.js";
|
|
2
|
+
export interface TypeSafeClientOptions {
|
|
3
|
+
/** TypeSafe API key. Keep it on the server; never ship it to a browser. */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
/** Model id. Defaults to `jev-latest`. */
|
|
6
|
+
model?: string;
|
|
7
|
+
/** Endpoint override, e.g. for a gateway or a test double. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** Fetch implementation. Defaults to the global `fetch`. */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/** Jev over TypeSafe's HTTP API. */
|
|
14
|
+
export declare function createTypeSafeClient(options: TypeSafeClientOptions): JevClient;
|
|
15
|
+
//# sourceMappingURL=typesafe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesafe.d.ts","sourceRoot":"","sources":["../../src/adapters/typesafe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,SAAS,EAAiB,MAAM,cAAc,CAAC;AAMzE,MAAM,WAAW,qBAAqB;IACrC,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAOD,oCAAoC;AACpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,CA6B9E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const DEFAULT_BASE_URL = "https://api.typesafe.ai/v1/systemone";
|
|
2
|
+
const DEFAULT_MODEL = "jev-latest";
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
4
|
+
/** Jev over TypeSafe's HTTP API. */
|
|
5
|
+
export function createTypeSafeClient(options) {
|
|
6
|
+
const { apiKey, model = DEFAULT_MODEL, baseUrl = DEFAULT_BASE_URL, timeoutMs = DEFAULT_TIMEOUT_MS, } = options;
|
|
7
|
+
const doFetch = options.fetch ?? fetch;
|
|
8
|
+
return {
|
|
9
|
+
async evaluate({ state, questions }) {
|
|
10
|
+
const response = await doFetch(baseUrl, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: {
|
|
13
|
+
authorization: `Bearer ${apiKey}`,
|
|
14
|
+
"content-type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify({ model, state, questions }),
|
|
17
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
throw new Error(`TypeSafe request failed: ${response.status}`);
|
|
21
|
+
}
|
|
22
|
+
const body = (await response.json());
|
|
23
|
+
return body.answers ?? {};
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=typesafe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typesafe.js","sourceRoot":"","sources":["../../src/adapters/typesafe.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GAAG,sCAAsC,CAAC;AAChE,MAAM,aAAa,GAAG,YAAY,CAAC;AACnC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAmBlC,oCAAoC;AACpC,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IAClE,MAAM,EACL,MAAM,EACN,KAAK,GAAG,aAAa,EACrB,OAAO,GAAG,gBAAgB,EAC1B,SAAS,GAAG,kBAAkB,GAC9B,GAAG,OAAO,CAAC;IACZ,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IAEvC,OAAO;QACN,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,SAAS,EAAiB;YACjD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACR,aAAa,EAAE,UAAU,MAAM,EAAE;oBACjC,cAAc,EAAE,kBAAkB;iBAClC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;gBACjD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;aACtC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAChE,CAAC;YAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAsB,CAAC;YAC1D,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC3B,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JevClient } from "../client.js";
|
|
2
|
+
/** The shape of a Workers AI binding (`env.AI`). Structural, so no CF types. */
|
|
3
|
+
export interface AiBinding {
|
|
4
|
+
run(model: string, input: unknown): Promise<unknown>;
|
|
5
|
+
}
|
|
6
|
+
/** Jev over a Cloudflare Workers AI binding (`env.AI`). */
|
|
7
|
+
export declare function createWorkersAiClient(binding: AiBinding, model?: string): JevClient;
|
|
8
|
+
//# sourceMappingURL=workers-ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workers-ai.d.ts","sourceRoot":"","sources":["../../src/adapters/workers-ai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,SAAS,EAAiB,MAAM,cAAc,CAAC;AAIzE,gFAAgF;AAChF,MAAM,WAAW,SAAS;IACzB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED,2DAA2D;AAC3D,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,SAAS,EAClB,KAAK,GAAE,MAAsB,GAC3B,SAAS,CASX"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const DEFAULT_MODEL = "typesafe/jev";
|
|
2
|
+
/** Jev over a Cloudflare Workers AI binding (`env.AI`). */
|
|
3
|
+
export function createWorkersAiClient(binding, model = DEFAULT_MODEL) {
|
|
4
|
+
return {
|
|
5
|
+
async evaluate({ state, questions }) {
|
|
6
|
+
const raw = (await binding.run(model, { state, questions }));
|
|
7
|
+
return raw?.answers ?? {};
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=workers-ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workers-ai.js","sourceRoot":"","sources":["../../src/adapters/workers-ai.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,cAAc,CAAC;AAOrC,2DAA2D;AAC3D,MAAM,UAAU,qBAAqB,CACpC,OAAkB,EAClB,QAAgB,aAAa;IAE7B,OAAO;QACN,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,SAAS,EAAiB;YACjD,MAAM,GAAG,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAE1D,CAAC;YACF,OAAO,GAAG,EAAE,OAAO,IAAI,EAAE,CAAC;QAC3B,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Aggregate noisy pairwise outcomes (which may cycle) into a global ranking. */
|
|
2
|
+
export interface Comparison {
|
|
3
|
+
a: string;
|
|
4
|
+
b: string;
|
|
5
|
+
/**
|
|
6
|
+
* Which side won: "first" is `a`, "second" is `b`. Named by position, not
|
|
7
|
+
* by id, so a player literally called "a" or "b" cannot be confused.
|
|
8
|
+
* "draw" splits the point.
|
|
9
|
+
*/
|
|
10
|
+
winner: "first" | "second" | "draw";
|
|
11
|
+
}
|
|
12
|
+
export interface EloOptions {
|
|
13
|
+
/** K-factor: how far one result moves a rating. Default 32. */
|
|
14
|
+
k?: number;
|
|
15
|
+
/** Starting rating for unseen players. Default 1000. */
|
|
16
|
+
initial?: number;
|
|
17
|
+
/** Passes over the comparison list. Default 1. */
|
|
18
|
+
rounds?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Elo ratings after replaying every comparison in order. */
|
|
21
|
+
export declare function elo(comparisons: readonly Comparison[], options?: EloOptions): Record<string, number>;
|
|
22
|
+
export interface BradleyTerryOptions {
|
|
23
|
+
/** MM iterations. Default 100. */
|
|
24
|
+
iterations?: number;
|
|
25
|
+
/** Stop when the largest change falls below this. Default 1e-9. */
|
|
26
|
+
tolerance?: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Bradley-Terry strengths via the MM (minorize-maximize) algorithm.
|
|
30
|
+
*
|
|
31
|
+
* Draws count as half a win for each side. Players with no wins keep strength
|
|
32
|
+
* 0, which is correct: BT is defined up to scale, only the order matters.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bradleyTerry(comparisons: readonly Comparison[], options?: BradleyTerryOptions): Record<string, number>;
|
|
35
|
+
/** Ids ordered strongest-first by Bradley-Terry, ties broken by id. */
|
|
36
|
+
export declare function rankByBradleyTerry(comparisons: readonly Comparison[]): string[];
|
|
37
|
+
/** Ids ordered strongest-first by Elo, ties broken by id. */
|
|
38
|
+
export declare function rankByElo(comparisons: readonly Comparison[]): string[];
|
|
39
|
+
//# sourceMappingURL=aggregate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/algorithms/aggregate.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,MAAM,WAAW,UAAU;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV;;;;OAIG;IACH,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IAC1B,+DAA+D;IAC/D,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,6DAA6D;AAC7D,wBAAgB,GAAG,CAClB,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,OAAO,GAAE,UAAe,GACtB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0BxB;AAED,MAAM,WAAW,mBAAmB;IACnC,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC3B,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,OAAO,GAAE,mBAAwB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAyDxB;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,EAAE,CAK/E;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,WAAW,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,EAAE,CAKtE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Aggregate noisy pairwise outcomes (which may cycle) into a global ranking. */
|
|
2
|
+
/** Elo ratings after replaying every comparison in order. */
|
|
3
|
+
export function elo(comparisons, options = {}) {
|
|
4
|
+
const { k = 32, initial = 1000, rounds = 1 } = options;
|
|
5
|
+
const ratings = new Map();
|
|
6
|
+
const rating = (id) => {
|
|
7
|
+
let value = ratings.get(id);
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
value = initial;
|
|
10
|
+
ratings.set(id, value);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
};
|
|
14
|
+
for (let round = 0; round < rounds; round++) {
|
|
15
|
+
for (const { a, b, winner } of comparisons) {
|
|
16
|
+
const ra = rating(a);
|
|
17
|
+
const rb = rating(b);
|
|
18
|
+
const expectedA = 1 / (1 + 10 ** ((rb - ra) / 400));
|
|
19
|
+
const scoreA = winner === "first" ? 1 : winner === "second" ? 0 : 0.5;
|
|
20
|
+
const delta = k * (scoreA - expectedA);
|
|
21
|
+
ratings.set(a, ra + delta);
|
|
22
|
+
ratings.set(b, rb - delta);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return Object.fromEntries(ratings);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Bradley-Terry strengths via the MM (minorize-maximize) algorithm.
|
|
29
|
+
*
|
|
30
|
+
* Draws count as half a win for each side. Players with no wins keep strength
|
|
31
|
+
* 0, which is correct: BT is defined up to scale, only the order matters.
|
|
32
|
+
*/
|
|
33
|
+
export function bradleyTerry(comparisons, options = {}) {
|
|
34
|
+
const { iterations = 100, tolerance = 1e-9 } = options;
|
|
35
|
+
const players = new Set();
|
|
36
|
+
const wins = new Map();
|
|
37
|
+
// Pairwise game counts, keyed "i\u0000j" with i < j.
|
|
38
|
+
const games = new Map();
|
|
39
|
+
const bump = (map, key, by) => {
|
|
40
|
+
map.set(key, (map.get(key) ?? 0) + by);
|
|
41
|
+
};
|
|
42
|
+
const pairKey = (i, j) => (i < j ? `${i}\u0000${j}` : `${j}\u0000${i}`);
|
|
43
|
+
for (const { a, b, winner } of comparisons) {
|
|
44
|
+
players.add(a);
|
|
45
|
+
players.add(b);
|
|
46
|
+
bump(games, pairKey(a, b), 1);
|
|
47
|
+
if (winner === "first")
|
|
48
|
+
bump(wins, a, 1);
|
|
49
|
+
else if (winner === "second")
|
|
50
|
+
bump(wins, b, 1);
|
|
51
|
+
else {
|
|
52
|
+
bump(wins, a, 0.5);
|
|
53
|
+
bump(wins, b, 0.5);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const ids = [...players];
|
|
57
|
+
const strength = new Map();
|
|
58
|
+
for (const id of ids)
|
|
59
|
+
strength.set(id, Math.max(wins.get(id) ?? 0, 1e-6));
|
|
60
|
+
// Denominator term for each player: sum_j n_ij / (p_i + p_j).
|
|
61
|
+
for (let iteration = 0; iteration < iterations; iteration++) {
|
|
62
|
+
const next = new Map();
|
|
63
|
+
let maxChange = 0;
|
|
64
|
+
for (const id of ids) {
|
|
65
|
+
let denominator = 0;
|
|
66
|
+
for (const other of ids) {
|
|
67
|
+
if (other === id)
|
|
68
|
+
continue;
|
|
69
|
+
const n = games.get(pairKey(id, other));
|
|
70
|
+
if (!n)
|
|
71
|
+
continue;
|
|
72
|
+
denominator += n / (strength.get(id) + strength.get(other));
|
|
73
|
+
}
|
|
74
|
+
const w = wins.get(id) ?? 0;
|
|
75
|
+
const value = denominator > 0 && w > 0 ? w / denominator : 1e-6;
|
|
76
|
+
next.set(id, value);
|
|
77
|
+
maxChange = Math.max(maxChange, Math.abs(value - strength.get(id)));
|
|
78
|
+
}
|
|
79
|
+
// Normalize to keep the scale from drifting.
|
|
80
|
+
const total = [...next.values()].reduce((sum, v) => sum + v, 0) || 1;
|
|
81
|
+
for (const [id, value] of next)
|
|
82
|
+
next.set(id, value / total);
|
|
83
|
+
for (const [id, value] of next)
|
|
84
|
+
strength.set(id, value);
|
|
85
|
+
if (maxChange < tolerance)
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return Object.fromEntries(strength);
|
|
89
|
+
}
|
|
90
|
+
/** Ids ordered strongest-first by Bradley-Terry, ties broken by id. */
|
|
91
|
+
export function rankByBradleyTerry(comparisons) {
|
|
92
|
+
const strengths = bradleyTerry(comparisons);
|
|
93
|
+
return Object.keys(strengths).sort((a, b) => strengths[b] - strengths[a] || a.localeCompare(b));
|
|
94
|
+
}
|
|
95
|
+
/** Ids ordered strongest-first by Elo, ties broken by id. */
|
|
96
|
+
export function rankByElo(comparisons) {
|
|
97
|
+
const ratings = elo(comparisons);
|
|
98
|
+
return Object.keys(ratings).sort((a, b) => ratings[b] - ratings[a] || a.localeCompare(b));
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/algorithms/aggregate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type JevClient, type JevState } from "../client.js";
|
|
2
|
+
export interface ClassifyChoiceOptions {
|
|
3
|
+
/** Question text, e.g. "Which team should handle this ticket?" */
|
|
4
|
+
instruction: string;
|
|
5
|
+
/** Option key -> description. */
|
|
6
|
+
labels: Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Abstain when Jev's confidence is below this. Default 0, which never
|
|
9
|
+
* abstains. Raise it to route uncertain cases to a human.
|
|
10
|
+
*/
|
|
11
|
+
abstainBelow?: number;
|
|
12
|
+
/** Question id. Default "choice". */
|
|
13
|
+
id?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ClassifyChoiceResult {
|
|
16
|
+
/** Chosen label, or null when abstaining. */
|
|
17
|
+
label: string | null;
|
|
18
|
+
confidence: number;
|
|
19
|
+
probabilities: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
/** Pick one label, abstaining below `abstainBelow` confidence. */
|
|
22
|
+
export declare function classifyChoice(client: JevClient, state: JevState, options: ClassifyChoiceOptions): Promise<ClassifyChoiceResult>;
|
|
23
|
+
export interface BooleanDecisionOptions {
|
|
24
|
+
instruction: string;
|
|
25
|
+
criteria?: {
|
|
26
|
+
true?: string;
|
|
27
|
+
false?: string;
|
|
28
|
+
};
|
|
29
|
+
/** Probability at or above which the decision is true. Default 0.5. */
|
|
30
|
+
threshold?: number;
|
|
31
|
+
/** Question id. Default "decision". */
|
|
32
|
+
id?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Ask a yes/no question and return the probability plus a thresholded verdict. */
|
|
35
|
+
export declare function booleanDecision(client: JevClient, state: JevState, options: BooleanDecisionOptions): Promise<{
|
|
36
|
+
value: boolean;
|
|
37
|
+
probability: number;
|
|
38
|
+
}>;
|
|
39
|
+
export interface RubricScoreOptions {
|
|
40
|
+
instruction: string;
|
|
41
|
+
/** Ordered labels, lowest first. */
|
|
42
|
+
levels: string[];
|
|
43
|
+
/** Question id. Default "rubric". */
|
|
44
|
+
id?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface RubricScoreResult {
|
|
47
|
+
/** Interpolated position on the scale, 0..levels.length-1. */
|
|
48
|
+
score: number;
|
|
49
|
+
/** Nearest level index. */
|
|
50
|
+
level: number;
|
|
51
|
+
/** Scale label for `level`. */
|
|
52
|
+
label: string;
|
|
53
|
+
/** `score` mapped to 0–1. */
|
|
54
|
+
normalized: number;
|
|
55
|
+
confidence: number;
|
|
56
|
+
}
|
|
57
|
+
/** Grade a state against an ordered rubric and return the interpolated score. */
|
|
58
|
+
export declare function rubricScore(client: JevClient, state: JevState, options: RubricScoreOptions): Promise<RubricScoreResult>;
|
|
59
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/algorithms/classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAElF,MAAM,WAAW,qBAAqB;IACrC,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,oBAAoB;IACpC,6CAA6C;IAC7C,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,kEAAkE;AAClE,wBAAsB,cAAc,CACnC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CA2B/B;AAED,MAAM,WAAW,sBAAsB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,mFAAmF;AACnF,wBAAsB,eAAe,CACpC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,sBAAsB,GAC7B,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAalD;AAED,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IACjC,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAsB,WAAW,CAChC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAyB5B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { choice, noul, score } from "../client.js";
|
|
2
|
+
/** Pick one label, abstaining below `abstainBelow` confidence. */
|
|
3
|
+
export async function classifyChoice(client, state, options) {
|
|
4
|
+
const { instruction, labels, abstainBelow = 0, id = "choice" } = options;
|
|
5
|
+
if (Object.keys(labels).length < 2) {
|
|
6
|
+
throw new Error("classifyChoice needs at least two labels");
|
|
7
|
+
}
|
|
8
|
+
const answers = await client.evaluate({
|
|
9
|
+
state,
|
|
10
|
+
questions: { [id]: choice(instruction, labels) },
|
|
11
|
+
});
|
|
12
|
+
const answer = answers[id];
|
|
13
|
+
if (!answer || !("choice" in answer)) {
|
|
14
|
+
throw new Error("classifyChoice: Jev returned no choice");
|
|
15
|
+
}
|
|
16
|
+
const probabilities = answer.probabilities ?? {};
|
|
17
|
+
const confidence = typeof answer.confidence === "number"
|
|
18
|
+
? answer.confidence
|
|
19
|
+
: (probabilities[answer.choice] ?? 1);
|
|
20
|
+
return {
|
|
21
|
+
label: confidence >= abstainBelow ? answer.choice : null,
|
|
22
|
+
confidence,
|
|
23
|
+
probabilities,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Ask a yes/no question and return the probability plus a thresholded verdict. */
|
|
27
|
+
export async function booleanDecision(client, state, options) {
|
|
28
|
+
const { instruction, criteria, threshold = 0.5, id = "decision" } = options;
|
|
29
|
+
const answers = await client.evaluate({
|
|
30
|
+
state,
|
|
31
|
+
questions: { [id]: noul(instruction, criteria) },
|
|
32
|
+
});
|
|
33
|
+
const answer = answers[id];
|
|
34
|
+
const probability = answer && "noul" in answer ? answer.noul : undefined;
|
|
35
|
+
if (typeof probability !== "number" || !Number.isFinite(probability)) {
|
|
36
|
+
throw new Error("booleanDecision: Jev returned no answer");
|
|
37
|
+
}
|
|
38
|
+
return { value: probability >= threshold, probability };
|
|
39
|
+
}
|
|
40
|
+
/** Grade a state against an ordered rubric and return the interpolated score. */
|
|
41
|
+
export async function rubricScore(client, state, options) {
|
|
42
|
+
const { instruction, levels, id = "rubric" } = options;
|
|
43
|
+
if (levels.length < 2)
|
|
44
|
+
throw new Error("rubricScore needs at least two levels");
|
|
45
|
+
const answers = await client.evaluate({
|
|
46
|
+
state,
|
|
47
|
+
questions: { [id]: score(instruction, levels) },
|
|
48
|
+
});
|
|
49
|
+
const answer = answers[id];
|
|
50
|
+
const value = answer && "score" in answer ? answer.score : undefined;
|
|
51
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
52
|
+
throw new Error("rubricScore: Jev returned no score");
|
|
53
|
+
}
|
|
54
|
+
const max = levels.length - 1;
|
|
55
|
+
const clamped = Math.max(0, Math.min(max, value));
|
|
56
|
+
const level = Math.round(clamped);
|
|
57
|
+
return {
|
|
58
|
+
score: value,
|
|
59
|
+
level,
|
|
60
|
+
label: levels[level],
|
|
61
|
+
normalized: clamped / max,
|
|
62
|
+
confidence: answer && "confidence" in answer ? (answer.confidence ?? 0) : 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/algorithms/classify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type JevClient, type JevState } from "../client.js";
|
|
2
|
+
/** Total comparisons allowed before the pair set is sampled. */
|
|
3
|
+
export declare const DEFAULT_MAX_COMPARISONS = 2000;
|
|
4
|
+
/** Pairs per Jev request. */
|
|
5
|
+
export declare const DEFAULT_MAX_CLUSTER_PAIRS_PER_REQUEST = 40;
|
|
6
|
+
/** Answers pairs with the probability that the two items are the same. */
|
|
7
|
+
export type EquivalenceComparator = (pairs: Array<[number, number]>) => Promise<number[]>;
|
|
8
|
+
export interface ClusterOptions<T> {
|
|
9
|
+
/** Relation name woven into the question, e.g. "duplicate support ticket". */
|
|
10
|
+
relation: string;
|
|
11
|
+
stateOf: (item: T) => JevState;
|
|
12
|
+
/** Probability at or above which two items are joined. Default 0.5. */
|
|
13
|
+
threshold?: number;
|
|
14
|
+
/** Pairs per request. Default {@link DEFAULT_MAX_CLUSTER_PAIRS_PER_REQUEST}. */
|
|
15
|
+
maxPairsPerRequest?: number;
|
|
16
|
+
/** Cap on total comparisons. Pairs are sampled beyond it. Default 2000. */
|
|
17
|
+
maxComparisons?: number;
|
|
18
|
+
/** Injectable RNG used when sampling. Default `Math.random`. */
|
|
19
|
+
random?: () => number;
|
|
20
|
+
}
|
|
21
|
+
export declare function createRelationComparator<T>(client: JevClient, items: readonly T[], options: Pick<ClusterOptions<T>, "relation" | "stateOf" | "maxPairsPerRequest">): EquivalenceComparator;
|
|
22
|
+
/** Union-find clustering over an injected equivalence comparator. */
|
|
23
|
+
export declare function clusterWith<T>(items: readonly T[], compare: EquivalenceComparator, options?: {
|
|
24
|
+
threshold?: number;
|
|
25
|
+
maxComparisons?: number;
|
|
26
|
+
random?: () => number;
|
|
27
|
+
}): Promise<T[][]>;
|
|
28
|
+
/** Cluster items by a Jev equivalence question, one request per 40 pairs. */
|
|
29
|
+
export declare function clusterByRelation<T>(client: JevClient, items: readonly T[], options: ClusterOptions<T>): Promise<T[][]>;
|
|
30
|
+
//# sourceMappingURL=cluster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/algorithms/cluster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,SAAS,EAAqB,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGtF,gEAAgE;AAChE,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,6BAA6B;AAC7B,eAAO,MAAM,qCAAqC,KAAK,CAAC;AAExD,0EAA0E;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CACnC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAC1B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAEvB,MAAM,WAAW,cAAc,CAAC,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,QAAQ,CAAC;IAC/B,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EACzC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,oBAAoB,CAAC,GAC7E,qBAAqB,CAwDvB;AAED,qEAAqE;AACrE,wBAAsB,WAAW,CAAC,CAAC,EAClC,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,qBAAqB,EAC9B,OAAO,GAAE;IACR,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACjB,GACJ,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CA4BhB;AAED,6EAA6E;AAC7E,wBAAsB,iBAAiB,CAAC,CAAC,EACxC,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,CAOhB"}
|