llmverify
Version:
AI Output Verification Toolkit — Local-first LLM safety, hallucination detection, PII redaction, prompt injection defense, and runtime monitoring. Zero telemetry. OWASP LLM Top 10 aligned.
132 lines • 14 kB
JavaScript
;
/**
* Fingerprint Engine
*
* Detects behavioral drift by analyzing response structure patterns.
* Uses entropy, sentence structure, and length patterns to identify changes.
*
* WHAT THIS DOES:
* ✅ Calculates response fingerprint (tokens, sentences, entropy)
* ✅ Compares to baseline fingerprint
* ✅ Detects structural drift in responses
*
* WHAT THIS DOES NOT DO:
* ❌ Analyze semantic content
* ❌ Detect quality changes
* ❌ Identify specific model changes
*
* @module engines/runtime/fingerprint
* @author Haiec
* @license MIT
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FingerprintEngine = FingerprintEngine;
exports.extractFingerprint = extractFingerprint;
const LIMITATIONS = [
'Structural analysis only - does not assess content quality',
'Entropy calculation is character-based, not semantic',
'Requires baseline for meaningful comparison',
'May flag legitimate style variations as drift'
];
/**
* Computes Shannon entropy of a text string.
* Higher entropy indicates more randomness/variety.
*/
function computeEntropy(text) {
if (!text || text.length === 0)
return 0;
const freq = {};
for (const ch of text) {
freq[ch] = (freq[ch] || 0) + 1;
}
const len = text.length;
let entropy = 0;
for (const ch in freq) {
const p = freq[ch] / len;
entropy -= p * Math.log2(p);
}
return entropy;
}
/**
* Calculates normalized difference between two values.
*/
function normalizedDiff(a, b) {
return Math.abs(a - b) / Math.max(b, 1);
}
/**
* Extracts fingerprint from response text.
*/
function extractFingerprint(text) {
const tokens = text.split(/\s+/).filter(Boolean).length;
const sentences = text.split(/[.!?]/).filter(s => s.trim().length > 0).length || 1;
const avgSentLength = tokens / sentences;
const entropy = computeEntropy(text);
return { tokens, sentences, avgSentLength, entropy };
}
/**
* Analyzes response fingerprint for behavioral drift.
*
* @param call - The call record to analyze
* @param baselineFingerprint - Baseline fingerprint for comparison
* @returns Engine result with fingerprint analysis
*
* @example
* const result = FingerprintEngine(callRecord, baseline.fingerprint);
* if (result.status === 'warn') {
* console.log('Response structure has changed');
* }
*/
function FingerprintEngine(call, baselineFingerprint) {
const text = call.responseText || '';
const curr = extractFingerprint(text);
// No baseline yet - initialize
if (!baselineFingerprint || !('tokens' in baselineFingerprint) || baselineFingerprint.tokens === undefined) {
return {
metric: 'fingerprint',
value: 0,
status: 'ok',
details: {
initialized: true,
curr,
message: 'Fingerprint baseline initialized'
},
limitations: LIMITATIONS
};
}
const baseline = baselineFingerprint;
// Calculate component differences
const dLen = normalizedDiff(curr.tokens, baseline.tokens);
const dSent = normalizedDiff(curr.avgSentLength, baseline.avgSentLength);
const dSentCount = normalizedDiff(curr.sentences, baseline.sentences);
const dEnt = normalizedDiff(curr.entropy, baseline.entropy);
// Weighted composite score
const value = Math.min(1, 0.25 * dLen + 0.25 * dSent + 0.25 * dSentCount + 0.25 * dEnt);
// Determine status
let status;
if (value < 0.3) {
status = 'ok';
}
else if (value < 0.6) {
status = 'warn';
}
else {
status = 'error';
}
return {
metric: 'fingerprint',
value,
status,
details: {
curr,
baseline,
diffs: {
tokenLength: Math.round(dLen * 100) / 100,
sentenceLength: Math.round(dSent * 100) / 100,
sentenceCount: Math.round(dSentCount * 100) / 100,
entropy: Math.round(dEnt * 100) / 100
}
},
limitations: LIMITATIONS
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmluZ2VycHJpbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvZW5naW5lcy9ydW50aW1lL2ZpbmdlcnByaW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRzs7QUFpRUgsOENBMkRDO0FBS1EsZ0RBQWtCO0FBN0gzQixNQUFNLFdBQVcsR0FBRztJQUNsQiw0REFBNEQ7SUFDNUQsc0RBQXNEO0lBQ3RELDZDQUE2QztJQUM3QywrQ0FBK0M7Q0FDaEQsQ0FBQztBQUVGOzs7R0FHRztBQUNILFNBQVMsY0FBYyxDQUFDLElBQVk7SUFDbEMsSUFBSSxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQztJQUV6QyxNQUFNLElBQUksR0FBMkIsRUFBRSxDQUFDO0lBQ3hDLEtBQUssTUFBTSxFQUFFLElBQUksSUFBSSxFQUFFLENBQUM7UUFDdEIsSUFBSSxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNqQyxDQUFDO0lBRUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztJQUN4QixJQUFJLE9BQU8sR0FBRyxDQUFDLENBQUM7SUFDaEIsS0FBSyxNQUFNLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQztRQUN0QixNQUFNLENBQUMsR0FBRyxJQUFJLENBQUMsRUFBRSxDQUFDLEdBQUcsR0FBRyxDQUFDO1FBQ3pCLE9BQU8sSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBRUQsT0FBTyxPQUFPLENBQUM7QUFDakIsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBUyxjQUFjLENBQUMsQ0FBUyxFQUFFLENBQVM7SUFDMUMsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFD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QUFRO1lBQ1IsS0FBSyxFQUFFO2dCQUNMLFdBQVcsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsR0FBRyxHQUFHO2dCQUN6QyxjQUFjLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLEdBQUcsR0FBRyxDQUFDLEdBQUcsR0FBRztnQkFDN0MsYUFBYSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxHQUFHLEdBQUcsQ0FBQyxHQUFHLEdBQUc7Z0JBQ2pELE9BQU8sRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsR0FBRyxHQUFHO2FBQ3RDO1NBQ0Y7UUFDRCxXQUFXLEVBQUUsV0FBVztLQUN6QixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRmluZ2VycHJpbnQgRW5naW5lXG4gKiBcbiAqIERldGVjdHMgYmVoYXZpb3JhbCBkcmlmdCBieSBhbmFseXppbmcgcmVzcG9uc2Ugc3RydWN0dXJlIHBhdHRlcm5zLlxuICogVXNlcyBlbnRyb3B5LCBzZW50ZW5jZSBzdHJ1Y3R1cmUsIGFuZCBsZW5ndGggcGF0dGVybnMgdG8gaWRlbnRpZnkgY2hhbmdlcy5cbiAqIFxuICogV0hBVCBUSElTIERPRVM6XG4gKiDinIUgQ2FsY3VsYXRlcyByZXNwb25zZSBmaW5nZXJwcmludCAodG9rZW5zLCBzZW50ZW5jZXMsIGVudHJvcHkpXG4gKiDinIUgQ29tcGFyZXMgdG8gYmFzZWxpbmUgZmluZ2VycHJpbnRcbiAqIOKchSBEZXRlY3RzIHN0cnVjdHVyYWwgZHJpZnQgaW4gcmVzcG9uc2VzXG4gKiBcbiAqIFdIQVQgVEhJUyBET0VTIE5PVCBETzpcbiAqIOKdjCBBbmFseXplIHNlbWFudGljIGNvbnRlbnRcbiAqIOKdjCBEZXRlY3QgcXVhbGl0eSBjaGFuZ2VzXG4gKiDinYwgSWRlbnRpZnkgc3BlY2lmaWMgbW9kZWwgY2hhbmdlc1xuICogXG4gKiBAbW9kdWxlIGVuZ2luZXMvcnVudGltZS9maW5nZXJwcmludFxuICogQGF1dGhvciBIYWllY1xuICogQGxpY2Vuc2UgTUlUXG4gKi9cblxuaW1wb3J0IHsgQ2FsbFJlY29yZCwgRW5naW5lUmVzdWx0LCBSZXNwb25zZUZpbmdlcnByaW50IH0gZnJvbSAnLi4vLi4vdHlwZXMvcnVudGltZSc7XG5cbmNvbnN0IExJTUlUQVRJT05TID0gW1xuICAnU3RydWN0dXJhbCBhbmFseXNpcyBvbmx5IC0gZG9lcyBub3QgYXNzZXNzIGNvbnRlbnQgcXVhbGl0eScsXG4gICdFbnRyb3B5IGNhbGN1bGF0aW9uIGlzIGNoYXJhY3Rlci1iYXNlZCwgbm90IHNlbWFudGljJyxcbiAgJ1JlcXVpcmVzIGJhc2VsaW5lIGZvciBtZWFuaW5nZnVsIGNvbXBhcmlzb24nLFxuICAnTWF5IGZsYWcgbGVnaXRpbWF0ZSBzdHlsZSB2YXJpYXRpb25zIGFzIGRyaWZ0J1xuXTtcblxuLyoqXG4gKiBDb21wdXRlcyBTaGFubm9uIGVudHJvcHkgb2YgYSB0ZXh0IHN0cmluZy5cbiAqIEhpZ2hlciBlbnRyb3B5IGluZGljYXRlcyBtb3JlIHJhbmRvbW5lc3MvdmFyaWV0eS5cbiAqL1xuZnVuY3Rpb24gY29tcHV0ZUVudHJvcHkodGV4dDogc3RyaW5nKTogbnVtYmVyIHtcbiAgaWYgKCF0ZXh0IHx8IHRleHQubGVuZ3RoID09PSAwKSByZXR1cm4gMDtcbiAgXG4gIGNvbnN0IGZyZXE6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gPSB7fTtcbiAgZm9yIChjb25zdCBjaCBvZiB0ZXh0KSB7XG4gICAgZnJlcVtjaF0gPSAoZnJlcVtjaF0gfHwgMCkgKyAxO1xuICB9XG4gIFxuICBjb25zdCBsZW4gPSB0ZXh0Lmxlbmd0aDtcbiAgbGV0IGVudHJvcHkgPSAwO1xuICBmb3IgKGNvbnN0IGNoIGluIGZyZXEpIHtcbiAgICBjb25zdCBwID0gZnJlcVtjaF0gLyBsZW47XG4gICAgZW50cm9weSAtPSBwICogTWF0aC5sb2cyKHApO1xuICB9XG4gIFxuICByZXR1cm4gZW50cm9weTtcbn1cblxuLyoqXG4gKiBDYWxjdWxhdGVzIG5vcm1hbGl6ZWQgZGlmZmVyZW5jZSBiZXR3ZWVuIHR3byB2YWx1ZXMuXG4gKi9cbmZ1bmN0aW9uIG5vcm1hbGl6ZWREaWZmKGE6IG51bWJlciwgYjogbnVtYmVyKTogbnVtYmVyIHtcbiAgcmV0dXJuIE1hdGguYWJzKGEgLSBiKSAvIE1hdGgubWF4KGIsIDEpO1xufVxuXG4vKipcbiAqIEV4dHJhY3RzIGZpbmdlcnByaW50IGZyb20gcmVzcG9uc2UgdGV4dC5cbiAqL1xuZnVuY3Rpb24gZXh0cmFjdEZpbmdlcnByaW50KHRleHQ6IHN0cmluZyk6IFJlc3BvbnNlRmluZ2VycHJpbnQge1xuICBjb25zdCB0b2tlbnMgPSB0ZXh0LnNwbGl0KC9cXHMrLykuZmlsdGVyKEJvb2xlYW4pLmxlbmd0aDtcbiAgY29uc3Qgc2VudGVuY2VzID0gdGV4dC5zcGxpdCgvWy4hP10vKS5maWx0ZXIocyA9PiBzLnRyaW0oKS5sZW5ndGggPiAwKS5sZW5ndGggfHwgMTtcbiAgY29uc3QgYXZnU2VudExlbmd0aCA9IHRva2VucyAvIHNlbnRlbmNlcztcbiAgY29uc3QgZW50cm9weSA9IGNvbXB1dGVFbnRyb3B5KHRleHQpO1xuICBcbiAgcmV0dXJuIHsgdG9rZW5zLCBzZW50ZW5jZXMsIGF2Z1NlbnRMZW5ndGgsIGVudHJvcHkgfTtcbn1cblxuLyoqXG4gKiBBbmFseXplcyByZXNwb25zZSBmaW5nZXJwcmludCBmb3IgYmVoYXZpb3JhbCBkcmlmdC5cbiAqIFxuICogQHBhcmFtIGNhbGwgLSBUaGUgY2FsbCByZWNvcmQgdG8gYW5hbHl6ZVxuICogQHBhcmFtIGJhc2VsaW5lRmluZ2VycHJpbnQgLSBCYXNlbGluZSBmaW5nZXJwcmludCBmb3IgY29tcGFyaXNvblxuICogQHJldHVybnMgRW5naW5lIHJlc3VsdCB3aXRoIGZpbmdlcnByaW50IGFuYWx5c2lzXG4gKiBcbiAqIEBleGFtcGxlXG4gKiBjb25zdCByZXN1bHQgPSBGaW5nZXJwcmludEVuZ2luZShjYWxsUmVjb3JkLCBiYXNlbGluZS5maW5nZXJwcmludCk7XG4gKiBpZiAocmVzdWx0LnN0YXR1cyA9PT0gJ3dhcm4nKSB7XG4gKiAgIGNvbnNvbGUubG9nKCdSZXNwb25zZSBzdHJ1Y3R1cmUgaGFzIGNoYW5nZWQnKTtcbiAqIH1cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIEZpbmdlcnByaW50RW5naW5lKFxuICBjYWxsOiBDYWxsUmVjb3JkLFxuICBiYXNlbGluZUZpbmdlcnByaW50OiBSZXNwb25zZUZpbmdlcnByaW50IHwgUmVjb3JkPHN0cmluZywgbmV2ZXI+XG4pOiBFbmdpbmVSZXN1bHQge1xuICBjb25zdCB0ZXh0ID0gY2FsbC5yZXNwb25zZVRleHQgfHwgJyc7XG4gIGNvbnN0IGN1cnIgPSBleHRyYWN0RmluZ2VycHJpbnQodGV4dCk7XG5cbiAgLy8gTm8gYmFzZWxpbmUgeWV0IC0gaW5pdGlhbGl6ZVxuICBpZiAoIWJhc2VsaW5lRmluZ2VycHJpbnQgfHwgISgndG9rZW5zJyBpbiBiYXNlbGluZUZpbmdlcnByaW50KSB8fCBiYXNlbGluZUZpbmdlcnByaW50LnRva2VucyA9PT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIG1ldHJpYzogJ2ZpbmdlcnByaW50JyxcbiAgICAgIHZhbHVlOiAwLFxuICAgICAgc3RhdHVzOiAnb2snLFxuICAgICAgZGV0YWlsczoge1xuICAgICAgICBpbml0aWFsaXplZDogdHJ1ZSxcbiAgICAgICAgY3VycixcbiAgICAgICAgbWVzc2FnZTogJ0ZpbmdlcnByaW50IGJhc2VsaW5lIGluaXRpYWxpemVkJ1xuICAgICAgfSxcbiAgICAgIGxpbWl0YXRpb25zOiBMSU1JVEFUSU9OU1xuICAgIH07XG4gIH1cblxuICBjb25zdCBiYXNlbGluZSA9IGJhc2VsaW5lRmluZ2VycHJpbnQgYXMgUmVzcG9uc2VGaW5nZXJwcmludDtcblxuICAvLyBDYWxjdWxhdGUgY29tcG9uZW50IGRpZmZlcmVuY2VzXG4gIGNvbnN0IGRMZW4gPSBub3JtYWxpemVkRGlmZihjdXJyLnRva2VucywgYmFzZWxpbmUudG9rZW5zKTtcbiAgY29uc3QgZFNlbnQgPSBub3JtYWxpemVkRGlmZihjdXJyLmF2Z1NlbnRMZW5ndGgsIGJhc2VsaW5lLmF2Z1NlbnRMZW5ndGgpO1xuICBjb25zdCBkU2VudENvdW50ID0gbm9ybWFsaXplZERpZmYoY3Vyci5zZW50ZW5jZXMsIGJhc2VsaW5lLnNlbnRlbmNlcyk7XG4gIGNvbnN0IGRFbnQgPSBub3JtYWxpemVkRGlmZihjdXJyLmVudHJvcHksIGJhc2VsaW5lLmVudHJvcHkpO1xuXG4gIC8vIFdlaWdodGVkIGNvbXBvc2l0ZSBzY29yZVxuICBjb25zdCB2YWx1ZSA9IE1hdGgubWluKDEsIDAuMjUgKiBkTGVuICsgMC4yNSAqIGRTZW50ICsgMC4yNSAqIGRTZW50Q291bnQgKyAwLjI1ICogZEVudCk7XG5cbiAgLy8gRGV0ZXJtaW5lIHN0YXR1c1xuICBsZXQgc3RhdHVzOiAnb2snIHwgJ3dhcm4nIHwgJ2Vycm9yJztcbiAgaWYgKHZhbHVlIDwgMC4zKSB7XG4gICAgc3RhdHVzID0gJ29rJztcbiAgfSBlbHNlIGlmICh2YWx1ZSA8IDAuNikge1xuICAgIHN0YXR1cyA9ICd3YXJuJztcbiAgfSBlbHNlIHtcbiAgICBzdGF0dXMgPSAnZXJyb3InO1xuICB9XG5cbiAgcmV0dXJuIHtcbiAgICBtZXRyaWM6ICdmaW5nZXJwcmludCcsXG4gICAgdmFsdWUsXG4gICAgc3RhdHVzLFxuICAgIGRldGFpbHM6IHtcbiAgICAgIGN1cnIsXG4gICAgICBiYXNlbGluZSxcbiAgICAgIGRpZmZzOiB7XG4gICAgICAgIHRva2VuTGVuZ3RoOiBNYXRoLnJvdW5kKGRMZW4gKiAxMDApIC8gMTAwLFxuICAgICAgICBzZW50ZW5jZUxlbmd0aDogTWF0aC5yb3VuZChkU2VudCAqIDEwMCkgLyAxMDAsXG4gICAgICAgIHNlbnRlbmNlQ291bnQ6IE1hdGgucm91bmQoZFNlbnRDb3VudCAqIDEwMCkgLyAxMDAsXG4gICAgICAgIGVudHJvcHk6IE1hdGgucm91bmQoZEVudCAqIDEwMCkgLyAxMDBcbiAgICAgIH1cbiAgICB9LFxuICAgIGxpbWl0YXRpb25zOiBMSU1JVEFUSU9OU1xuICB9O1xufVxuXG4vKipcbiAqIFV0aWxpdHkgdG8gZXh0cmFjdCBmaW5nZXJwcmludCBmb3IgZXh0ZXJuYWwgdXNlLlxuICovXG5leHBvcnQgeyBleHRyYWN0RmluZ2VycHJpbnQgfTtcbiJdfQ==