prepia 1.0.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 +312 -0
- package/bin/prepia.mjs +119 -0
- package/package.json +53 -0
- package/skill/SKILL.md +148 -0
- package/skill/config.json +29 -0
- package/src/analytics/dashboard.mjs +84 -0
- package/src/analytics/tracker.mjs +131 -0
- package/src/api/middleware.mjs +219 -0
- package/src/api/routes.mjs +142 -0
- package/src/api/server.mjs +150 -0
- package/src/cache/disk-store.mjs +199 -0
- package/src/cache/manager.mjs +142 -0
- package/src/cache/memory-store.mjs +205 -0
- package/src/chain/dag.mjs +209 -0
- package/src/chain/executor.mjs +103 -0
- package/src/chain/scheduler.mjs +89 -0
- package/src/client/adapters.mjs +483 -0
- package/src/client/connector.mjs +391 -0
- package/src/client/index.mjs +483 -0
- package/src/client/websocket.mjs +353 -0
- package/src/core/context-packager.mjs +169 -0
- package/src/core/engine.mjs +338 -0
- package/src/core/event-bus.mjs +84 -0
- package/src/core/prepimshot.mjs +120 -0
- package/src/core/task-decomposer.mjs +158 -0
- package/src/edge/lite.mjs +90 -0
- package/src/guard/checker.mjs +123 -0
- package/src/guard/fact-checker.mjs +105 -0
- package/src/guard/hallucination.mjs +108 -0
- package/src/index.mjs +67 -0
- package/src/models/local-model.mjs +171 -0
- package/src/models/provider.mjs +192 -0
- package/src/models/router.mjs +156 -0
- package/src/morph/optimizer.mjs +142 -0
- package/src/network/p2p.mjs +146 -0
- package/src/persona/detector.mjs +118 -0
- package/src/plugins/loader.mjs +120 -0
- package/src/plugins/registry.mjs +164 -0
- package/src/plugins/sandbox.mjs +79 -0
- package/src/rate/limiter.mjs +145 -0
- package/src/rate/shield.mjs +150 -0
- package/src/script/executor.mjs +164 -0
- package/src/script/parser.mjs +134 -0
- package/src/security/privacy.mjs +108 -0
- package/src/security/sanitizer.mjs +133 -0
- package/src/shadow/daemon.mjs +128 -0
- package/src/stream/handler.mjs +204 -0
- package/src/tools/calculator.mjs +312 -0
- package/src/tools/file-ops.mjs +138 -0
- package/src/tools/http-client.mjs +127 -0
- package/src/tools/orchestrator.mjs +205 -0
- package/src/tools/web-scraper.mjs +159 -0
- package/src/tools/web-search.mjs +129 -0
- package/src/vault/knowledge-base.mjs +207 -0
- package/src/vault/pattern-learner.mjs +192 -0
- package/workflows/analyze.json +32 -0
- package/workflows/automate.json +32 -0
- package/workflows/research.json +37 -0
- package/workflows/summarize.json +32 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lightweight edge-compatible mode with minimal memory footprint.
|
|
3
|
+
* @module edge/lite
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class LiteMode {
|
|
7
|
+
/**
|
|
8
|
+
* @param {Object} [options]
|
|
9
|
+
* @param {boolean} [options.enableCache=true] - Enable caching
|
|
10
|
+
* @param {boolean} [options.enableAnalytics=false] - Disable analytics for speed
|
|
11
|
+
* @param {boolean} [options.enablePlugins=false] - Disable plugins
|
|
12
|
+
* @param {boolean} [options.enableP2P=false] - Disable P2P
|
|
13
|
+
*/
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this._config = {
|
|
16
|
+
enableCache: options.enableCache ?? true,
|
|
17
|
+
enableAnalytics: options.enableAnalytics ?? false,
|
|
18
|
+
enablePlugins: options.enablePlugins ?? false,
|
|
19
|
+
enableP2P: options.enableP2P ?? false,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the feature flags for lite mode.
|
|
25
|
+
* @returns {Object}
|
|
26
|
+
*/
|
|
27
|
+
getConfig() {
|
|
28
|
+
return { ...this._config };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a feature is enabled.
|
|
33
|
+
* @param {string} feature
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
isEnabled(feature) {
|
|
37
|
+
return this._config[feature] ?? false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a minimal engine configuration for edge deployment.
|
|
42
|
+
* @returns {Object}
|
|
43
|
+
*/
|
|
44
|
+
createMinimalConfig() {
|
|
45
|
+
return {
|
|
46
|
+
cache: {
|
|
47
|
+
memoryMaxSize: 100,
|
|
48
|
+
memoryTTL: 60000,
|
|
49
|
+
enableDisk: false,
|
|
50
|
+
},
|
|
51
|
+
rate: {
|
|
52
|
+
providers: {},
|
|
53
|
+
},
|
|
54
|
+
tools: ['calculator', 'web-search'],
|
|
55
|
+
analytics: this._config.enableAnalytics,
|
|
56
|
+
plugins: this._config.enablePlugins,
|
|
57
|
+
p2p: this._config.enableP2P,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Estimate memory usage for the current configuration.
|
|
63
|
+
* @returns {Object}
|
|
64
|
+
*/
|
|
65
|
+
estimateMemory() {
|
|
66
|
+
let bytes = 1024; // Base overhead
|
|
67
|
+
if (this._config.enableCache) bytes += 50 * 1024; // ~50KB for cache structures
|
|
68
|
+
if (this._config.enableAnalytics) bytes += 20 * 1024;
|
|
69
|
+
if (this._config.enablePlugins) bytes += 30 * 1024;
|
|
70
|
+
if (this._config.enableP2P) bytes += 40 * 1024;
|
|
71
|
+
return {
|
|
72
|
+
estimatedBytes: bytes,
|
|
73
|
+
estimatedKB: Math.round(bytes / 1024),
|
|
74
|
+
estimatedMB: Math.round(bytes / (1024 * 1024) * 100) / 100,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Enable or disable a feature.
|
|
80
|
+
* @param {string} feature
|
|
81
|
+
* @param {boolean} enabled
|
|
82
|
+
*/
|
|
83
|
+
setFeature(feature, enabled) {
|
|
84
|
+
if (feature in this._config) {
|
|
85
|
+
this._config[feature] = enabled;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default LiteMode;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Output quality verification.
|
|
3
|
+
* @module guard/checker
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} QualityScore
|
|
8
|
+
* @property {number} overall - Overall score (0-1)
|
|
9
|
+
* @property {number} completeness - How complete the answer is (0-1)
|
|
10
|
+
* @property {number} coherence - How coherent the text is (0-1)
|
|
11
|
+
* @property {number} relevance - How relevant to the query (0-1)
|
|
12
|
+
* @property {string[]} flags - Quality issues found
|
|
13
|
+
* @property {string[]} suggestions - Improvement suggestions
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check the quality of an LLM output.
|
|
18
|
+
* @param {string} output - The LLM output text
|
|
19
|
+
* @param {Object} [context]
|
|
20
|
+
* @param {string} [context.query] - Original query
|
|
21
|
+
* @param {number} [context.expectedLength] - Expected output length
|
|
22
|
+
* @returns {QualityScore}
|
|
23
|
+
*/
|
|
24
|
+
export function checkQuality(output, context = {}) {
|
|
25
|
+
if (!output || typeof output !== 'string') {
|
|
26
|
+
return {
|
|
27
|
+
overall: 0,
|
|
28
|
+
completeness: 0,
|
|
29
|
+
coherence: 0,
|
|
30
|
+
relevance: 0,
|
|
31
|
+
flags: ['Empty or invalid output'],
|
|
32
|
+
suggestions: ['Provide a non-empty response'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const flags = [];
|
|
37
|
+
const suggestions = [];
|
|
38
|
+
const trimmed = output.trim();
|
|
39
|
+
|
|
40
|
+
// Completeness check
|
|
41
|
+
let completeness = 1.0;
|
|
42
|
+
if (trimmed.length < 10) {
|
|
43
|
+
completeness -= 0.5;
|
|
44
|
+
flags.push('Output is very short');
|
|
45
|
+
suggestions.push('Provide a more detailed response');
|
|
46
|
+
}
|
|
47
|
+
if (trimmed.endsWith('...') || trimmed.endsWith('…')) {
|
|
48
|
+
completeness -= 0.3;
|
|
49
|
+
flags.push('Output appears truncated');
|
|
50
|
+
}
|
|
51
|
+
if (context.expectedLength && trimmed.length < context.expectedLength * 0.5) {
|
|
52
|
+
completeness -= 0.2;
|
|
53
|
+
flags.push('Output shorter than expected');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Coherence check
|
|
57
|
+
let coherence = 1.0;
|
|
58
|
+
const sentences = trimmed.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
59
|
+
if (sentences.length > 1) {
|
|
60
|
+
// Check for repetitive sentences
|
|
61
|
+
const unique = new Set(sentences.map(s => s.trim().toLowerCase()));
|
|
62
|
+
const repetitionRatio = unique.size / sentences.length;
|
|
63
|
+
if (repetitionRatio < 0.5) {
|
|
64
|
+
coherence -= 0.4;
|
|
65
|
+
flags.push('High repetition detected');
|
|
66
|
+
suggestions.push('Reduce repetitive content');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Check for broken sentences
|
|
70
|
+
const brokenPattern = /\b(\w+)\s+\1\s+\1\b/g;
|
|
71
|
+
if (brokenPattern.test(trimmed)) {
|
|
72
|
+
coherence -= 0.3;
|
|
73
|
+
flags.push('Broken/repetitive text detected');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Relevance check
|
|
77
|
+
let relevance = 0.8; // Default reasonable relevance
|
|
78
|
+
if (context.query) {
|
|
79
|
+
const queryWords = new Set(context.query.toLowerCase().split(/\s+/).filter(w => w.length > 3));
|
|
80
|
+
const outputWords = new Set(trimmed.toLowerCase().split(/\s+/));
|
|
81
|
+
let matches = 0;
|
|
82
|
+
for (const word of queryWords) {
|
|
83
|
+
if (outputWords.has(word)) matches++;
|
|
84
|
+
}
|
|
85
|
+
relevance = queryWords.size > 0 ? Math.min(1, matches / queryWords.size + 0.3) : 0.8;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Check for common issues
|
|
89
|
+
if (/I (?:can't|cannot|am unable to|don't know)/i.test(trimmed)) {
|
|
90
|
+
flags.push('Output expresses uncertainty');
|
|
91
|
+
}
|
|
92
|
+
if (/sorry|apologize|apologies/i.test(trimmed) && trimmed.length < 100) {
|
|
93
|
+
completeness -= 0.3;
|
|
94
|
+
flags.push('Output is primarily an apology');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
completeness = Math.max(0, Math.min(1, completeness));
|
|
98
|
+
coherence = Math.max(0, Math.min(1, coherence));
|
|
99
|
+
relevance = Math.max(0, Math.min(1, relevance));
|
|
100
|
+
|
|
101
|
+
const overall = (completeness * 0.4 + coherence * 0.3 + relevance * 0.3);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
overall: Math.round(overall * 100) / 100,
|
|
105
|
+
completeness: Math.round(completeness * 100) / 100,
|
|
106
|
+
coherence: Math.round(coherence * 100) / 100,
|
|
107
|
+
relevance: Math.round(relevance * 100) / 100,
|
|
108
|
+
flags,
|
|
109
|
+
suggestions,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if output passes minimum quality threshold.
|
|
115
|
+
* @param {string} output
|
|
116
|
+
* @param {number} [threshold=0.5]
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
export function passesThreshold(output, threshold = 0.5) {
|
|
120
|
+
return checkQuality(output).overall >= threshold;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default { checkQuality, passesThreshold };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Cross-reference verification against cached knowledge.
|
|
3
|
+
* @module guard/fact-checker
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} FactCheckResult
|
|
8
|
+
* @property {boolean} verified - Whether claims could be verified
|
|
9
|
+
* @property {number} confidence - Confidence score (0-1)
|
|
10
|
+
* @property {Object[]} claims - Individual claim results
|
|
11
|
+
* @property {string[]} contradictions - Found contradictions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extract factual claims from text.
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {string[]}
|
|
18
|
+
*/
|
|
19
|
+
export function extractClaims(text) {
|
|
20
|
+
if (!text) return [];
|
|
21
|
+
const claims = [];
|
|
22
|
+
|
|
23
|
+
// Sentences that look like factual claims
|
|
24
|
+
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 10);
|
|
25
|
+
|
|
26
|
+
for (const sentence of sentences) {
|
|
27
|
+
const trimmed = sentence.trim();
|
|
28
|
+
// Skip questions and opinions
|
|
29
|
+
if (/^(what|who|when|where|why|how|is|are|do|does|can|could|would|should)\b/i.test(trimmed)) continue;
|
|
30
|
+
if (/^(i think|in my opinion|perhaps|maybe|it seems)/i.test(trimmed)) continue;
|
|
31
|
+
|
|
32
|
+
// Look for factual patterns
|
|
33
|
+
if (
|
|
34
|
+
/\d+/.test(trimmed) || // Contains numbers
|
|
35
|
+
/\b(is|are|was|were|has|have|had)\b/i.test(trimmed) || // State verbs
|
|
36
|
+
/\b(according to|research shows|studies indicate|data suggests)\b/i.test(trimmed) // Attribution
|
|
37
|
+
) {
|
|
38
|
+
claims.push(trimmed);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return claims;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Verify claims against a knowledge base.
|
|
47
|
+
* @param {string} text - Text to fact-check
|
|
48
|
+
* @param {Object} knowledgeBase - Knowledge base instance
|
|
49
|
+
* @returns {Promise<FactCheckResult>}
|
|
50
|
+
*/
|
|
51
|
+
export async function factCheck(text, knowledgeBase) {
|
|
52
|
+
const claims = extractClaims(text);
|
|
53
|
+
if (claims.length === 0) {
|
|
54
|
+
return { verified: true, confidence: 0.5, claims: [], contradictions: [] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const results = [];
|
|
58
|
+
const contradictions = [];
|
|
59
|
+
let totalConfidence = 0;
|
|
60
|
+
|
|
61
|
+
for (const claim of claims) {
|
|
62
|
+
const keywords = claim.toLowerCase().split(/\s+/).filter(w => w.length > 4);
|
|
63
|
+
const searchResults = await knowledgeBase.search(keywords.join(' '), { limit: 3 });
|
|
64
|
+
|
|
65
|
+
let verified = false;
|
|
66
|
+
let confidence = 0;
|
|
67
|
+
|
|
68
|
+
if (searchResults.length > 0) {
|
|
69
|
+
// Check if any knowledge entry supports or contradicts the claim
|
|
70
|
+
for (const entry of searchResults) {
|
|
71
|
+
const entryText = typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value);
|
|
72
|
+
const claimWords = new Set(claim.toLowerCase().split(/\s+/));
|
|
73
|
+
const entryWords = new Set(entryText.toLowerCase().split(/\s+/));
|
|
74
|
+
|
|
75
|
+
let overlap = 0;
|
|
76
|
+
for (const word of claimWords) {
|
|
77
|
+
if (entryWords.has(word) && word.length > 3) overlap++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const similarity = overlap / Math.max(claimWords.size, 1);
|
|
81
|
+
if (similarity > 0.3) {
|
|
82
|
+
verified = true;
|
|
83
|
+
confidence = Math.min(1, similarity + (entry.confidence || 0.5) * 0.3);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Heuristic confidence for unverified claims
|
|
89
|
+
if (!verified) {
|
|
90
|
+
confidence = 0.3; // Low confidence for unverifiable claims
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
totalConfidence += confidence;
|
|
94
|
+
results.push({ claim, verified, confidence });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
verified: results.every(r => r.verified),
|
|
99
|
+
confidence: claims.length > 0 ? totalConfidence / claims.length : 0.5,
|
|
100
|
+
claims: results,
|
|
101
|
+
contradictions,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default { extractClaims, factCheck };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Hallucination detection patterns.
|
|
3
|
+
* @module guard/hallucination
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} HallucinationResult
|
|
8
|
+
* @property {boolean} detected - Whether hallucination patterns were found
|
|
9
|
+
* @property {number} score - Hallucination likelihood (0-1, higher = more likely)
|
|
10
|
+
* @property {Object[]} flags - Specific issues found
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect potential hallucinations in LLM output.
|
|
15
|
+
* @param {string} text - LLM output to check
|
|
16
|
+
* @returns {HallucinationResult}
|
|
17
|
+
*/
|
|
18
|
+
export function detectHallucination(text) {
|
|
19
|
+
if (!text || typeof text !== 'string') {
|
|
20
|
+
return { detected: false, score: 0, flags: [] };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const flags = [];
|
|
24
|
+
let score = 0;
|
|
25
|
+
|
|
26
|
+
// Check for suspicious URLs
|
|
27
|
+
const urlPattern = /https?:\/\/[^\s]+/g;
|
|
28
|
+
const urls = text.match(urlPattern) || [];
|
|
29
|
+
for (const url of urls) {
|
|
30
|
+
// Check for obviously fake domains
|
|
31
|
+
if (/\.xyz\b|\.tk\b|\.ml\b|\.ga\b|\.cf\b/i.test(url)) {
|
|
32
|
+
flags.push({ type: 'suspicious_url', detail: `Suspicious TLD: ${url}` });
|
|
33
|
+
score += 0.3;
|
|
34
|
+
}
|
|
35
|
+
// Check for made-up-looking paths
|
|
36
|
+
if (/\/[a-z]{20,}\//i.test(url)) {
|
|
37
|
+
flags.push({ type: 'suspicious_url', detail: `Suspiciously long path: ${url}` });
|
|
38
|
+
score += 0.2;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check for fake statistics
|
|
43
|
+
const statPatterns = [
|
|
44
|
+
/(?:approximately|about|roughly|around)\s+\d+\.?\d*\s*%/gi,
|
|
45
|
+
/\d+\.?\d*\s*(?:percent|percentage)\s+of/gi,
|
|
46
|
+
/studies?\s+(?:show|indicate|suggest|found)\s+that\s+\d+/gi,
|
|
47
|
+
/according\s+to\s+(?:a\s+)?(?:recent|new|latest)\s+(?:study|research|report)/gi,
|
|
48
|
+
];
|
|
49
|
+
for (const pattern of statPatterns) {
|
|
50
|
+
const matches = text.match(pattern) || [];
|
|
51
|
+
for (const match of matches) {
|
|
52
|
+
// Vague attribution is suspicious
|
|
53
|
+
if (/according to (?:a |the )?(?:recent|new|latest|some)/i.test(match)) {
|
|
54
|
+
flags.push({ type: 'vague_attribution', detail: match });
|
|
55
|
+
score += 0.2;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for invented quotes
|
|
61
|
+
const quotePattern = /[""]([^""]{20,})[""](?:\s*[-–—]\s*(?:\w+)|(?:\s+said\s+))/g;
|
|
62
|
+
let quoteMatch;
|
|
63
|
+
while ((quoteMatch = quotePattern.exec(text)) !== null) {
|
|
64
|
+
// Quotes without proper attribution
|
|
65
|
+
if (!/(?:said|stated|wrote|according to|noted|explained)\s+\w+\s+\w+/.test(text.substring(Math.max(0, quoteMatch.index - 50), quoteMatch.index + quoteMatch[0].length + 50))) {
|
|
66
|
+
flags.push({ type: 'unverified_quote', detail: quoteMatch[0].substring(0, 80) });
|
|
67
|
+
score += 0.2;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check for overly specific numbers without sources
|
|
72
|
+
const specificStats = text.match(/\b\d{1,3}(?:,\d{3})+(?:\.\d+)?\b/g) || [];
|
|
73
|
+
if (specificStats.length > 3 && !/source|reference|study|report|data|census|survey/i.test(text)) {
|
|
74
|
+
flags.push({ type: 'unsourced_statistics', detail: `${specificStats.length} specific numbers without sources` });
|
|
75
|
+
score += 0.15;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Check for hedging language that might indicate uncertainty
|
|
79
|
+
const hedging = (text.match(/\b(may|might|could|possibly|perhaps|allegedly|reportedly|supposedly)\b/gi) || []).length;
|
|
80
|
+
if (hedging > 5) {
|
|
81
|
+
flags.push({ type: 'excessive_hedging', detail: `${hedging} hedging words detected` });
|
|
82
|
+
score += 0.1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for contradictory statements
|
|
86
|
+
const contradictions = [
|
|
87
|
+
{ positive: /\balways\b/gi, negative: /\bnever\b/gi },
|
|
88
|
+
{ positive: /\bincrease[sd]?\b/gi, negative: /\bdecrease[sd]?\b/gi },
|
|
89
|
+
{ positive: /\bmore\b/gi, negative: /\bless\b/gi },
|
|
90
|
+
];
|
|
91
|
+
for (const { positive, negative } of contradictions) {
|
|
92
|
+
const posMatches = text.match(positive) || [];
|
|
93
|
+
const negMatches = text.match(negative) || [];
|
|
94
|
+
if (posMatches.length > 0 && negMatches.length > 2) {
|
|
95
|
+
flags.push({ type: 'potential_contradiction', detail: 'Contains both positive and negative qualifiers' });
|
|
96
|
+
score += 0.1;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
detected: score > 0.3,
|
|
103
|
+
score: Math.min(1, score),
|
|
104
|
+
flags,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default { detectHallucination };
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Prepia - AI middleware that reduces LLM quota usage by 80-95%.
|
|
3
|
+
* Main entry point and public API.
|
|
4
|
+
* @module prepia
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { PrepiaEngine } from './core/engine.mjs';
|
|
8
|
+
export { decompose, partitionTasks } from './core/task-decomposer.mjs';
|
|
9
|
+
export { packageContext, estimateTokens } from './core/context-packager.mjs';
|
|
10
|
+
export { generatePrompt, mergePrompts } from './core/prepimshot.mjs';
|
|
11
|
+
export { emit, on, once, off, removeAll } from './core/event-bus.mjs';
|
|
12
|
+
|
|
13
|
+
export { Orchestrator } from './tools/orchestrator.mjs';
|
|
14
|
+
export { search as webSearch } from './tools/web-search.mjs';
|
|
15
|
+
export { scrape as webScrape } from './tools/web-scraper.mjs';
|
|
16
|
+
export { evaluate as calculate } from './tools/calculator.mjs';
|
|
17
|
+
export * as fileOps from './tools/file-ops.mjs';
|
|
18
|
+
export * as httpClient from './tools/http-client.mjs';
|
|
19
|
+
|
|
20
|
+
export { CacheManager } from './cache/manager.mjs';
|
|
21
|
+
export { MemoryStore } from './cache/memory-store.mjs';
|
|
22
|
+
export { DiskStore } from './cache/disk-store.mjs';
|
|
23
|
+
|
|
24
|
+
export { ModelRouter } from './models/router.mjs';
|
|
25
|
+
export { classify, canHandleLocally, getLocalResponse, Complexity } from './models/local-model.mjs';
|
|
26
|
+
export * as provider from './models/provider.mjs';
|
|
27
|
+
|
|
28
|
+
export { RateShield } from './rate/shield.mjs';
|
|
29
|
+
export { TokenBucket, SlidingWindow } from './rate/limiter.mjs';
|
|
30
|
+
|
|
31
|
+
export { DAG } from './chain/dag.mjs';
|
|
32
|
+
export { Scheduler } from './chain/scheduler.mjs';
|
|
33
|
+
export { Executor } from './chain/executor.mjs';
|
|
34
|
+
|
|
35
|
+
export { KnowledgeBase } from './vault/knowledge-base.mjs';
|
|
36
|
+
export { PatternLearner } from './vault/pattern-learner.mjs';
|
|
37
|
+
|
|
38
|
+
export { PluginLoader } from './plugins/loader.mjs';
|
|
39
|
+
export { PluginRegistry } from './plugins/registry.mjs';
|
|
40
|
+
export { Sandbox } from './plugins/sandbox.mjs';
|
|
41
|
+
|
|
42
|
+
export { checkQuality, passesThreshold } from './guard/checker.mjs';
|
|
43
|
+
export { factCheck, extractClaims } from './guard/fact-checker.mjs';
|
|
44
|
+
export { detectHallucination } from './guard/hallucination.mjs';
|
|
45
|
+
|
|
46
|
+
export { detectPersona, getPersonas, getPersona } from './persona/detector.mjs';
|
|
47
|
+
|
|
48
|
+
export { Tracker } from './analytics/tracker.mjs';
|
|
49
|
+
export { generateReport, generateSummary } from './analytics/dashboard.mjs';
|
|
50
|
+
|
|
51
|
+
export { StreamHandler, Phases } from './stream/handler.mjs';
|
|
52
|
+
|
|
53
|
+
export { sanitize, validateParams, sanitizeOutput } from './security/sanitizer.mjs';
|
|
54
|
+
export { detectPII, redactPII, containsPII } from './security/privacy.mjs';
|
|
55
|
+
|
|
56
|
+
export { Daemon } from './shadow/daemon.mjs';
|
|
57
|
+
export { LiteMode } from './edge/lite.mjs';
|
|
58
|
+
export { Optimizer } from './morph/optimizer.mjs';
|
|
59
|
+
export { P2PNetwork } from './network/p2p.mjs';
|
|
60
|
+
|
|
61
|
+
export { parse as parseScript, validate as validateScript } from './script/parser.mjs';
|
|
62
|
+
export { ScriptExecutor } from './script/executor.mjs';
|
|
63
|
+
|
|
64
|
+
export { Server, createServer } from './api/server.mjs';
|
|
65
|
+
|
|
66
|
+
/** Version */
|
|
67
|
+
export const VERSION = '1.0.0';
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Lightweight local pattern matching / rule engine.
|
|
3
|
+
* Handles common queries without LLM calls.
|
|
4
|
+
* @module models/local-model
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Query complexity levels.
|
|
9
|
+
* @enum {string}
|
|
10
|
+
*/
|
|
11
|
+
export const Complexity = {
|
|
12
|
+
SIMPLE: 'simple',
|
|
13
|
+
MEDIUM: 'medium',
|
|
14
|
+
COMPLEX: 'complex',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pattern definitions for local matching.
|
|
19
|
+
*/
|
|
20
|
+
const PATTERNS = [
|
|
21
|
+
// Greetings
|
|
22
|
+
{
|
|
23
|
+
pattern: /^(hi|hello|hey|greetings|good\s+(morning|afternoon|evening))[\s!.]*$/i,
|
|
24
|
+
handler: () => ({ content: 'Hello! How can I help you?', type: 'greeting', complexity: Complexity.SIMPLE }),
|
|
25
|
+
},
|
|
26
|
+
// Time/date queries
|
|
27
|
+
{
|
|
28
|
+
pattern: /what\s+(?:time|date|day)\s+is\s+it|what(?:'s| is) (?:the )?(?:current )?(?:time|date|day)/i,
|
|
29
|
+
handler: () => {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
return {
|
|
32
|
+
content: `Current date and time: ${now.toISOString()}`,
|
|
33
|
+
type: 'datetime',
|
|
34
|
+
complexity: Complexity.SIMPLE,
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
// Simple math
|
|
39
|
+
{
|
|
40
|
+
pattern: /^[\d\s+\-*/().%^]+$/,
|
|
41
|
+
handler: (query) => {
|
|
42
|
+
try {
|
|
43
|
+
// Simple arithmetic validation - must only contain math chars
|
|
44
|
+
const cleaned = query.replace(/\s/g, '');
|
|
45
|
+
if (!/^[\d+\-*/().%^]+$/.test(cleaned)) return null;
|
|
46
|
+
// Use Function constructor for safe eval (no access to globals)
|
|
47
|
+
const result = new Function(`"use strict"; return (${cleaned})`)();
|
|
48
|
+
if (typeof result === 'number' && isFinite(result)) {
|
|
49
|
+
return { content: String(result), type: 'math', complexity: Complexity.SIMPLE };
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
52
|
+
return null;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
// Unit conversions
|
|
56
|
+
{
|
|
57
|
+
pattern: /(\d+\.?\d*)\s*(km|mi|miles?|meters?|feet|ft|inches?|in|cm|mm|kg|lbs?|pounds?|oz|ounces?|g|grams?|celsius|fahrenheit|°[cf])\s+(?:to|in|as)\s+(km|mi|miles?|meters?|feet|ft|inches?|in|cm|mm|kg|lbs?|pounds?|oz|ounces?|g|grams?|celsius|fahrenheit|°[cf])/i,
|
|
58
|
+
handler: () => null, // Handled by calculator
|
|
59
|
+
},
|
|
60
|
+
// Yes/no questions
|
|
61
|
+
{
|
|
62
|
+
pattern: /^(is|are|was|were|do|does|did|can|could|will|would|should|has|have)\s/i,
|
|
63
|
+
handler: (query) => ({
|
|
64
|
+
content: null, // Needs LLM
|
|
65
|
+
type: 'question',
|
|
66
|
+
complexity: Complexity.MEDIUM,
|
|
67
|
+
hint: 'This appears to be a factual question that may benefit from web search.',
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
// Definitions
|
|
71
|
+
{
|
|
72
|
+
pattern: /^what(?:'s| is| are)\s+(?:a |an |the )?(.+)/i,
|
|
73
|
+
handler: (query, match) => ({
|
|
74
|
+
content: null,
|
|
75
|
+
type: 'definition',
|
|
76
|
+
topic: match[1],
|
|
77
|
+
complexity: Complexity.MEDIUM,
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
// How-to
|
|
81
|
+
{
|
|
82
|
+
pattern: /^how\s+(?:do|to|can)\s+(.+)/i,
|
|
83
|
+
handler: (query, match) => ({
|
|
84
|
+
content: null,
|
|
85
|
+
type: 'howto',
|
|
86
|
+
topic: match[1],
|
|
87
|
+
complexity: Complexity.MEDIUM,
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
// Summarize requests
|
|
91
|
+
{
|
|
92
|
+
pattern: /^(summarize|summarise|tldr|tl;dr|brief)\s/i,
|
|
93
|
+
handler: () => ({
|
|
94
|
+
content: null,
|
|
95
|
+
type: 'summarization',
|
|
96
|
+
complexity: Complexity.MEDIUM,
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Classify and attempt to handle a query locally.
|
|
103
|
+
* @param {string} query - User query
|
|
104
|
+
* @returns {Object} Result with content (if handled locally), type, and complexity
|
|
105
|
+
*/
|
|
106
|
+
export function classify(query) {
|
|
107
|
+
if (typeof query !== 'string') {
|
|
108
|
+
return { content: null, type: 'unknown', complexity: Complexity.COMPLEX };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const trimmed = query.trim();
|
|
112
|
+
if (trimmed.length === 0) {
|
|
113
|
+
return { content: '', type: 'empty', complexity: Complexity.SIMPLE };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const { pattern, handler } of PATTERNS) {
|
|
117
|
+
const match = trimmed.match(pattern);
|
|
118
|
+
if (match) {
|
|
119
|
+
const result = handler(trimmed, match);
|
|
120
|
+
if (result) return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Default: needs LLM
|
|
125
|
+
return {
|
|
126
|
+
content: null,
|
|
127
|
+
type: 'general',
|
|
128
|
+
complexity: estimateComplexity(trimmed),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Estimate query complexity based on heuristics.
|
|
134
|
+
* @param {string} query
|
|
135
|
+
* @returns {string}
|
|
136
|
+
*/
|
|
137
|
+
function estimateComplexity(query) {
|
|
138
|
+
const wordCount = query.split(/\s+/).length;
|
|
139
|
+
const hasMultipleQuestions = (query.match(/\?/g) || []).length > 1;
|
|
140
|
+
const hasConjunctions = /\b(and|but|or|however|although|because)\b/i.test(query);
|
|
141
|
+
|
|
142
|
+
if (wordCount < 10 && !hasMultipleQuestions && !hasConjunctions) {
|
|
143
|
+
return Complexity.SIMPLE;
|
|
144
|
+
}
|
|
145
|
+
if (wordCount > 30 || hasMultipleQuestions || (wordCount > 15 && hasConjunctions)) {
|
|
146
|
+
return Complexity.COMPLEX;
|
|
147
|
+
}
|
|
148
|
+
return Complexity.MEDIUM;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check if a query can be fully handled locally (no LLM needed).
|
|
153
|
+
* @param {string} query
|
|
154
|
+
* @returns {boolean}
|
|
155
|
+
*/
|
|
156
|
+
export function canHandleLocally(query) {
|
|
157
|
+
const result = classify(query);
|
|
158
|
+
return result.content !== null && result.content !== undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get a local response for a query (returns null if LLM needed).
|
|
163
|
+
* @param {string} query
|
|
164
|
+
* @returns {string|null}
|
|
165
|
+
*/
|
|
166
|
+
export function getLocalResponse(query) {
|
|
167
|
+
const result = classify(query);
|
|
168
|
+
return result.content;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default { classify, canHandleLocally, getLocalResponse, Complexity };
|