llm-entropy-filter 1.1.1 → 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.
@@ -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
+ }