@effect-uai/core 0.16.0 → 0.17.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/dist/decision-model/Decision.d.mts +155 -0
- package/dist/decision-model/Decision.d.mts.map +1 -0
- package/dist/decision-model/Decision.mjs +118 -0
- package/dist/decision-model/Decision.mjs.map +1 -0
- package/dist/decision-model/Decision.test.d.mts +1 -0
- package/dist/decision-model/Decision.test.mjs +164 -0
- package/dist/decision-model/Decision.test.mjs.map +1 -0
- package/dist/decision-model/DecisionModel.d.mts +45 -0
- package/dist/decision-model/DecisionModel.d.mts.map +1 -0
- package/dist/decision-model/DecisionModel.mjs +25 -0
- package/dist/decision-model/DecisionModel.mjs.map +1 -0
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +4 -1
- package/dist/math/Probability.d.mts +21 -0
- package/dist/math/Probability.d.mts.map +1 -0
- package/dist/math/Probability.mjs +31 -0
- package/dist/math/Probability.mjs.map +1 -0
- package/dist/math/Probability.test.d.mts +1 -0
- package/dist/math/Probability.test.mjs +95 -0
- package/dist/math/Probability.test.mjs.map +1 -0
- package/package.json +13 -1
- package/src/decision-model/Decision.test.ts +146 -0
- package/src/decision-model/Decision.ts +273 -0
- package/src/decision-model/DecisionModel.ts +53 -0
- package/src/index.ts +3 -0
- package/src/math/Probability.test.ts +61 -0
- package/src/math/Probability.ts +44 -0
package/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * as BrowserTool from "./browser/BrowserTool.js"
|
|
|
6
6
|
export * as Capabilities from "./capabilities/Capabilities.js"
|
|
7
7
|
export * as Chunker from "./chunker/Chunker.js"
|
|
8
8
|
export * as Citation from "./domain/Citation.js"
|
|
9
|
+
export * as Decision from "./decision-model/Decision.js"
|
|
10
|
+
export * as DecisionModel from "./decision-model/DecisionModel.js"
|
|
9
11
|
export * as DeepResearch from "./research/DeepResearch.js"
|
|
10
12
|
export * as Image from "./domain/Image.js"
|
|
11
13
|
export * as Items from "./domain/Items.js"
|
|
@@ -37,6 +39,7 @@ export * as WebRead from "./web-read/WebRead.js"
|
|
|
37
39
|
export * as WebReadTool from "./web-read/WebReadTool.js"
|
|
38
40
|
export * as WebSearch from "./web-search/WebSearch.js"
|
|
39
41
|
export * as WebSearchTool from "./web-search/WebSearchTool.js"
|
|
42
|
+
export * as Probability from "./math/Probability.js"
|
|
40
43
|
export * as Vector from "./math/Vector.js"
|
|
41
44
|
export * as Tokenizer from "./tokenizer/Tokenizer.js"
|
|
42
45
|
export * as Settle from "./streaming/Settle.js"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { confidence, margin } from "./Probability.js"
|
|
3
|
+
|
|
4
|
+
describe("confidence", () => {
|
|
5
|
+
it("is 1 when all mass sits on one outcome", () => {
|
|
6
|
+
expect(confidence([1, 0, 0])).toBeCloseTo(1, 10)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it("is 0 when mass is spread evenly, at any length", () => {
|
|
10
|
+
expect(confidence([0.5, 0.5])).toBeCloseTo(0, 10)
|
|
11
|
+
expect(confidence([0.25, 0.25, 0.25, 0.25])).toBeCloseTo(0, 10)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("is 1 for fewer than two outcomes, which have nothing to be uncertain between", () => {
|
|
15
|
+
expect(confidence([])).toBe(1)
|
|
16
|
+
expect(confidence([1])).toBe(1)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("rises as mass concentrates", () => {
|
|
20
|
+
const spread = confidence([0.4, 0.35, 0.25])
|
|
21
|
+
const leaning = confidence([0.6, 0.25, 0.15])
|
|
22
|
+
const decided = confidence([0.9, 0.07, 0.03])
|
|
23
|
+
expect(spread).toBeLessThan(leaning)
|
|
24
|
+
expect(leaning).toBeLessThan(decided)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("is conservative on two outcomes, so a threshold does not transfer across lengths", () => {
|
|
28
|
+
expect(confidence([0.94, 0.06])).toBeCloseTo(0.67, 2)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe("margin", () => {
|
|
33
|
+
it("is the gap between the top two, whatever order they arrive in", () => {
|
|
34
|
+
expect(margin([0.6, 0.3, 0.1])).toBeCloseTo(0.3, 10)
|
|
35
|
+
expect(margin([0.1, 0.6, 0.3])).toBeCloseTo(0.3, 10)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("is 0 on a tie and 0 when empty", () => {
|
|
39
|
+
expect(margin([0.5, 0.5])).toBeCloseTo(0, 10)
|
|
40
|
+
expect(margin([])).toBe(0)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("yields the lone probability for a single outcome", () => {
|
|
44
|
+
expect(margin([0.7])).toBeCloseTo(0.7, 10)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe("confidence and margin disagree, which is why both exist", () => {
|
|
49
|
+
// Two plausible answers versus no idea. Same winner, same top mass.
|
|
50
|
+
const closeSecond = [0.45, 0.42, 0.13]
|
|
51
|
+
const flatSpread = [0.45, 0.11, 0.11, 0.11, 0.11, 0.11]
|
|
52
|
+
|
|
53
|
+
it("confidence cannot separate them", () => {
|
|
54
|
+
expect(Math.abs(confidence(closeSecond) - confidence(flatSpread))).toBeLessThan(0.05)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("margin separates them sharply", () => {
|
|
58
|
+
expect(margin(closeSecond)).toBeCloseTo(0.03, 10)
|
|
59
|
+
expect(margin(flatSpread)).toBeCloseTo(0.34, 10)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Certainty measures over a probability vector. */
|
|
2
|
+
import { Array as A, Number as N, Option, Order, pipe } from "effect"
|
|
3
|
+
|
|
4
|
+
const descending = Order.flip(Order.Number)
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 1 minus normalized Shannon entropy: 1 when all mass sits on one outcome, 0
|
|
8
|
+
* when spread evenly. Fewer than two outcomes yields 1.
|
|
9
|
+
*
|
|
10
|
+
* Conservative on two outcomes, where `[0.94, 0.06]` scores ~0.67, so a
|
|
11
|
+
* threshold tuned on a long vector does not transfer. {@link margin} is the
|
|
12
|
+
* more stable gate across vectors of different length.
|
|
13
|
+
*/
|
|
14
|
+
export const confidence = (probabilities: ReadonlyArray<number>): number =>
|
|
15
|
+
probabilities.length < 2
|
|
16
|
+
? 1
|
|
17
|
+
: 1 -
|
|
18
|
+
pipe(
|
|
19
|
+
probabilities,
|
|
20
|
+
A.filter((p) => p > 0),
|
|
21
|
+
A.map((p) => -p * Math.log(p)),
|
|
22
|
+
N.sumAll,
|
|
23
|
+
) /
|
|
24
|
+
Math.log(probabilities.length)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Top mass minus runner-up mass, which unlike {@link confidence} separates a
|
|
28
|
+
* close second from a flat spread. A single outcome yields its own
|
|
29
|
+
* probability; empty yields 0.
|
|
30
|
+
*/
|
|
31
|
+
export const margin = (probabilities: ReadonlyArray<number>): number => {
|
|
32
|
+
const top = pipe(probabilities, A.sort(descending), A.take(2))
|
|
33
|
+
return pipe(
|
|
34
|
+
A.get(top, 0),
|
|
35
|
+
Option.map((first) =>
|
|
36
|
+
pipe(
|
|
37
|
+
A.get(top, 1),
|
|
38
|
+
Option.getOrElse(() => 0),
|
|
39
|
+
(second) => first - second,
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
Option.getOrElse(() => 0),
|
|
43
|
+
)
|
|
44
|
+
}
|