@posthog/ai 5.0.1 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,14 +2,18 @@ import OpenAIOrignal, { AzureOpenAI } from 'openai'
2
2
  import { PostHog } from 'posthog-node'
3
3
  import { v4 as uuidv4 } from 'uuid'
4
4
  import { formatResponseOpenAI, MonitoringParams, sendEventToPosthog } from '../utils'
5
+ import type { APIPromise } from 'openai'
6
+ import type { Stream } from 'openai/streaming'
7
+ import type { ParsedResponse } from 'openai/resources/responses/responses'
5
8
 
6
9
  type ChatCompletion = OpenAIOrignal.ChatCompletion
7
10
  type ChatCompletionChunk = OpenAIOrignal.ChatCompletionChunk
8
11
  type ChatCompletionCreateParamsBase = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParams
9
12
  type ChatCompletionCreateParamsNonStreaming = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParamsNonStreaming
10
13
  type ChatCompletionCreateParamsStreaming = OpenAIOrignal.Chat.Completions.ChatCompletionCreateParamsStreaming
11
- import type { APIPromise, RequestOptions } from 'openai/core'
12
- import type { Stream } from 'openai/streaming'
14
+ type ResponsesCreateParamsBase = OpenAIOrignal.Responses.ResponseCreateParams
15
+ type ResponsesCreateParamsNonStreaming = OpenAIOrignal.Responses.ResponseCreateParamsNonStreaming
16
+ type ResponsesCreateParamsStreaming = OpenAIOrignal.Responses.ResponseCreateParamsStreaming
13
17
 
