UNPKG

@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

86 lines (85 loc) • 3.32 kB
import { MAX_BYTES_PER_REQUEST, MAX_SPANS_PER_REQUEST, OTLP_STATUS_CODES, } from "../constants/langfuse.js"; import { SpanStatus } from "../types/index.js"; import { protectAttributes } from "./langfuseMasking.js"; const SPAN_KIND_INTERNAL = 1; function toNanos(iso) { return (BigInt(Date.parse(iso)) * 1000000n).toString(); } function toOtlpValue(value) { if (typeof value === "boolean") { return { boolValue: value }; } if (typeof value === "number") { return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value }; } if (Array.isArray(value)) { return { arrayValue: { values: value.map(toOtlpValue) } }; } return { stringValue: String(value) }; } /** * @description Encodes attributes as OTLP/JSON key-values, dropping `undefined` and `null` entries. * @param attributes {SpanAttributes} - Attribute record to encode. * @returns {OtlpKeyValue[]} One typed key-value per kept attribute. */ export function toOtlpAttributes(attributes) { return Object.entries(attributes) .filter(([, value]) => value !== undefined && value !== null) .map(([key, value]) => ({ key, value: toOtlpValue(value) })); } /** * @description Encodes one span as OTLP/JSON, applying the exporter's attribute policy to its attributes. * @param span {SpanData} - Span to encode. * @param policy {LangfuseAttributePolicy} - Environment, release, redaction and mask settings. * @returns {OtlpSpan} The span in the shape Langfuse's ingestion endpoint reads. */ export function toOtlpSpan(span, policy) { const failed = span.status === SpanStatus.ERROR; return { traceId: span.traceId, spanId: span.spanId, ...(span.parentSpanId && { parentSpanId: span.parentSpanId }), name: span.name, kind: SPAN_KIND_INTERNAL, startTimeUnixNano: toNanos(span.startTime), endTimeUnixNano: toNanos(span.endTime ?? span.startTime), attributes: toOtlpAttributes(protectAttributes(span.attributes, policy)), events: span.events.map((event) => ({ name: event.name, timeUnixNano: toNanos(event.timestamp), attributes: toOtlpAttributes(event.attributes ?? {}), })), status: { code: OTLP_STATUS_CODES[span.status], ...(failed && span.statusMessage && { message: span.statusMessage }), }, }; } /** * @description Splits encoded spans into request-sized batches, bounded by span count and serialized bytes. * @param spans {OtlpSpan[]} - Encoded spans, in export order. * @returns {OtlpSpan[][]} Batches that each fit one export request; a single oversized span still gets its own batch. */ export function chunkOtlpSpans(spans) { const chunks = []; let current = []; let bytes = 0; for (const span of spans) { const size = Buffer.byteLength(JSON.stringify(span)); if (current.length > 0 && (current.length >= MAX_SPANS_PER_REQUEST || bytes + size > MAX_BYTES_PER_REQUEST)) { chunks.push(current); current = []; bytes = 0; } current.push(span); bytes += size; } if (current.length > 0) { chunks.push(current); } return chunks; }