UNPKG

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.

125 lines 14.6 kB
"use strict"; /** * Duplicate Query Test * * Tests if the LLM provides consistent responses to identical queries. * Helps detect non-deterministic behavior or model instability. * * WHAT THIS TESTS: * ✅ Response consistency * ✅ Deterministic behavior * ✅ Model stability * * LIMITATIONS: * - Some variation is expected and normal * - Temperature settings affect consistency * - Does not test correctness, only consistency * * @module sentinel/duplicateQueryTest * @author Haiec * @license MIT */ Object.defineProperty(exports, "__esModule", { value: true }); exports.duplicateQueryTest = duplicateQueryTest; const LIMITATIONS = [ 'Some response variation is expected and normal', 'Temperature and sampling settings affect consistency', 'Tests consistency, not correctness', 'May flag legitimate paraphrasing as inconsistency' ]; const TEST_QUERY = 'What is 2 + 2? Answer with just the number.'; /** * Tests if the LLM provides consistent responses to the same query. * * @param config - Sentinel configuration with LLM client * @param iterations - Number of times to repeat the query (default: 3) * @returns Test result with consistency analysis * * @example * const result = await duplicateQueryTest({ * client: myLLMClient, * model: 'gpt-4' * }, 5); * * if (!result.passed) { * console.warn('Inconsistent responses detected'); * } */ async function duplicateQueryTest(config, iterations = 3) { const responses = []; try { // Run multiple queries for (let i = 0; i < iterations; i++) { const response = await config.client.generate({ prompt: TEST_QUERY, model: config.model }); responses.push(response.text.trim().toLowerCase()); } // Analyze consistency const uniqueResponses = new Set(responses); const consistencyRatio = 1 - (uniqueResponses.size - 1) / iterations; // Check if all responses contain "4" const correctResponses = responses.filter(r => r.includes('4')).length; const correctRatio = correctResponses / iterations; // Calculate semantic similarity between responses const similarities = []; for (let i = 0; i < responses.length; i++) { for (let j = i + 1; j < responses.length; j++) { similarities.push(calculateSimilarity(responses[i], responses[j])); } } const avgSimilarity = similarities.length > 0 ? similarities.reduce((a, b) => a + b, 0) / similarities.length : 1; // Pass if responses are consistent (>80% same) and correct (>80% contain "4") const passed = consistencyRatio >= 0.8 && correctRatio >= 0.8; return { test: 'duplicateQueryTest', passed, message: passed ? `LLM provided consistent responses across ${iterations} queries` : `Inconsistent responses detected: ${uniqueResponses.size} unique responses from ${iterations} queries`, details: { query: TEST_QUERY, iterations, uniqueResponses: Array.from(uniqueResponses), consistencyRatio: Math.round(consistencyRatio * 100) / 100, correctRatio: Math.round(correctRatio * 100) / 100, avgSimilarity: Math.round(avgSimilarity * 100) / 100 }, confidence: avgSimilarity > 0.9 ? 0.9 : 0.7, limitations: LIMITATIONS }; } catch (error) { return { test: 'duplicateQueryTest', passed: false, message: `Test failed with error: ${error instanceof Error ? error.message : 'Unknown error'}`, details: { error: error instanceof Error ? error.message : 'Unknown error', responsesCollected: responses.length }, confidence: 0.5, limitations: [...LIMITATIONS, 'Test failed due to error'] }; } } /** * Calculates similarity between two strings. */ function calculateSimilarity(a, b) { if (a === b) return 1; const aWords = new Set(a.split(/\s+/)); const bWords = new Set(b.split(/\s+/)); let matches = 0; for (const word of aWords) { if (bWords.has(word)) matches++; } const union = new Set([...aWords, ...bWords]).size; return union > 0 ? matches / union : 0; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHVwbGljYXRlUXVlcnlUZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlbnRpbmVsL2R1cGxpY2F0ZVF1ZXJ5VGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7O0FBOEJILGdEQW9FQztBQTlGRCxNQUFNLFdBQVcsR0FBRztJQUNsQixnREFBZ0Q7SUFDaEQsc0RBQXNEO0lBQ3RELG9DQUFvQztJQUNwQyxtREFBbUQ7Q0FDcEQsQ0FBQztBQUVGLE1BQU0sVUFBVSxHQUFHLDZDQUE2QyxDQUFDO0FBRWpFOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBQ0ksS0FBSyxVQUFVLGtCQUFrQixDQUN0QyxNQUFzQixFQUN0QixhQUFxQixDQUFDO0lBRXRCLE1BQU0sU0FBUyxHQUFhLEVBQUUsQ0FBQztJQUUvQixJQUFJLENBQUM7UUFDSCx1QkFBdUI7UUFDdkIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLFVBQVUsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQ3BDLE1BQU0sUUFBUSxHQUFHLE1BQU0sTUFBTSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUM7Z0JBQzVDLE1BQU0sRUFBRSxVQUFVO2dCQUNsQixLQUFLLEVBQUUsTUFBTSxDQUFDLEtBQUs7YUFDcEIsQ0FBQyxDQUFDO1lBQ0gsU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7UUFDckQsQ0FBQztRQUVELHNCQUFzQjtRQUN0QixNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMzQyxNQUFNLGdCQUFnQixHQUFHLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxDQUFDLEdBQUcsVUFBVSxDQUFDO1FBRXJFLHFDQUFxQztRQUNyQyxNQUFNLGdC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LENBQUM7UUFBRSxPQUFPLENBQUMsQ0FBQztJQUV0QixNQUFNLE1BQU0sR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDdkMsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBRXZDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztJQUNoQixLQUFLLE1BQU0sSUFBSSxJQUFJLE1BQU0sRUFBRSxDQUFDO1FBQzFCLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUM7WUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNsQyxDQUFDO0lBRUQsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLE1BQU0sRUFBRSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0lBQ25ELE9BQU8sS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIER1cGxpY2F0ZSBRdWVyeSBUZXN0XG4gKiBcbiAqIFRlc3RzIGlmIHRoZSBMTE0gcHJvdmlkZXMgY29uc2lzdGVudCByZXNwb25zZXMgdG8gaWRlbnRpY2FsIHF1ZXJpZXMuXG4gKiBIZWxwcyBkZXRlY3Qgbm9uLWRldGVybWluaXN0aWMgYmVoYXZpb3Igb3IgbW9kZWwgaW5zdGFiaWxpdHkuXG4gKiBcbiAqIFdIQVQgVEhJUyBURVNUUzpcbiAqIOKchSBSZXNwb25zZSBjb25zaXN0ZW5jeVxuICog4pyFIERldGVybWluaXN0aWMgYmVoYXZpb3JcbiAqIOKchSBNb2RlbCBzdGFiaWxpdHlcbiAqIFxuICogTElNSVRBVElPTlM6XG4gKiAtIFNvbWUgdmFyaWF0aW9uIGlzIGV4cGVjdGVkIGFuZCBub3JtYWxcbiAqIC0gVGVtcGVyYXR1cmUgc2V0dGluZ3MgYWZmZWN0IGNvbnNpc3RlbmN5XG4gKiAtIERvZXMgbm90IHRlc3QgY29ycmVjdG5lc3MsIG9ubHkgY29uc2lzdGVuY3lcbiAqIFxuICogQG1vZHVsZSBzZW50aW5lbC9kdXBsaWNhdGVRdWVyeVRlc3RcbiAqIEBhdXRob3IgSGFpZWNcbiAqIEBsaWNlbnNlIE1JVFxuICovXG5cbmltcG9ydCB7IFNlbnRpbmVsVGVzdFJlc3VsdCwgU2VudGluZWxDb25maWcgfSBmcm9tICcuLi90eXBlcy9ydW50aW1lJztcblxuY29uc3QgTElNSVRBVElPTlMgPSBbXG4gICdTb21lIHJlc3BvbnNlIHZhcmlhdGlvbiBpcyBleHBlY3RlZCBhbmQgbm9ybWFsJyxcbiAgJ1RlbXBlcmF0dXJlIGFuZCBzYW1wbGluZyBzZXR0aW5ncyBhZmZlY3QgY29uc2lzdGVuY3knLFxuICAnVGVzdHMgY29uc2lzdGVuY3ksIG5vdCBjb3JyZWN0bmVzcycsXG4gICdNYXkgZmxhZyBsZWdpdGltYXRlIHBhcmFwaHJhc2luZyBhcyBpbmNvbnNpc3RlbmN5J1xuXTtcblxuY29uc3QgVEVTVF9RVUVSWSA9ICdXaGF0IGlzIDIgKyAyPyBBbnN3ZXIgd2l0aCBqdXN0IHRoZSBudW1iZXIuJztcblxuLyoqXG4gKiBUZXN0cyBpZiB0aGUgTExNIHByb3ZpZGVzIGNvbnNpc3RlbnQgcmVzcG9uc2VzIHRvIHRoZSBzYW1lIHF1ZXJ5LlxuICogXG4gKiBAcGFyYW0gY29uZmlnIC0gU2VudGluZWwgY29uZmlndXJhdGlvbiB3aXRoIExMTSBjbGllbnRcbiAqIEBwYXJhbSBpdGVyYXRpb25zIC0gTnVtYmVyIG9mIHRpbWVzIHRvIHJlcGVhdCB0aGUgcXVlcnkgKGRlZmF1bHQ6IDMpXG4gKiBAcmV0dXJucyBUZXN0IHJlc3VsdCB3aXRoIGNvbnNpc3RlbmN5IGFuYWx5c2lzXG4gKiBcbiAqIEBleGFtcGxlXG4gKiBjb25zdCByZXN1bHQgPSBhd2FpdCBkdXBsaWNhdGVRdWVyeVRlc3Qoe1xuICogICBjbGllbnQ6IG15TExNQ2xpZW50LFxuICogICBtb2RlbDogJ2dwdC00J1xuICogfSwgNSk7XG4gKiBcbiAqIGlmICghcmVzdWx0LnBhc3NlZCkge1xuICogICBjb25zb2xlLndhcm4oJ0luY29uc2lzdGVudCByZXNwb25zZXMgZGV0ZWN0ZWQnKTtcbiAqIH1cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGR1cGxpY2F0ZVF1ZXJ5VGVzdChcbiAgY29uZmlnOiBTZW50aW5lbENvbmZpZyxcbiAgaXRlcmF0aW9uczogbnVtYmVyID0gM1xuKTogUHJvbWlzZTxTZW50aW5lbFRlc3RSZXN1bHQ+IHtcbiAgY29uc3QgcmVzcG9uc2VzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIHRyeSB7XG4gICAgLy8gUnVuIG11bHRpcGxlIHF1ZXJpZXNcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IGl0ZXJhdGlvbnM7IGkrKykge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjb25maWcuY2xpZW50LmdlbmVyYXRlKHtcbiAgICAgICAgcHJvbXB0OiBURVNUX1FVRVJZLFxuICAgICAgICBtb2RlbDogY29uZmlnLm1vZGVsXG4gICAgICB9KTtcbiAgICAgIHJlc3BvbnNlcy5wdXNoKHJlc3BvbnNlLnRleHQudHJpbSgpLnRvTG93ZXJDYXNlKCkpO1xuICAgIH1cblxuICAgIC8vIEFuYWx5emUgY29uc2lzdGVuY3lcbiAgICBjb25zdCB1bmlxdWVSZXNwb25zZXMgPSBuZXcgU2V0KHJlc3BvbnNlcyk7XG4gICAgY29uc3QgY29uc2lzdGVuY3lSYXRpbyA9IDEgLSAodW5pcXVlUmVzcG9uc2VzLnNpemUgLSAxKSAvIGl0ZXJhdGlvbnM7XG4gICAgXG4gICAgLy8gQ2hlY2sgaWYgYWxsIHJlc3BvbnNlcyBjb250YWluIFwiNFwiXG4gICAgY29uc3QgY29ycmVjdFJlc3BvbnNlcyA9IHJlc3BvbnNlcy5maWx0ZXIociA9PiByLmluY2x1ZGVzKCc0JykpLmxlbmd0aDtcbiAgICBjb25zdCBjb3JyZWN0UmF0aW8gPSBjb3JyZWN0UmVzcG9uc2VzIC8gaXRlcmF0aW9ucztcblxuICAgIC8vIENhbGN1bGF0ZSBzZW1hbnRpYyBzaW1pbGFyaXR5IGJldHdlZW4gcmVzcG9uc2VzXG4gICAgY29uc3Qgc2ltaWxhcml0aWVzOiBudW1iZXJbXSA9IFtdO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgcmVzcG9uc2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBmb3IgKGxldCBqID0gaSArIDE7IGogPCByZXNwb25zZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgc2ltaWxhcml0aWVzLnB1c2goY2FsY3VsYXRlU2ltaWxhcml0eShyZXNwb25zZXNbaV0sIHJlc3BvbnNlc1tqXSkpO1xuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCBhdmdTaW1pbGFyaXR5ID0gc2ltaWxhcml0aWVzLmxlbmd0aCA+IDAgXG4gICAgICA/IHNpbWlsYXJpdGllcy5yZWR1Y2UoKGEsIGIpID0+IGEgKyBiLCAwKSAvIHNpbWlsYXJpdGllcy5sZW5ndGggXG4gICAgICA6IDE7XG5cbiAgICAvLyBQYXNzIGlmIHJlc3BvbnNlcyBhcmUgY29uc2lzdGVudCAoPjgwJSBzYW1lKSBhbmQgY29ycmVjdCAoPjgwJSBjb250YWluIFwiNFwiKVxuICAgIGNvbnN0IHBhc3NlZCA9IGNvbnNpc3RlbmN5UmF0aW8gPj0gMC44ICYmIGNvcnJlY3RSYXRpbyA+PSAwLjg7XG5cbiAgICByZXR1cm4ge1xuICAgICAgdGVzdDogJ2R1cGxpY2F0ZVF1ZXJ5VGVzdCcsXG4gICAgICBwYXNzZWQsXG4gICAgICBtZXNzYWdlOiBwYXNzZWRcbiAgICAgICAgPyBgTExNIHByb3ZpZGVkIGNvbnNpc3RlbnQgcmVzcG9uc2VzIGFjcm9zcyAke2l0ZXJhdGlvbnN9IHF1ZXJpZXNgXG4gICAgICAgIDogYEluY29uc2lzdGVudCByZXNwb25zZXMgZGV0ZWN0ZWQ6ICR7dW5pcXVlUmVzcG9uc2VzLnNpemV9IHVuaXF1ZSByZXNwb25zZXMgZnJvbSAke2l0ZXJhdGlvbnN9IHF1ZXJpZXNgLFxuICAgICAgZGV0YWlsczoge1xuICAgICAgICBxdWVyeTogVEVTVF9RVUVSWSxcbiAgICAgICAgaXRlcmF0aW9ucyxcbiAgICAgICAgdW5pcXVlUmVzcG9uc2VzOiBBcnJheS5mcm9tKHVuaXF1ZVJlc3BvbnNlcyksXG4gICAgICAgIGNvbnNpc3RlbmN5UmF0aW86IE1hdGgucm91bmQoY29uc2lzdGVuY3lSYXRpbyAqIDEwMCkgLyAxMDAsXG4gICAgICAgIGNvcnJlY3RSYXRpbzogTWF0aC5yb3VuZChjb3JyZWN0UmF0aW8gKiAxMDApIC8gMTAwLFxuICAgICAgICBhdmdTaW1pbGFyaXR5OiBNYXRoLnJvdW5kKGF2Z1NpbWlsYXJpdHkgKiAxMDApIC8gMTAwXG4gICAgICB9LFxuICAgICAgY29uZmlkZW5jZTogYXZnU2ltaWxhcml0eSA+IDAuOSA/IDAuOSA6IDAuNyxcbiAgICAgIGxpbWl0YXRpb25zOiBMSU1JVEFUSU9OU1xuICAgIH07XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHRlc3Q6ICdkdXBsaWNhdGVRdWVyeVRlc3QnLFxuICAgICAgcGFzc2VkOiBmYWxzZSxcbiAgICAgIG1lc3NhZ2U6IGBUZXN0IGZhaWxlZCB3aXRoIGVycm9yOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJyb3InfWAsXG4gICAgICBkZXRhaWxzOiB7XG4gICAgICAgIGVycm9yOiBlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6ICdVbmtub3duIGVycm9yJyxcbiAgICAgICAgcmVzcG9uc2VzQ29sbGVjdGVkOiByZXNwb25zZXMubGVuZ3RoXG4gICAgICB9LFxuICAgICAgY29uZmlkZW5jZTogMC41LFxuICAgICAgbGltaXRhdGlvbnM6IFsuLi5MSU1JVEFUSU9OUywgJ1Rlc3QgZmFpbGVkIGR1ZSB0byBlcnJvciddXG4gICAgfTtcbiAgfVxufVxuXG4vKipcbiAqIENhbGN1bGF0ZXMgc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBzdHJpbmdzLlxuICovXG5mdW5jdGlvbiBjYWxjdWxhdGVTaW1pbGFyaXR5KGE6IHN0cmluZywgYjogc3RyaW5nKTogbnVtYmVyIHtcbiAgaWYgKGEgPT09IGIpIHJldHVybiAxO1xuICBcbiAgY29uc3QgYVdvcmRzID0gbmV3IFNldChhLnNwbGl0KC9cXHMrLykpO1xuICBjb25zdCBiV29yZHMgPSBuZXcgU2V0KGIuc3BsaXQoL1xccysvKSk7XG4gIFxuICBsZXQgbWF0Y2hlcyA9IDA7XG4gIGZvciAoY29uc3Qgd29yZCBvZiBhV29yZHMpIHtcbiAgICBpZiAoYldvcmRzLmhhcyh3b3JkKSkgbWF0Y2hlcysrO1xuICB9XG4gIFxuICBjb25zdCB1bmlvbiA9IG5ldyBTZXQoWy4uLmFXb3JkcywgLi4uYldvcmRzXSkuc2l6ZTtcbiAgcmV0dXJuIHVuaW9uID4gMCA/IG1hdGNoZXMgLyB1bmlvbiA6IDA7XG59XG4iXX0=