1+ import {
2+ Instrumentation ,
3+ registerInstrumentations ,
4+ } from "@opentelemetry/instrumentation" ;
5+ import { Resource } from "@opentelemetry/resources" ;
16import { NodeTracerProvider , Tracer } from "@opentelemetry/sdk-trace-node" ;
27import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic" ;
38import { CohereInstrumentation } from "@traceloop/instrumentation-cohere" ;
49import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai" ;
5- import { Evaluators } from "api/resources/evaluators/client/Client" ;
610
711import { HumanloopClient as BaseHumanloopClient } from "./Client" ;
812import { ChatMessage } from "./api" ;
913import { Evaluations as BaseEvaluations } from "./api/resources/evaluations/client/Client" ;
14+ import { Evaluators } from "./api/resources/evaluators/client/Client" ;
1015import { Flows } from "./api/resources/flows/client/Client" ;
1116import { Prompts } from "./api/resources/prompts/client/Client" ;
1217import { Tools } from "./api/resources/tools/client/Client" ;
1318import { ToolKernelRequest } from "./api/types/ToolKernelRequest" ;
1419import { flowUtilityFactory } from "./decorators/flow" ;
1520import { promptDecoratorFactory } from "./decorators/prompt" ;
1621import { toolUtilityFactory } from "./decorators/tool" ;
22+ import { HumanloopEnvironment } from "./environments" ;
1723import { HumanloopRuntimeError } from "./error" ;
1824import { runEval } from "./evals/run" ;
1925import {
@@ -26,6 +32,7 @@ import {
2632import { HumanloopSpanExporter } from "./otel/exporter" ;
2733import { HumanloopSpanProcessor } from "./otel/processor" ;
2834import { overloadCall , overloadLog } from "./overload" ;
35+ import { SDK_VERSION } from "./version" ;
2936
3037const RED = "\x1b[91m" ;
3138const RESET = "\x1b[0m" ;
@@ -125,25 +132,123 @@ class ExtendedEvaluations extends BaseEvaluations {
125132 }
126133}
127134
135+ class HumanloopTracerSingleton {
136+ private static instance : HumanloopTracerSingleton ;
137+ private readonly tracerProvider : NodeTracerProvider ;
138+ public readonly tracer : Tracer ;
139+
140+ private constructor ( config : {
141+ hlClientApiKey : string ;
142+ hlClientBaseUrl : string ;
143+ instrumentProviders ?: {
144+ OpenAI ?: any ;
145+ Anthropic ?: any ;
146+ CohereAI ?: any ;
147+ } ;
148+ } ) {
149+ this . tracerProvider = new NodeTracerProvider ( {
150+ resource : new Resource ( {
151+ attributes : {
152+ // @ts -ignore
153+ "service.name" : "humanloop-typescript-sdk" ,
154+ "service.version" : SDK_VERSION ,
155+ } ,
156+ } ) ,
157+ spanProcessors : [
158+ new HumanloopSpanProcessor (
159+ new HumanloopSpanExporter ( {
160+ hlClientHeaders : {
161+ "X-API-KEY" : config . hlClientApiKey ,
162+ "X-Fern-Language" : "Typescript" ,
163+ "X-Fern-SDK-Name" : "humanloop" ,
164+ "X-Fern-SDK-Version" : SDK_VERSION ,
165+ } ,
166+ hlClientBaseUrl : config . hlClientBaseUrl ,
167+ } ) ,
168+ ) ,
169+ ] ,
170+ } ) ;
171+ const instrumentations : Instrumentation [ ] = [ ] ;
172+ if ( config . instrumentProviders ?. OpenAI ) {
173+ const openaiInstrumentation = new OpenAIInstrumentation ( {
174+ enrichTokens : true ,
175+ } ) ;
176+ openaiInstrumentation . manuallyInstrument ( config . instrumentProviders . OpenAI ) ;
177+ openaiInstrumentation . setTracerProvider ( this . tracerProvider ) ;
178+ openaiInstrumentation . enable ( ) ;
179+ instrumentations . push ( openaiInstrumentation ) ;
180+ }
181+ if ( config . instrumentProviders ?. Anthropic ) {
182+ const anthropicInstrumentation = new AnthropicInstrumentation ( ) ;
183+ anthropicInstrumentation . manuallyInstrument (
184+ config . instrumentProviders . Anthropic ,
185+ ) ;
186+ anthropicInstrumentation . setTracerProvider ( this . tracerProvider ) ;
187+ anthropicInstrumentation . enable ( ) ;
188+ instrumentations . push ( anthropicInstrumentation ) ;
189+ }
190+ if ( config . instrumentProviders ?. CohereAI ) {
191+ const cohereInstrumentation = new CohereInstrumentation ( ) ;
192+ cohereInstrumentation . manuallyInstrument (
193+ config . instrumentProviders . CohereAI ,
194+ ) ;
195+ cohereInstrumentation . setTracerProvider ( this . tracerProvider ) ;
196+ cohereInstrumentation . enable ( ) ;
197+ instrumentations . push ( cohereInstrumentation ) ;
198+ }
199+
200+ this . tracerProvider . register ( ) ;
201+
202+ registerInstrumentations ( {
203+ tracerProvider : this . tracerProvider ,
204+ instrumentations,
205+ } ) ;
206+
207+ this . tracer = this . tracerProvider . getTracer ( "humanloop.sdk" ) ;
208+ }
209+
210+ public static getInstance ( config : {
211+ hlClientApiKey : string ;
212+ hlClientBaseUrl : string ;
213+ instrumentProviders ?: {
214+ OpenAI ?: any ;
215+ Anthropic ?: any ;
216+ CohereAI ?: any ;
217+ } ;
218+ } ) : HumanloopTracerSingleton {
219+ if ( ! HumanloopTracerSingleton . instance ) {
220+ HumanloopTracerSingleton . instance = new HumanloopTracerSingleton ( config ) ;
221+ }
222+ return HumanloopTracerSingleton . instance ;
223+ }
224+ }
225+
128226export class HumanloopClient extends BaseHumanloopClient {
129227 protected readonly _evaluations : ExtendedEvaluations ;
130228 protected readonly _prompts_overloaded : Prompts ;
131229 protected readonly _flows_overloaded : Flows ;
132230 protected readonly _tools_overloaded : Tools ;
133231 protected readonly _evaluators_overloaded : Evaluators ;
134-
135- protected readonly OpenAI ?: any ;
136- protected readonly Anthropic ?: any ;
137- protected readonly CohereAI ?: any ;
138-
139- protected readonly opentelemetryTracerProvider : NodeTracerProvider ;
140- protected readonly opentelemetryTracer : Tracer ;
232+ protected readonly instrumentProviders : {
233+ OpenAI ?: any ;
234+ Anthropic ?: any ;
235+ CohereAI ?: any ;
236+ } ;
237+
238+ protected get opentelemetryTracer ( ) : Tracer {
239+ return HumanloopTracerSingleton . getInstance ( {
240+ hlClientApiKey : this . options ( ) . apiKey ! . toString ( ) ,
241+ hlClientBaseUrl :
242+ this . options ( ) . baseUrl ! . toString ( ) || HumanloopEnvironment . Default ,
243+ instrumentProviders : this . instrumentProviders ,
244+ } ) . tracer ;
245+ }
141246
142247 /**
143248 * Constructs a new instance of the Humanloop client.
144249 *
145250 * @param _options - The base options for the Humanloop client.
146- * @param providerModules - LLM provider modules to instrument. Allows prompt decorator to spy on LLM provider calls and log them to Humanloop.
251+ * @param _options.instrumentProviders - LLM provider modules to instrument. Allows the prompt decorator to spy on provider calls and log them to Humanloop
147252 *
148253 * Pass LLM provider modules as such:
149254 *
@@ -152,27 +257,27 @@ export class HumanloopClient extends BaseHumanloopClient {
152257 * import { Anthropic } from "anthropic";
153258 * import { HumanloopClient } from "humanloop";
154259 *
155- * const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI, Anthropic });
260+ * const humanloop = new HumanloopClient({
261+ * apiKey: process.env.HUMANLOOP_KEY,
262+ * instrumentProviders: { OpenAI, Anthropic },
263+ * });
156264 *
157265 * const openai = new OpenAI({apiKey: process.env.OPENAI_KEY});
158266 * const anthropic = new Anthropic({apiKey: process.env.ANTHROPIC_KEY});
159267 * ```
160268 */
161269 constructor (
162- _options : BaseHumanloopClient . Options ,
163- providers ?: {
164- OpenAI ?: any ;
165- Anthropic ?: any ;
166- CohereAI ?: any ;
270+ _options : BaseHumanloopClient . Options & {
271+ instrumentProviders ?: {
272+ OpenAI ?: any ;
273+ Anthropic ?: any ;
274+ CohereAI ?: any ;
275+ } ;
167276 } ,
168277 ) {
169278 super ( _options ) ;
170279
171- const { OpenAI, Anthropic, CohereAI } = providers ?? { } ;
172-
173- this . OpenAI = OpenAI ;
174- this . Anthropic = Anthropic ;
175- this . CohereAI = CohereAI ;
280+ this . instrumentProviders = _options . instrumentProviders || { } ;
176281
177282 this . _prompts_overloaded = overloadLog ( super . prompts ) ;
178283 this . _prompts_overloaded = overloadCall ( this . _prompts_overloaded ) ;
@@ -185,60 +290,31 @@ export class HumanloopClient extends BaseHumanloopClient {
185290
186291 this . _evaluations = new ExtendedEvaluations ( _options , this ) ;
187292
188- this . opentelemetryTracerProvider = new NodeTracerProvider ( {
189- spanProcessors : [
190- new HumanloopSpanProcessor ( new HumanloopSpanExporter ( this ) ) ,
191- ] ,
293+ // Initialize the tracer singleton
294+ HumanloopTracerSingleton . getInstance ( {
295+ hlClientApiKey : this . options ( ) . apiKey ! . toString ( ) ,
296+ hlClientBaseUrl :
297+ this . options ( ) . baseUrl ! . toString ( ) || HumanloopEnvironment . Default ,
298+ instrumentProviders : this . instrumentProviders ,
192299 } ) ;
193-
194- if ( OpenAI ) {
195- const instrumentor = new OpenAIInstrumentation ( {
196- enrichTokens : true ,
197- } ) ;
198- instrumentor . manuallyInstrument ( OpenAI ) ;
199- instrumentor . setTracerProvider ( this . opentelemetryTracerProvider ) ;
200- instrumentor . enable ( ) ;
201- }
202-
203- if ( Anthropic ) {
204- const instrumentor = new AnthropicInstrumentation ( ) ;
205- instrumentor . manuallyInstrument ( Anthropic ) ;
206- instrumentor . setTracerProvider ( this . opentelemetryTracerProvider ) ;
207- instrumentor . enable ( ) ;
208- }
209-
210- if ( CohereAI ) {
211- const instrumentor = new CohereInstrumentation ( ) ;
212- instrumentor . manuallyInstrument ( CohereAI ) ;
213- instrumentor . setTracerProvider ( this . opentelemetryTracerProvider ) ;
214- instrumentor . enable ( ) ;
215- }
216-
217- this . opentelemetryTracerProvider . register ( ) ;
218-
219- this . opentelemetryTracer =
220- this . opentelemetryTracerProvider . getTracer ( "humanloop.sdk" ) ;
221300 }
222301
223302 public options ( ) : BaseHumanloopClient . Options {
224303 return this . _options ;
225304 }
226305
227306 // Check if user has passed the LLM provider instrumentors
228- private assertProviders ( func : Function ) {
229- const noProviderInstrumented = [
230- this . OpenAI ,
231- this . Anthropic ,
232- this . CohereAI ,
233- ] . every ( ( p ) => ! p ) ;
234- if ( noProviderInstrumented ) {
307+ private assertProviders ( ) {
308+ const userDidNotPassProviders = Object . values ( this . instrumentProviders ) . every (
309+ ( provider ) => ! provider ,
310+ ) ;
311+ if ( userDidNotPassProviders ) {
235312 throw new HumanloopRuntimeError (
236313 `${ RED } To use the @prompt decorator, pass your LLM client library into the Humanloop client constructor. For example:\n\n
237314import { OpenAI } from "openai";
238315import { HumanloopClient } from "humanloop";
239316
240317const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI });
241- // Create the the OpenAI client after the client is initialized
242318const openai = new OpenAI();
243319${ RESET } `,
244320 ) ;
@@ -251,9 +327,13 @@ ${RESET}`,
251327 *
252328 * ```typescript
253329 * import { OpenAI } from "openai";
330+ * import { Anthropic } from "anthropic";
254331 * import { HumanloopClient } from "humanloop";
255332 *
256- * const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI });
333+ * const humanloop = new HumanloopClient({
334+ * apiKey: process.env.HUMANLOOP_KEY,
335+ * instrumentProviders: { OpenAI, Anthropic },
336+ * });
257337 * const openai = new OpenAI({apiKey: process.env.OPENAI_KEY});
258338 *
259339 * const callOpenaiWithHumanloop = humanloop.prompt({
@@ -350,7 +430,7 @@ ${RESET}`,
350430 : ( ) => O extends Promise < infer R >
351431 ? Promise < R | undefined >
352432 : Promise < O | undefined > {
353- this . assertProviders ( args . callable ) ;
433+ this . assertProviders ( ) ;
354434 // @ts -ignore
355435 return promptDecoratorFactory ( args . path , args . callable ) ;
356436 }
0 commit comments