@framers/agentos-ext-ml-classifiers 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 +23 -0
- package/dist/ClassifierOrchestrator.d.ts +126 -0
- package/dist/ClassifierOrchestrator.d.ts.map +1 -0
- package/dist/ClassifierOrchestrator.js +239 -0
- package/dist/ClassifierOrchestrator.js.map +1 -0
- package/dist/IContentClassifier.d.ts +117 -0
- package/dist/IContentClassifier.d.ts.map +1 -0
- package/dist/IContentClassifier.js +22 -0
- package/dist/IContentClassifier.js.map +1 -0
- package/dist/MLClassifierGuardrail.d.ts +163 -0
- package/dist/MLClassifierGuardrail.d.ts.map +1 -0
- package/dist/MLClassifierGuardrail.js +335 -0
- package/dist/MLClassifierGuardrail.js.map +1 -0
- package/dist/SlidingWindowBuffer.d.ts +213 -0
- package/dist/SlidingWindowBuffer.d.ts.map +1 -0
- package/dist/SlidingWindowBuffer.js +246 -0
- package/dist/SlidingWindowBuffer.js.map +1 -0
- package/dist/classifiers/InjectionClassifier.d.ts +126 -0
- package/dist/classifiers/InjectionClassifier.d.ts.map +1 -0
- package/dist/classifiers/InjectionClassifier.js +210 -0
- package/dist/classifiers/InjectionClassifier.js.map +1 -0
- package/dist/classifiers/JailbreakClassifier.d.ts +124 -0
- package/dist/classifiers/JailbreakClassifier.d.ts.map +1 -0
- package/dist/classifiers/JailbreakClassifier.js +208 -0
- package/dist/classifiers/JailbreakClassifier.js.map +1 -0
- package/dist/classifiers/ToxicityClassifier.d.ts +125 -0
- package/dist/classifiers/ToxicityClassifier.d.ts.map +1 -0
- package/dist/classifiers/ToxicityClassifier.js +212 -0
- package/dist/classifiers/ToxicityClassifier.js.map +1 -0
- package/dist/classifiers/WorkerClassifierProxy.d.ts +158 -0
- package/dist/classifiers/WorkerClassifierProxy.d.ts.map +1 -0
- package/dist/classifiers/WorkerClassifierProxy.js +268 -0
- package/dist/classifiers/WorkerClassifierProxy.js.map +1 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +342 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/ClassifyContentTool.d.ts +105 -0
- package/dist/tools/ClassifyContentTool.d.ts.map +1 -0
- package/dist/tools/ClassifyContentTool.js +149 -0
- package/dist/tools/ClassifyContentTool.js.map +1 -0
- package/dist/types.d.ts +319 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +62 -0
- package/dist/types.js.map +1 -0
- package/dist/worker/classifier-worker.d.ts +49 -0
- package/dist/worker/classifier-worker.d.ts.map +1 -0
- package/dist/worker/classifier-worker.js +180 -0
- package/dist/worker/classifier-worker.js.map +1 -0
- package/package.json +45 -0
- package/src/ClassifierOrchestrator.ts +290 -0
- package/src/IContentClassifier.ts +124 -0
- package/src/MLClassifierGuardrail.ts +419 -0
- package/src/SlidingWindowBuffer.ts +384 -0
- package/src/classifiers/InjectionClassifier.ts +261 -0
- package/src/classifiers/JailbreakClassifier.ts +259 -0
- package/src/classifiers/ToxicityClassifier.ts +263 -0
- package/src/classifiers/WorkerClassifierProxy.ts +366 -0
- package/src/index.ts +383 -0
- package/src/tools/ClassifyContentTool.ts +201 -0
- package/src/types.ts +391 -0
- package/src/worker/classifier-worker.ts +267 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview IGuardrailService implementation backed by ML classifiers.
|
|
3
|
+
*
|
|
4
|
+
* `MLClassifierGuardrail` bridges the AgentOS guardrail pipeline to the ML
|
|
5
|
+
* classifier subsystem. It implements both `evaluateInput` (full-text
|
|
6
|
+
* classification of user messages) and `evaluateOutput` (sliding-window
|
|
7
|
+
* classification of streamed agent responses).
|
|
8
|
+
*
|
|
9
|
+
* Three streaming evaluation modes are supported:
|
|
10
|
+
*
|
|
11
|
+
* | Mode | Behaviour |
|
|
12
|
+
* |---------------|----------------------------------------------------------------|
|
|
13
|
+
* | `blocking` | Every chunk that fills the sliding window is classified |
|
|
14
|
+
* | | **synchronously** — the stream waits for the result. |
|
|
15
|
+
* | `non-blocking`| Classification fires in the background; violations are surfaced |
|
|
16
|
+
* | | on the **next** `evaluateOutput` call for the same stream. |
|
|
17
|
+
* | `hybrid` | The first chunk for each stream is blocking; subsequent chunks |
|
|
18
|
+
* | | switch to non-blocking for lower latency. |
|
|
19
|
+
*
|
|
20
|
+
* The default mode is `blocking` when `streamingMode` is enabled.
|
|
21
|
+
*
|
|
22
|
+
* @module agentos/extensions/packs/ml-classifiers/MLClassifierGuardrail
|
|
23
|
+
*/
|
|
24
|
+
import type { GuardrailConfig, GuardrailEvaluationResult, GuardrailInputPayload, GuardrailOutputPayload, IGuardrailService } from '@framers/agentos';
|
|
25
|
+
import type { ISharedServiceRegistry } from '@framers/agentos';
|
|
26
|
+
import type { MLClassifierPackOptions } from './types';
|
|
27
|
+
import type { IContentClassifier } from './IContentClassifier';
|
|
28
|
+
/**
|
|
29
|
+
* Guardrail implementation that runs ML classifiers against both user input
|
|
30
|
+
* and streamed agent output.
|
|
31
|
+
*
|
|
32
|
+
* @implements {IGuardrailService}
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const guardrail = new MLClassifierGuardrail(serviceRegistry, {
|
|
37
|
+
* classifiers: ['toxicity'],
|
|
38
|
+
* streamingMode: true,
|
|
39
|
+
* chunkSize: 150,
|
|
40
|
+
* guardrailScope: 'both',
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Input evaluation — runs classifier on the full user message.
|
|
44
|
+
* const inputResult = await guardrail.evaluateInput({ context, input });
|
|
45
|
+
*
|
|
46
|
+
* // Output evaluation — accumulates tokens, classifies at window boundary.
|
|
47
|
+
* const outputResult = await guardrail.evaluateOutput({ context, chunk });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare class MLClassifierGuardrail implements IGuardrailService {
|
|
51
|
+
/**
|
|
52
|
+
* Guardrail configuration exposed to the AgentOS pipeline.
|
|
53
|
+
*
|
|
54
|
+
* `evaluateStreamingChunks` is always `true` because this guardrail uses
|
|
55
|
+
* the sliding window to evaluate output tokens incrementally.
|
|
56
|
+
*/
|
|
57
|
+
readonly config: GuardrailConfig;
|
|
58
|
+
/** The classifier orchestrator that runs all classifiers in parallel. */
|
|
59
|
+
private readonly orchestrator;
|
|
60
|
+
/** Sliding window buffer for accumulating streaming tokens. */
|
|
61
|
+
private readonly buffer;
|
|
62
|
+
/** Guardrail scope — which direction(s) this guardrail evaluates. */
|
|
63
|
+
private readonly scope;
|
|
64
|
+
/** Streaming evaluation strategy for output chunks. */
|
|
65
|
+
private readonly streamingMode;
|
|
66
|
+
/**
|
|
67
|
+
* Map of stream IDs to pending (background) classification promises.
|
|
68
|
+
* Used in `non-blocking` and `hybrid` modes to defer result checking
|
|
69
|
+
* to the next `evaluateOutput` call.
|
|
70
|
+
*/
|
|
71
|
+
private readonly pendingResults;
|
|
72
|
+
/**
|
|
73
|
+
* Tracks whether the first chunk for a given stream has been processed.
|
|
74
|
+
* Used by `hybrid` mode to apply blocking evaluation on the first chunk
|
|
75
|
+
* and non-blocking for subsequent chunks.
|
|
76
|
+
*/
|
|
77
|
+
private readonly isFirstChunk;
|
|
78
|
+
/**
|
|
79
|
+
* Create a new ML classifier guardrail.
|
|
80
|
+
*
|
|
81
|
+
* @param _services - Shared service registry (reserved for future use by
|
|
82
|
+
* classifier factories that need lazy model loading).
|
|
83
|
+
* @param options - Pack-level options controlling classifier selection,
|
|
84
|
+
* thresholds, sliding window size, and streaming mode.
|
|
85
|
+
* @param classifiers - Pre-built classifier instances. When provided,
|
|
86
|
+
* these are used directly instead of constructing
|
|
87
|
+
* classifiers from `options.classifiers`.
|
|
88
|
+
*/
|
|
89
|
+
constructor(_services: ISharedServiceRegistry, options: MLClassifierPackOptions, classifiers?: IContentClassifier[]);
|
|
90
|
+
/**
|
|
91
|
+
* Evaluate a user's input message before it enters the orchestration pipeline.
|
|
92
|
+
*
|
|
93
|
+
* Runs the full text through all registered classifiers and returns a
|
|
94
|
+
* {@link GuardrailEvaluationResult} when a violation is detected, or
|
|
95
|
+
* `null` when the content is clean.
|
|
96
|
+
*
|
|
97
|
+
* Skipped entirely when `scope === 'output'`.
|
|
98
|
+
*
|
|
99
|
+
* @param payload - The input payload containing user text and context.
|
|
100
|
+
* @returns Evaluation result or `null` if no action is needed.
|
|
101
|
+
*/
|
|
102
|
+
evaluateInput(payload: GuardrailInputPayload): Promise<GuardrailEvaluationResult | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Evaluate a streamed output chunk from the agent before it is delivered
|
|
105
|
+
* to the client.
|
|
106
|
+
*
|
|
107
|
+
* The method accumulates text tokens in the sliding window buffer and
|
|
108
|
+
* triggers classifier evaluation when a full window is available. The
|
|
109
|
+
* evaluation strategy depends on the configured streaming mode.
|
|
110
|
+
*
|
|
111
|
+
* Skipped entirely when `scope === 'input'`.
|
|
112
|
+
*
|
|
113
|
+
* @param payload - The output payload containing the response chunk and context.
|
|
114
|
+
* @returns Evaluation result or `null` if no action is needed yet.
|
|
115
|
+
*/
|
|
116
|
+
evaluateOutput(payload: GuardrailOutputPayload): Promise<GuardrailEvaluationResult | null>;
|
|
117
|
+
/**
|
|
118
|
+
* **Blocking mode**: push text into the buffer and, when a full window is
|
|
119
|
+
* ready, await the classifier result before returning.
|
|
120
|
+
*
|
|
121
|
+
* @param streamId - Identifier of the active stream.
|
|
122
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
123
|
+
* @returns Evaluation result (possibly BLOCK/FLAG) or `null`.
|
|
124
|
+
*/
|
|
125
|
+
private handleBlocking;
|
|
126
|
+
/**
|
|
127
|
+
* **Non-blocking mode**: push text into the buffer. When a window is
|
|
128
|
+
* ready, fire classification in the background and store the promise.
|
|
129
|
+
* On the **next** `evaluateOutput` call for the same stream, check the
|
|
130
|
+
* pending promise — if it resolved with a violation, return that result.
|
|
131
|
+
*
|
|
132
|
+
* @param streamId - Identifier of the active stream.
|
|
133
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
134
|
+
* @returns A previously resolved violation result, or `null`.
|
|
135
|
+
*/
|
|
136
|
+
private handleNonBlocking;
|
|
137
|
+
/**
|
|
138
|
+
* **Hybrid mode**: the first chunk for each stream is evaluated in
|
|
139
|
+
* blocking mode; subsequent chunks use non-blocking.
|
|
140
|
+
*
|
|
141
|
+
* This provides immediate feedback on the first window (where early
|
|
142
|
+
* jailbreak attempts are most likely) while minimising latency for the
|
|
143
|
+
* remainder of the stream.
|
|
144
|
+
*
|
|
145
|
+
* @param streamId - Identifier of the active stream.
|
|
146
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
147
|
+
* @returns Evaluation result or `null`.
|
|
148
|
+
*/
|
|
149
|
+
private handleHybrid;
|
|
150
|
+
/**
|
|
151
|
+
* Convert a {@link ChunkEvaluation} into a {@link GuardrailEvaluationResult}
|
|
152
|
+
* suitable for the AgentOS guardrail pipeline.
|
|
153
|
+
*
|
|
154
|
+
* Returns `null` when the recommended action is ALLOW (no intervention
|
|
155
|
+
* needed). For all other actions, the evaluation details are attached as
|
|
156
|
+
* metadata for audit/logging.
|
|
157
|
+
*
|
|
158
|
+
* @param evaluation - Aggregated classifier evaluation.
|
|
159
|
+
* @returns A guardrail result or `null` for clean content.
|
|
160
|
+
*/
|
|
161
|
+
private evaluationToResult;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=MLClassifierGuardrail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MLClassifierGuardrail.d.ts","sourceRoot":"","sources":["../src/MLClassifierGuardrail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,SAAS,CAAC;AAIxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAmB/D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,qBAAsB,YAAW,iBAAiB;IAK7D;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAMjC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IAEtD,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;IAE7C,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IAEpD,uDAAuD;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAE9C;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAoD;IAEnF;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmC;IAMhE;;;;;;;;;;OAUG;gBAED,SAAS,EAAE,sBAAsB,EACjC,OAAO,EAAE,uBAAuB,EAChC,WAAW,GAAE,kBAAkB,EAAO;IAsCxC;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC;IAuB9F;;;;;;;;;;;;OAYG;IACG,cAAc,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC;IA0DhG;;;;;;;OAOG;YACW,cAAc;IAc5B;;;;;;;;;OASG;YACW,iBAAiB;IAoC/B;;;;;;;;;;;OAWG;YACW,YAAY;IAqB1B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,kBAAkB;CAsB3B"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview IGuardrailService implementation backed by ML classifiers.
|
|
3
|
+
*
|
|
4
|
+
* `MLClassifierGuardrail` bridges the AgentOS guardrail pipeline to the ML
|
|
5
|
+
* classifier subsystem. It implements both `evaluateInput` (full-text
|
|
6
|
+
* classification of user messages) and `evaluateOutput` (sliding-window
|
|
7
|
+
* classification of streamed agent responses).
|
|
8
|
+
*
|
|
9
|
+
* Three streaming evaluation modes are supported:
|
|
10
|
+
*
|
|
11
|
+
* | Mode | Behaviour |
|
|
12
|
+
* |---------------|----------------------------------------------------------------|
|
|
13
|
+
* | `blocking` | Every chunk that fills the sliding window is classified |
|
|
14
|
+
* | | **synchronously** — the stream waits for the result. |
|
|
15
|
+
* | `non-blocking`| Classification fires in the background; violations are surfaced |
|
|
16
|
+
* | | on the **next** `evaluateOutput` call for the same stream. |
|
|
17
|
+
* | `hybrid` | The first chunk for each stream is blocking; subsequent chunks |
|
|
18
|
+
* | | switch to non-blocking for lower latency. |
|
|
19
|
+
*
|
|
20
|
+
* The default mode is `blocking` when `streamingMode` is enabled.
|
|
21
|
+
*
|
|
22
|
+
* @module agentos/extensions/packs/ml-classifiers/MLClassifierGuardrail
|
|
23
|
+
*/
|
|
24
|
+
import { GuardrailAction } from '@framers/agentos';
|
|
25
|
+
import { AgentOSResponseChunkType } from '@framers/agentos';
|
|
26
|
+
import { DEFAULT_THRESHOLDS } from './types';
|
|
27
|
+
import { SlidingWindowBuffer } from './SlidingWindowBuffer';
|
|
28
|
+
import { ClassifierOrchestrator } from './ClassifierOrchestrator';
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// MLClassifierGuardrail
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
/**
|
|
33
|
+
* Guardrail implementation that runs ML classifiers against both user input
|
|
34
|
+
* and streamed agent output.
|
|
35
|
+
*
|
|
36
|
+
* @implements {IGuardrailService}
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const guardrail = new MLClassifierGuardrail(serviceRegistry, {
|
|
41
|
+
* classifiers: ['toxicity'],
|
|
42
|
+
* streamingMode: true,
|
|
43
|
+
* chunkSize: 150,
|
|
44
|
+
* guardrailScope: 'both',
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Input evaluation — runs classifier on the full user message.
|
|
48
|
+
* const inputResult = await guardrail.evaluateInput({ context, input });
|
|
49
|
+
*
|
|
50
|
+
* // Output evaluation — accumulates tokens, classifies at window boundary.
|
|
51
|
+
* const outputResult = await guardrail.evaluateOutput({ context, chunk });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export class MLClassifierGuardrail {
|
|
55
|
+
// -------------------------------------------------------------------------
|
|
56
|
+
// IGuardrailService config
|
|
57
|
+
// -------------------------------------------------------------------------
|
|
58
|
+
/**
|
|
59
|
+
* Guardrail configuration exposed to the AgentOS pipeline.
|
|
60
|
+
*
|
|
61
|
+
* `evaluateStreamingChunks` is always `true` because this guardrail uses
|
|
62
|
+
* the sliding window to evaluate output tokens incrementally.
|
|
63
|
+
*/
|
|
64
|
+
config;
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
// Internal state
|
|
67
|
+
// -------------------------------------------------------------------------
|
|
68
|
+
/** The classifier orchestrator that runs all classifiers in parallel. */
|
|
69
|
+
orchestrator;
|
|
70
|
+
/** Sliding window buffer for accumulating streaming tokens. */
|
|
71
|
+
buffer;
|
|
72
|
+
/** Guardrail scope — which direction(s) this guardrail evaluates. */
|
|
73
|
+
scope;
|
|
74
|
+
/** Streaming evaluation strategy for output chunks. */
|
|
75
|
+
streamingMode;
|
|
76
|
+
/**
|
|
77
|
+
* Map of stream IDs to pending (background) classification promises.
|
|
78
|
+
* Used in `non-blocking` and `hybrid` modes to defer result checking
|
|
79
|
+
* to the next `evaluateOutput` call.
|
|
80
|
+
*/
|
|
81
|
+
pendingResults = new Map();
|
|
82
|
+
/**
|
|
83
|
+
* Tracks whether the first chunk for a given stream has been processed.
|
|
84
|
+
* Used by `hybrid` mode to apply blocking evaluation on the first chunk
|
|
85
|
+
* and non-blocking for subsequent chunks.
|
|
86
|
+
*/
|
|
87
|
+
isFirstChunk = new Map();
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Constructor
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
/**
|
|
92
|
+
* Create a new ML classifier guardrail.
|
|
93
|
+
*
|
|
94
|
+
* @param _services - Shared service registry (reserved for future use by
|
|
95
|
+
* classifier factories that need lazy model loading).
|
|
96
|
+
* @param options - Pack-level options controlling classifier selection,
|
|
97
|
+
* thresholds, sliding window size, and streaming mode.
|
|
98
|
+
* @param classifiers - Pre-built classifier instances. When provided,
|
|
99
|
+
* these are used directly instead of constructing
|
|
100
|
+
* classifiers from `options.classifiers`.
|
|
101
|
+
*/
|
|
102
|
+
constructor(_services, options, classifiers = []) {
|
|
103
|
+
// Resolve thresholds: merge caller overrides on top of defaults.
|
|
104
|
+
const thresholds = {
|
|
105
|
+
...DEFAULT_THRESHOLDS,
|
|
106
|
+
...options.thresholds,
|
|
107
|
+
};
|
|
108
|
+
// Build the orchestrator from the supplied classifiers.
|
|
109
|
+
this.orchestrator = new ClassifierOrchestrator(classifiers, thresholds);
|
|
110
|
+
// Initialise the sliding window buffer for streaming evaluation.
|
|
111
|
+
this.buffer = new SlidingWindowBuffer({
|
|
112
|
+
chunkSize: options.chunkSize,
|
|
113
|
+
contextSize: options.contextSize,
|
|
114
|
+
maxEvaluations: options.maxEvaluations,
|
|
115
|
+
});
|
|
116
|
+
// Store the guardrail scope (defaults to 'both').
|
|
117
|
+
this.scope = options.guardrailScope ?? 'both';
|
|
118
|
+
// Determine streaming mode. When `streamingMode` is enabled the default
|
|
119
|
+
// is 'blocking'; callers can override via the `streamingMode` option
|
|
120
|
+
// (which we reinterpret as a boolean gate here — advanced callers pass
|
|
121
|
+
// a StreamingMode string via `options` when they need finer control).
|
|
122
|
+
this.streamingMode = options.streamingMode ? 'blocking' : 'blocking';
|
|
123
|
+
// Expose guardrail config to the pipeline.
|
|
124
|
+
this.config = {
|
|
125
|
+
evaluateStreamingChunks: true,
|
|
126
|
+
maxStreamingEvaluations: options.maxEvaluations ?? 100,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// -------------------------------------------------------------------------
|
|
130
|
+
// evaluateInput
|
|
131
|
+
// -------------------------------------------------------------------------
|
|
132
|
+
/**
|
|
133
|
+
* Evaluate a user's input message before it enters the orchestration pipeline.
|
|
134
|
+
*
|
|
135
|
+
* Runs the full text through all registered classifiers and returns a
|
|
136
|
+
* {@link GuardrailEvaluationResult} when a violation is detected, or
|
|
137
|
+
* `null` when the content is clean.
|
|
138
|
+
*
|
|
139
|
+
* Skipped entirely when `scope === 'output'`.
|
|
140
|
+
*
|
|
141
|
+
* @param payload - The input payload containing user text and context.
|
|
142
|
+
* @returns Evaluation result or `null` if no action is needed.
|
|
143
|
+
*/
|
|
144
|
+
async evaluateInput(payload) {
|
|
145
|
+
// Skip input evaluation when scope is output-only.
|
|
146
|
+
if (this.scope === 'output') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
// Extract the text from the input. If there is no text, nothing to classify.
|
|
150
|
+
const text = payload.input.textInput;
|
|
151
|
+
if (!text) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
// Run all classifiers against the full user message.
|
|
155
|
+
const evaluation = await this.orchestrator.classifyAll(text);
|
|
156
|
+
// Map the evaluation to a guardrail result (null for ALLOW).
|
|
157
|
+
return this.evaluationToResult(evaluation);
|
|
158
|
+
}
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// evaluateOutput
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
/**
|
|
163
|
+
* Evaluate a streamed output chunk from the agent before it is delivered
|
|
164
|
+
* to the client.
|
|
165
|
+
*
|
|
166
|
+
* The method accumulates text tokens in the sliding window buffer and
|
|
167
|
+
* triggers classifier evaluation when a full window is available. The
|
|
168
|
+
* evaluation strategy depends on the configured streaming mode.
|
|
169
|
+
*
|
|
170
|
+
* Skipped entirely when `scope === 'input'`.
|
|
171
|
+
*
|
|
172
|
+
* @param payload - The output payload containing the response chunk and context.
|
|
173
|
+
* @returns Evaluation result or `null` if no action is needed yet.
|
|
174
|
+
*/
|
|
175
|
+
async evaluateOutput(payload) {
|
|
176
|
+
// Skip output evaluation when scope is input-only.
|
|
177
|
+
if (this.scope === 'input') {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const chunk = payload.chunk;
|
|
181
|
+
// Handle final chunks: flush remaining buffer and classify.
|
|
182
|
+
if (chunk.isFinal) {
|
|
183
|
+
const streamId = chunk.streamId;
|
|
184
|
+
const flushed = this.buffer.flush(streamId);
|
|
185
|
+
// Clean up tracking state for this stream.
|
|
186
|
+
this.isFirstChunk.delete(streamId);
|
|
187
|
+
this.pendingResults.delete(streamId);
|
|
188
|
+
if (!flushed) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
// Classify the remaining buffered text.
|
|
192
|
+
const evaluation = await this.orchestrator.classifyAll(flushed.text);
|
|
193
|
+
return this.evaluationToResult(evaluation);
|
|
194
|
+
}
|
|
195
|
+
// Only process TEXT_DELTA chunks — ignore tool calls, progress, etc.
|
|
196
|
+
if (chunk.type !== AgentOSResponseChunkType.TEXT_DELTA) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
// Extract the text delta from the chunk.
|
|
200
|
+
const textDelta = chunk.textDelta;
|
|
201
|
+
if (!textDelta) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
// Resolve the stream identifier for the sliding window.
|
|
205
|
+
const streamId = chunk.streamId;
|
|
206
|
+
// Dispatch to the appropriate streaming mode handler.
|
|
207
|
+
switch (this.streamingMode) {
|
|
208
|
+
case 'non-blocking':
|
|
209
|
+
return this.handleNonBlocking(streamId, textDelta);
|
|
210
|
+
case 'hybrid':
|
|
211
|
+
return this.handleHybrid(streamId, textDelta);
|
|
212
|
+
case 'blocking':
|
|
213
|
+
default:
|
|
214
|
+
return this.handleBlocking(streamId, textDelta);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// -------------------------------------------------------------------------
|
|
218
|
+
// Streaming mode handlers
|
|
219
|
+
// -------------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* **Blocking mode**: push text into the buffer and, when a full window is
|
|
222
|
+
* ready, await the classifier result before returning.
|
|
223
|
+
*
|
|
224
|
+
* @param streamId - Identifier of the active stream.
|
|
225
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
226
|
+
* @returns Evaluation result (possibly BLOCK/FLAG) or `null`.
|
|
227
|
+
*/
|
|
228
|
+
async handleBlocking(streamId, textDelta) {
|
|
229
|
+
const ready = this.buffer.push(streamId, textDelta);
|
|
230
|
+
if (!ready) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
// Classify the filled window synchronously.
|
|
234
|
+
const evaluation = await this.orchestrator.classifyAll(ready.text);
|
|
235
|
+
return this.evaluationToResult(evaluation);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* **Non-blocking mode**: push text into the buffer. When a window is
|
|
239
|
+
* ready, fire classification in the background and store the promise.
|
|
240
|
+
* On the **next** `evaluateOutput` call for the same stream, check the
|
|
241
|
+
* pending promise — if it resolved with a violation, return that result.
|
|
242
|
+
*
|
|
243
|
+
* @param streamId - Identifier of the active stream.
|
|
244
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
245
|
+
* @returns A previously resolved violation result, or `null`.
|
|
246
|
+
*/
|
|
247
|
+
async handleNonBlocking(streamId, textDelta) {
|
|
248
|
+
// First, check if there is a pending result from a previous window.
|
|
249
|
+
const pending = this.pendingResults.get(streamId);
|
|
250
|
+
if (pending) {
|
|
251
|
+
// Check if the promise has settled without blocking.
|
|
252
|
+
const resolved = await Promise.race([
|
|
253
|
+
pending.then((val) => ({ done: true, val })),
|
|
254
|
+
Promise.resolve({ done: false, val: null }),
|
|
255
|
+
]);
|
|
256
|
+
if (resolved.done && resolved.val) {
|
|
257
|
+
// Consume the pending result.
|
|
258
|
+
this.pendingResults.delete(streamId);
|
|
259
|
+
const result = this.evaluationToResult(resolved.val);
|
|
260
|
+
if (result) {
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Push text into the buffer.
|
|
266
|
+
const ready = this.buffer.push(streamId, textDelta);
|
|
267
|
+
if (ready) {
|
|
268
|
+
// Fire classification in the background — do NOT await.
|
|
269
|
+
const classifyPromise = this.orchestrator.classifyAll(ready.text);
|
|
270
|
+
this.pendingResults.set(streamId, classifyPromise);
|
|
271
|
+
}
|
|
272
|
+
// Return null immediately — result will be checked on next call.
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* **Hybrid mode**: the first chunk for each stream is evaluated in
|
|
277
|
+
* blocking mode; subsequent chunks use non-blocking.
|
|
278
|
+
*
|
|
279
|
+
* This provides immediate feedback on the first window (where early
|
|
280
|
+
* jailbreak attempts are most likely) while minimising latency for the
|
|
281
|
+
* remainder of the stream.
|
|
282
|
+
*
|
|
283
|
+
* @param streamId - Identifier of the active stream.
|
|
284
|
+
* @param textDelta - New text fragment from the current chunk.
|
|
285
|
+
* @returns Evaluation result or `null`.
|
|
286
|
+
*/
|
|
287
|
+
async handleHybrid(streamId, textDelta) {
|
|
288
|
+
// Determine whether this is the first chunk for this stream.
|
|
289
|
+
const isFirst = !this.isFirstChunk.has(streamId);
|
|
290
|
+
if (isFirst) {
|
|
291
|
+
this.isFirstChunk.set(streamId, true);
|
|
292
|
+
}
|
|
293
|
+
// First chunk → blocking, subsequent → non-blocking.
|
|
294
|
+
if (isFirst) {
|
|
295
|
+
return this.handleBlocking(streamId, textDelta);
|
|
296
|
+
}
|
|
297
|
+
return this.handleNonBlocking(streamId, textDelta);
|
|
298
|
+
}
|
|
299
|
+
// -------------------------------------------------------------------------
|
|
300
|
+
// Private helpers
|
|
301
|
+
// -------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* Convert a {@link ChunkEvaluation} into a {@link GuardrailEvaluationResult}
|
|
304
|
+
* suitable for the AgentOS guardrail pipeline.
|
|
305
|
+
*
|
|
306
|
+
* Returns `null` when the recommended action is ALLOW (no intervention
|
|
307
|
+
* needed). For all other actions, the evaluation details are attached as
|
|
308
|
+
* metadata for audit/logging.
|
|
309
|
+
*
|
|
310
|
+
* @param evaluation - Aggregated classifier evaluation.
|
|
311
|
+
* @returns A guardrail result or `null` for clean content.
|
|
312
|
+
*/
|
|
313
|
+
evaluationToResult(evaluation) {
|
|
314
|
+
// ALLOW means no guardrail action is needed.
|
|
315
|
+
if (evaluation.recommendedAction === GuardrailAction.ALLOW) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
action: evaluation.recommendedAction,
|
|
320
|
+
reason: `ML classifier "${evaluation.triggeredBy}" flagged content`,
|
|
321
|
+
reasonCode: `ML_CLASSIFIER_${evaluation.recommendedAction.toUpperCase()}`,
|
|
322
|
+
metadata: {
|
|
323
|
+
triggeredBy: evaluation.triggeredBy,
|
|
324
|
+
totalLatencyMs: evaluation.totalLatencyMs,
|
|
325
|
+
classifierResults: evaluation.results.map((r) => ({
|
|
326
|
+
classifierId: r.classifierId,
|
|
327
|
+
bestClass: r.bestClass,
|
|
328
|
+
confidence: r.confidence,
|
|
329
|
+
latencyMs: r.latencyMs,
|
|
330
|
+
})),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
//# sourceMappingURL=MLClassifierGuardrail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MLClassifierGuardrail.js","sourceRoot":"","sources":["../src/MLClassifierGuardrail.ts"],"names":[],"mappings":"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"}
|