@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
187 lines (186 loc) • 5.71 kB
JavaScript
/**
* @file Observability Hooks
* OpenTelemetry integration for evaluation tracing
*/
import { logger } from "../../utils/logger.js";
/**
* Observability hooks manager
*/
export class ObservabilityHooks {
_handlers = new Map();
_traceContext;
_enabled = true;
/**
* Enable/disable observability
*/
set enabled(value) {
this._enabled = value;
}
get enabled() {
return this._enabled;
}
/**
* Set trace context for all events
*/
setTraceContext(context) {
this._traceContext = context;
}
/**
* Clear trace context
*/
clearTraceContext() {
this._traceContext = undefined;
}
/**
* Get current trace context
*/
getTraceContext() {
return this._traceContext;
}
/**
* Register an event handler
*/
on(event, handler) {
let handlers = this._handlers.get(event);
if (!handlers) {
handlers = new Set();
this._handlers.set(event, handlers);
}
handlers.add(handler);
// Return unsubscribe function
return () => {
this._handlers.get(event)?.delete(handler);
};
}
/**
* Remove an event handler
*/
off(event, handler) {
this._handlers.get(event)?.delete(handler);
}
/**
* Emit an event
*/
async emit(event, data) {
if (!this._enabled) {
return;
}
const handlers = this._handlers.get(event);
if (!handlers || handlers.size === 0) {
return;
}
const eventData = {
...data,
traceContext: this._traceContext,
};
const promises = [];
for (const handler of handlers) {
try {
const result = handler(eventData);
if (result instanceof Promise) {
promises.push(result.catch((err) => {
logger.error(`Event handler error for ${event}`, { error: err });
}));
}
}
catch (error) {
logger.error(`Event handler error for ${event}`, { error });
}
}
// Wait for async handlers
await Promise.all(promises);
}
/**
* Clear all handlers
*/
clear() {
this._handlers.clear();
}
/**
* Get handler count for an event
*/
listenerCount(event) {
return this._handlers.get(event)?.size ?? 0;
}
}
/**
* Global observability hooks instance
*/
export const observabilityHooks = new ObservabilityHooks();
/**
* Helper: Create a console logger hook
*/
export function createConsoleLoggerHook() {
observabilityHooks.on("scorer:start", (event) => {
logger.info(`[SCORER] ${event.scorerName} started at ${new Date(event.timestamp).toISOString()}`);
});
observabilityHooks.on("scorer:end", (event) => {
logger.info(`[SCORER] ${event.scorerName} completed: score=${event.result.score.toFixed(1)}, ` +
`passed=${event.result.passed}, duration=${event.duration}ms`);
});
observabilityHooks.on("scorer:error", (event) => {
logger.error(`[SCORER] ${event.scorerName} error: ${event.error}`);
});
observabilityHooks.on("pipeline:start", (event) => {
logger.info(`[PIPELINE] ${event.pipelineName} started with ${event.scorerCount} scorers ` +
`(correlationId: ${event.correlationId})`);
});
observabilityHooks.on("pipeline:end", (event) => {
logger.info(`[PIPELINE] ${event.pipelineName} completed: overall=${event.result.overallScore.toFixed(1)}, ` +
`passed=${event.result.passed}, duration=${event.duration}ms`);
});
observabilityHooks.on("pipeline:error", (event) => {
logger.error(`[PIPELINE] ${event.pipelineName} error: ${event.error}`);
});
}
/**
* Helper: Create a metrics collector hook
* Accepts the actual MetricsCollector interface from reporting/metricsCollector
*/
export function createMetricsCollectorHook(collector) {
observabilityHooks.on("scorer:end", (event) => {
collector.recordScorer(event.scorerId, event.scorerName, event.result);
});
observabilityHooks.on("pipeline:end", (event) => {
collector.recordPipeline(event.result);
});
}
/**
* OpenTelemetry span attributes
*/
/**
* Create span attributes from scorer result
*/
export function scorerToSpanAttributes(result) {
return {
"scorer.id": result.scorerId,
"scorer.name": result.scorerName,
"scorer.score": result.score,
"scorer.normalizedScore": result.normalizedScore,
"scorer.passed": result.passed,
"scorer.threshold": result.threshold,
"scorer.computeTime": result.computeTime,
...(result.confidence !== undefined && {
"scorer.confidence": result.confidence,
}),
...(result.error && { "scorer.error": result.error }),
};
}
/**
* Create span attributes from pipeline result
*/
export function pipelineToSpanAttributes(result) {
return {
"pipeline.name": result.pipelineConfig.name ?? "unnamed",
"pipeline.overallScore": result.overallScore,
"pipeline.passed": result.passed,
"pipeline.aggregationMethod": result.aggregationMethod,
"pipeline.scorerCount": result.scores.length,
"pipeline.totalComputeTime": result.totalComputeTime,
"pipeline.errorCount": result.errors.length,
"pipeline.skippedCount": result.skippedScorers.length,
...(result.correlationId && {
"pipeline.correlationId": result.correlationId,
}),
};
}