@posthog/ai 7.16.15 → 7.17.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/README.md +31 -0
- package/dist/anthropic/index.cjs +83 -95
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +83 -95
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +92 -106
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +92 -106
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +267 -318
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +83 -2
- package/dist/index.mjs +245 -297
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +2 -3
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +2 -3
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +161 -185
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +161 -185
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +2 -3
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.mjs +2 -3
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +104 -111
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +104 -111
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -39,6 +39,37 @@ console.log(completion.choices[0].message.content)
|
|
|
39
39
|
await phClient.shutdown()
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
## Custom and unsupported providers
|
|
43
|
+
|
|
44
|
+
For LLM calls that don't go through one of the wrapped clients — direct Cloudflare Workers AI bindings, TanStack AI adapters, custom HTTP clients — use `captureAiGeneration` to emit the same `$ai_generation` events the wrappers produce.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { captureAiGeneration } from '@posthog/ai'
|
|
48
|
+
import { PostHog } from 'posthog-node'
|
|
49
|
+
|
|
50
|
+
const phClient = new PostHog('<YOUR_PROJECT_API_KEY>', { host: 'https://us.i.posthog.com' })
|
|
51
|
+
|
|
52
|
+
const start = Date.now()
|
|
53
|
+
const result = await env.AI.run('@cf/zai-org/glm-4.7-flash', { messages, reasoning_effort: 'high' })
|
|
54
|
+
|
|
55
|
+
await captureAiGeneration(phClient, {
|
|
56
|
+
distinctId: 'user_123',
|
|
57
|
+
traceId: 'trace_abc',
|
|
58
|
+
provider: 'cloudflare-workers-ai',
|
|
59
|
+
model: '@cf/zai-org/glm-4.7-flash',
|
|
60
|
+
input: messages,
|
|
61
|
+
output: result.response,
|
|
62
|
+
modelParameters: { reasoning_effort: 'high' },
|
|
63
|
+
usage: { inputTokens: result.usage?.prompt_tokens, outputTokens: result.usage?.completion_tokens },
|
|
64
|
+
latency: (Date.now() - start) / 1000,
|
|
65
|
+
properties: { feature: 'transcript-toc' },
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
await phClient.shutdown()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`captureAiGeneration` is the same primitive that every other `@posthog/ai` wrapper funnels through, so the resulting events are indistinguishable from those produced by `withTracing`, `OpenAI`, `Anthropic`, etc.
|
|
72
|
+
|
|
42
73
|
## OpenTelemetry
|
|
43
74
|
|
|
44
75
|
`@posthog/ai/otel` provides two ways to send AI traces to PostHog via OpenTelemetry. Both automatically filter to AI-related spans only (`gen_ai.*`, `llm.*`, `ai.*`, `traceloop.*`) and PostHog converts them into `$ai_generation` events server-side. This works with any LLM provider SDK that supports OpenTelemetry.
|
package/dist/anthropic/index.cjs
CHANGED
|
@@ -10,8 +10,6 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
10
10
|
|
|
11
11
|
var AnthropicOriginal__default = /*#__PURE__*/_interopDefault(AnthropicOriginal);
|
|
12
12
|
|
|
13
|
-
var version = "7.16.15";
|
|
14
|
-
|
|
15
13
|
// Type guards for safer type checking
|
|
16
14
|
|
|
17
15
|
const isObject = value => {
|
|
@@ -211,73 +209,69 @@ function addDefaults(params) {
|
|
|
211
209
|
traceId: params.traceId ?? uuid.v4()
|
|
212
210
|
};
|
|
213
211
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
await sendEventToPosthog(properties);
|
|
239
|
-
return enrichedError;
|
|
240
|
-
};
|
|
241
|
-
const sendEventToPosthog = async ({
|
|
242
|
-
client,
|
|
243
|
-
eventType = AIEvent.Generation,
|
|
244
|
-
distinctId,
|
|
245
|
-
traceId,
|
|
246
|
-
model,
|
|
247
|
-
provider,
|
|
248
|
-
input,
|
|
249
|
-
output,
|
|
250
|
-
latency,
|
|
251
|
-
timeToFirstToken,
|
|
252
|
-
baseURL,
|
|
253
|
-
params,
|
|
254
|
-
httpStatus = 200,
|
|
255
|
-
usage = {},
|
|
256
|
-
error,
|
|
257
|
-
exceptionId,
|
|
258
|
-
stopReason,
|
|
259
|
-
tools,
|
|
260
|
-
captureImmediate = false
|
|
261
|
-
}) => {
|
|
212
|
+
|
|
213
|
+
var version = "7.17.0";
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
|
|
217
|
+
* directly so that any caller — first-party SDK wrappers and external code
|
|
218
|
+
* alike — produces an identical event.
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
|
|
223
|
+
*
|
|
224
|
+
* This is the canonical primitive that every `@posthog/ai` wrapper
|
|
225
|
+
* (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
|
|
226
|
+
* external code can use it directly to instrument LLM calls made through
|
|
227
|
+
* arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
|
|
228
|
+
* same events the SDK wrappers produce.
|
|
229
|
+
*
|
|
230
|
+
* When `error` is set, the event is captured as an error. If the error is an
|
|
231
|
+
* object, it is mutated in place to set `__posthog_previously_captured_error`
|
|
232
|
+
* so callers can re-throw the original error reference safely.
|
|
233
|
+
*/
|
|
234
|
+
const captureAiGeneration = async (client, options) => {
|
|
262
235
|
if (!client.capture) {
|
|
263
|
-
return
|
|
236
|
+
return;
|
|
264
237
|
}
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
238
|
+
const traceId = options.traceId ?? uuid.v4();
|
|
239
|
+
const eventType = options.eventType ?? AIEvent.Generation;
|
|
240
|
+
const privacyMode = options.privacyMode ?? false;
|
|
241
|
+
const usage = options.usage ?? {};
|
|
242
|
+
const safeInput = sanitizeValues(options.input);
|
|
243
|
+
const safeOutput = sanitizeValues(options.output);
|
|
244
|
+
let httpStatus = options.httpStatus;
|
|
269
245
|
let errorData = {};
|
|
270
|
-
if (error) {
|
|
246
|
+
if (options.error) {
|
|
247
|
+
if (httpStatus === undefined) {
|
|
248
|
+
if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
|
|
249
|
+
httpStatus = options.error.status;
|
|
250
|
+
} else {
|
|
251
|
+
httpStatus = 500;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
let exceptionId;
|
|
255
|
+
if (client.options?.enableExceptionAutocapture) {
|
|
256
|
+
exceptionId = core.uuidv7();
|
|
257
|
+
client.captureException(options.error, undefined, {
|
|
258
|
+
$ai_trace_id: traceId
|
|
259
|
+
}, exceptionId);
|
|
260
|
+
if (typeof options.error === 'object') {
|
|
261
|
+
options.error.__posthog_previously_captured_error = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
271
264
|
errorData = {
|
|
272
265
|
$ai_is_error: true,
|
|
273
|
-
$ai_error:
|
|
266
|
+
$ai_error: sanitizeValues(JSON.stringify(options.error)),
|
|
274
267
|
$exception_event_id: exceptionId
|
|
275
268
|
};
|
|
276
269
|
}
|
|
270
|
+
httpStatus = httpStatus ?? 200;
|
|
277
271
|
let costOverrideData = {};
|
|
278
|
-
if (
|
|
279
|
-
const inputCostUSD = (
|
|
280
|
-
const outputCostUSD = (
|
|
272
|
+
if (options.costOverride) {
|
|
273
|
+
const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
|
|
274
|
+
const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
|
|
281
275
|
costOverrideData = {
|
|
282
276
|
$ai_input_cost_usd: inputCostUSD,
|
|
283
277
|
$ai_output_cost_usd: outputCostUSD,
|
|
@@ -304,50 +298,48 @@ const sendEventToPosthog = async ({
|
|
|
304
298
|
const properties = {
|
|
305
299
|
$ai_lib: 'posthog-ai',
|
|
306
300
|
$ai_lib_version: version,
|
|
307
|
-
$ai_provider:
|
|
308
|
-
$ai_model:
|
|
309
|
-
$ai_model_parameters:
|
|
310
|
-
$ai_input: withPrivacyMode(client,
|
|
311
|
-
$ai_output_choices: withPrivacyMode(client,
|
|
301
|
+
$ai_provider: options.providerOverride ?? options.provider,
|
|
302
|
+
$ai_model: options.modelOverride ?? options.model,
|
|
303
|
+
$ai_model_parameters: options.modelParameters ?? {},
|
|
304
|
+
$ai_input: withPrivacyMode(client, privacyMode, safeInput),
|
|
305
|
+
$ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
|
|
312
306
|
$ai_http_status: httpStatus,
|
|
313
307
|
$ai_input_tokens: usage.inputTokens ?? 0,
|
|
314
308
|
...(usage.outputTokens !== undefined ? {
|
|
315
309
|
$ai_output_tokens: usage.outputTokens
|
|
316
310
|
} : {}),
|
|
317
311
|
...additionalTokenValues,
|
|
318
|
-
$ai_latency: latency,
|
|
319
|
-
...(timeToFirstToken !== undefined ? {
|
|
320
|
-
$ai_time_to_first_token: timeToFirstToken
|
|
312
|
+
$ai_latency: options.latency ?? 0,
|
|
313
|
+
...(options.timeToFirstToken !== undefined ? {
|
|
314
|
+
$ai_time_to_first_token: options.timeToFirstToken
|
|
321
315
|
} : {}),
|
|
322
316
|
$ai_trace_id: traceId,
|
|
323
|
-
$ai_base_url: baseURL,
|
|
324
|
-
...
|
|
325
|
-
$ai_tokens_source: getTokensSource(
|
|
326
|
-
...(distinctId ? {} : {
|
|
317
|
+
$ai_base_url: options.baseURL ?? '',
|
|
318
|
+
...options.properties,
|
|
319
|
+
$ai_tokens_source: getTokensSource(options.properties),
|
|
320
|
+
...(options.distinctId ? {} : {
|
|
327
321
|
$process_person_profile: false
|
|
328
322
|
}),
|
|
329
|
-
...(stopReason ? {
|
|
330
|
-
$ai_stop_reason: stopReason
|
|
323
|
+
...(options.stopReason ? {
|
|
324
|
+
$ai_stop_reason: options.stopReason
|
|
331
325
|
} : {}),
|
|
332
|
-
...(tools ? {
|
|
333
|
-
$ai_tools: tools
|
|
326
|
+
...(options.tools ? {
|
|
327
|
+
$ai_tools: options.tools
|
|
334
328
|
} : {}),
|
|
335
329
|
...errorData,
|
|
336
330
|
...costOverrideData
|
|
337
331
|
};
|
|
338
332
|
const event = {
|
|
339
|
-
distinctId: distinctId ?? traceId,
|
|
333
|
+
distinctId: options.distinctId ?? traceId,
|
|
340
334
|
event: eventType,
|
|
341
335
|
properties,
|
|
342
|
-
groups:
|
|
336
|
+
groups: options.groups
|
|
343
337
|
};
|
|
344
|
-
if (captureImmediate) {
|
|
345
|
-
// await capture promise to send single event in serverless environments
|
|
338
|
+
if (options.captureImmediate) {
|
|
346
339
|
await client.captureImmediate(event);
|
|
347
340
|
} else {
|
|
348
341
|
client.capture(event);
|
|
349
342
|
}
|
|
350
|
-
return Promise.resolve();
|
|
351
343
|
};
|
|
352
344
|
|
|
353
345
|
class PostHogAnthropic extends AnthropicOriginal__default.default {
|
|
@@ -509,8 +501,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
509
501
|
text: accumulatedContent
|
|
510
502
|
}]
|
|
511
503
|
}];
|
|
512
|
-
await
|
|
513
|
-
client: this.phClient,
|
|
504
|
+
await captureAiGeneration(this.phClient, {
|
|
514
505
|
...posthogParams,
|
|
515
506
|
model: anthropicParams.model,
|
|
516
507
|
provider: 'anthropic',
|
|
@@ -519,15 +510,14 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
519
510
|
latency,
|
|
520
511
|
timeToFirstToken,
|
|
521
512
|
baseURL: this.baseURL,
|
|
522
|
-
|
|
513
|
+
modelParameters: getModelParams(body),
|
|
523
514
|
httpStatus: 200,
|
|
524
515
|
usage,
|
|
525
516
|
stopReason,
|
|
526
517
|
tools: availableTools
|
|
527
518
|
});
|
|
528
519
|
} catch (error) {
|
|
529
|
-
|
|
530
|
-
client: this.phClient,
|
|
520
|
+
await captureAiGeneration(this.phClient, {
|
|
531
521
|
...posthogParams,
|
|
532
522
|
model: anthropicParams.model,
|
|
533
523
|
provider: 'anthropic',
|
|
@@ -535,14 +525,14 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
535
525
|
output: [],
|
|
536
526
|
latency: 0,
|
|
537
527
|
baseURL: this.baseURL,
|
|
538
|
-
|
|
528
|
+
modelParameters: getModelParams(body),
|
|
539
529
|
usage: {
|
|
540
530
|
inputTokens: 0,
|
|
541
531
|
outputTokens: 0
|
|
542
532
|
},
|
|
543
533
|
error: error
|
|
544
534
|
});
|
|
545
|
-
throw
|
|
535
|
+
throw error;
|
|
546
536
|
}
|
|
547
537
|
})();
|
|
548
538
|
|
|
@@ -556,8 +546,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
556
546
|
if ('content' in result) {
|
|
557
547
|
const latency = (Date.now() - startTime) / 1000;
|
|
558
548
|
const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
|
|
559
|
-
await
|
|
560
|
-
client: this.phClient,
|
|
549
|
+
await captureAiGeneration(this.phClient, {
|
|
561
550
|
...posthogParams,
|
|
562
551
|
model: anthropicParams.model,
|
|
563
552
|
provider: 'anthropic',
|
|
@@ -565,7 +554,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
565
554
|
output: formatResponseAnthropic(result),
|
|
566
555
|
latency,
|
|
567
556
|
baseURL: this.baseURL,
|
|
568
|
-
|
|
557
|
+
modelParameters: getModelParams(body),
|
|
569
558
|
httpStatus: 200,
|
|
570
559
|
usage: {
|
|
571
560
|
inputTokens: result.usage.input_tokens ?? 0,
|
|
@@ -581,8 +570,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
581
570
|
}
|
|
582
571
|
return result;
|
|
583
572
|
}, async error => {
|
|
584
|
-
await
|
|
585
|
-
client: this.phClient,
|
|
573
|
+
await captureAiGeneration(this.phClient, {
|
|
586
574
|
...posthogParams,
|
|
587
575
|
model: anthropicParams.model,
|
|
588
576
|
provider: 'anthropic',
|
|
@@ -590,13 +578,13 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
590
578
|
output: [],
|
|
591
579
|
latency: 0,
|
|
592
580
|
baseURL: this.baseURL,
|
|
593
|
-
|
|
581
|
+
modelParameters: getModelParams(body),
|
|
594
582
|
httpStatus: error?.status ? error.status : 500,
|
|
595
583
|
usage: {
|
|
596
584
|
inputTokens: 0,
|
|
597
585
|
outputTokens: 0
|
|
598
586
|
},
|
|
599
|
-
error:
|
|
587
|
+
error: error
|
|
600
588
|
});
|
|
601
589
|
throw error;
|
|
602
590
|
});
|