Integrate any custom LLM with Helicone using the TypeScript Manual Logger. Step-by-step guide for NodeJS implementation to connect your proprietary or open-source models.
Logging calls to custom models is supported via the Helicone NodeJS SDK.
1
To get started, install the `@helicone/helpers` package
Copy
Ask AI
npm install @helicone/helpers
2
Set `HELICONE_API_KEY` as an environment variable
Copy
Ask AI
export HELICONE_API_KEY=sk-<your-api-key>
You can also set the Helicone API Key in your code (See below)
3
Create a new HeliconeManualLogger instance
Copy
Ask AI
import { HeliconeManualLogger } from "@helicone/helpers";const heliconeLogger = new HeliconeManualLogger({apiKey: process.env.HELICONE_API_KEY, // Can be set as env variableheaders: {} // Additional headers to be sent with the request});
4
Log your request
Copy
Ask AI
const reqBody = { model: "text-embedding-ada-002", input: "The food was delicious and the waiter was very friendly.", encoding_format: "float"}const res = await heliconeLogger.logRequest( reqBody, async (resultRecorder) => { const r = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify(reqBody) }) const resBody = await r.json(); resultRecorder.appendResults(resBody); return resBody; // this will be returned by the logRequest function }, { // Additional headers to be sent with the request });
1
To get started, install the `@helicone/helpers` package
Copy
Ask AI
npm install @helicone/helpers
2
Set `HELICONE_API_KEY` as an environment variable
Copy
Ask AI
export HELICONE_API_KEY=sk-<your-api-key>
You can also set the Helicone API Key in your code (See below)
3
Create a new HeliconeManualLogger instance
Copy
Ask AI
import { HeliconeManualLogger } from "@helicone/helpers";const heliconeLogger = new HeliconeManualLogger({apiKey: process.env.HELICONE_API_KEY, // Can be set as env variableheaders: {} // Additional headers to be sent with the request});
4
Log your request
Copy
Ask AI
const reqBody = { model: "text-embedding-ada-002", input: "The food was delicious and the waiter was very friendly.", encoding_format: "float"}const res = await heliconeLogger.logRequest( reqBody, async (resultRecorder) => { const r = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, body: JSON.stringify(reqBody) }) const resBody = await r.json(); resultRecorder.appendResults(resBody); return resBody; // this will be returned by the logRequest function }, { // Additional headers to be sent with the request });
1
Install dependencies
Copy
Ask AI
npm install @helicone/helpers openai
2
Set up the logger and OpenAI client
Copy
Ask AI
import { HeliconeManualLogger } from "@helicone/helpers";import OpenAI from "openai";// Initialize the Helicone loggerconst helicone = new HeliconeManualLogger({ apiKey: process.env.HELICONE_API_KEY!,});// Initialize the OpenAI clientconst openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!,});
3
Using logSingleRequest for non-streaming responses
Copy
Ask AI
// Define your requestconst requestBody = { model: "gpt-4", messages: [ { role: "user", content: "Explain quantum computing in simple terms" }, ],};// Make the API callconst response = await openai.chat.completions.create(requestBody);// Log the request and response to Heliconeawait helicone.logSingleRequest(requestBody, JSON.stringify(response), { additionalHeaders: { "Helicone-User-Id": "user-123" }, // Optional additional headers});console.log(response.choices[0].message.content);
4
Using logSingleStream for streaming responses
Copy
Ask AI
const streamingRequestBody = { model: "gpt-4", messages: [{ role: "user", content: "Write a short story about AI" }], stream: true,};const streamingResponse = await openai.chat.completions.create( streamingRequestBody);const [streamForUser, streamForLogging] = stream.tee();helicone.logSingleStream(streamingRequestBody, streamForLogging, { "Helicone-User-Id": "user-123",});
1
Install dependencies
Copy
Ask AI
npm install @helicone/helpers together-ai next
2
Create an API route handler with Together AI
Copy
Ask AI
// app/api/chat/route.tsimport { HeliconeManualLogger } from "@helicone/helpers";import { after } from "next/server";import Together from "together-ai";export async function POST(request: Request) {const { question } = await request.json(); const together = new Together(); const helicone = new HeliconeManualLogger({ apiKey: process.env.HELICONE_API_KEY!, }); const nonStreamingBody = { model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", messages: [{ role: "user", content: question }], stream: false, } as Together.Chat.CompletionCreateParamsNonStreaming & { stream: false }; const completion = await together.chat.completions.create(nonStreamingBody); after( helicone.logSingleRequest(nonStreamingBody, JSON.stringify(completion), { additionalHeaders: { "Helicone-User-Id": "123" }, }), ); const body = { model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", messages: [{ role: "user", content: question }], stream: true, } as Together.Chat.CompletionCreateParamsStreaming & { stream: true }; const response = await together.chat.completions.create(body); const [stream1, stream2] = response.tee(); after( helicone.logSingleStream(body, stream2.toReadableStream(), { "Helicone-User-Id": "123", }), ); return new Response(stream1.toReadableStream());}
3
Important note about the `after` function
The after function allows you to perform operations after the response has been sent to the client. This is crucial for logging operations as it ensures they don’t delay the response to the user.
When using this approach:
Logging happens asynchronously after the response is sent
The user experience isn’t affected by logging latency
You still capture all the necessary data for observability
This is especially important for streaming responses where any delay would be noticeable to the user.
The HeliconeLogBuilder provides a simplified way to handle streaming LLM responses with better error handling and async support. It’s created using the logBuilder method of HeliconeManualLogger.
request: HeliconeLogRequest - The request object to log
Copy
Ask AI
type HeliconeLogRequest = ILogRequest | HeliconeCustomEventRequest; // ILogRequest is the type for the request object for custom model logging// The name and structure of the prompt field depends on the model you are using.// Eg: for chat models it is named "messages", for embeddings models it is named "input".// Hence, the only enforced type is `model`, you need still add the respective prompt property for your model.// You may also add more properties (eg: temperature, stop reason etc)type ILogRequest = { model: string; [key: string]: any;};
operation: (resultRecorder: HeliconeResultRecorder) => Promise<T> - The operation to be executed and logged
The HeliconeManualLogger class provides several methods for logging different types of requests and responses. Here’s a comprehensive overview of each method:
additionalHeaders: Optional additional headers to include with the log request
Example:
Copy
Ask AI
// Create a log builderconst heliconeLogBuilder = helicone.logBuilder(requestBody, { "Helicone-User-Id": userId,});try { // Make the LLM API call const response = await llmProvider.createChatCompletion({ stream: true, ...requestBody, }); // Convert the API response to a readable stream and return it return new Response(heliconeLogBuilder.toReadableStream(response));} catch (error) { // Record any errors that occur heliconeLogBuilder.setError(error); throw error;} finally { // Send the log (can be used with Vercel's "after" function) await heliconeLogBuilder.sendLog();}
Helicone provides an asynchronous stream parser for efficient handling of streamed responses. This is particularly useful when working with custom integrations that support streaming.
Here’s an example of how to use the async stream parser with a custom integration:
Copy
Ask AI
import { HeliconeManualLogger } from "@helicone/helpers";// Initialize the Helicone loggerconst heliconeLogger = new HeliconeManualLogger({ apiKey: process.env.HELICONE_API_KEY!, headers: {}, // You can add custom headers here});// Your custom model API call that returns a streamconst response = await customModelAPI.generateStream(prompt);// If your API supports splitting the streamconst [stream1, stream2] = response.tee();// Log the stream to Helicone using the async stream parserheliconeLogger.logStream(requestBody, async (resultRecorder) => { resultRecorder.attachStream(stream1);});// Process the stream for your applicationfor await (const chunk of stream2) { console.log(chunk);}
The async stream parser offers several benefits:
Processes stream chunks asynchronously for better performance
Reduces latency when handling large streamed responses
Provides more reliable token counting for streamed content
When building applications with Next.js App Router on Vercel, you can use the after function to log streaming responses without blocking the client response:
Copy
Ask AI
import { HeliconeManualLogger } from "@helicone/helpers";import { after } from "next/server";import Together from "together-ai";export async function POST(request: Request) { const { prompt } = await request.json(); const together = new Together({ apiKey: process.env.TOGETHER_API_KEY }); const helicone = new HeliconeManualLogger({ apiKey: process.env.HELICONE_API_KEY!, }); // Example with non-streaming response const nonStreamingBody = { model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", messages: [{ role: "user", content: prompt }], stream: false, }; const completion = await together.chat.completions.create(nonStreamingBody); // Log non-streaming response after sending the response to the client after( helicone.logSingleRequest(nonStreamingBody, JSON.stringify(completion)) ); // Example with streaming response const streamingBody = { model: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", messages: [{ role: "user", content: prompt }], stream: true, }; const response = await together.chat.completions.create(streamingBody); const [stream1, stream2] = response.tee(); // Log streaming response after sending the response to the client after(helicone.logSingleStream(streamingBody, stream2.toReadableStream())); return new Response(stream1.toReadableStream());}
For a comprehensive guide on using the Manual Logger with streaming functionality, check out our Manual Logger with Streaming cookbook.