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 +21 -0
- package/README.md +149 -0
- package/dist/batch.d.ts +1 -0
- package/dist/batch.js +116 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +97 -0
- package/dist/core/helpers.d.ts +18 -0
- package/dist/core/helpers.js +38 -0
- package/dist/core/registry.d.ts +5 -0
- package/dist/core/registry.js +27 -0
- package/dist/core/score.d.ts +2 -0
- package/dist/core/score.js +18 -0
- package/dist/core/validate.d.ts +6 -0
- package/dist/core/validate.js +31 -0
- package/dist/demo.d.ts +1 -0
- package/dist/demo.js +34 -0
- package/dist/detectors/hallucination.d.ts +3 -0
- package/dist/detectors/hallucination.js +53 -0
- package/dist/detectors/inconsistency.d.ts +3 -0
- package/dist/detectors/inconsistency.js +33 -0
- package/dist/detectors/numeric.d.ts +3 -0
- package/dist/detectors/numeric.js +55 -0
- package/dist/detectors/overconfidence.d.ts +2 -0
- package/dist/detectors/overconfidence.js +50 -0
- package/dist/generate_samples.d.ts +1 -0
- package/dist/generate_samples.js +41 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/schema/validator.d.ts +4 -0
- package/dist/schema/validator.js +21 -0
- package/dist/types/index.d.ts +22 -0
- package/dist/types/index.js +2 -0
- package/package.json +45 -0
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.
|
package/dist/batch.d.ts
ADDED
|
@@ -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, '<').replace(/>/g, '>');
|
|
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
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,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,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,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,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,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();
|
package/dist/index.d.ts
ADDED
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,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
|
+
}
|
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
|
+
|