@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
285 lines (284 loc) • 9.73 kB
JavaScript
/**
* TripwireEvaluator - Standalone health-check utility
*
* Evaluates blocking conditions against LLM response data to detect
* runtime issues such as empty responses, high latency, max tokens hit,
* and repetition loops.
*
* This module has no dependency on the processor pipeline system and can
* be used independently.
*
* @module utils/tripwireEvaluator
*/
// ============================================================================
// TripwireEvaluator
// ============================================================================
/**
* Manages and evaluates tripwire conditions against LLM response data.
*
* @example
* ```typescript
* const evaluator = createDefaultTripwireEvaluator();
*
* const result = evaluator.evaluate({
* responseText: "",
* latencyMs: 45000,
* finishReason: "length",
* });
*
* if (result.triggered && result.action === "abort") {
* throw new Error(result.message);
* }
* ```
*/
export class TripwireEvaluator {
tripwires = [];
/**
* Register a tripwire. Replaces any existing tripwire with the same id.
*/
register(tripwire) {
const existingIndex = this.tripwires.findIndex((t) => t.id === tripwire.id);
if (existingIndex !== -1) {
this.tripwires[existingIndex] = tripwire;
}
else {
this.tripwires.push(tripwire);
}
}
/**
* Remove a registered tripwire by id.
* @returns true if the tripwire was found and removed, false otherwise.
*/
unregister(id) {
const index = this.tripwires.findIndex((t) => t.id === id);
if (index !== -1) {
this.tripwires.splice(index, 1);
return true;
}
return false;
}
/**
* Evaluate all tripwires and return the highest-priority triggered result.
*
* Priority order: "abort" > "warn" > "log"
*
* Bug fix (C1): The original implementation returned on the FIRST triggered
* tripwire regardless of action, which meant a "warn" registered before an
* "abort" would mask the abort. This implementation evaluates ALL tripwires
* and promotes the highest-severity action.
*/
evaluate(data) {
let firstAbort = null;
let firstWarn = null;
let firstLog = null;
for (const tripwire of this.tripwires) {
let triggered;
try {
triggered = tripwire.condition(data);
}
catch {
// Skip tripwires whose condition throws — don't let evaluation errors
// block normal operation.
continue;
}
if (!triggered) {
continue;
}
const message = typeof tripwire.message === "function"
? tripwire.message(data)
: tripwire.message;
const result = {
triggered: true,
tripwire,
message,
action: tripwire.action,
};
if (tripwire.action === "abort" && firstAbort === null) {
firstAbort = result;
}
else if (tripwire.action === "warn" && firstWarn === null) {
firstWarn = result;
}
else if (tripwire.action === "log" && firstLog === null) {
firstLog = result;
}
}
if (firstAbort !== null) {
return firstAbort;
}
if (firstWarn !== null) {
return firstWarn;
}
if (firstLog !== null) {
return firstLog;
}
return { triggered: false };
}
/**
* Evaluate all tripwires and return every triggered result.
*/
evaluateAll(data) {
const results = [];
for (const tripwire of this.tripwires) {
let triggered;
try {
triggered = tripwire.condition(data);
}
catch {
continue;
}
if (!triggered) {
continue;
}
const message = typeof tripwire.message === "function"
? tripwire.message(data)
: tripwire.message;
results.push({
triggered: true,
tripwire,
message,
action: tripwire.action,
});
}
return results;
}
/**
* Return a shallow copy of all registered tripwires.
*/
getTripwires() {
return [...this.tripwires];
}
}
// ============================================================================
// Built-in tripwires
// ============================================================================
/**
* Triggers when the model hit its token output limit (finishReason === "length").
*/
const maxTokensTripwire = {
id: "max-tokens",
name: "Maximum Tokens Hit",
description: "Triggers when the model stopped because it reached the token output limit.",
action: "abort",
condition: (data) => data.finishReason === "length",
message: "Model stopped at token limit — response may be truncated.",
};
/**
* Triggers when the response is empty or only whitespace.
*/
const emptyResponseTripwire = {
id: "empty-response",
name: "Empty Response",
description: "Triggers when the model returns an empty or whitespace-only response.",
action: "abort",
condition: (data) => data.responseText !== undefined && data.responseText.trim().length === 0,
message: "Model returned an empty response.",
};
/**
* Triggers when the response contains highly repetitive phrases.
* Uses a sliding-window approach: splits into overlapping 10-word windows and
* counts duplicates. If more than 20% of windows are repeated the response is
* flagged as a repetition loop.
*/
const repetitionLoopTripwire = {
id: "repetition-loop",
name: "Repetition Loop",
description: "Detects when the model is generating repetitive content, indicating a loop.",
action: "warn",
condition: (data) => {
const text = data.responseText;
if (!text) {
return false;
}
const words = text.split(/\s+/);
if (words.length < 30) {
return false;
}
const windowSize = 10;
const windows = new Map();
let duplicates = 0;
const totalWindows = words.length - windowSize + 1;
for (let i = 0; i <= words.length - windowSize; i++) {
const window = words.slice(i, i + windowSize).join(" ");
const count = (windows.get(window) ?? 0) + 1;
windows.set(window, count);
if (count > 1) {
duplicates++;
}
}
return duplicates / totalWindows > 0.2;
},
message: "Response contains highly repetitive content — possible generation loop.",
};
/**
* Triggers when the input text exceeds 100 000 characters.
*/
const inputTooLongTripwire = {
id: "input-too-long",
name: "Input Too Long",
description: "Triggers when the input text exceeds the recommended length.",
action: "abort",
condition: (data) => data.inputText !== undefined && data.inputText.length > 100_000,
message: (data) => `Input is ${data.inputText?.length ?? 0} characters — exceeds the 100 000-character limit.`,
};
/**
* Triggers when the conversation has more than 100 messages.
*/
const tooManyMessagesTripwire = {
id: "too-many-messages",
name: "Too Many Messages",
description: "Triggers when the conversation message count exceeds the recommended maximum.",
action: "warn",
condition: (data) => data.messageCount !== undefined && data.messageCount > 100,
message: (data) => `Conversation has ${data.messageCount} messages — consider summarising context.`,
};
/**
* Triggers when the response text exceeds 50 000 characters.
*/
const responseTooLongTripwire = {
id: "response-too-long",
name: "Response Too Long",
description: "Triggers when the response text exceeds the recommended length.",
action: "warn",
condition: (data) => data.responseText !== undefined && data.responseText.length > 50_000,
message: (data) => `Response is ${data.responseText?.length ?? 0} characters — exceeds the 50 000-character limit.`,
};
/**
* Triggers when the measured end-to-end latency exceeds 30 seconds.
*/
const highLatencyTripwire = {
id: "high-latency",
name: "High Latency",
description: "Triggers when the measured response latency exceeds the acceptable threshold.",
action: "warn",
condition: (data) => data.latencyMs !== undefined && data.latencyMs > 30_000,
message: (data) => `Response latency was ${data.latencyMs}ms — exceeds the 30 000ms threshold.`,
};
/**
* All built-in tripwires in default registration order.
*
* Registration order does not affect priority — `evaluate()` always promotes
* the highest-severity action ("abort" > "warn" > "log").
*/
export const commonTripwires = [
maxTokensTripwire,
emptyResponseTripwire,
repetitionLoopTripwire,
inputTooLongTripwire,
tooManyMessagesTripwire,
responseTooLongTripwire,
highLatencyTripwire,
];
// ============================================================================
// Factory
// ============================================================================
/**
* Create a TripwireEvaluator pre-loaded with all built-in tripwires.
*/
export function createDefaultTripwireEvaluator() {
const evaluator = new TripwireEvaluator();
for (const tripwire of commonTripwires) {
evaluator.register(tripwire);
}
return evaluator;
}