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.

155 lines 14.3 kB
"use strict"; /** * Structure Engine * * Analyzes response structure for expected patterns. * Detects JSON, lists, code blocks, and other structural elements. * * WHAT THIS DOES: * ✅ Detects JSON in responses * ✅ Counts list items and bullet points * ✅ Identifies code blocks * ✅ Flags unstructured responses when structure expected * * WHAT THIS DOES NOT DO: * ❌ Validate JSON schema * ❌ Assess content quality * ❌ Determine if structure is appropriate for query * * @module engines/runtime/structure * @author Haiec * @license MIT */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StructureEngine = StructureEngine; const LIMITATIONS = [ 'Pattern-based detection only', 'Cannot determine if structure is appropriate for the query', 'May miss custom formatting patterns', 'Does not validate structural correctness' ]; /** * Checks if text contains valid JSON. */ function containsJSON(text) { // Look for JSON-like patterns const jsonPatterns = [ /\{[\s\S]*\}/, // Object /\[[\s\S]*\]/ // Array ]; for (const pattern of jsonPatterns) { const match = text.match(pattern); if (match) { try { JSON.parse(match[0]); return { found: true, valid: true }; } catch { return { found: true, valid: false }; } } } return { found: false, valid: false }; } /** * Counts list items in text. */ function countListItems(text) { const patterns = [ /^[-*•]\s+/gm, // Bullet points /^\d+[.)]\s+/gm, // Numbered lists /^[a-z][.)]\s+/gim // Lettered lists ]; let count = 0; for (const pattern of patterns) { const matches = text.match(pattern); if (matches) count += matches.length; } return count; } /** * Counts code blocks in text. */ function countCodeBlocks(text) { const fencedBlocks = (text.match(/```[\s\S]*?```/g) || []).length; const indentedBlocks = (text.match(/^( |\t).+$/gm) || []).length; return fencedBlocks + Math.floor(indentedBlocks / 3); // Group indented lines } /** * Counts headers/sections in text. */ function countHeaders(text) { const markdownHeaders = (text.match(/^#{1,6}\s+.+$/gm) || []).length; const underlineHeaders = (text.match(/^.+\n[=-]+$/gm) || []).length; return markdownHeaders + underlineHeaders; } /** * Analyzes response structure for patterns. * * @param call - The call record to analyze * @param expectStructure - Whether structure is expected (optional) * @returns Engine result with structure analysis * * @example * const result = StructureEngine(callRecord); * if (result.details.isJson) { * console.log('Response contains JSON'); * } */ function StructureEngine(call, expectStructure) { const text = call.responseText || ''; // Analyze structure const json = containsJSON(text); const listCount = countListItems(text); const codeBlockCount = countCodeBlocks(text); const headerCount = countHeaders(text); // Count structural elements const structuralElements = [ json.found, listCount > 0, codeBlockCount > 0, headerCount > 0 ].filter(Boolean).length; // Calculate anomaly score let anomalies = 0; const maxAnomalies = 3; // Check for broken JSON if (json.found && !json.valid) anomalies++; // Check for no structure when expected if (expectStructure && structuralElements === 0) anomalies++; // Check for very short responses (potential truncation) if (text.length < 50 && call.responseTokens > 10) anomalies++; const value = anomalies / maxAnomalies; // Determine status let status; if (value === 0) { status = 'ok'; } else if (value < 0.5) { status = 'warn'; } else { status = 'error'; } return { metric: 'structure', value, status, details: { isJson: json.found, jsonValid: json.valid, listCount, codeBlockCount, headerCount, structuralElements, anomalies, textLength: text.length }, limitations: LIMITATIONS }; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RydWN0dXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2VuZ2luZXMvcnVudGltZS9zdHJ1Y3R1cmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9CRzs7QUFzRkgsMENBNkRDO0FBL0lELE1BQU0sV0FBVyxHQUFHO0lBQ2xCLDhCQUE4QjtJQUM5Qiw0REFBNEQ7SUFDNUQscUNBQXFDO0lBQ3JDLDBDQUEwQztDQUMzQyxDQUFDO0FBRUY7O0dBRUc7QUFDSCxTQUFTLFlBQVksQ0FBQyxJQUFZO0lBQ2hDLDhCQUE4QjtJQUM5QixNQUFNLFlBQVksR0FBRztRQUNuQixhQUFhLEVBQUcsU0FBUztRQUN6QixhQUFhLENBQUcsUUFBUTtLQUN6QixDQUFDO0lBRUYsS0FBSyxNQUFNLE9BQU8sSUFBSSxZQUFZLEVBQUUsQ0FBQztRQUNuQyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2xDLElBQUksS0FBSyxFQUFFLENBQUM7WUFDVixJQUFJLENBQUM7Z0JBQ0gsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDckIsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDO1lBQ3RDLENBQUM7WUFBQyxNQUFNLENBQUM7Z0JBQ1AsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDO1lBQ3ZDLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVELE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsQ0FBQztBQUN4QyxDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFTLGNBQWMsQ0FBQyxJQUFZO0lBQ2xDLE1BQU0sUUFBUSxHQUFHO1FBQ2YsYUFBYSxFQUFZLGdC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S0FBSyxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDbEIsQ0FBQztTQUFNLENBQUM7UUFDTixNQUFNLEdBQUcsT0FBTyxDQUFDO0lBQ25CLENBQUM7SUFFRCxPQUFPO1FBQ0wsTUFBTSxFQUFFLFdBQVc7UUFDbkIsS0FBSztRQUNMLE1BQU07UUFDTixPQUFPLEVBQUU7WUFDUCxNQUFNLEVBQUUsSUFBSSxDQUFDLEtBQUs7WUFDbEIsU0FBUyxFQUFFLElBQUksQ0FBQyxLQUFLO1lBQ3JCLFNBQVM7WUFDVCxjQUFjO1lBQ2QsV0FBVztZQUNYLGtCQUFrQjtZQUNsQixTQUFTO1lBQ1QsVUFBVSxFQUFFLElBQUksQ0FBQyxNQUFNO1NBQ3hCO1FBQ0QsV0FBVyxFQUFFLFdBQVc7S0FDekIsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFN0cnVjdHVyZSBFbmdpbmVcbiAqIFxuICogQW5hbHl6ZXMgcmVzcG9uc2Ugc3RydWN0dXJlIGZvciBleHBlY3RlZCBwYXR0ZXJucy5cbiAqIERldGVjdHMgSlNPTiwgbGlzdHMsIGNvZGUgYmxvY2tzLCBhbmQgb3RoZXIgc3RydWN0dXJhbCBlbGVtZW50cy5cbiAqIFxuICogV0hBVCBUSElTIERPRVM6XG4gKiDinIUgRGV0ZWN0cyBKU09OIGluIHJlc3BvbnNlc1xuICog4pyFIENvdW50cyBsaXN0IGl0ZW1zIGFuZCBidWxsZXQgcG9pbnRzXG4gKiDinIUgSWRlbnRpZmllcyBjb2RlIGJsb2Nrc1xuICog4pyFIEZsYWdzIHVuc3RydWN0dXJlZCByZXNwb25zZXMgd2hlbiBzdHJ1Y3R1cmUgZXhwZWN0ZWRcbiAqIFxuICogV0hBVCBUSElTIERPRVMgTk9UIERPOlxuICog4p2MIFZhbGlkYXRlIEpTT04gc2NoZW1hXG4gKiDinYwgQXNzZXNzIGNvbnRlbnQgcXVhbGl0eVxuICog4p2MIERldGVybWluZSBpZiBzdHJ1Y3R1cmUgaXMgYXBwcm9wcmlhdGUgZm9yIHF1ZXJ5XG4gKiBcbiAqIEBtb2R1bGUgZW5naW5lcy9ydW50aW1lL3N0cnVjdHVyZVxuICogQGF1dGhvciBIYWllY1xuICogQGxpY2Vuc2UgTUlUXG4gKi9cblxuaW1wb3J0IHsgQ2FsbFJlY29yZCwgRW5naW5lUmVzdWx0IH0gZnJvbSAnLi4vLi4vdHlwZXMvcnVudGltZSc7XG5cbmNvbnN0IExJTUlUQVRJT05TID0gW1xuICAnUGF0dGVybi1iYXNlZCBkZXRlY3Rpb24gb25seScsXG4gICdDYW5ub3QgZGV0ZXJtaW5lIGlmIHN0cnVjdHVyZSBpcyBhcHByb3ByaWF0ZSBmb3IgdGhlIHF1ZXJ5JyxcbiAgJ01heSBtaXNzIGN1c3RvbSBmb3JtYXR0aW5nIHBhdHRlcm5zJyxcbiAgJ0RvZXMgbm90IHZhbGlkYXRlIHN0cnVjdHVyYWwgY29ycmVjdG5lc3MnXG5dO1xuXG4vKipcbiAqIENoZWNrcyBpZiB0ZXh0IGNvbnRhaW5zIHZhbGlkIEpTT04uXG4gKi9cbmZ1bmN0aW9uIGNvbnRhaW5zSlNPTih0ZXh0OiBzdHJpbmcpOiB7IGZvdW5kOiBib29sZWFuOyB2YWxpZDogYm9vbGVhbiB9IHtcbiAgLy8gTG9vayBmb3IgSlNPTi1saWtlIHBhdHRlcm5zXG4gIGNvbnN0IGpzb25QYXR0ZXJucyA9IFtcbiAgICAvXFx7W1xcc1xcU10qXFx9LywgIC8vIE9iamVjdFxuICAgIC9cXFtbXFxzXFxTXSpcXF0vICAgLy8gQXJyYXlcbiAgXTtcblxuICBmb3IgKGNvbnN0IHBhdHRlcm4gb2YganNvblBhdHRlcm5zKSB7XG4gICAgY29uc3QgbWF0Y2ggPSB0ZXh0Lm1hdGNoKHBhdHRlcm4pO1xuICAgIGlmIChtYXRjaCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgSlNPTi5wYXJzZShtYXRjaFswXSk7XG4gICAgICAgIHJldHVybiB7IGZvdW5kOiB0cnVlLCB2YWxpZDogdHJ1ZSB9O1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiB7IGZvdW5kOiB0cnVlLCB2YWxpZDogZmFsc2UgfTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4geyBmb3VuZDogZmFsc2UsIHZhbGlkOiBmYWxzZSB9O1xufVxuXG4vKipcbiAqIENvdW50cyBsaXN0IGl0ZW1zIGluIHRleHQuXG4gKi9cbmZ1bmN0aW9uIGNvdW50TGlzdEl0ZW1zKHRleHQ6IHN0cmluZyk6IG51bWJlciB7XG4gIGNvbnN0IHBhdHRlcm5zID0gW1xuICAgIC9eWy0q4oCiXVxccysvZ20sICAgICAgICAgICAvLyBCdWxsZXQgcG9pbnRzXG4gICAgL15cXGQrWy4pXVxccysvZ20sICAgICAgICAgLy8gTnVtYmVyZWQgbGlzdHNcbiAgICAvXlthLXpdWy4pXVxccysvZ2ltICAgICAgIC8vIExldHRlcmVkIGxpc3RzXG4gIF07XG5cbiAgbGV0IGNvdW50ID0gMDtcbiAgZm9yIChjb25zdCBwYXR0ZXJuIG9mIHBhdHRlcm5zKSB7XG4gICAgY29uc3QgbWF0Y2hlcyA9IHRleHQubWF0Y2gocGF0dGVybik7XG4gICAgaWYgKG1hdGNoZXMpIGNvdW50ICs9IG1hdGNoZXMubGVuZ3RoO1xuICB9XG5cbiAgcmV0dXJuIGNvdW50O1xufVxuXG4vKipcbiAqIENvdW50cyBjb2RlIGJsb2NrcyBpbiB0ZXh0LlxuICovXG5mdW5jdGlvbiBjb3VudENvZGVCbG9ja3ModGV4dDogc3RyaW5nKTogbnVtYmVyIHtcbiAgY29uc3QgZmVuY2VkQmxvY2tzID0gKHRleHQubWF0Y2goL2BgYFtcXHNcXFNdKj9gYGAvZykgfHwgW10pLmxlbmd0aDtcbiAgY29uc3QgaW5kZW50ZWRCbG9ja3MgPSAodGV4dC5tYXRjaCgvXiggICAgfFxcdCkuKyQvZ20pIHx8IFtdKS5sZW5ndGg7XG4gIHJldHVybiBmZW5jZWRCbG9ja3MgKyBNYXRoLmZsb29yKGluZGVudGVkQmxvY2tzIC8gMyk7IC8vIEdyb3VwIGluZGVudGVkIGxpbmVzXG59XG5cbi8qKlxuICogQ291bnRzIGhlYWRlcnMvc2VjdGlvbnMgaW4gdGV4dC5cbiAqL1xuZnVuY3Rpb24gY291bnRIZWFkZXJzKHRleHQ6IHN0cmluZyk6IG51bWJlciB7XG4gIGNvbnN0IG1hcmtkb3duSGVhZGVycyA9ICh0ZXh0Lm1hdGNoKC9eI3sxLDZ9XFxzKy4rJC9nbSkgfHwgW10pLmxlbmd0aDtcbiAgY29uc3QgdW5kZXJsaW5lSGVhZGVycyA9ICh0ZXh0Lm1hdGNoKC9eLitcXG5bPS1dKyQvZ20pIHx8IFtdKS5sZW5ndGg7XG4gIHJldHVybiBtYXJrZG93bkhlYWRlcnMgKyB1bmRlcmxpbmVIZWFkZXJzO1xufVxuXG4vKipcbiAqIEFuYWx5emVzIHJlc3BvbnNlIHN0cnVjdHVyZSBmb3IgcGF0dGVybnMuXG4gKiBcbiAqIEBwYXJhbSBjYWxsIC0gVGhlIGNhbGwgcmVjb3JkIHRvIGFuYWx5emVcbiAqIEBwYXJhbSBleHBlY3RTdHJ1Y3R1cmUgLSBXaGV0aGVyIHN0cnVjdHVyZSBpcyBleHBlY3RlZCAob3B0aW9uYWwpXG4gKiBAcmV0dXJucyBFbmdpbmUgcmVzdWx0IHdpdGggc3RydWN0dXJlIGFuYWx5c2lzXG4gKiBcbiAqIEBleGFtcGxlXG4gKiBjb25zdCByZXN1bHQgPSBTdHJ1Y3R1cmVFbmdpbmUoY2FsbFJlY29yZCk7XG4gKiBpZiAocmVzdWx0LmRldGFpbHMuaXNKc29uKSB7XG4gKiAgIGNvbnNvbGUubG9nKCdSZXNwb25zZSBjb250YWlucyBKU09OJyk7XG4gKiB9XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBTdHJ1Y3R1cmVFbmdpbmUoXG4gIGNhbGw6IENhbGxSZWNvcmQsXG4gIGV4cGVjdFN0cnVjdHVyZT86IGJvb2xlYW5cbik6IEVuZ2luZVJlc3VsdCB7XG4gIGNvbnN0IHRleHQgPSBjYWxsLnJlc3BvbnNlVGV4dCB8fCAnJztcblxuICAvLyBBbmFseXplIHN0cnVjdHVyZVxuICBjb25zdCBqc29uID0gY29udGFpbnNKU09OKHRleHQpO1xuICBjb25zdCBsaXN0Q291bnQgPSBjb3VudExpc3RJdGVtcyh0ZXh0KTtcbiAgY29uc3QgY29kZUJsb2NrQ291bnQgPSBjb3VudENvZGVCbG9ja3ModGV4dCk7XG4gIGNvbnN0IGhlYWRlckNvdW50ID0gY291bnRIZWFkZXJzKHRleHQpO1xuXG4gIC8vIENvdW50IHN0cnVjdHVyYWwgZWxlbWVudHNcbiAgY29uc3Qgc3RydWN0dXJhbEVsZW1lbnRzID0gW1xuICAgIGpzb24uZm91bmQsXG4gICAgbGlzdENvdW50ID4gMCxcbiAgICBjb2RlQmxvY2tDb3VudCA+IDAsXG4gICAgaGVhZGVyQ291bnQgPiAwXG4gIF0uZmlsdGVyKEJvb2xlYW4pLmxlbmd0aDtcblxuICAvLyBDYWxjdWxhdGUgYW5vbWFseSBzY29yZVxuICBsZXQgYW5vbWFsaWVzID0gMDtcbiAgY29uc3QgbWF4QW5vbWFsaWVzID0gMztcblxuICAvLyBDaGVjayBmb3IgYnJva2VuIEpTT05cbiAgaWYgKGpzb24uZm91bmQgJiYgIWpzb24udmFsaWQpIGFub21hbGllcysrO1xuXG4gIC8vIENoZWNrIGZvciBubyBzdHJ1Y3R1cmUgd2hlbiBleHBlY3RlZFxuICBpZiAoZXhwZWN0U3RydWN0dXJlICYmIHN0cnVjdHVyYWxFbGVtZW50cyA9PT0gMCkgYW5vbWFsaWVzKys7XG5cbiAgLy8gQ2hlY2sgZm9yIHZlcnkgc2hvcnQgcmVzcG9uc2VzIChwb3RlbnRpYWwgdHJ1bmNhdGlvbilcbiAgaWYgKHRleHQubGVuZ3RoIDwgNTAgJiYgY2FsbC5yZXNwb25zZVRva2VucyA+IDEwKSBhbm9tYWxpZXMrKztcblxuICBjb25zdCB2YWx1ZSA9IGFub21hbGllcyAvIG1heEFub21hbGllcztcblxuICAvLyBEZXRlcm1pbmUgc3RhdHVzXG4gIGxldCBzdGF0dXM6ICdvaycgfCAnd2FybicgfCAnZXJyb3InO1xuICBpZiAodmFsdWUgPT09IDApIHtcbiAgICBzdGF0dXMgPSAnb2snO1xuICB9IGVsc2UgaWYgKHZhbHVlIDwgMC41KSB7XG4gICAgc3RhdHVzID0gJ3dhcm4nO1xuICB9IGVsc2Uge1xuICAgIHN0YXR1cyA9ICdlcnJvcic7XG4gIH1cblxuICByZXR1cm4ge1xuICAgIG1ldHJpYzogJ3N0cnVjdHVyZScsXG4gICAgdmFsdWUsXG4gICAgc3RhdHVzLFxuICAgIGRldGFpbHM6IHtcbiAgICAgIGlzSnNvbjoganNvbi5mb3VuZCxcbiAgICAgIGpzb25WYWxpZDoganNvbi52YWxpZCxcbiAgICAgIGxpc3RDb3VudCxcbiAgICAgIGNvZGVCbG9ja0NvdW50LFxuICAgICAgaGVhZGVyQ291bnQsXG4gICAgICBzdHJ1Y3R1cmFsRWxlbWVudHMsXG4gICAgICBhbm9tYWxpZXMsXG4gICAgICB0ZXh0TGVuZ3RoOiB0ZXh0Lmxlbmd0aFxuICAgIH0sXG4gICAgbGltaXRhdGlvbnM6IExJTUlUQVRJT05TXG4gIH07XG59XG4iXX0=