UNPKG

@helicone/helpers

Version:

A Node.js wrapper for some of Helicone's common functionalities

265 lines 9.61 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HeliconeManualLogger = exports.HeliconeLogBuilder = void 0; const HeliconeLogBuilder_1 = require("./HeliconeLogBuilder"); Object.defineProperty(exports, "HeliconeLogBuilder", { enumerable: true, get: function () { return HeliconeLogBuilder_1.HeliconeLogBuilder; } }); class HeliconeManualLogger { constructor(opts) { this.LOGGING_ENDPOINT = "https://api.worker.helicone.ai/custom/v1/log"; this.apiKey = opts.apiKey; this.headers = opts.headers || {}; this.LOGGING_ENDPOINT = opts.loggingEndpoint || this.LOGGING_ENDPOINT; } /** * Creates a log builder for more flexible stream handling with error management * @param request - The request object to log * @param additionalHeaders - Additional headers to send with the request * @returns A HeliconeLogBuilder instance */ logBuilder(request, additionalHeaders) { return new HeliconeLogBuilder_1.HeliconeLogBuilder(this, request, additionalHeaders); } /** * Logs a custom request to Helicone * @param request - The request object to log * @param operation - The operation which will be executed and logged * @param additionalHeaders - Additional headers to send with the request * @returns The result of the `operation` function */ async logRequest(request, operation, additionalHeaders) { const startTime = Date.now(); const resultRecorder = new HeliconeResultRecorder(); try { const result = await operation(resultRecorder); const endTime = Date.now(); await this.sendLog(request, resultRecorder.getResults(), { startTime, endTime, additionalHeaders, status: 200, }); return result; } catch (error) { console.error("Error during operation:", error); throw error; } } /** * Logs a single stream to Helicone * @param request - The request object to log * @param stream - The ReadableStream to consume and log * @param additionalHeaders - Additional headers to send with the request * @returns A Promise that resolves when logging is complete */ async logSingleStream(request, stream, additionalHeaders) { const startTime = Date.now(); const resultRecorder = new HeliconeStreamResultRecorder(); resultRecorder.attachStream(stream); let firstChunkTimeUnix = null; const streamedData = []; const decoder = new TextDecoder(); for await (const chunk of stream) { if (!firstChunkTimeUnix) { firstChunkTimeUnix = Date.now(); } streamedData.push(decoder.decode(chunk)); } await this.sendLog(request, streamedData.join(""), { startTime, endTime: Date.now(), additionalHeaders, timeToFirstToken: firstChunkTimeUnix ? firstChunkTimeUnix - startTime : undefined, status: 200, }); } /** * Logs a single request with a response body to Helicone * @param request - The request object to log * @param body - The response body as a string * @param additionalHeaders - Additional headers to send with the request * @param latencyMs - The latency of the request in milliseconds * @returns A Promise that resolves when logging is complete * * @example * ```typescript * helicone.logSingleRequest(request, body, { additionalHeaders: { "Helicone-User-Id": userId }, latencyMs: 1000 }); * ``` */ async logSingleRequest(request, body, options) { const startTime = Date.now(); const endTime = options.latencyMs ? startTime + options.latencyMs : Date.now(); await this.sendLog(request, body, { startTime, endTime, additionalHeaders: options.additionalHeaders, status: 200, }); } /** * Logs a streaming operation to Helicone * @param request - The request object to log * @param operation - The operation which will be executed and logged, with access to a stream recorder * @param additionalHeaders - Additional headers to send with the request * @returns The result of the `operation` function * * @example * ```typescript * const response = await llmProvider.createChatCompletion({ stream: true, ... }); * const [stream1, stream2] = response.tee(); * * helicone.logStream( * requestBody, * async (resultRecorder) => { * resultRecorder.attachStream(stream2.toReadableStream()); * return stream1; * }, * { "Helicone-User-Id": userId } * ); * ``` */ async logStream(request, operation, additionalHeaders) { const startTime = Date.now(); const resultRecorder = new HeliconeStreamResultRecorder(); const result = await operation(resultRecorder); try { await resultRecorder.getStreamTexts().then(async (texts) => { const endTime = Date.now(); await this.sendLog(request, texts.join(""), { startTime, endTime, additionalHeaders, timeToFirstToken: resultRecorder.firstChunkTimeUnix ? resultRecorder.firstChunkTimeUnix - startTime : undefined, status: 200, }); }); return result; } catch (error) { console.error("Helicone error during stream logging:", error); throw error; } } async sendLog(request, response, options) { const { startTime, endTime, additionalHeaders, status = 200 } = options; const providerRequest = { url: "custom-model-nopath", json: { ...request, }, meta: {}, }; const isResponseString = typeof response === "string"; const providerResponse = { headers: this.headers, status: status, json: isResponseString ? {} : { ...response, _type: request._type, toolName: request.toolName, }, textBody: isResponseString ? response : undefined, }; const timing = { startTime: { seconds: Math.trunc(startTime / 1000), milliseconds: startTime % 1000, }, endTime: { seconds: Math.trunc(endTime / 1000), milliseconds: endTime % 1000, }, timeToFirstToken: options.timeToFirstToken, }; const fetchOptions = { method: "POST", headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", ...this.headers, ...additionalHeaders, }, body: JSON.stringify({ providerRequest, providerResponse, timing, }), }; try { const response = await fetch(this.LOGGING_ENDPOINT, fetchOptions); if (!response.ok) { console.error("Error making request to Helicone log endpoint:", response.statusText); } } catch (error) { console.error("Error making request to Helicone log endpoint:", error?.message, error); } } } exports.HeliconeManualLogger = HeliconeManualLogger; /** * Recorder for handling and processing streams in Helicone logging * Used to capture and process streaming responses from LLM providers */ class HeliconeStreamResultRecorder { constructor() { this.streams = []; this.firstChunkTimeUnix = null; } /** * Attaches a ReadableStream to be processed * @param stream - The ReadableStream to attach */ attachStream(stream) { this.streams.push(stream); } /** * Processes all attached streams and returns their contents as strings * @returns Promise resolving to an array of strings containing the content of each stream */ async getStreamTexts() { const decoder = new TextDecoder(); return Promise.all(this.streams.map(async (stream) => { if (!this.firstChunkTimeUnix) { this.firstChunkTimeUnix = Date.now(); } const streamedData = []; for await (const chunk of stream) { streamedData.push(decoder.decode(chunk)); } return streamedData.join(""); })); } } /** * Recorder for handling and storing results in Helicone logging * Used to capture non-streaming responses from operations */ class HeliconeResultRecorder { constructor() { this.results = {}; } /** * Appends data to the results object * @param data - The data to append to the results */ appendResults(data) { this.results = { ...this.results, ...data }; } /** * Gets the current results * @returns The current results object */ getResults() { return this.results; } } //# sourceMappingURL=HeliconeManualLogger.js.map