ai-trust-score 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # ai-trust-score — deterministic, local validator for LLM outputs
2
+
3
+ ai-trust-score helps teams detect and block common, deterministic problems in model-generated text and structured outputs. It's designed to run locally (no external APIs), in CI pipelines, or inside backend services that produce or relay LLM responses. The goal is to give you small, auditable reports that help automate gating, auditing, and monitoring of LLM outputs.
4
+
5
+ This README is intentionally comprehensive: quickstart, programmatic examples (CommonJS and ESM), how to apply ai-trust-score to API responses, CLI/batch usage, an illustrative report, configuration options, recommended policies, and contribution notes.
6
+
7
+ Why ai-trust-score
8
+
9
+ - Deterministic and auditable — no external black-box calls.
10
+ - Low operational cost — runs on your infrastructure.
11
+ - Extensible — add domain-specific JSON rule packs or custom detectors.
12
+
13
+ What ai-trust-score checks (examples)
14
+
15
+ - Schema validation: verify JSON outputs conform to your schema.
16
+ - Numeric consistency: flag mismatched percentages, impossible arithmetic, or inconsistent ranges.
17
+ - Hallucination heuristics: detect claims that appear fabricated or unverifiable.
18
+ - Overconfidence: detect absolute or sweeping claims presented without evidence.
19
+ - Simple contradiction checks: find sentence-level contradictions within one output.
20
+
21
+ Quick install
22
+
23
+ ```bash
24
+ npm install ai-trust-score
25
+ # or
26
+ yarn add ai-trust-score
27
+ ```
28
+
29
+ Programmatic usage — CommonJS (server-side)
30
+
31
+ ```js
32
+ // Import the validator and run it on a single string output
33
+ const { validateLLM } = require('ai-trust-score');
34
+
35
+ const text = 'The product revenue grew 20% from 100 to 150.';
36
+ const report = validateLLM(text, {
37
+ detectors: { numericConsistency: true, overconfidence: true, hallucination: true },
38
+ // customRules: { /* optional domain-specific rules */ }
39
+ });
40
+
41
+ console.log(report.score); // 0..100
42
+ console.log(report.issues); // array of issues
43
+ ```
44
+
45
+ Programmatic usage — ESM / TypeScript
46
+
47
+ ```ts
48
+ import { validateLLM } from 'ai-trust-score';
49
+
50
+ const report = validateLLM('The capital of France is Berlin.', { detectors: { hallucination: true } });
51
+ console.log(report);
52
+ ```
53
+
54
+ Applying ai-trust-score to API responses (recommended patterns)
55
+
56
+ Inline validation (explicit)
57
+
58
+ ```js
59
+ const modelOutput = await myLLM.generate(prompt);
60
+ const report = validateLLM(modelOutput);
61
+ if (report.score < 75) {
62
+ // return a safe fallback, request regeneration, or present a human review flag
63
+ }
64
+ ```
65
+
66
+ Middleware pattern (convenience)
67
+
68
+ ```js
69
+ import express from 'express';
70
+ import { llmGuardMiddleware } from 'ai-trust-score';
71
+
72
+ const app = express();
73
+ app.use(express.json());
74
+
75
+ app.post('/generate', llmGuardMiddleware({ threshold: 80 }), (req, res) => {
76
+ const { allowed, report, output } = res.locals;
77
+ if (!allowed) return res.status(422).json({ error: 'Blocked by ai-trust-score', report });
78
+ res.json({ reply: output, report });
79
+ });
80
+ ```
81
+
82
+ Policy notes
83
+
84
+ - Thresholds are organizational: 80 is a sensible starting point. Use higher thresholds in high-risk domains.
85
+ - Instead of outright blocking, consider fallback behaviors: regenerate, lower-risk response, or human review.
86
+
87
+ CLI & batch usage
88
+
89
+ The package exposes a small CLI for ad-hoc checks and a batch mode for audits. Use the published package via `npx ai-trust-score` or install it locally.
90
+
91
+ ```bash
92
+ # Human-friendly table
93
+ npx ai-trust-score check --file path/to/output.txt
94
+
95
+ # Machine-readable JSON
96
+ npx ai-trust-score check --file path/to/output.txt --json
97
+
98
+ # Batch audit (JSONL -> results + HTML summary)
99
+ npx ai-trust-score batch --file samples.jsonl --out results.jsonl --parallel 8 --html report.html
100
+ ```
101
+
102
+ Illustration — sample GuardReport
103
+
104
+ Calling `validateLLM(text, config)` returns a `GuardReport` object. Example:
105
+
106
+ ```json
107
+ {
108
+ "score": 92,
109
+ "issues": [
110
+ {
111
+ "detector": "numeric-consistency",
112
+ "severity": "medium",
113
+ "message": "20% inconsistent with increase from 100 to 150 (~50%).",
114
+ "meta": { "found": "20%", "expectedApprox": "50%", "evidence": "increase from 100 to 150" }
115
+ }
116
+ ],
117
+ "summary": "Detected 1 issue(s). Trust score 92/100."
118
+ }
119
+ ```
120
+
121
+ Fields explained
122
+
123
+ - `score`: integer 0–100. Starts at 100 and subtracts penalties according to issue severities.
124
+ - `issues`: array of objects { detector, severity, message, meta }.
125
+ - `summary`: short, human-friendly summary.
126
+
127
+ Configuration and extension points
128
+
129
+ - `GuardConfig` (second parameter to `validateLLM`) accepts:
130
+ - `detectors`: enable/disable detector groups (e.g., `numericConsistency`, `hallucination`).
131
+ - `customRules`: JSON rule packs to add or override existing patterns.
132
+ - `threshold`: an app-level policy useful for middleware.
133
+
134
+ Custom rules example
135
+
136
+ ```js
137
+ const custom = {
138
+ hallucination: [
139
+ { id: 'hall-001', pattern: "\\bthe capital of mars\\b", severity: 'high', message: 'Fictional location' }
140
+ ]
141
+ };
142
+ const r = validateLLM('The capital of Mars is Olympus City.', { customRules: custom });
143
+ ```
144
+
145
+
146
+ License, support, and ethos
147
+
148
+ - License: see `LICENSE`.
149
+ - Made with ❤️ by ahmadraza100. Open to expand — if you need help with curated rule packs or secure deployment, open an issue or reach out via the repository.
@@ -0,0 +1 @@
1
+ export {};
package/dist/batch.js ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const readline_1 = __importDefault(require("readline"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const validate_1 = require("./core/validate");
10
+ function parseArgs() {
11
+ const argv = process.argv.slice(2);
12
+ const out = { parallel: 4 };
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const a = argv[i];
15
+ if (a === '--file' && argv[i + 1]) {
16
+ out.file = argv[++i];
17
+ }
18
+ else if (a === '--out' && argv[i + 1]) {
19
+ out.out = argv[++i];
20
+ }
21
+ else if (a === '--parallel' && argv[i + 1]) {
22
+ out.parallel = parseInt(argv[++i], 10);
23
+ }
24
+ else if (a === '--html' && argv[i + 1]) {
25
+ out.html = argv[++i];
26
+ }
27
+ }
28
+ return out;
29
+ }
30
+ async function processFile(file, outPath, parallel = 4) {
31
+ const abs = path_1.default.resolve(process.cwd(), file);
32
+ if (!fs_1.default.existsSync(abs))
33
+ throw new Error('File not found: ' + abs);
34
+ const rl = readline_1.default.createInterface({ input: fs_1.default.createReadStream(abs), crlfDelay: Infinity });
35
+ const outStream = outPath ? fs_1.default.createWriteStream(path_1.default.resolve(process.cwd(), outPath), { encoding: 'utf-8' }) : null;
36
+ const htmlRows = [];
37
+ let total = 0;
38
+ let sumScore = 0;
39
+ const typeCounts = {};
40
+ const severityCounts = {};
41
+ const active = [];
42
+ for await (const line of rl) {
43
+ if (!line.trim())
44
+ continue;
45
+ const item = JSON.parse(line);
46
+ const p = (async () => {
47
+ try {
48
+ const report = (0, validate_1.validateLLM)(item.text ?? item, {});
49
+ total += 1;
50
+ sumScore += report.score;
51
+ for (const it of report.issues) {
52
+ typeCounts[it.type] = (typeCounts[it.type] || 0) + 1;
53
+ severityCounts[it.severity] = (severityCounts[it.severity] || 0) + 1;
54
+ }
55
+ if (outStream) {
56
+ outStream.write(JSON.stringify({ id: item.id, report }) + '\n');
57
+ }
58
+ // collect html row (inside scope where report is defined)
59
+ if (typeof item.id !== 'undefined') {
60
+ const safeText = (item.text || '').toString().replace(/</g, '&lt;').replace(/>/g, '&gt;');
61
+ const issuesHtml = report.issues.map((it) => `<div><strong>${it.type}</strong> <em>${it.severity}</em>: ${it.message}</div>`).join('');
62
+ htmlRows.push(`<tr><td>${item.id}</td><td>${report.score}</td><td>${safeText}</td><td>${issuesHtml}</td></tr>`);
63
+ }
64
+ }
65
+ catch (err) {
66
+ // log and continue
67
+ console.error('Item processing error:', err);
68
+ }
69
+ })();
70
+ active.push(p);
71
+ p.finally(() => {
72
+ const idx = active.indexOf(p);
73
+ if (idx >= 0)
74
+ active.splice(idx, 1);
75
+ });
76
+ if (active.length >= parallel) {
77
+ // wait for any to finish
78
+ try {
79
+ await Promise.race(active);
80
+ }
81
+ catch (e) { /* ignore single task errors */ }
82
+ }
83
+ }
84
+ // wait for remaining
85
+ await Promise.all(active.map(p => p.catch(() => { })));
86
+ if (outStream)
87
+ outStream.end();
88
+ const avg = total ? (sumScore / total) : 0;
89
+ return { total, avgScore: avg, typeCounts, severityCounts, htmlRows };
90
+ }
91
+ async function main() {
92
+ const args = parseArgs();
93
+ if (!args.file) {
94
+ console.error('Missing --file <path>');
95
+ process.exit(1);
96
+ }
97
+ try {
98
+ const res = await processFile(args.file, args.out, args.parallel ?? 4);
99
+ console.log('Batch run summary:');
100
+ console.log('Total items:', res.total);
101
+ console.log('Average score:', res.avgScore.toFixed(2));
102
+ console.log('Issue types:', res.typeCounts);
103
+ console.log('Severity counts:', res.severityCounts);
104
+ if (args.html) {
105
+ const htmlFile = path_1.default.resolve(process.cwd(), args.html);
106
+ const html = `<!doctype html><html><head><meta charset="utf-8"><title>ai-trust-score report</title><style>body{font-family:Arial,Helvetica,sans-serif}table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:8px}th{background:#f2f2f2}</style></head><body><h1>ai-trust-score batch report</h1><p>Total: ${res.total} — Average score: ${res.avgScore.toFixed(2)}</p><table><thead><tr><th>ID</th><th>Score</th><th>Text</th><th>Issues</th></tr></thead><tbody>${res.htmlRows.join('')}</tbody></table></body></html>`;
107
+ fs_1.default.writeFileSync(htmlFile, html, 'utf-8');
108
+ console.log('Wrote HTML report to', htmlFile);
109
+ }
110
+ }
111
+ catch (err) {
112
+ console.error('Batch run failed:', err.message || err);
113
+ process.exit(2);
114
+ }
115
+ }
116
+ main();
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const validate_1 = require("./core/validate");
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const cli_table3_1 = __importDefault(require("cli-table3"));
12
+ function printUsage() {
13
+ console.log(chalk_1.default.bold('ai-trust-score CLI'));
14
+ console.log('Usage: ai-trust-score check --file <path> [--threshold <number>]');
15
+ console.log('Or: cat output.txt | ai-trust-score check --stdin');
16
+ }
17
+ function colorForSeverity(s) {
18
+ if (s === 'high')
19
+ return chalk_1.default.red;
20
+ if (s === 'medium')
21
+ return chalk_1.default.yellow;
22
+ return chalk_1.default.gray;
23
+ }
24
+ async function main() {
25
+ const argv = process.argv.slice(2);
26
+ if (argv.length === 0) {
27
+ printUsage();
28
+ process.exit(1);
29
+ }
30
+ const cmd = argv[0];
31
+ const args = argv.slice(1);
32
+ if (cmd !== 'check') {
33
+ printUsage();
34
+ process.exit(1);
35
+ }
36
+ let file;
37
+ let useStdin = false;
38
+ let threshold = 0;
39
+ for (let i = 0; i < args.length; i++) {
40
+ const a = args[i];
41
+ if (a === '--file' && args[i + 1]) {
42
+ file = args[i + 1];
43
+ i++;
44
+ }
45
+ else if (a === '--stdin') {
46
+ useStdin = true;
47
+ }
48
+ else if (a === '--threshold' && args[i + 1]) {
49
+ threshold = parseInt(args[i + 1], 10);
50
+ i++;
51
+ }
52
+ }
53
+ let input = '';
54
+ if (useStdin) {
55
+ input = fs_1.default.readFileSync(0, 'utf-8');
56
+ }
57
+ else if (file) {
58
+ const p = path_1.default.resolve(process.cwd(), file);
59
+ if (!fs_1.default.existsSync(p)) {
60
+ console.error(chalk_1.default.red(`File not found: ${p}`));
61
+ process.exit(2);
62
+ }
63
+ input = fs_1.default.readFileSync(p, 'utf-8');
64
+ }
65
+ else {
66
+ console.error(chalk_1.default.red('No input: provide --file or --stdin'));
67
+ process.exit(2);
68
+ }
69
+ // Try to parse JSON, otherwise pass as text
70
+ let parsed = input;
71
+ try {
72
+ parsed = JSON.parse(input);
73
+ }
74
+ catch (e) { /* leave as string */ }
75
+ const report = (0, validate_1.validateLLM)(parsed, { numericConsistency: true, hallucinationCheck: true, overconfidenceCheck: true });
76
+ // Pretty output
77
+ console.log(chalk_1.default.bold(`Trust score: ${report.score}/100`));
78
+ console.log(chalk_1.default.dim(report.summary));
79
+ if (report.issues.length === 0) {
80
+ console.log(chalk_1.default.green('No issues detected.'));
81
+ }
82
+ else {
83
+ const table = new cli_table3_1.default({ head: ['Type', 'Severity', 'Message'] });
84
+ for (const it of report.issues) {
85
+ const color = colorForSeverity(it.severity);
86
+ table.push([it.type, color(it.severity), it.message]);
87
+ }
88
+ console.log(table.toString());
89
+ }
90
+ if (threshold && report.score < threshold) {
91
+ process.exit(3);
92
+ }
93
+ }
94
+ main().catch(err => {
95
+ console.error(err);
96
+ process.exit(10);
97
+ });
@@ -0,0 +1,18 @@
1
+ import { GuardConfig, GuardReport } from '../types';
2
+ /**
3
+ * Helper to validate a single output and decide whether to allow it.
4
+ * Returns { allowed, report } where allowed is true when score >= threshold.
5
+ */
6
+ export declare function validateAndDecide(output: string | object, config?: GuardConfig, threshold?: number): {
7
+ allowed: boolean;
8
+ report: GuardReport;
9
+ };
10
+ /**
11
+ * Express middleware factory example (lightweight):
12
+ * Use `app.post('/generate', llmGuardMiddleware({ threshold: 80 }), handler)`
13
+ * The middleware expects `req.body.output` (string) and will attach `req.llmGuardReport`.
14
+ */
15
+ export declare function llmGuardMiddleware(opts?: {
16
+ threshold?: number;
17
+ config?: GuardConfig;
18
+ }): (req: any, res: any, next: any) => any;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateAndDecide = validateAndDecide;
4
+ exports.llmGuardMiddleware = llmGuardMiddleware;
5
+ const validate_1 = require("./validate");
6
+ /**
7
+ * Helper to validate a single output and decide whether to allow it.
8
+ * Returns { allowed, report } where allowed is true when score >= threshold.
9
+ */
10
+ function validateAndDecide(output, config = {}, threshold = 70) {
11
+ const report = (0, validate_1.validateLLM)(output, config);
12
+ return { allowed: report.score >= threshold, report };
13
+ }
14
+ /**
15
+ * Express middleware factory example (lightweight):
16
+ * Use `app.post('/generate', llmGuardMiddleware({ threshold: 80 }), handler)`
17
+ * The middleware expects `req.body.output` (string) and will attach `req.llmGuardReport`.
18
+ */
19
+ function llmGuardMiddleware(opts = {}) {
20
+ const threshold = opts.threshold ?? 70;
21
+ const config = opts.config ?? {};
22
+ return (req, res, next) => {
23
+ try {
24
+ const text = req.body && req.body.output;
25
+ if (!text)
26
+ return next();
27
+ const { allowed, report } = validateAndDecide(text, config, threshold);
28
+ req.llmGuardReport = report;
29
+ if (!allowed) {
30
+ return res.status(422).json({ error: 'Output failed ai-trust-score validation', report });
31
+ }
32
+ next();
33
+ }
34
+ catch (e) {
35
+ next(e);
36
+ }
37
+ };
38
+ }
@@ -0,0 +1,5 @@
1
+ import { Issue, GuardConfig } from '../types';
2
+ export type DetectorFn = (text: string, config: GuardConfig) => Issue[];
3
+ export declare function registerDetector(name: string, fn: DetectorFn): void;
4
+ export declare function runDetectors(text: string, config: GuardConfig): Issue[];
5
+ export declare function listDetectors(): string[];
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDetector = registerDetector;
4
+ exports.runDetectors = runDetectors;
5
+ exports.listDetectors = listDetectors;
6
+ const detectors = [];
7
+ function registerDetector(name, fn) {
8
+ detectors.push({ name, fn });
9
+ }
10
+ function runDetectors(text, config) {
11
+ const issues = [];
12
+ for (const d of detectors) {
13
+ try {
14
+ const res = d.fn(text, config);
15
+ if (Array.isArray(res) && res.length)
16
+ issues.push(...res);
17
+ }
18
+ catch (err) {
19
+ // detector error should not crash pipeline
20
+ issues.push({ type: 'schema', severity: 'low', message: `Detector ${d.name} failed: ${String(err)}` });
21
+ }
22
+ }
23
+ return issues;
24
+ }
25
+ function listDetectors() {
26
+ return detectors.map(d => d.name);
27
+ }
@@ -0,0 +1,2 @@
1
+ import { Issue } from "../types";
2
+ export declare function computeScore(issues: Issue[]): number;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeScore = computeScore;
4
+ const penaltyBySeverity = {
5
+ high: 15,
6
+ medium: 8,
7
+ low: 3,
8
+ };
9
+ function computeScore(issues) {
10
+ let score = 100;
11
+ for (const it of issues) {
12
+ const p = penaltyBySeverity[it.severity] ?? 0;
13
+ score -= p;
14
+ }
15
+ if (score < 0)
16
+ score = 0;
17
+ return score;
18
+ }
@@ -0,0 +1,6 @@
1
+ import { GuardConfig, GuardReport } from "../types";
2
+ import '../detectors/hallucination';
3
+ import '../detectors/overconfidence';
4
+ import '../detectors/numeric';
5
+ import '../detectors/inconsistency';
6
+ export declare function validateLLM(output: string | object, config?: GuardConfig): GuardReport;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateLLM = validateLLM;
4
+ const validator_1 = require("../schema/validator");
5
+ const registry_1 = require("./registry");
6
+ const score_1 = require("./score");
7
+ // Import detectors for side-effects so they register themselves with the registry
8
+ require("../detectors/hallucination");
9
+ require("../detectors/overconfidence");
10
+ require("../detectors/numeric");
11
+ require("../detectors/inconsistency");
12
+ function validateLLM(output, config = {}) {
13
+ const issues = [];
14
+ // If object and schema provided, run JSON Schema validation
15
+ if (config.schema && typeof output === 'object') {
16
+ const res = (0, validator_1.validateSchema)(output, config.schema);
17
+ if (!res.valid) {
18
+ for (const msg of res.errors) {
19
+ issues.push({ type: 'schema', severity: 'high', message: msg });
20
+ }
21
+ }
22
+ }
23
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
24
+ // Run registered detectors (they decide internally whether to run given config)
25
+ issues.push(...(0, registry_1.runDetectors)(text, config));
26
+ // (for debugging) you can inspect available detectors via listDetectors()
27
+ // const available = listDetectors();
28
+ const score = (0, score_1.computeScore)(issues);
29
+ const summary = `Detected ${issues.length} issue(s). Trust score ${score}/100.`;
30
+ return { score, issues, summary };
31
+ }
package/dist/demo.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/demo.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const validate_1 = require("./core/validate");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const cli_table3_1 = __importDefault(require("cli-table3"));
11
+ async function runDemo() {
12
+ const p = path_1.default.resolve(process.cwd(), 'examples', 'sample_output.txt');
13
+ if (!fs_1.default.existsSync(p)) {
14
+ console.error(chalk_1.default.red('demo example not found:'), p);
15
+ process.exit(2);
16
+ }
17
+ const input = fs_1.default.readFileSync(p, 'utf-8');
18
+ const report = (0, validate_1.validateLLM)(input, { numericConsistency: true, hallucinationCheck: true, overconfidenceCheck: true });
19
+ console.log(chalk_1.default.bold('--- ai-trust-score demo output ---'));
20
+ console.log(chalk_1.default.bold(`Trust score: ${report.score}/100`));
21
+ console.log(chalk_1.default.dim(report.summary));
22
+ if (report.issues.length === 0) {
23
+ console.log(chalk_1.default.green('No issues detected.'));
24
+ }
25
+ else {
26
+ const table = new cli_table3_1.default({ head: ['Type', 'Severity', 'Message'] });
27
+ for (const it of report.issues) {
28
+ const color = it.severity === 'high' ? chalk_1.default.red : it.severity === 'medium' ? chalk_1.default.yellow : chalk_1.default.gray;
29
+ table.push([it.type, color(it.severity), it.message]);
30
+ }
31
+ console.log(table.toString());
32
+ }
33
+ }
34
+ runDemo().catch(e => { console.error(e); process.exit(1); });
@@ -0,0 +1,3 @@
1
+ import { Issue } from "../types";
2
+ import { GuardConfig } from '../types';
3
+ export declare function detectHallucination(text: string, config?: GuardConfig): Issue[];
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectHallucination = detectHallucination;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const registry_1 = require("../core/registry");
10
+ let rules = [];
11
+ try {
12
+ const p = path_1.default.resolve(__dirname, 'patterns.json');
13
+ if (fs_1.default.existsSync(p)) {
14
+ const raw = fs_1.default.readFileSync(p, 'utf-8');
15
+ const json = JSON.parse(raw);
16
+ rules = json.hallucination || [];
17
+ }
18
+ }
19
+ catch (e) {
20
+ // ignore rule loading errors
21
+ }
22
+ function detectHallucination(text, config) {
23
+ const issues = [];
24
+ // combine built-in rules with custom rules from config
25
+ const merged = [...rules];
26
+ try {
27
+ if (config && config.customRules && Array.isArray(config.customRules['hallucination'])) {
28
+ merged.push(...config.customRules['hallucination']);
29
+ }
30
+ }
31
+ catch (e) { /* ignore */ }
32
+ for (const r of merged) {
33
+ try {
34
+ const re = new RegExp(r.pattern, r.flags || 'i');
35
+ if (re.test(text)) {
36
+ issues.push({ type: r.type || 'hallucination', severity: r.severity, message: r.message });
37
+ }
38
+ }
39
+ catch (e) {
40
+ // skip invalid pattern
41
+ }
42
+ }
43
+ // small fallback heuristic for institutions
44
+ const instRe = /([A-Z][a-z]+ (Institute|University|Center|Lab|Academy))/g;
45
+ let m;
46
+ while ((m = instRe.exec(text))) {
47
+ const name = m[1];
48
+ issues.push({ type: 'hallucination', severity: 'low', message: `Named institution detected: ${name}` });
49
+ }
50
+ return issues;
51
+ }
52
+ // register with registry so validateLLM picks it up automatically
53
+ (0, registry_1.registerDetector)('hallucination', (text, config) => detectHallucination(text, config));
@@ -0,0 +1,3 @@
1
+ import { Issue } from "../types";
2
+ import { GuardConfig } from '../types';
3
+ export declare function detectInconsistency(text: string, config?: GuardConfig): Issue[];
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectInconsistency = detectInconsistency;
4
+ const opposites = {
5
+ 'cost': ['increase', 'decrease', 'reduces', 'increases'],
6
+ 'costs': ['increase', 'decrease', 'reduces', 'increases'],
7
+ 'growth': ['increase', 'decrease', 'fell', 'rose'],
8
+ 'reduce': ['reduce', 'increase', 'increases', 'reduces']
9
+ };
10
+ function detectInconsistency(text, config) {
11
+ const issues = [];
12
+ const sentences = text.split(/[\.\n\!\?]+/).map(s => s.trim()).filter(Boolean);
13
+ for (let i = 0; i < sentences.length; i++) {
14
+ for (let j = i + 1; j < Math.min(sentences.length, i + 6); j++) {
15
+ const a = sentences[i].toLowerCase();
16
+ const b = sentences[j].toLowerCase();
17
+ for (const key of Object.keys(opposites)) {
18
+ if (a.includes(key) && (a.includes('increase') || a.includes('decrease') || a.includes('reduce') || a.includes('reduce'))) {
19
+ // check b for opposite word
20
+ if ((b.includes('increase') || b.includes('increases') || b.includes('rose')) && (a.includes('decrease') || a.includes('fell') || a.includes('reduce'))) {
21
+ issues.push({ type: 'inconsistency', severity: 'high', message: `Contradicting statements between sentences: "${sentences[i]}" vs "${sentences[j]}"` });
22
+ }
23
+ if ((b.includes('decrease') || b.includes('fell') || b.includes('reduce')) && (a.includes('increase') || a.includes('increases') || a.includes('rose'))) {
24
+ issues.push({ type: 'inconsistency', severity: 'high', message: `Contradicting statements between sentences: "${sentences[i]}" vs "${sentences[j]}"` });
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
30
+ return issues;
31
+ }
32
+ const registry_1 = require("../core/registry");
33
+ (0, registry_1.registerDetector)('inconsistency', (text) => detectInconsistency(text));
@@ -0,0 +1,3 @@
1
+ import { Issue } from "../types";
2
+ import { GuardConfig } from '../types';
3
+ export declare function detectNumeric(text: string, config?: GuardConfig): Issue[];
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectNumeric = detectNumeric;
4
+ // Very small heuristic numeric checks: extracts simple number pairs and checks basic arithmetic
5
+ const registry_1 = require("../core/registry");
6
+ function detectNumeric(text, config) {
7
+ const issues = [];
8
+ // Detect patterns like "from X to Y" and check percent claims nearby
9
+ const rangeRe = /from\s+(\d+(?:\.\d+)?)\s*(million|billion|k|m)?\s*to\s+(\d+(?:\.\d+)?)\s*(million|billion|k|m)?/i;
10
+ const pctRe = /(\d{1,3}(?:\.\d+)?)%/g;
11
+ const r = rangeRe.exec(text);
12
+ if (r) {
13
+ const a = parseFloat(r[1]);
14
+ const b = parseFloat(r[3]);
15
+ if (!isNaN(a) && !isNaN(b) && a > 0) {
16
+ const implied = ((b - a) / a) * 100;
17
+ // See if a nearby percent is claimed that differs by >5 percentage points
18
+ const window = text.slice(Math.max(0, r.index - 100), Math.min(text.length, r.index + 200));
19
+ const pcts = [];
20
+ let m;
21
+ while ((m = pctRe.exec(window))) {
22
+ pcts.push(parseFloat(m[1]));
23
+ }
24
+ if (pcts.length > 0) {
25
+ const nearest = pcts[0];
26
+ if (Math.abs(nearest - implied) > 5) {
27
+ issues.push({ type: 'numeric', severity: 'medium', message: `Percentage ${nearest}% inconsistent with increase from ${a} to ${b} (~${implied.toFixed(1)}%).` });
28
+ }
29
+ }
30
+ }
31
+ }
32
+ // Detect simple sum inconsistencies: look for "total" and list of numbers
33
+ const totalRe = /total(?:ed|s|:)\s*(\$?\d[\d,\.kmbMKB]*)/i;
34
+ const numsRe = /(\$?\d[\d,\.kmbMKB]*)/g;
35
+ const tot = totalRe.exec(text);
36
+ if (tot) {
37
+ // crude: sum first three numbers and compare
38
+ const window = text.slice(0, Math.min(text.length, tot.index));
39
+ const nums = [];
40
+ let m;
41
+ while ((m = numsRe.exec(window)) && nums.length < 5) {
42
+ const cleaned = m[1].replace(/[$,]/g, '');
43
+ const parsed = parseFloat(cleaned);
44
+ if (!isNaN(parsed))
45
+ nums.push(parsed);
46
+ }
47
+ const totalVal = parseFloat(tot[1].replace(/[$,]/g, ''));
48
+ const sum = nums.reduce((s, x) => s + x, 0);
49
+ if (nums.length >= 2 && Math.abs(sum - totalVal) / Math.max(1, totalVal) > 0.05) {
50
+ issues.push({ type: 'numeric', severity: 'medium', message: `Listed components sum (${sum}) differs from claimed total (${totalVal}).` });
51
+ }
52
+ }
53
+ return issues;
54
+ }
55
+ (0, registry_1.registerDetector)('numeric', (text) => detectNumeric(text));
@@ -0,0 +1,2 @@
1
+ import { Issue, GuardConfig } from "../types";
2
+ export declare function detectOverconfidence(text: string, config?: GuardConfig): Issue[];
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectOverconfidence = detectOverconfidence;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const registry_1 = require("../core/registry");
10
+ let rules = [];
11
+ try {
12
+ const p = path_1.default.resolve(__dirname, 'patterns.json');
13
+ if (fs_1.default.existsSync(p)) {
14
+ const raw = fs_1.default.readFileSync(p, 'utf-8');
15
+ const json = JSON.parse(raw);
16
+ rules = json.overconfidence || [];
17
+ }
18
+ }
19
+ catch (e) {
20
+ // ignore
21
+ }
22
+ function detectOverconfidence(text, config) {
23
+ const issues = [];
24
+ let count = 0;
25
+ const merged = [...rules];
26
+ try {
27
+ if (config && config.customRules && Array.isArray(config.customRules['overconfidence'])) {
28
+ merged.push(...config.customRules['overconfidence']);
29
+ }
30
+ }
31
+ catch (e) { }
32
+ for (const r of merged) {
33
+ try {
34
+ const re = new RegExp(r.pattern, r.flags || 'i');
35
+ const m = text.match(re);
36
+ if (m) {
37
+ count += 1;
38
+ issues.push({ type: r.type || 'confidence', severity: r.severity, message: `${r.message} "${m[0]}"` });
39
+ }
40
+ }
41
+ catch (e) {
42
+ // ignore
43
+ }
44
+ }
45
+ if (count > 1) {
46
+ issues.push({ type: 'confidence', severity: 'medium', message: `${count} strong certainty markers found.` });
47
+ }
48
+ return issues;
49
+ }
50
+ (0, registry_1.registerDetector)('overconfidence', (text, config) => detectOverconfidence(text, config));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ // Improved generator: creates varied samples by altering numbers and certainty phrases
9
+ function usage() { console.log('Usage: generate_samples <count> <out.jsonl>'); }
10
+ function randChoice(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
11
+ function makeSample(i, base) {
12
+ // variations: replace percentage, numbers, and certainty phrases
13
+ const pct = Math.floor(Math.random() * 80) + 1; // 1-80%
14
+ const a = Math.floor(50 + Math.random() * 200);
15
+ const b = Math.floor(a + (Math.random() * a));
16
+ const cert = randChoice(['Definitely', 'Probably', 'It seems', 'Experts say', 'Without a doubt', 'Research shows']);
17
+ // build a sentence emulating the base
18
+ const text = `${cert} the product revenue grew ${pct}% from ${a} to ${b}. ${randChoice(['This is the best outcome.', 'This is an expected result.', 'This is surprising.'])}`;
19
+ return { id: i + 1, text };
20
+ }
21
+ function main() {
22
+ const argv = process.argv.slice(2);
23
+ if (argv.length < 2) {
24
+ usage();
25
+ process.exit(1);
26
+ }
27
+ const count = parseInt(argv[0], 10);
28
+ const out = path_1.default.resolve(process.cwd(), argv[1]);
29
+ if (isNaN(count) || count <= 0) {
30
+ console.error('count must be > 0');
31
+ process.exit(2);
32
+ }
33
+ const stream = fs_1.default.createWriteStream(out, { encoding: 'utf-8' });
34
+ for (let i = 0; i < count; i++) {
35
+ const obj = makeSample(i, '');
36
+ stream.write(JSON.stringify(obj) + '\n');
37
+ }
38
+ stream.end();
39
+ console.log(`Wrote ${count} samples to ${out}`);
40
+ }
41
+ main();
@@ -0,0 +1,4 @@
1
+ export { validateLLM } from './core/validate';
2
+ export * from './types';
3
+ import { validateLLM as _validateLLM } from './core/validate';
4
+ export default _validateLLM;
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.validateLLM = void 0;
18
+ var validate_1 = require("./core/validate");
19
+ Object.defineProperty(exports, "validateLLM", { enumerable: true, get: function () { return validate_1.validateLLM; } });
20
+ __exportStar(require("./types"), exports);
21
+ // Default export for convenience
22
+ const validate_2 = require("./core/validate");
23
+ exports.default = validate_2.validateLLM;
@@ -0,0 +1,4 @@
1
+ export declare function validateSchema(data: object, schema: object): {
2
+ valid: boolean;
3
+ errors: string[];
4
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateSchema = validateSchema;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const ajv = new ajv_1.default({ allErrors: true, strict: false });
9
+ function validateSchema(data, schema) {
10
+ try {
11
+ const validate = ajv.compile(schema);
12
+ const valid = validate(data);
13
+ if (valid)
14
+ return { valid: true, errors: [] };
15
+ const errors = (validate.errors || []).map((e) => `${e.instancePath} ${e.message}`);
16
+ return { valid: false, errors };
17
+ }
18
+ catch (err) {
19
+ return { valid: false, errors: [String(err.message || err)] };
20
+ }
21
+ }
@@ -0,0 +1,22 @@
1
+ export type Severity = 'low' | 'medium' | 'high';
2
+ export type IssueType = 'hallucination' | 'schema' | 'numeric' | 'confidence' | 'inconsistency';
3
+ export interface Issue {
4
+ type: IssueType;
5
+ severity: Severity;
6
+ message: string;
7
+ location?: string;
8
+ }
9
+ export interface GuardReport {
10
+ score: number;
11
+ issues: Issue[];
12
+ summary: string;
13
+ }
14
+ export interface GuardConfig {
15
+ schema?: object;
16
+ requiredSections?: string[];
17
+ numericConsistency?: boolean;
18
+ hallucinationCheck?: boolean;
19
+ overconfidenceCheck?: boolean;
20
+ maxConfidenceWithoutEvidence?: number;
21
+ customRules?: Record<string, Array<Record<string, any>>>;
22
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "ai-trust-score",
3
+ "version": "0.1.0",
4
+ "description": "ai-trust-score — deterministic validator for LLM outputs (schema, heuristics, consistency checks)",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json",
9
+ "test": "vitest",
10
+ "prepare": "echo 'skip build on pack'",
11
+ "cli": "node ./dist/cli.js",
12
+ "demo": "node ./dist/demo.js"
13
+ },
14
+ "keywords": ["llm","validator","schema","hallucination","offline","safety","audit"],
15
+ "author": "ai-trust-score Contributors <contributors@ai-trust-score.dev>",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/ahmadraza/ai-trust-score.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/ahmadraza/ai-trust-score/issues"
22
+ },
23
+ "homepage": "https://github.com/ahmadraza/ai-trust-score#readme",
24
+ "devDependencies": {
25
+ "@types/node": "^20.4.2",
26
+ "ts-node": "^10.9.1",
27
+ "typescript": "^5.1.6",
28
+ "vitest": "^1.3.0"
29
+ },
30
+ "bin": {
31
+ "ai-trust-score": "dist/cli.js"
32
+ },
33
+ "files": ["dist","README.md"],
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "license": "MIT",
38
+ "dependencies": {
39
+ "ajv": "^8.12.0",
40
+ "chalk": "^4.1.2",
41
+ "cli-table3": "^0.6.0"
42
+ }
43
+ }
44
+
45
+