14
18
  interface MonitoringOpenAIConfig {
15
19
  apiKey: string
@@ -17,6 +21,8 @@ interface MonitoringOpenAIConfig {
17
21
  baseURL?: string
18
22
  }
19
23
 
24
+ type RequestOptions = Record<string, any>
25
+
20
26
  export class PostHogAzureOpenAI extends AzureOpenAI {
21
27
  private readonly phClient: PostHog
22
28
  public chat: WrappedChat
@@ -82,32 +88,30 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
82
88
 
83
89
  const traceId = posthogTraceId ?? uuidv4()
84
90
  const startTime = Date.now()
91
+
85
92
  const parentPromise = super.create(openAIParams, options)
86
93
 
87
94
  if (openAIParams.stream) {
88
95
  return parentPromise.then((value) => {
89
- let accumulatedContent = ''
90
- let usage: {
91
- inputTokens: number
92
- outputTokens: number
93
- reasoningTokens?: number
94
- cacheReadInputTokens?: number
95
- } = {
96
- inputTokens: 0,
97
- outputTokens: 0,
98
- }
99
- let model = openAIParams.model
100
96
  if ('tee' in value) {
101
97
  const [stream1, stream2] = value.tee()
102
98
  ;(async () => {
103
99
  try {
100
+ let accumulatedContent = ''
101
+ let usage: {
102
+ inputTokens?: number
103
+ outputTokens?: number
104
+ reasoningTokens?: number
105
+ cacheReadInputTokens?: number
106
+ } = {
107
+ inputTokens: 0,
108
+ outputTokens: 0,
109
+ }
110
+
104
111
  for await (const chunk of stream1) {
105
112
  const delta = chunk?.choices?.[0]?.delta?.content ?? ''
106
113
  accumulatedContent += delta
107
114
  if (chunk.usage) {
108
- if (chunk.model != model) {
109
- model = chunk.model
110
- }
111
115
  usage = {
112
116
  inputTokens: chunk.usage.prompt_tokens ?? 0,
113
117
  outputTokens: chunk.usage.completion_tokens ?? 0,
@@ -116,12 +120,13 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
116
120
  }
117
121
  }
118
122
  }
123
+
119
124
  const latency = (Date.now() - startTime) / 1000
120
125
  await sendEventToPosthog({
121
126
  client: this.phClient,
122
- distinctId: posthogDistinctId ?? traceId,
127
+ distinctId: posthogDistinctId,
123
128
  traceId,
124
- model,
129
+ model: openAIParams.model,
125
130
  provider: 'azure',
126
131
  input: openAIParams.messages,
127
132
  output: [{ content: accumulatedContent, role: 'assistant' }],
@@ -133,23 +138,19 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
133
138
  captureImmediate: posthogCaptureImmediate,
134
139
  })
135
140
  } catch (error: any) {
136
- // error handling
137
141
  await sendEventToPosthog({
138
142
  client: this.phClient,
139
- distinctId: posthogDistinctId ?? traceId,
143
+ distinctId: posthogDistinctId,
140
144
  traceId,
141
- model,
145
+ model: openAIParams.model,
142
146
  provider: 'azure',
143
147
  input: openAIParams.messages,
144
- output: JSON.stringify(error),
148
+ output: [],
145
149
  latency: 0,
146
150
  baseURL: (this as any).baseURL ?? '',
147
151
  params: body,
148
152
  httpStatus: error?.status ? error.status : 500,
149
- usage: {
150
- inputTokens: 0,
151
- outputTokens: 0,
152
- },
153
+ usage: { inputTokens: 0, outputTokens: 0 },
153
154
  isError: true,
154
155
  error: JSON.stringify(error),
155
156
  captureImmediate: posthogCaptureImmediate,
@@ -167,15 +168,11 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
167
168
  async (result) => {
168
169
  if ('choices' in result) {
169
170
  const latency = (Date.now() - startTime) / 1000
170
- let model = openAIParams.model
171
- if (result.model != model) {
172
- model = result.model
173
- }
174
171
  await sendEventToPosthog({
175
172
  client: this.phClient,
176
- distinctId: posthogDistinctId ?? traceId,
173
+ distinctId: posthogDistinctId,
177
174
  traceId,
178
- model,
175
+ model: openAIParams.model,
179
176
  provider: 'azure',
180
177
  input: openAIParams.messages,
181
178
  output: formatResponseOpenAI(result),
@@ -197,7 +194,7 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
197
194
  async (error: any) => {
198
195
  await sendEventToPosthog({
199
196
  client: this.phClient,
200
- distinctId: posthogDistinctId ?? traceId,
197
+ distinctId: posthogDistinctId,
201
198
  traceId,
202
199
  model: openAIParams.model,
203
200
  provider: 'azure',
@@ -224,4 +221,261 @@ export class WrappedCompletions extends AzureOpenAI.Chat.Completions {
224
221
  }
225
222
  }
226
223
 
224
+ export class WrappedResponses extends AzureOpenAI.Responses {
225
+ private readonly phClient: PostHog
226
+
227
+ constructor(client: AzureOpenAI, phClient: PostHog) {
228
+ super(client)
229
+ this.phClient = phClient
230
+ }
231
+
232
+ // --- Overload #1: Non-streaming
233
+ public create(
234
+ body: ResponsesCreateParamsNonStreaming & MonitoringParams,
235
+ options?: RequestOptions
236
+ ): APIPromise<OpenAIOrignal.Responses.Response>
237
+
238
+ // --- Overload #2: Streaming
239
+ public create(
240
+ body: ResponsesCreateParamsStreaming & MonitoringParams,
241
+ options?: RequestOptions
242
+ ): APIPromise<Stream<OpenAIOrignal.Responses.ResponseStreamEvent>>
243
+
244
+ // --- Overload #3: Generic base
245
+ public create(
246
+ body: ResponsesCreateParamsBase & MonitoringParams,
247
+ options?: RequestOptions
248
+ ): APIPromise<OpenAIOrignal.Responses.Response | Stream<OpenAIOrignal.Responses.ResponseStreamEvent>>
249
+
250
+ // --- Implementation Signature
251
+ public create(
252
+ body: ResponsesCreateParamsBase & MonitoringParams,
253
+ options?: RequestOptions
254
+ ): APIPromise<OpenAIOrignal.Responses.Response | Stream<OpenAIOrignal.Responses.ResponseStreamEvent>> {
255
+ const {
256
+ posthogDistinctId,
257
+ posthogTraceId,
258
+ posthogProperties,
259
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
260
+ posthogPrivacyMode = false,
261
+ posthogGroups,
262
+ posthogCaptureImmediate,
263
+ ...openAIParams
264
+ } = body
265
+
266
+ const traceId = posthogTraceId ?? uuidv4()
267
+ const startTime = Date.now()
268
+
269
+ const parentPromise = super.create(openAIParams, options)
270
+
271
+ if (openAIParams.stream) {
272
+ return parentPromise.then((value) => {
273
+ if ('tee' in value && typeof (value as any).tee === 'function') {
274
+ const [stream1, stream2] = (value as any).tee()
275
+ ;(async () => {
276
+ try {
277
+ let finalContent: any[] = []
278
+ let usage: {
279
+ inputTokens?: number
280
+ outputTokens?: number
281
+ reasoningTokens?: number
282
+ cacheReadInputTokens?: number
283
+ } = {
284
+ inputTokens: 0,
285
+ outputTokens: 0,
286
+ }
287
+
288
+ for await (const chunk of stream1) {
289
+ if (
290
+ chunk.type === 'response.completed' &&
291
+ 'response' in chunk &&
292
+ chunk.response?.output &&
293
+ chunk.response.output.length > 0
294
+ ) {
295
+ finalContent = chunk.response.output
296
+ }
297
+ if ('usage' in chunk && chunk.usage) {
298
+ usage = {
299
+ inputTokens: chunk.usage.input_tokens ?? 0,
300
+ outputTokens: chunk.usage.output_tokens ?? 0,
301
+ reasoningTokens: chunk.usage.output_tokens_details?.reasoning_tokens ?? 0,
302
+ cacheReadInputTokens: chunk.usage.input_tokens_details?.cached_tokens ?? 0,
303
+ }
304
+ }
305
+ }
306
+
307
+ const latency = (Date.now() - startTime) / 1000
308
+ await sendEventToPosthog({
309
+ client: this.phClient,
310
+ distinctId: posthogDistinctId,
311
+ traceId,
312
+ model: openAIParams.model,
313
+ provider: 'azure',
314
+ input: openAIParams.input,
315
+ output: finalContent,
316
+ latency,
317
+ baseURL: (this as any).baseURL ?? '',
318
+ params: body,
319
+ httpStatus: 200,
320
+ usage,
321
+ captureImmediate: posthogCaptureImmediate,
322
+ })
323
+ } catch (error: any) {
324
+ await sendEventToPosthog({
325
+ client: this.phClient,
326
+ distinctId: posthogDistinctId,
327
+ traceId,
328
+ model: openAIParams.model,
329
+ provider: 'azure',
330
+ input: openAIParams.input,
331
+ output: [],
332
+ latency: 0,
333
+ baseURL: (this as any).baseURL ?? '',
334
+ params: body,
335
+ httpStatus: error?.status ? error.status : 500,
336
+ usage: { inputTokens: 0, outputTokens: 0 },
337
+ isError: true,
338
+ error: JSON.stringify(error),
339
+ captureImmediate: posthogCaptureImmediate,
340
+ })
341
+ }
342
+ })()
343
+
344
+ return stream2
345
+ }
346
+ return value
347
+ }) as APIPromise<Stream<OpenAIOrignal.Responses.ResponseStreamEvent>>
348
+ } else {
349
+ const wrappedPromise = parentPromise.then(
350
+ async (result) => {
351
+ if ('output' in result) {
352
+ const latency = (Date.now() - startTime) / 1000
353
+ await sendEventToPosthog({
354
+ client: this.phClient,
355
+ distinctId: posthogDistinctId,
356
+ traceId,
357
+ model: openAIParams.model,
358
+ provider: 'azure',
359
+ input: openAIParams.input,
360
+ output: result.output,
361
+ latency,
362
+ baseURL: (this as any).baseURL ?? '',
363
+ params: body,
364
+ httpStatus: 200,
365
+ usage: {
366
+ inputTokens: result.usage?.input_tokens ?? 0,
367
+ outputTokens: result.usage?.output_tokens ?? 0,
368
+ reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
369
+ cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
370
+ },
371
+ captureImmediate: posthogCaptureImmediate,
372
+ })
373
+ }
374
+ return result
375
+ },
376
+ async (error: any) => {
377
+ await sendEventToPosthog({
378
+ client: this.phClient,
379
+ distinctId: posthogDistinctId,
380
+ traceId,
381
+ model: openAIParams.model,
382
+ provider: 'azure',
383
+ input: openAIParams.input,
384
+ output: [],
385
+ latency: 0,
386
+ baseURL: (this as any).baseURL ?? '',
387
+ params: body,
388
+ httpStatus: error?.status ? error.status : 500,
389
+ usage: {
390
+ inputTokens: 0,
391
+ outputTokens: 0,
392
+ },
393
+ isError: true,
394
+ error: JSON.stringify(error),
395
+ captureImmediate: posthogCaptureImmediate,
396
+ })
397
+ throw error
398
+ }
399
+ ) as APIPromise<OpenAIOrignal.Responses.Response>
400
+
401
+ return wrappedPromise
402
+ }
403
+ }
404
+
405
+ public parse<Params extends OpenAIOrignal.Responses.ResponseCreateParams, ParsedT = any>(
406
+ body: Params & MonitoringParams,
407
+ options?: RequestOptions
408
+ ): APIPromise<ParsedResponse<ParsedT>> {
409
+ const {
410
+ posthogDistinctId,
411
+ posthogTraceId,
412
+ posthogProperties,
413
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
414
+ posthogPrivacyMode = false,
415
+ posthogGroups,
416
+ posthogCaptureImmediate,
417
+ ...openAIParams
418
+ } = body
419
+
420
+ const traceId = posthogTraceId ?? uuidv4()
421
+ const startTime = Date.now()
422
+
423
+ const parentPromise = super.parse(openAIParams, options)
424
+
425
+ const wrappedPromise = parentPromise.then(
426
+ async (result) => {
427
+ const latency = (Date.now() - startTime) / 1000
428
+ await sendEventToPosthog({
429
+ client: this.phClient,
430
+ distinctId: posthogDistinctId,
431
+ traceId,
432
+ model: openAIParams.model,
433
+ provider: 'azure',
434
+ input: openAIParams.input,
435
+ output: result.output,
436
+ latency,
437
+ baseURL: (this as any).baseURL ?? '',
438
+ params: body,
439
+ httpStatus: 200,
440
+ usage: {
441
+ inputTokens: result.usage?.input_tokens ?? 0,
442
+ outputTokens: result.usage?.output_tokens ?? 0,
443
+ reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
444
+ cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
445
+ },
446
+ captureImmediate: posthogCaptureImmediate,
447
+ })
448
+ return result
449
+ },
450
+ async (error: any) => {
451
+ await sendEventToPosthog({
452
+ client: this.phClient,
453
+ distinctId: posthogDistinctId,
454
+ traceId,
455
+ model: openAIParams.model,
456
+ provider: 'azure',
457
+ input: openAIParams.input,
458
+ output: [],
459
+ latency: 0,
460
+ baseURL: (this as any).baseURL ?? '',
461
+ params: body,
462
+ httpStatus: error?.status ? error.status : 500,
463
+ usage: {
464
+ inputTokens: 0,
465
+ outputTokens: 0,
466
+ },
467
+ isError: true,
468
+ error: JSON.stringify(error),
469
+ captureImmediate: posthogCaptureImmediate,
470
+ })
471
+ throw error
472
+ }
473
+ )
474
+
475
+ return wrappedPromise as APIPromise<ParsedResponse<ParsedT>>
476
+ }
477
+ }
478
+
227
479
  export default PostHogAzureOpenAI
480
+
481
+ export { PostHogAzureOpenAI as OpenAI }