@helicone/helpers
Version:
A Node.js wrapper for some of Helicone's common functionalities
265 lines • 9.61 kB
JavaScript
;
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