@posthog/ai 4.2.0 → 4.3.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.
- package/CHANGELOG.md +6 -1
- package/lib/anthropic/index.cjs.js +82 -76
- package/lib/anthropic/index.cjs.js.map +1 -1
- package/lib/anthropic/index.d.ts +1 -1
- package/lib/anthropic/index.esm.js +82 -76
- package/lib/anthropic/index.esm.js.map +1 -1
- package/lib/index.cjs.js +133 -112
- package/lib/index.cjs.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.esm.js +133 -112
- package/lib/index.esm.js.map +1 -1
- package/lib/langchain/index.cjs.js.map +1 -1
- package/lib/langchain/index.esm.js.map +1 -1
- package/lib/openai/index.cjs.js +82 -76
- package/lib/openai/index.cjs.js.map +1 -1
- package/lib/openai/index.d.ts +1 -1
- package/lib/openai/index.esm.js +82 -76
- package/lib/openai/index.esm.js.map +1 -1
- package/lib/posthog-ai/src/utils.d.ts +3 -3
- package/lib/posthog-ai/src/vercel/middleware.d.ts +2 -2
- package/lib/vercel/index.cjs.js +88 -82
- package/lib/vercel/index.cjs.js.map +1 -1
- package/lib/vercel/index.d.ts +1 -1
- package/lib/vercel/index.esm.js +88 -82
- package/lib/vercel/index.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/anthropic/index.ts +11 -6
- package/src/openai/azure.ts +11 -6
- package/src/openai/index.ts +11 -6
- package/src/utils.ts +61 -60
- package/src/vercel/middleware.ts +24 -16
- package/tests/openai.test.ts +15 -0
- package/tsconfig.json +1 -1
package/src/openai/index.ts
CHANGED
|
@@ -76,6 +76,7 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
76
76
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
77
77
|
posthogPrivacyMode = false,
|
|
78
78
|
posthogGroups,
|
|
79
|
+
posthogCaptureImmediate,
|
|
79
80
|
...openAIParams
|
|
80
81
|
} = body
|
|
81
82
|
|
|
@@ -115,7 +116,7 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
const latency = (Date.now() - startTime) / 1000
|
|
118
|
-
sendEventToPosthog({
|
|
119
|
+
await sendEventToPosthog({
|
|
119
120
|
client: this.phClient,
|
|
120
121
|
distinctId: posthogDistinctId ?? traceId,
|
|
121
122
|
traceId,
|
|
@@ -128,9 +129,10 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
128
129
|
params: body,
|
|
129
130
|
httpStatus: 200,
|
|
130
131
|
usage,
|
|
132
|
+
captureImmediate: posthogCaptureImmediate,
|
|
131
133
|
})
|
|
132
134
|
} catch (error: any) {
|
|
133
|
-
sendEventToPosthog({
|
|
135
|
+
await sendEventToPosthog({
|
|
134
136
|
client: this.phClient,
|
|
135
137
|
distinctId: posthogDistinctId ?? traceId,
|
|
136
138
|
traceId,
|
|
@@ -145,6 +147,7 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
145
147
|
usage: { inputTokens: 0, outputTokens: 0 },
|
|
146
148
|
isError: true,
|
|
147
149
|
error: JSON.stringify(error),
|
|
150
|
+
captureImmediate: posthogCaptureImmediate,
|
|
148
151
|
})
|
|
149
152
|
}
|
|
150
153
|
})()
|
|
@@ -156,10 +159,10 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
156
159
|
}) as APIPromise<Stream<ChatCompletionChunk>>
|
|
157
160
|
} else {
|
|
158
161
|
const wrappedPromise = parentPromise.then(
|
|
159
|
-
(result) => {
|
|
162
|
+
async (result) => {
|
|
160
163
|
if ('choices' in result) {
|
|
161
164
|
const latency = (Date.now() - startTime) / 1000
|
|
162
|
-
sendEventToPosthog({
|
|
165
|
+
await sendEventToPosthog({
|
|
163
166
|
client: this.phClient,
|
|
164
167
|
distinctId: posthogDistinctId ?? traceId,
|
|
165
168
|
traceId,
|
|
@@ -177,12 +180,13 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
177
180
|
reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
178
181
|
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
|
|
179
182
|
},
|
|
183
|
+
captureImmediate: posthogCaptureImmediate,
|
|
180
184
|
})
|
|
181
185
|
}
|
|
182
186
|
return result
|
|
183
187
|
},
|
|
184
|
-
(error: any) => {
|
|
185
|
-
sendEventToPosthog({
|
|
188
|
+
async (error: any) => {
|
|
189
|
+
await sendEventToPosthog({
|
|
186
190
|
client: this.phClient,
|
|
187
191
|
distinctId: posthogDistinctId ?? traceId,
|
|
188
192
|
traceId,
|
|
@@ -200,6 +204,7 @@ export class WrappedCompletions extends OpenAIOrignal.Chat.Completions {
|
|
|
200
204
|
},
|
|
201
205
|
isError: true,
|
|
202
206
|
error: JSON.stringify(error),
|
|
207
|
+
captureImmediate: posthogCaptureImmediate,
|
|
203
208
|
})
|
|
204
209
|
throw error
|
|
205
210
|
}
|
package/src/utils.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface MonitoringParams {
|
|
|
19
19
|
posthogModelOverride?: string
|
|
20
20
|
posthogProviderOverride?: string
|
|
21
21
|
posthogCostOverride?: CostOverride
|
|
22
|
-
|
|
22
|
+
posthogCaptureImmediate?: boolean
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface CostOverride {
|
|
@@ -149,7 +149,7 @@ export type SendEventToPosthogParams = {
|
|
|
149
149
|
isError?: boolean
|
|
150
150
|
error?: string
|
|
151
151
|
tools?: any
|
|
152
|
-
|
|
152
|
+
captureImmediate?: boolean
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function sanitizeValues(obj: any): any {
|
|
@@ -167,7 +167,7 @@ function sanitizeValues(obj: any): any {
|
|
|
167
167
|
return jsonSafe
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
export const sendEventToPosthog = ({
|
|
170
|
+
export const sendEventToPosthog = async ({
|
|
171
171
|
client,
|
|
172
172
|
distinctId,
|
|
173
173
|
traceId,
|
|
@@ -183,68 +183,69 @@ export const sendEventToPosthog = ({
|
|
|
183
183
|
isError = false,
|
|
184
184
|
error,
|
|
185
185
|
tools,
|
|
186
|
-
|
|
187
|
-
}: SendEventToPosthogParams): void => {
|
|
188
|
-
if (client.capture)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
186
|
+
captureImmediate = false,
|
|
187
|
+
}: SendEventToPosthogParams): Promise<void> => {
|
|
188
|
+
if (!client.capture) return Promise.resolve()
|
|
189
|
+
// sanitize input and output for UTF-8 validity
|
|
190
|
+
const safeInput = sanitizeValues(input)
|
|
191
|
+
const safeOutput = sanitizeValues(output)
|
|
192
|
+
const safeError = sanitizeValues(error)
|
|
193
|
+
|
|
194
|
+
let errorData = {}
|
|
195
|
+
if (isError) {
|
|
196
|
+
errorData = {
|
|
197
|
+
$ai_is_error: true,
|
|
198
|
+
$ai_error: safeError,
|
|
200
199
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
200
|
+
}
|
|
201
|
+
let costOverrideData = {}
|
|
202
|
+
if (params.posthogCostOverride) {
|
|
203
|
+
const inputCostUSD = (params.posthogCostOverride.inputCost ?? 0) * (usage.inputTokens ?? 0)
|
|
204
|
+
const outputCostUSD = (params.posthogCostOverride.outputCost ?? 0) * (usage.outputTokens ?? 0)
|
|
205
|
+
costOverrideData = {
|
|
206
|
+
$ai_input_cost_usd: inputCostUSD,
|
|
207
|
+
$ai_output_cost_usd: outputCostUSD,
|
|
208
|
+
$ai_total_cost_usd: inputCostUSD + outputCostUSD,
|
|
210
209
|
}
|
|
210
|
+
}
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
const additionalTokenValues = {
|
|
213
|
+
...(usage.reasoningTokens ? { $ai_reasoning_tokens: usage.reasoningTokens } : {}),
|
|
214
|
+
...(usage.cacheReadInputTokens ? { $ai_cache_read_input_tokens: usage.cacheReadInputTokens } : {}),
|
|
215
|
+
...(usage.cacheCreationInputTokens ? { $ai_cache_creation_input_tokens: usage.cacheCreationInputTokens } : {}),
|
|
216
|
+
}
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
218
|
+
const properties = {
|
|
219
|
+
$ai_provider: params.posthogProviderOverride ?? provider,
|
|
220
|
+
$ai_model: params.posthogModelOverride ?? model,
|
|
221
|
+
$ai_model_parameters: getModelParams(params),
|
|
222
|
+
$ai_input: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeInput),
|
|
223
|
+
$ai_output_choices: withPrivacyMode(client, params.posthogPrivacyMode ?? false, safeOutput),
|
|
224
|
+
$ai_http_status: httpStatus,
|
|
225
|
+
$ai_input_tokens: usage.inputTokens ?? 0,
|
|
226
|
+
$ai_output_tokens: usage.outputTokens ?? 0,
|
|
227
|
+
...additionalTokenValues,
|
|
228
|
+
$ai_latency: latency,
|
|
229
|
+
$ai_trace_id: traceId,
|
|
230
|
+
$ai_base_url: baseURL,
|
|
231
|
+
...params.posthogProperties,
|
|
232
|
+
...(distinctId ? {} : { $process_person_profile: false }),
|
|
233
|
+
...(tools ? { $ai_tools: tools } : {}),
|
|
234
|
+
...errorData,
|
|
235
|
+
...costOverrideData,
|
|
236
|
+
}
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
const event = {
|
|
239
|
+
distinctId: distinctId ?? traceId,
|
|
240
|
+
event: '$ai_generation',
|
|
241
|
+
properties,
|
|
242
|
+
groups: params.posthogGroups,
|
|
243
|
+
}
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
})
|
|
245
|
+
if (captureImmediate) {
|
|
246
|
+
// await capture promise to send single event in serverless environments
|
|
247
|
+
await client.captureImmediate(event)
|
|
248
|
+
} else {
|
|
249
|
+
client.capture(event)
|
|
249
250
|
}
|
|
250
251
|
}
|
package/src/vercel/middleware.ts
CHANGED
|
@@ -14,7 +14,7 @@ interface ClientOptions {
|
|
|
14
14
|
posthogModelOverride?: string
|
|
15
15
|
posthogProviderOverride?: string
|
|
16
16
|
posthogCostOverride?: CostOverride
|
|
17
|
-
|
|
17
|
+
posthogCaptureImmediate?: boolean
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
interface CreateInstrumentationMiddlewareOptions {
|
|
@@ -26,7 +26,7 @@ interface CreateInstrumentationMiddlewareOptions {
|
|
|
26
26
|
posthogModelOverride?: string
|
|
27
27
|
posthogProviderOverride?: string
|
|
28
28
|
posthogCostOverride?: CostOverride
|
|
29
|
-
|
|
29
|
+
posthogCaptureImmediate?: boolean
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
interface PostHogInput {
|
|
@@ -127,15 +127,23 @@ const mapVercelPrompt = (prompt: LanguageModelV1Prompt): PostHogInput[] => {
|
|
|
127
127
|
try {
|
|
128
128
|
// Trim the inputs array until its JSON size fits within MAX_OUTPUT_SIZE
|
|
129
129
|
let serialized = JSON.stringify(inputs)
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
let removedCount = 0
|
|
131
|
+
// We need to keep track of the initial size of the inputs array because we're going to be mutating it
|
|
132
|
+
let initialSize = inputs.length
|
|
133
|
+
for (let i = 0; i < initialSize && Buffer.byteLength(serialized, 'utf8') > MAX_OUTPUT_SIZE; i++) {
|
|
132
134
|
inputs.shift()
|
|
133
|
-
|
|
134
|
-
inputs.unshift({ role: 'assistant', content: '[removed message due to size limit]' })
|
|
135
|
+
removedCount++
|
|
135
136
|
serialized = JSON.stringify(inputs)
|
|
136
137
|
}
|
|
138
|
+
if (removedCount > 0) {
|
|
139
|
+
// Add one placeholder to indicate how many were removed
|
|
140
|
+
inputs.unshift({
|
|
141
|
+
role: 'posthog',
|
|
142
|
+
content: `[${removedCount} message${removedCount === 1 ? '' : 's'} removed due to size limit]`,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
137
145
|
} catch (error) {
|
|
138
|
-
console.error('Error stringifying inputs')
|
|
146
|
+
console.error('Error stringifying inputs', error)
|
|
139
147
|
return [{ role: 'posthog', content: 'An error occurred while processing your request. Please try again.' }]
|
|
140
148
|
}
|
|
141
149
|
return inputs
|
|
@@ -218,7 +226,7 @@ export const createInstrumentationMiddleware = (
|
|
|
218
226
|
}
|
|
219
227
|
: {}),
|
|
220
228
|
}
|
|
221
|
-
sendEventToPosthog({
|
|
229
|
+
await sendEventToPosthog({
|
|
222
230
|
client: phClient,
|
|
223
231
|
distinctId: options.posthogDistinctId,
|
|
224
232
|
traceId: options.posthogTraceId,
|
|
@@ -235,13 +243,13 @@ export const createInstrumentationMiddleware = (
|
|
|
235
243
|
outputTokens: result.usage.completionTokens,
|
|
236
244
|
...additionalTokenValues,
|
|
237
245
|
},
|
|
238
|
-
|
|
246
|
+
captureImmediate: options.posthogCaptureImmediate,
|
|
239
247
|
})
|
|
240
248
|
|
|
241
249
|
return result
|
|
242
250
|
} catch (error: any) {
|
|
243
251
|
const modelId = model.modelId
|
|
244
|
-
sendEventToPosthog({
|
|
252
|
+
await sendEventToPosthog({
|
|
245
253
|
client: phClient,
|
|
246
254
|
distinctId: options.posthogDistinctId,
|
|
247
255
|
traceId: options.posthogTraceId,
|
|
@@ -259,7 +267,7 @@ export const createInstrumentationMiddleware = (
|
|
|
259
267
|
},
|
|
260
268
|
isError: true,
|
|
261
269
|
error: truncate(JSON.stringify(error)),
|
|
262
|
-
|
|
270
|
+
captureImmediate: options.posthogCaptureImmediate,
|
|
263
271
|
})
|
|
264
272
|
throw error
|
|
265
273
|
}
|
|
@@ -311,9 +319,9 @@ export const createInstrumentationMiddleware = (
|
|
|
311
319
|
controller.enqueue(chunk)
|
|
312
320
|
},
|
|
313
321
|
|
|
314
|
-
flush() {
|
|
322
|
+
flush: async () => {
|
|
315
323
|
const latency = (Date.now() - startTime) / 1000
|
|
316
|
-
sendEventToPosthog({
|
|
324
|
+
await sendEventToPosthog({
|
|
317
325
|
client: phClient,
|
|
318
326
|
distinctId: options.posthogDistinctId,
|
|
319
327
|
traceId: options.posthogTraceId,
|
|
@@ -326,7 +334,7 @@ export const createInstrumentationMiddleware = (
|
|
|
326
334
|
params: mergedParams as any,
|
|
327
335
|
httpStatus: 200,
|
|
328
336
|
usage,
|
|
329
|
-
|
|
337
|
+
captureImmediate: options.posthogCaptureImmediate,
|
|
330
338
|
})
|
|
331
339
|
},
|
|
332
340
|
})
|
|
@@ -336,7 +344,7 @@ export const createInstrumentationMiddleware = (
|
|
|
336
344
|
...rest,
|
|
337
345
|
}
|
|
338
346
|
} catch (error: any) {
|
|
339
|
-
sendEventToPosthog({
|
|
347
|
+
await sendEventToPosthog({
|
|
340
348
|
client: phClient,
|
|
341
349
|
distinctId: options.posthogDistinctId,
|
|
342
350
|
traceId: options.posthogTraceId,
|
|
@@ -354,7 +362,7 @@ export const createInstrumentationMiddleware = (
|
|
|
354
362
|
},
|
|
355
363
|
isError: true,
|
|
356
364
|
error: truncate(JSON.stringify(error)),
|
|
357
|
-
|
|
365
|
+
captureImmediate: options.posthogCaptureImmediate,
|
|
358
366
|
})
|
|
359
367
|
throw error
|
|
360
368
|
}
|
package/tests/openai.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ jest.mock('posthog-node', () => {
|
|
|
6
6
|
PostHog: jest.fn().mockImplementation(() => {
|
|
7
7
|
return {
|
|
8
8
|
capture: jest.fn(),
|
|
9
|
+
captureImmediate: jest.fn(),
|
|
9
10
|
privacyMode: false,
|
|
10
11
|
}
|
|
11
12
|
}),
|
|
@@ -261,4 +262,18 @@ describe('PostHogOpenAI - Jest test suite', () => {
|
|
|
261
262
|
expect(properties['$ai_reasoning_tokens']).toBe(15)
|
|
262
263
|
expect(properties['$ai_cache_read_input_tokens']).toBe(5)
|
|
263
264
|
})
|
|
265
|
+
|
|
266
|
+
// New test: ensure captureImmediate is used when flag is set
|
|
267
|
+
conditionalTest('captureImmediate flag', async () => {
|
|
268
|
+
await client.chat.completions.create({
|
|
269
|
+
model: 'gpt-4',
|
|
270
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
271
|
+
posthogDistinctId: 'test-id',
|
|
272
|
+
posthogCaptureImmediate: true,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// captureImmediate should be called once, and capture should not be called
|
|
276
|
+
expect(mockPostHogClient.captureImmediate).toHaveBeenCalledTimes(1)
|
|
277
|
+
expect(mockPostHogClient.capture).toHaveBeenCalledTimes(0)
|
|
278
|
+
})
|
|
264
279
|
})
|
package/tsconfig.json
CHANGED