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
JavaScript
;
/**
* 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=