llm-entropy-filter 1.1.0 → 1.2.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/CHANGELOG.md +155 -51
- package/LICENSE +93 -93
- package/README.md +297 -352
- package/dist/index.cjs +104 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +104 -70
- package/dist/index.js.map +1 -1
- package/integrations/express.mjs +117 -117
- package/integrations/fastify.mjs +106 -106
- package/integrations/langchain.mjs +98 -98
- package/integrations/vercel-ai-sdk.mjs +44 -44
- package/package.json +99 -59
- package/rulesets/default.js +8 -0
- package/rulesets/default.json +77 -73
- package/rulesets/public-api.js +7 -0
- package/rulesets/public-api.json +179 -27
- package/rulesets/schema +24 -24
- package/rulesets/strict.js +7 -0
- package/rulesets/strict.json +173 -25
- package/rulesets/support.js +7 -0
- package/rulesets/support.json +22 -22
package/integrations/fastify.mjs
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
// integrations/fastify.mjs
|
|
2
|
-
import { gate } from "llm-entropy-filter";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Fastify plugin: adds a preHandler that runs `gate()` before your route handler.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* fastify.register(entropyGatePlugin, { bodyField: "text", blockOn: "BLOCK" })
|
|
9
|
-
*
|
|
10
|
-
* Design:
|
|
11
|
-
* - Deterministic, local
|
|
12
|
-
* - Fail-open by default (if gate throws, request continues unless you override)
|
|
13
|
-
*/
|
|
14
|
-
export async function entropyGatePlugin(fastify, opts = {}) {
|
|
15
|
-
const {
|
|
16
|
-
bodyField = "text",
|
|
17
|
-
queryField,
|
|
18
|
-
attachResult = true,
|
|
19
|
-
blockOn = "BLOCK",
|
|
20
|
-
blockStatus = 400,
|
|
21
|
-
blockResponse,
|
|
22
|
-
warnHeader = true,
|
|
23
|
-
alwaysHeader = false,
|
|
24
|
-
onResult,
|
|
25
|
-
getText,
|
|
26
|
-
failClosed = false, // if true: return 500 on errors instead of passing through
|
|
27
|
-
} = opts;
|
|
28
|
-
|
|
29
|
-
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
30
|
-
|
|
31
|
-
fastify.decorateRequest("entropyGate", null);
|
|
32
|
-
|
|
33
|
-
fastify.addHook("preHandler", async (request, reply) => {
|
|
34
|
-
try {
|
|
35
|
-
// 1) Extract text
|
|
36
|
-
let text = "";
|
|
37
|
-
if (typeof getText === "function") {
|
|
38
|
-
text = String(getText(request) ?? "");
|
|
39
|
-
} else {
|
|
40
|
-
const bodyVal = request?.body?.[bodyField];
|
|
41
|
-
const queryVal = queryField ? request?.query?.[queryField] : undefined;
|
|
42
|
-
text = String(bodyVal ?? queryVal ?? "");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 2) Run gate
|
|
46
|
-
const result = gate(text);
|
|
47
|
-
|
|
48
|
-
// 3) Attach
|
|
49
|
-
if (attachResult) {
|
|
50
|
-
request.entropyGate = result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (typeof onResult === "function") {
|
|
54
|
-
onResult(request, result);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 4) Headers
|
|
58
|
-
const shouldHeader =
|
|
59
|
-
alwaysHeader || (warnHeader && result?.action === "WARN");
|
|
60
|
-
if (shouldHeader) {
|
|
61
|
-
reply.header("x-entropy-action", String(result?.action ?? ""));
|
|
62
|
-
reply.header("x-entropy-score", String(result?.entropy_score ?? ""));
|
|
63
|
-
reply.header("x-entropy-intention", String(result?.intention ?? ""));
|
|
64
|
-
if (Array.isArray(result?.flags)) {
|
|
65
|
-
reply.header("x-entropy-flags", result.flags.slice(0, 10).join(","));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 5) Block
|
|
70
|
-
if (blockSet.has(result?.action)) {
|
|
71
|
-
reply.code(blockStatus);
|
|
72
|
-
|
|
73
|
-
if (typeof blockResponse === "function") {
|
|
74
|
-
return reply.send(blockResponse(request, reply, result));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (blockResponse && typeof blockResponse === "object") {
|
|
78
|
-
return reply.send(blockResponse);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return reply.send({
|
|
82
|
-
ok: false,
|
|
83
|
-
blocked: true,
|
|
84
|
-
gate: result,
|
|
85
|
-
message:
|
|
86
|
-
"Request blocked by llm-entropy-filter (high-entropy / low-signal input).",
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
} catch (err) {
|
|
90
|
-
if (failClosed) {
|
|
91
|
-
reply.code(500);
|
|
92
|
-
return reply.send({
|
|
93
|
-
ok: false,
|
|
94
|
-
error: "entropy_gate_error",
|
|
95
|
-
message: String(err?.message ?? err),
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
// fail-open: continue request
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Manual helper if you prefer using it inside handlers */
|
|
104
|
-
export function runEntropyGate(text) {
|
|
105
|
-
return gate(String(text ?? ""));
|
|
106
|
-
}
|
|
1
|
+
// integrations/fastify.mjs
|
|
2
|
+
import { gate } from "llm-entropy-filter";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fastify plugin: adds a preHandler that runs `gate()` before your route handler.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* fastify.register(entropyGatePlugin, { bodyField: "text", blockOn: "BLOCK" })
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - Deterministic, local
|
|
12
|
+
* - Fail-open by default (if gate throws, request continues unless you override)
|
|
13
|
+
*/
|
|
14
|
+
export async function entropyGatePlugin(fastify, opts = {}) {
|
|
15
|
+
const {
|
|
16
|
+
bodyField = "text",
|
|
17
|
+
queryField,
|
|
18
|
+
attachResult = true,
|
|
19
|
+
blockOn = "BLOCK",
|
|
20
|
+
blockStatus = 400,
|
|
21
|
+
blockResponse,
|
|
22
|
+
warnHeader = true,
|
|
23
|
+
alwaysHeader = false,
|
|
24
|
+
onResult,
|
|
25
|
+
getText,
|
|
26
|
+
failClosed = false, // if true: return 500 on errors instead of passing through
|
|
27
|
+
} = opts;
|
|
28
|
+
|
|
29
|
+
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
30
|
+
|
|
31
|
+
fastify.decorateRequest("entropyGate", null);
|
|
32
|
+
|
|
33
|
+
fastify.addHook("preHandler", async (request, reply) => {
|
|
34
|
+
try {
|
|
35
|
+
// 1) Extract text
|
|
36
|
+
let text = "";
|
|
37
|
+
if (typeof getText === "function") {
|
|
38
|
+
text = String(getText(request) ?? "");
|
|
39
|
+
} else {
|
|
40
|
+
const bodyVal = request?.body?.[bodyField];
|
|
41
|
+
const queryVal = queryField ? request?.query?.[queryField] : undefined;
|
|
42
|
+
text = String(bodyVal ?? queryVal ?? "");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2) Run gate
|
|
46
|
+
const result = gate(text);
|
|
47
|
+
|
|
48
|
+
// 3) Attach
|
|
49
|
+
if (attachResult) {
|
|
50
|
+
request.entropyGate = result;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof onResult === "function") {
|
|
54
|
+
onResult(request, result);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 4) Headers
|
|
58
|
+
const shouldHeader =
|
|
59
|
+
alwaysHeader || (warnHeader && result?.action === "WARN");
|
|
60
|
+
if (shouldHeader) {
|
|
61
|
+
reply.header("x-entropy-action", String(result?.action ?? ""));
|
|
62
|
+
reply.header("x-entropy-score", String(result?.entropy_score ?? ""));
|
|
63
|
+
reply.header("x-entropy-intention", String(result?.intention ?? ""));
|
|
64
|
+
if (Array.isArray(result?.flags)) {
|
|
65
|
+
reply.header("x-entropy-flags", result.flags.slice(0, 10).join(","));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 5) Block
|
|
70
|
+
if (blockSet.has(result?.action)) {
|
|
71
|
+
reply.code(blockStatus);
|
|
72
|
+
|
|
73
|
+
if (typeof blockResponse === "function") {
|
|
74
|
+
return reply.send(blockResponse(request, reply, result));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (blockResponse && typeof blockResponse === "object") {
|
|
78
|
+
return reply.send(blockResponse);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return reply.send({
|
|
82
|
+
ok: false,
|
|
83
|
+
blocked: true,
|
|
84
|
+
gate: result,
|
|
85
|
+
message:
|
|
86
|
+
"Request blocked by llm-entropy-filter (high-entropy / low-signal input).",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (failClosed) {
|
|
91
|
+
reply.code(500);
|
|
92
|
+
return reply.send({
|
|
93
|
+
ok: false,
|
|
94
|
+
error: "entropy_gate_error",
|
|
95
|
+
message: String(err?.message ?? err),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// fail-open: continue request
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Manual helper if you prefer using it inside handlers */
|
|
104
|
+
export function runEntropyGate(text) {
|
|
105
|
+
return gate(String(text ?? ""));
|
|
106
|
+
}
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
// integrations/langchain.mjs
|
|
2
|
-
import { gate } from "llm-entropy-filter";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Minimal pre-gate for LangChain flows.
|
|
6
|
-
* Use this before calling any LLM / chain / agent.
|
|
7
|
-
*/
|
|
8
|
-
export function entropyPreGate(input, opts = {}) {
|
|
9
|
-
const { blockOn = "BLOCK" } = opts;
|
|
10
|
-
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
11
|
-
|
|
12
|
-
const text = typeof input === "string" ? input : String(input?.text ?? input ?? "");
|
|
13
|
-
const result = gate(text);
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
gate: result,
|
|
17
|
-
inputText: text,
|
|
18
|
-
shouldBlock: blockSet.has(result?.action),
|
|
19
|
-
shouldWarn: result?.action === "WARN",
|
|
20
|
-
shouldAllow: result?.action === "ALLOW",
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Standard error object you can throw in API routes.
|
|
26
|
-
*/
|
|
27
|
-
export function entropyBlockedError(gateResult, opts = {}) {
|
|
28
|
-
const { status = 400, code = "ENTROPY_BLOCKED" } = opts;
|
|
29
|
-
const err = new Error("Blocked by llm-entropy-filter (high-entropy / low-signal input).");
|
|
30
|
-
err.name = "EntropyBlockedError";
|
|
31
|
-
err.status = status;
|
|
32
|
-
err.code = code;
|
|
33
|
-
err.gate = gateResult;
|
|
34
|
-
return err;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* LCEL wrapper: wrap a Runnable / function to enforce gate before execution.
|
|
39
|
-
* Works with `@langchain/core/runnables` (RunnableLambda).
|
|
40
|
-
*
|
|
41
|
-
* Usage:
|
|
42
|
-
* const safe = withEntropyGate(myRunnableOrFn, { pickText: (i)=> i.input })
|
|
43
|
-
* await safe.invoke({ input: "..." })
|
|
44
|
-
*/
|
|
45
|
-
export function withEntropyGate(target, opts = {}) {
|
|
46
|
-
const {
|
|
47
|
-
blockOn = "BLOCK",
|
|
48
|
-
pickText, // (input) => string
|
|
49
|
-
onWarn, // (gateResult, input) => void
|
|
50
|
-
} = opts;
|
|
51
|
-
|
|
52
|
-
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
53
|
-
|
|
54
|
-
// lazy import to avoid forcing langchain deps
|
|
55
|
-
let RunnableLambda;
|
|
56
|
-
async function getRunnableLambda() {
|
|
57
|
-
if (RunnableLambda) return RunnableLambda;
|
|
58
|
-
const mod = await import("@langchain/core/runnables");
|
|
59
|
-
RunnableLambda = mod.RunnableLambda;
|
|
60
|
-
return RunnableLambda;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
async invoke(input, config) {
|
|
65
|
-
const text = typeof pickText === "function"
|
|
66
|
-
? String(pickText(input) ?? "")
|
|
67
|
-
: (typeof input === "string" ? input : String(input?.input ?? input?.text ?? ""));
|
|
68
|
-
|
|
69
|
-
const g = gate(text);
|
|
70
|
-
|
|
71
|
-
if (g?.action === "WARN" && typeof onWarn === "function") onWarn(g, input);
|
|
72
|
-
|
|
73
|
-
if (blockSet.has(g?.action)) {
|
|
74
|
-
throw entropyBlockedError(g, { status: 400 });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// If target is a Runnable with invoke()
|
|
78
|
-
if (target && typeof target.invoke === "function") {
|
|
79
|
-
return target.invoke(input, config);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If target is a function
|
|
83
|
-
if (typeof target === "function") {
|
|
84
|
-
return target(input, config);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
throw new Error("withEntropyGate: target must be a Runnable (invoke) or a function.");
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
// Optional: make it LCEL-friendly by exposing `asRunnable()`
|
|
91
|
-
async asRunnable() {
|
|
92
|
-
const RL = await getRunnableLambda();
|
|
93
|
-
return new RL({
|
|
94
|
-
func: async (input, config) => this.invoke(input, config),
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
1
|
+
// integrations/langchain.mjs
|
|
2
|
+
import { gate } from "llm-entropy-filter";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Minimal pre-gate for LangChain flows.
|
|
6
|
+
* Use this before calling any LLM / chain / agent.
|
|
7
|
+
*/
|
|
8
|
+
export function entropyPreGate(input, opts = {}) {
|
|
9
|
+
const { blockOn = "BLOCK" } = opts;
|
|
10
|
+
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
11
|
+
|
|
12
|
+
const text = typeof input === "string" ? input : String(input?.text ?? input ?? "");
|
|
13
|
+
const result = gate(text);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
gate: result,
|
|
17
|
+
inputText: text,
|
|
18
|
+
shouldBlock: blockSet.has(result?.action),
|
|
19
|
+
shouldWarn: result?.action === "WARN",
|
|
20
|
+
shouldAllow: result?.action === "ALLOW",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Standard error object you can throw in API routes.
|
|
26
|
+
*/
|
|
27
|
+
export function entropyBlockedError(gateResult, opts = {}) {
|
|
28
|
+
const { status = 400, code = "ENTROPY_BLOCKED" } = opts;
|
|
29
|
+
const err = new Error("Blocked by llm-entropy-filter (high-entropy / low-signal input).");
|
|
30
|
+
err.name = "EntropyBlockedError";
|
|
31
|
+
err.status = status;
|
|
32
|
+
err.code = code;
|
|
33
|
+
err.gate = gateResult;
|
|
34
|
+
return err;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* LCEL wrapper: wrap a Runnable / function to enforce gate before execution.
|
|
39
|
+
* Works with `@langchain/core/runnables` (RunnableLambda).
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* const safe = withEntropyGate(myRunnableOrFn, { pickText: (i)=> i.input })
|
|
43
|
+
* await safe.invoke({ input: "..." })
|
|
44
|
+
*/
|
|
45
|
+
export function withEntropyGate(target, opts = {}) {
|
|
46
|
+
const {
|
|
47
|
+
blockOn = "BLOCK",
|
|
48
|
+
pickText, // (input) => string
|
|
49
|
+
onWarn, // (gateResult, input) => void
|
|
50
|
+
} = opts;
|
|
51
|
+
|
|
52
|
+
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
53
|
+
|
|
54
|
+
// lazy import to avoid forcing langchain deps
|
|
55
|
+
let RunnableLambda;
|
|
56
|
+
async function getRunnableLambda() {
|
|
57
|
+
if (RunnableLambda) return RunnableLambda;
|
|
58
|
+
const mod = await import("@langchain/core/runnables");
|
|
59
|
+
RunnableLambda = mod.RunnableLambda;
|
|
60
|
+
return RunnableLambda;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
async invoke(input, config) {
|
|
65
|
+
const text = typeof pickText === "function"
|
|
66
|
+
? String(pickText(input) ?? "")
|
|
67
|
+
: (typeof input === "string" ? input : String(input?.input ?? input?.text ?? ""));
|
|
68
|
+
|
|
69
|
+
const g = gate(text);
|
|
70
|
+
|
|
71
|
+
if (g?.action === "WARN" && typeof onWarn === "function") onWarn(g, input);
|
|
72
|
+
|
|
73
|
+
if (blockSet.has(g?.action)) {
|
|
74
|
+
throw entropyBlockedError(g, { status: 400 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If target is a Runnable with invoke()
|
|
78
|
+
if (target && typeof target.invoke === "function") {
|
|
79
|
+
return target.invoke(input, config);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// If target is a function
|
|
83
|
+
if (typeof target === "function") {
|
|
84
|
+
return target(input, config);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new Error("withEntropyGate: target must be a Runnable (invoke) or a function.");
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Optional: make it LCEL-friendly by exposing `asRunnable()`
|
|
91
|
+
async asRunnable() {
|
|
92
|
+
const RL = await getRunnableLambda();
|
|
93
|
+
return new RL({
|
|
94
|
+
func: async (input, config) => this.invoke(input, config),
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
// integrations/vercel-ai-sdk.mjs
|
|
2
|
-
import { gate } from "llm-entropy-filter";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Pre-gate helper for Vercel AI SDK style flows.
|
|
6
|
-
*
|
|
7
|
-
* Typical usage:
|
|
8
|
-
* - Compute gate result
|
|
9
|
-
* - If BLOCK: return early
|
|
10
|
-
* - If WARN: optionally add metadata/logging and continue
|
|
11
|
-
*/
|
|
12
|
-
export function entropyPreGate(input, opts = {}) {
|
|
13
|
-
const { blockOn = "BLOCK" } = opts;
|
|
14
|
-
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
15
|
-
|
|
16
|
-
const result = gate(String(input ?? ""));
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
gate: result,
|
|
20
|
-
shouldBlock: blockSet.has(result?.action),
|
|
21
|
-
shouldWarn: result?.action === "WARN",
|
|
22
|
-
shouldAllow: result?.action === "ALLOW",
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Helper to build a standard Response when blocked (Edge/Node compatible).
|
|
28
|
-
*/
|
|
29
|
-
export function blockedResponse(gateResult, opts = {}) {
|
|
30
|
-
const { status = 400 } = opts;
|
|
31
|
-
return new Response(
|
|
32
|
-
JSON.stringify({
|
|
33
|
-
ok: false,
|
|
34
|
-
blocked: true,
|
|
35
|
-
gate: gateResult,
|
|
36
|
-
message:
|
|
37
|
-
"Request blocked by llm-entropy-filter (high-entropy / low-signal input).",
|
|
38
|
-
}),
|
|
39
|
-
{
|
|
40
|
-
status,
|
|
41
|
-
headers: { "content-type": "application/json; charset=utf-8" },
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
}
|
|
1
|
+
// integrations/vercel-ai-sdk.mjs
|
|
2
|
+
import { gate } from "llm-entropy-filter";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pre-gate helper for Vercel AI SDK style flows.
|
|
6
|
+
*
|
|
7
|
+
* Typical usage:
|
|
8
|
+
* - Compute gate result
|
|
9
|
+
* - If BLOCK: return early
|
|
10
|
+
* - If WARN: optionally add metadata/logging and continue
|
|
11
|
+
*/
|
|
12
|
+
export function entropyPreGate(input, opts = {}) {
|
|
13
|
+
const { blockOn = "BLOCK" } = opts;
|
|
14
|
+
const blockSet = Array.isArray(blockOn) ? new Set(blockOn) : new Set([blockOn]);
|
|
15
|
+
|
|
16
|
+
const result = gate(String(input ?? ""));
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
gate: result,
|
|
20
|
+
shouldBlock: blockSet.has(result?.action),
|
|
21
|
+
shouldWarn: result?.action === "WARN",
|
|
22
|
+
shouldAllow: result?.action === "ALLOW",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Helper to build a standard Response when blocked (Edge/Node compatible).
|
|
28
|
+
*/
|
|
29
|
+
export function blockedResponse(gateResult, opts = {}) {
|
|
30
|
+
const { status = 400 } = opts;
|
|
31
|
+
return new Response(
|
|
32
|
+
JSON.stringify({
|
|
33
|
+
ok: false,
|
|
34
|
+
blocked: true,
|
|
35
|
+
gate: gateResult,
|
|
36
|
+
message:
|
|
37
|
+
"Request blocked by llm-entropy-filter (high-entropy / low-signal input).",
|
|
38
|
+
}),
|
|
39
|
+
{
|
|
40
|
+
status,
|
|
41
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,59 +1,99 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "llm-entropy-filter",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"license": "Apache-2.0",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"import": "./
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"rulesets"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "llm-entropy-filter",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Fast entropy and intent gate for LLM inputs (ALLOW/WARN/BLOCK). Reduces hallucinations, cost and spam before calling an LLM.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"require": "./dist/index.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./integrations/express": {
|
|
21
|
+
"import": "./integrations/express.mjs"
|
|
22
|
+
},
|
|
23
|
+
"./integrations/fastify": {
|
|
24
|
+
"import": "./integrations/fastify.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./integrations/vercel-ai-sdk": {
|
|
27
|
+
"import": "./integrations/vercel-ai-sdk.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./integrations/langchain": {
|
|
30
|
+
"import": "./integrations/langchain.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./rulesets/default": {
|
|
33
|
+
"import": "./rulesets/default.js"
|
|
34
|
+
},
|
|
35
|
+
"./rulesets/strict": {
|
|
36
|
+
"import": "./rulesets/strict.js"
|
|
37
|
+
},
|
|
38
|
+
"./rulesets/public-api": {
|
|
39
|
+
"import": "./rulesets/public-api.js"
|
|
40
|
+
},
|
|
41
|
+
"./rulesets/support": {
|
|
42
|
+
"import": "./rulesets/support.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"integrations",
|
|
48
|
+
"rulesets",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"README.md",
|
|
51
|
+
"CHANGELOG.md"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"clean": "rimraf dist",
|
|
55
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
56
|
+
"demo": "npm run build && node demo/demo.mjs",
|
|
57
|
+
"bench": "npm run build && node bench/benchmark.mjs",
|
|
58
|
+
|
|
59
|
+
"bench:metrics": "npm run build && node bench/metrics.mjs --dataset bench/datasets/core_v1.jsonl --ruleset default",
|
|
60
|
+
"bench:all": "npm run bench && npm run bench:metrics",
|
|
61
|
+
|
|
62
|
+
"bench:metrics:strict": "npm run build && node bench/metrics.mjs --dataset bench/datasets/core_v1.jsonl --ruleset strict",
|
|
63
|
+
"bench:metrics:public-api": "npm run build && node bench/metrics.mjs --dataset bench/datasets/core_v1.jsonl --ruleset public-api",
|
|
64
|
+
|
|
65
|
+
"serve": "npm run build && node demo/server.mjs",
|
|
66
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "git+https://github.com/rosatisoft/llm-entropy-filter.git"
|
|
71
|
+
},
|
|
72
|
+
"bugs": {
|
|
73
|
+
"url": "https://github.com/rosatisoft/llm-entropy-filter/issues"
|
|
74
|
+
},
|
|
75
|
+
"homepage": "https://github.com/rosatisoft/llm-entropy-filter#readme",
|
|
76
|
+
"keywords": [
|
|
77
|
+
"llm",
|
|
78
|
+
"ai",
|
|
79
|
+
"prompt-filter",
|
|
80
|
+
"input-validation",
|
|
81
|
+
"entropy",
|
|
82
|
+
"spam-detection",
|
|
83
|
+
"content-moderation",
|
|
84
|
+
"heuristics",
|
|
85
|
+
"guardrails",
|
|
86
|
+
"openai"
|
|
87
|
+
],
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@types/express": "^5.0.6",
|
|
90
|
+
"@types/node": "^25.0.10",
|
|
91
|
+
"autocannon": "^8.0.0",
|
|
92
|
+
"express": "^5.2.1",
|
|
93
|
+
"minimist": "^1.2.8",
|
|
94
|
+
"openai": "^6.16.0",
|
|
95
|
+
"rimraf": "^6.0.1",
|
|
96
|
+
"tsup": "^8.5.1",
|
|
97
|
+
"typescript": "^5.9.3"
|
|
98
|
+
}
|
|
99
|
+
}
|