verifiable-thinking-mcp 0.4.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 +339 -0
- package/package.json +75 -0
- package/src/index.ts +38 -0
- package/src/lib/cache.ts +246 -0
- package/src/lib/compression.ts +804 -0
- package/src/lib/compute/cache.ts +86 -0
- package/src/lib/compute/classifier.ts +555 -0
- package/src/lib/compute/confidence.ts +79 -0
- package/src/lib/compute/context.ts +154 -0
- package/src/lib/compute/extract.ts +200 -0
- package/src/lib/compute/filter.ts +224 -0
- package/src/lib/compute/index.ts +171 -0
- package/src/lib/compute/math.ts +247 -0
- package/src/lib/compute/patterns.ts +564 -0
- package/src/lib/compute/registry.ts +145 -0
- package/src/lib/compute/solvers/arithmetic.ts +65 -0
- package/src/lib/compute/solvers/calculus.ts +249 -0
- package/src/lib/compute/solvers/derivation-core.ts +371 -0
- package/src/lib/compute/solvers/derivation-latex.ts +160 -0
- package/src/lib/compute/solvers/derivation-mistakes.ts +1046 -0
- package/src/lib/compute/solvers/derivation-simplify.ts +451 -0
- package/src/lib/compute/solvers/derivation-transform.ts +620 -0
- package/src/lib/compute/solvers/derivation.ts +67 -0
- package/src/lib/compute/solvers/facts.ts +120 -0
- package/src/lib/compute/solvers/formula.ts +728 -0
- package/src/lib/compute/solvers/index.ts +36 -0
- package/src/lib/compute/solvers/logic.ts +422 -0
- package/src/lib/compute/solvers/probability.ts +307 -0
- package/src/lib/compute/solvers/statistics.ts +262 -0
- package/src/lib/compute/solvers/word-problems.ts +408 -0
- package/src/lib/compute/types.ts +107 -0
- package/src/lib/concepts.ts +111 -0
- package/src/lib/domain.ts +731 -0
- package/src/lib/extraction.ts +912 -0
- package/src/lib/index.ts +122 -0
- package/src/lib/judge.ts +260 -0
- package/src/lib/math/ast.ts +842 -0
- package/src/lib/math/index.ts +8 -0
- package/src/lib/math/operators.ts +171 -0
- package/src/lib/math/tokenizer.ts +477 -0
- package/src/lib/patterns.ts +200 -0
- package/src/lib/session.ts +825 -0
- package/src/lib/think/challenge.ts +323 -0
- package/src/lib/think/complexity.ts +504 -0
- package/src/lib/think/confidence-drift.ts +507 -0
- package/src/lib/think/consistency.ts +347 -0
- package/src/lib/think/guidance.ts +188 -0
- package/src/lib/think/helpers.ts +568 -0
- package/src/lib/think/hypothesis.ts +216 -0
- package/src/lib/think/index.ts +127 -0
- package/src/lib/think/prompts.ts +262 -0
- package/src/lib/think/route.ts +358 -0
- package/src/lib/think/schema.ts +98 -0
- package/src/lib/think/scratchpad-schema.ts +662 -0
- package/src/lib/think/spot-check.ts +961 -0
- package/src/lib/think/types.ts +93 -0
- package/src/lib/think/verification.ts +260 -0
- package/src/lib/tokens.ts +177 -0
- package/src/lib/verification.ts +620 -0
- package/src/prompts/index.ts +10 -0
- package/src/prompts/templates.ts +336 -0
- package/src/resources/index.ts +8 -0
- package/src/resources/sessions.ts +196 -0
- package/src/tools/compress.ts +138 -0
- package/src/tools/index.ts +5 -0
- package/src/tools/scratchpad.ts +2659 -0
- package/src/tools/sessions.ts +144 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confidence scoring for local compute
|
|
3
|
+
* Returns probability (0-1) that local compute will solve the question
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CONFIDENCE_NEGATIVE,
|
|
8
|
+
CONFIDENCE_POSITIVE,
|
|
9
|
+
LIKELY_COMPUTABLE_NEGATIVE,
|
|
10
|
+
LIKELY_COMPUTABLE_POSITIVE,
|
|
11
|
+
} from "./patterns.ts";
|
|
12
|
+
import type { ComputeConfidence } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if a question is likely computable locally
|
|
16
|
+
* Use this for routing decisions before attempting compute
|
|
17
|
+
*/
|
|
18
|
+
export function isLikelyComputable(text: string): boolean {
|
|
19
|
+
const lower = text.toLowerCase();
|
|
20
|
+
|
|
21
|
+
const hasCompute = LIKELY_COMPUTABLE_POSITIVE.some((p) => p.test(text));
|
|
22
|
+
const hasReasoning = LIKELY_COMPUTABLE_NEGATIVE.some((p) => p.test(lower));
|
|
23
|
+
|
|
24
|
+
return hasCompute && !hasReasoning;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate confidence that a question can be computed locally
|
|
29
|
+
* Returns a score from 0-1 with breakdown of matching signals
|
|
30
|
+
*/
|
|
31
|
+
export function computeConfidence(text: string): ComputeConfidence {
|
|
32
|
+
const lower = text.toLowerCase();
|
|
33
|
+
|
|
34
|
+
// Calculate positive score (max of matching signals)
|
|
35
|
+
const matchedPositive: string[] = [];
|
|
36
|
+
let positiveScore = 0;
|
|
37
|
+
|
|
38
|
+
for (const { pattern, weight, name } of CONFIDENCE_POSITIVE) {
|
|
39
|
+
if (pattern.test(text)) {
|
|
40
|
+
matchedPositive.push(name);
|
|
41
|
+
positiveScore = Math.max(positiveScore, weight);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Calculate negative penalty (multiplicative)
|
|
46
|
+
const matchedNegative: string[] = [];
|
|
47
|
+
let negativePenalty = 1.0;
|
|
48
|
+
|
|
49
|
+
for (const { pattern, penalty, name } of CONFIDENCE_NEGATIVE) {
|
|
50
|
+
if (pattern.test(lower)) {
|
|
51
|
+
matchedNegative.push(name);
|
|
52
|
+
negativePenalty *= 1 - penalty;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Final score
|
|
57
|
+
const score = Math.max(0, Math.min(1, positiveScore * (1 - (1 - negativePenalty))));
|
|
58
|
+
|
|
59
|
+
// Determine recommendation
|
|
60
|
+
let recommendation: ComputeConfidence["recommendation"];
|
|
61
|
+
if (score >= 0.85) {
|
|
62
|
+
recommendation = "local_only";
|
|
63
|
+
} else if (score >= 0.6) {
|
|
64
|
+
recommendation = "try_local_first";
|
|
65
|
+
} else if (score >= 0.3) {
|
|
66
|
+
recommendation = "try_local";
|
|
67
|
+
} else {
|
|
68
|
+
recommendation = "skip";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
score,
|
|
73
|
+
signals: {
|
|
74
|
+
positive: matchedPositive,
|
|
75
|
+
negative: matchedNegative,
|
|
76
|
+
},
|
|
77
|
+
recommendation,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-Aware Compute
|
|
3
|
+
*
|
|
4
|
+
* Combines system prompt awareness with local compute.
|
|
5
|
+
* Filters computations by domain relevance before injection.
|
|
6
|
+
*
|
|
7
|
+
* Use case: A financial advisor system prompt shouldn't trigger
|
|
8
|
+
* calculus injections even if the user mentions derivatives.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { detectMetaDomain, getRelevantSolvers, type MetaDomain } from "../domain.ts";
|
|
12
|
+
import { extractAndCompute } from "./extract.ts";
|
|
13
|
+
import { filterByDomainRelevance, isMethodRelevant } from "./filter.ts";
|
|
14
|
+
import type { AugmentedResult, ExtractedComputation } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// TYPES
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
export interface ContextAwareInput {
|
|
21
|
+
/** System prompt (defines domain context) */
|
|
22
|
+
systemPrompt?: string;
|
|
23
|
+
/** User query (secondary context) */
|
|
24
|
+
userQuery?: string;
|
|
25
|
+
/** Thought text to compute */
|
|
26
|
+
thought: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ContextAwareResult extends AugmentedResult {
|
|
30
|
+
/** Detected meta-domain from context */
|
|
31
|
+
domain: MetaDomain;
|
|
32
|
+
/** How many computations were filtered out by domain */
|
|
33
|
+
filteredCount: number;
|
|
34
|
+
/** Computations that were removed (for debugging) */
|
|
35
|
+
filteredComputations: ExtractedComputation[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// MAIN FUNCTION
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract and compute with domain-aware filtering.
|
|
44
|
+
*
|
|
45
|
+
* Algorithm:
|
|
46
|
+
* 1. Detect domain from context (systemPrompt > userQuery > thought)
|
|
47
|
+
* 2. Extract all computations from thought
|
|
48
|
+
* 3. Filter by domain relevance
|
|
49
|
+
* 4. Inject only relevant computations
|
|
50
|
+
*
|
|
51
|
+
* @param input - Thought text with optional system prompt context
|
|
52
|
+
* @returns Augmented thought with only domain-relevant computations
|
|
53
|
+
*/
|
|
54
|
+
export function contextAwareCompute(input: ContextAwareInput): ContextAwareResult {
|
|
55
|
+
const start = performance.now();
|
|
56
|
+
|
|
57
|
+
// Determine context for domain detection
|
|
58
|
+
// Priority: systemPrompt > userQuery > thought
|
|
59
|
+
const contextText = input.systemPrompt || input.userQuery || input.thought;
|
|
60
|
+
|
|
61
|
+
// Detect domain
|
|
62
|
+
const domain = detectMetaDomain(contextText);
|
|
63
|
+
|
|
64
|
+
// Extract all computations from thought
|
|
65
|
+
const extraction = extractAndCompute(input.thought);
|
|
66
|
+
|
|
67
|
+
// If no computations, fast path
|
|
68
|
+
if (!extraction.hasComputations) {
|
|
69
|
+
return {
|
|
70
|
+
...extraction,
|
|
71
|
+
domain,
|
|
72
|
+
filteredCount: 0,
|
|
73
|
+
filteredComputations: [],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter by domain relevance
|
|
78
|
+
const filterResult = filterByDomainRelevance(extraction.computations, contextText);
|
|
79
|
+
|
|
80
|
+
// If nothing filtered, return original
|
|
81
|
+
if (filterResult.stats.removed === 0) {
|
|
82
|
+
return {
|
|
83
|
+
...extraction,
|
|
84
|
+
domain,
|
|
85
|
+
filteredCount: 0,
|
|
86
|
+
filteredComputations: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Re-inject only relevant computations
|
|
91
|
+
const augmented = injectComputations(input.thought, filterResult.relevant);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
augmented,
|
|
95
|
+
computations: filterResult.relevant,
|
|
96
|
+
hasComputations: filterResult.relevant.length > 0,
|
|
97
|
+
time_ms: performance.now() - start,
|
|
98
|
+
domain,
|
|
99
|
+
filteredCount: filterResult.stats.removed,
|
|
100
|
+
filteredComputations: filterResult.filtered,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Inject computations into text.
|
|
106
|
+
* Used when we need to re-inject after filtering.
|
|
107
|
+
*/
|
|
108
|
+
function injectComputations(text: string, computations: ExtractedComputation[]): string {
|
|
109
|
+
if (computations.length === 0) return text;
|
|
110
|
+
|
|
111
|
+
// Sort by position descending to preserve indices
|
|
112
|
+
const sorted = [...computations].sort((a, b) => b.start - a.start);
|
|
113
|
+
|
|
114
|
+
let result = text;
|
|
115
|
+
for (const comp of sorted) {
|
|
116
|
+
const insertPos = comp.end;
|
|
117
|
+
// Check for existing injection
|
|
118
|
+
if (result.slice(insertPos, insertPos + 3) !== " [=") {
|
|
119
|
+
const injection = ` [=${comp.result}]`;
|
|
120
|
+
result = result.slice(0, insertPos) + injection + result.slice(insertPos);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// CONVENIENCE FUNCTIONS
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Simple interface: compute with system prompt context.
|
|
133
|
+
* Returns augmented text only.
|
|
134
|
+
*
|
|
135
|
+
* @param thought - Text to compute
|
|
136
|
+
* @param systemPrompt - Optional system prompt for context
|
|
137
|
+
* @returns Augmented text with domain-relevant computations
|
|
138
|
+
*/
|
|
139
|
+
export function computeWithContext(thought: string, systemPrompt?: string): string {
|
|
140
|
+
return contextAwareCompute({ thought, systemPrompt }).augmented;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if a computation would be filtered given a context.
|
|
145
|
+
* Useful for testing/debugging.
|
|
146
|
+
*
|
|
147
|
+
* @param method - Computation method string
|
|
148
|
+
* @param contextText - Context text for domain detection
|
|
149
|
+
* @returns True if the computation would be kept
|
|
150
|
+
*/
|
|
151
|
+
export function wouldKeepComputation(method: string, contextText: string): boolean {
|
|
152
|
+
const relevantMask = getRelevantSolvers(contextText);
|
|
153
|
+
return isMethodRelevant(method, relevantMask);
|
|
154
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-expression extractor
|
|
3
|
+
* Finds and computes ALL mathematical expressions in a text
|
|
4
|
+
*
|
|
5
|
+
* Performance: O(n) using combined regex with alternation
|
|
6
|
+
* V8 optimizes alternation patterns into efficient automata
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { tryLocalCompute } from "./index.ts";
|
|
10
|
+
import { buildCombinedSpanRegex, EXTRACT, WORD_PROBLEM_PATTERNS } from "./patterns.ts";
|
|
11
|
+
import type { AugmentedResult, ExtractedComputation } from "./types.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Format a numeric result for display
|
|
15
|
+
*/
|
|
16
|
+
function formatResult(result: number | string): number | string {
|
|
17
|
+
if (typeof result === "string") return result;
|
|
18
|
+
return Number.isInteger(result) ? result : +result.toFixed(6);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if a position in text already has an injection marker immediately following
|
|
23
|
+
*/
|
|
24
|
+
function hasInjectionAt(text: string, pos: number): boolean {
|
|
25
|
+
// Check for " [=" immediately at position (the injection format we use)
|
|
26
|
+
return text.slice(pos, pos + 3) === " [=";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Remove overlapping computations, keeping the first (leftmost) match
|
|
31
|
+
*/
|
|
32
|
+
function dedupeOverlaps(computations: ExtractedComputation[]): ExtractedComputation[] {
|
|
33
|
+
if (computations.length <= 1) return computations;
|
|
34
|
+
|
|
35
|
+
// Sort by start position
|
|
36
|
+
const sorted = [...computations].sort((a, b) => a.start - b.start);
|
|
37
|
+
const result: ExtractedComputation[] = [];
|
|
38
|
+
let lastEnd = -1;
|
|
39
|
+
|
|
40
|
+
for (const comp of sorted) {
|
|
41
|
+
// Skip if this overlaps with previous
|
|
42
|
+
if (comp.start < lastEnd) continue;
|
|
43
|
+
result.push(comp);
|
|
44
|
+
lastEnd = comp.end;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract and compute all mathematical expressions in text
|
|
52
|
+
* Returns augmented text with computed values injected
|
|
53
|
+
*
|
|
54
|
+
* Algorithm:
|
|
55
|
+
* 1. Single O(n) pass with combined regex finds formula spans
|
|
56
|
+
* 2. Single O(n) pass finds binary arithmetic
|
|
57
|
+
* 3. Single O(n) pass finds word problems
|
|
58
|
+
* 4. Dedupe overlaps O(c log c)
|
|
59
|
+
* 5. Inject results in reverse order O(c)
|
|
60
|
+
*
|
|
61
|
+
* Total: O(n + c log c) where c = number of matches
|
|
62
|
+
*/
|
|
63
|
+
export function extractAndCompute(text: string): AugmentedResult {
|
|
64
|
+
const start = performance.now();
|
|
65
|
+
const computations: ExtractedComputation[] = [];
|
|
66
|
+
|
|
67
|
+
// Phase 1: Formula patterns (sqrt, factorial, power, etc.) - O(n)
|
|
68
|
+
// Use fresh regex instance to reset lastIndex
|
|
69
|
+
const spanRegex = buildCombinedSpanRegex();
|
|
70
|
+
let match: RegExpExecArray | null;
|
|
71
|
+
|
|
72
|
+
while ((match = spanRegex.exec(text)) !== null) {
|
|
73
|
+
const span = match[0];
|
|
74
|
+
const matchStart = match.index;
|
|
75
|
+
|
|
76
|
+
// Try to compute this span using the full solver pipeline
|
|
77
|
+
const result = tryLocalCompute(span);
|
|
78
|
+
|
|
79
|
+
if (result.solved && result.result !== undefined) {
|
|
80
|
+
computations.push({
|
|
81
|
+
original: span,
|
|
82
|
+
result: formatResult(result.result),
|
|
83
|
+
method: result.method || "formula",
|
|
84
|
+
start: matchStart,
|
|
85
|
+
end: matchStart + span.length,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Phase 2: Simple binary operations (5 + 3, 12 * 4) - O(n)
|
|
91
|
+
const binaryPattern = new RegExp(EXTRACT.binaryOp.source, "g");
|
|
92
|
+
|
|
93
|
+
while ((match = binaryPattern.exec(text)) !== null) {
|
|
94
|
+
const [full, a, op, b] = match;
|
|
95
|
+
if (!a || !b || !op) continue;
|
|
96
|
+
|
|
97
|
+
const matchStart = match.index;
|
|
98
|
+
|
|
99
|
+
// Skip if already covered by a formula pattern
|
|
100
|
+
const alreadyCovered = computations.some(
|
|
101
|
+
(c) => c.start <= matchStart && c.end >= matchStart + full.length,
|
|
102
|
+
);
|
|
103
|
+
if (alreadyCovered) continue;
|
|
104
|
+
|
|
105
|
+
const numA = parseFloat(a);
|
|
106
|
+
const numB = parseFloat(b);
|
|
107
|
+
let result: number | null = null;
|
|
108
|
+
|
|
109
|
+
switch (op) {
|
|
110
|
+
case "+":
|
|
111
|
+
result = numA + numB;
|
|
112
|
+
break;
|
|
113
|
+
case "-":
|
|
114
|
+
result = numA - numB;
|
|
115
|
+
break;
|
|
116
|
+
case "*":
|
|
117
|
+
result = numA * numB;
|
|
118
|
+
break;
|
|
119
|
+
case "/":
|
|
120
|
+
result = numB !== 0 ? numA / numB : null;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (result !== null && Number.isFinite(result)) {
|
|
125
|
+
computations.push({
|
|
126
|
+
original: full,
|
|
127
|
+
result: formatResult(result),
|
|
128
|
+
method: "inline_arithmetic",
|
|
129
|
+
start: matchStart,
|
|
130
|
+
end: matchStart + full.length,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Phase 3: Word problems - O(n * p) where p = word patterns (small constant)
|
|
136
|
+
for (const { pattern, compute, method } of WORD_PROBLEM_PATTERNS) {
|
|
137
|
+
const globalPattern = new RegExp(pattern.source, "gi");
|
|
138
|
+
|
|
139
|
+
while ((match = globalPattern.exec(text)) !== null) {
|
|
140
|
+
const result = compute(match);
|
|
141
|
+
if (result !== null && Number.isFinite(result)) {
|
|
142
|
+
const matchStart = match.index;
|
|
143
|
+
const matchLen = match[0].length;
|
|
144
|
+
|
|
145
|
+
// Skip if already covered
|
|
146
|
+
const alreadyCovered = computations.some(
|
|
147
|
+
(c) => c.start <= matchStart && c.end >= matchStart + matchLen,
|
|
148
|
+
);
|
|
149
|
+
if (alreadyCovered) continue;
|
|
150
|
+
|
|
151
|
+
computations.push({
|
|
152
|
+
original: match[0],
|
|
153
|
+
result: formatResult(result),
|
|
154
|
+
method,
|
|
155
|
+
start: matchStart,
|
|
156
|
+
end: matchStart + matchLen,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Phase 4: Dedupe overlapping matches - O(c log c)
|
|
163
|
+
const deduped = dedupeOverlaps(computations);
|
|
164
|
+
|
|
165
|
+
// Phase 5: Inject results in reverse order to preserve positions - O(c)
|
|
166
|
+
// By processing from end to start, each injection doesn't affect earlier positions
|
|
167
|
+
let augmented = text;
|
|
168
|
+
const sortedByPosDesc = [...deduped].sort((a, b) => b.start - a.start);
|
|
169
|
+
|
|
170
|
+
for (const comp of sortedByPosDesc) {
|
|
171
|
+
const insertPos = comp.end;
|
|
172
|
+
// Only inject if not already present
|
|
173
|
+
if (!hasInjectionAt(augmented, insertPos)) {
|
|
174
|
+
const injection = ` [=${comp.result}]`;
|
|
175
|
+
augmented = augmented.slice(0, insertPos) + injection + augmented.slice(insertPos);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
augmented,
|
|
181
|
+
computations: deduped,
|
|
182
|
+
hasComputations: deduped.length > 0,
|
|
183
|
+
time_ms: performance.now() - start,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Convenience function: compute all expressions and return augmented text only.
|
|
189
|
+
* Use when you just need the result string without metadata.
|
|
190
|
+
*
|
|
191
|
+
* @param text - Input text with mathematical expressions
|
|
192
|
+
* @returns Text with computed values injected as [=result]
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* computeAndReplace("The sqrt(16) is important")
|
|
196
|
+
* // => "The sqrt(16) [=4] is important"
|
|
197
|
+
*/
|
|
198
|
+
export function computeAndReplace(text: string): string {
|
|
199
|
+
return extractAndCompute(text).augmented;
|
|
200
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain-Aware Computation Filtering
|
|
3
|
+
*
|
|
4
|
+
* Filters computations based on domain relevance.
|
|
5
|
+
* Financial context doesn't need calculus, coding context doesn't need probability.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* 1. Map computation methods → SolverType bitmask
|
|
9
|
+
* 2. Get domain's relevant solvers from domain.ts
|
|
10
|
+
* 3. Filter computations that match both
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { detectMetaDomain, getRelevantSolvers, type MetaDomain } from "../domain.ts";
|
|
14
|
+
import { type SolverMask, SolverType } from "./classifier.ts";
|
|
15
|
+
import type { ExtractedComputation } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// METHOD → SOLVER TYPE MAPPING
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Maps computation method strings to their SolverType bitmask.
|
|
23
|
+
* Methods returned by solvers are mapped to the solver type that produced them.
|
|
24
|
+
*/
|
|
25
|
+
const METHOD_TO_SOLVER: Record<string, SolverMask> = {
|
|
26
|
+
// Arithmetic
|
|
27
|
+
arithmetic: SolverType.ARITHMETIC,
|
|
28
|
+
inline_arithmetic: SolverType.ARITHMETIC,
|
|
29
|
+
|
|
30
|
+
// Formula Tier 1 (percentage, factorial, modulo, prime, fibonacci)
|
|
31
|
+
percentage: SolverType.FORMULA_TIER1,
|
|
32
|
+
factorial: SolverType.FORMULA_TIER1,
|
|
33
|
+
modulo: SolverType.FORMULA_TIER1,
|
|
34
|
+
prime: SolverType.FORMULA_TIER1,
|
|
35
|
+
fibonacci: SolverType.FORMULA_TIER1,
|
|
36
|
+
|
|
37
|
+
// Formula Tier 2 (sqrt, power, gcd, lcm)
|
|
38
|
+
sqrt: SolverType.FORMULA_TIER2,
|
|
39
|
+
power: SolverType.FORMULA_TIER2,
|
|
40
|
+
gcd: SolverType.FORMULA_TIER2,
|
|
41
|
+
lcm: SolverType.FORMULA_TIER2,
|
|
42
|
+
|
|
43
|
+
// Formula Tier 3 (log, quadratic, combinations, permutations, last digit)
|
|
44
|
+
logarithm_base10: SolverType.FORMULA_TIER3,
|
|
45
|
+
natural_log: SolverType.FORMULA_TIER3,
|
|
46
|
+
quadratic: SolverType.FORMULA_TIER3,
|
|
47
|
+
quadratic_larger: SolverType.FORMULA_TIER3,
|
|
48
|
+
quadratic_smaller: SolverType.FORMULA_TIER3,
|
|
49
|
+
combinations: SolverType.FORMULA_TIER3,
|
|
50
|
+
permutations: SolverType.FORMULA_TIER3,
|
|
51
|
+
last_digit: SolverType.FORMULA_TIER3,
|
|
52
|
+
|
|
53
|
+
// Formula Tier 4 (pythagorean, trailing zeros, series, matrix, interest)
|
|
54
|
+
pythagorean: SolverType.FORMULA_TIER4,
|
|
55
|
+
trailing_zeros: SolverType.FORMULA_TIER4,
|
|
56
|
+
geometric_series: SolverType.FORMULA_TIER4,
|
|
57
|
+
matrix_determinant: SolverType.FORMULA_TIER4,
|
|
58
|
+
compound_interest: SolverType.FORMULA_TIER4,
|
|
59
|
+
|
|
60
|
+
// Word problems
|
|
61
|
+
word_twice: SolverType.WORD_PROBLEM,
|
|
62
|
+
word_times: SolverType.WORD_PROBLEM,
|
|
63
|
+
word_triple: SolverType.WORD_PROBLEM,
|
|
64
|
+
word_double: SolverType.WORD_PROBLEM,
|
|
65
|
+
word_half: SolverType.WORD_PROBLEM,
|
|
66
|
+
word_third: SolverType.WORD_PROBLEM,
|
|
67
|
+
word_quarter: SolverType.WORD_PROBLEM,
|
|
68
|
+
word_divide: SolverType.WORD_PROBLEM,
|
|
69
|
+
word_sum: SolverType.WORD_PROBLEM,
|
|
70
|
+
word_plus: SolverType.WORD_PROBLEM,
|
|
71
|
+
word_add: SolverType.WORD_PROBLEM,
|
|
72
|
+
word_total: SolverType.WORD_PROBLEM,
|
|
73
|
+
word_difference: SolverType.WORD_PROBLEM,
|
|
74
|
+
word_minus: SolverType.WORD_PROBLEM,
|
|
75
|
+
word_less_than: SolverType.WORD_PROBLEM,
|
|
76
|
+
word_subtract: SolverType.WORD_PROBLEM,
|
|
77
|
+
word_product: SolverType.WORD_PROBLEM,
|
|
78
|
+
word_quotient: SolverType.WORD_PROBLEM,
|
|
79
|
+
word_more_than: SolverType.WORD_PROBLEM,
|
|
80
|
+
word_squared: SolverType.WORD_PROBLEM,
|
|
81
|
+
word_cubed: SolverType.WORD_PROBLEM,
|
|
82
|
+
word_rate: SolverType.WORD_PROBLEM,
|
|
83
|
+
word_average: SolverType.WORD_PROBLEM,
|
|
84
|
+
|
|
85
|
+
// CRT word problems
|
|
86
|
+
crt_bat_ball: SolverType.WORD_PROBLEM,
|
|
87
|
+
crt_lily_pad: SolverType.WORD_PROBLEM,
|
|
88
|
+
crt_widget: SolverType.WORD_PROBLEM,
|
|
89
|
+
crt_harmonic: SolverType.WORD_PROBLEM,
|
|
90
|
+
crt_catchup: SolverType.WORD_PROBLEM,
|
|
91
|
+
crt_pigeonhole: SolverType.WORD_PROBLEM,
|
|
92
|
+
|
|
93
|
+
// Multi-step
|
|
94
|
+
multi_step_word: SolverType.MULTI_STEP,
|
|
95
|
+
multi_step_total: SolverType.MULTI_STEP,
|
|
96
|
+
|
|
97
|
+
// Calculus
|
|
98
|
+
derivative_eval: SolverType.CALCULUS,
|
|
99
|
+
derivative_symbolic: SolverType.CALCULUS,
|
|
100
|
+
definite_integral: SolverType.CALCULUS,
|
|
101
|
+
numerical_integral: SolverType.CALCULUS,
|
|
102
|
+
|
|
103
|
+
// Facts
|
|
104
|
+
math_fact_rationality: SolverType.FACTS,
|
|
105
|
+
math_fact_known_irrational: SolverType.FACTS,
|
|
106
|
+
math_fact_integer: SolverType.FACTS,
|
|
107
|
+
math_fact_fraction: SolverType.FACTS,
|
|
108
|
+
|
|
109
|
+
// Logic
|
|
110
|
+
modus_ponens: SolverType.LOGIC,
|
|
111
|
+
modus_tollens: SolverType.LOGIC,
|
|
112
|
+
syllogism: SolverType.LOGIC,
|
|
113
|
+
xor_violation: SolverType.LOGIC,
|
|
114
|
+
|
|
115
|
+
// Probability
|
|
116
|
+
fair_coin_independence: SolverType.PROBABILITY,
|
|
117
|
+
fair_coin_direct: SolverType.PROBABILITY,
|
|
118
|
+
independent_event: SolverType.PROBABILITY,
|
|
119
|
+
hot_hand_independence: SolverType.PROBABILITY,
|
|
120
|
+
|
|
121
|
+
// Generic formula (from tryLocalCompute pipeline)
|
|
122
|
+
formula: SolverType.FORMULA_TIER1 | SolverType.FORMULA_TIER2,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// FILTER FUNCTIONS
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get the SolverType for a computation method.
|
|
131
|
+
* Returns ARITHMETIC as fallback for unknown methods.
|
|
132
|
+
*/
|
|
133
|
+
export function methodToSolverType(method: string): SolverMask {
|
|
134
|
+
return METHOD_TO_SOLVER[method] ?? SolverType.ARITHMETIC;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a computation's method is relevant for a domain.
|
|
139
|
+
*
|
|
140
|
+
* @param method - Computation method string (e.g., "derivative_eval")
|
|
141
|
+
* @param relevantMask - Bitmask of relevant solver types
|
|
142
|
+
* @returns True if the method is relevant
|
|
143
|
+
*/
|
|
144
|
+
export function isMethodRelevant(method: string, relevantMask: SolverMask): boolean {
|
|
145
|
+
const methodMask = methodToSolverType(method);
|
|
146
|
+
return (methodMask & relevantMask) !== 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Filter computations by domain relevance.
|
|
151
|
+
* Keeps only computations whose methods match the domain's relevant solvers.
|
|
152
|
+
*
|
|
153
|
+
* @param computations - Array of extracted computations
|
|
154
|
+
* @param contextText - System prompt or combined context for domain detection
|
|
155
|
+
* @returns Filtered computations + metadata
|
|
156
|
+
*/
|
|
157
|
+
export function filterByDomainRelevance(
|
|
158
|
+
computations: ExtractedComputation[],
|
|
159
|
+
contextText: string,
|
|
160
|
+
): FilterResult {
|
|
161
|
+
// Get domain's relevant solver mask
|
|
162
|
+
const relevantMask = getRelevantSolvers(contextText);
|
|
163
|
+
const meta = detectMetaDomain(contextText);
|
|
164
|
+
|
|
165
|
+
// Filter computations
|
|
166
|
+
const relevant: ExtractedComputation[] = [];
|
|
167
|
+
const filtered: ExtractedComputation[] = [];
|
|
168
|
+
|
|
169
|
+
for (const comp of computations) {
|
|
170
|
+
if (isMethodRelevant(comp.method, relevantMask)) {
|
|
171
|
+
relevant.push(comp);
|
|
172
|
+
} else {
|
|
173
|
+
filtered.push(comp);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
relevant,
|
|
179
|
+
filtered,
|
|
180
|
+
meta,
|
|
181
|
+
relevantMask,
|
|
182
|
+
stats: {
|
|
183
|
+
total: computations.length,
|
|
184
|
+
kept: relevant.length,
|
|
185
|
+
removed: filtered.length,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Filter computations by explicit solver mask (no domain detection).
|
|
192
|
+
* Useful when you already know the relevant solvers.
|
|
193
|
+
*
|
|
194
|
+
* @param computations - Array of extracted computations
|
|
195
|
+
* @param relevantMask - Bitmask of relevant solver types
|
|
196
|
+
* @returns Filtered computations
|
|
197
|
+
*/
|
|
198
|
+
export function filterByMask(
|
|
199
|
+
computations: ExtractedComputation[],
|
|
200
|
+
relevantMask: SolverMask,
|
|
201
|
+
): ExtractedComputation[] {
|
|
202
|
+
return computations.filter((comp) => isMethodRelevant(comp.method, relevantMask));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// TYPES
|
|
207
|
+
// =============================================================================
|
|
208
|
+
|
|
209
|
+
export interface FilterResult {
|
|
210
|
+
/** Computations that match the domain's relevant solvers */
|
|
211
|
+
relevant: ExtractedComputation[];
|
|
212
|
+
/** Computations that were filtered out */
|
|
213
|
+
filtered: ExtractedComputation[];
|
|
214
|
+
/** Detected meta-domain */
|
|
215
|
+
meta: MetaDomain;
|
|
216
|
+
/** Bitmask of relevant solver types */
|
|
217
|
+
relevantMask: SolverMask;
|
|
218
|
+
/** Statistics */
|
|
219
|
+
stats: {
|
|
220
|
+
total: number;
|
|
221
|
+
kept: number;
|
|
222
|
+
removed: number;
|
|
223
|
+
};
|
|
224
|
+
}
|