@posthog/ai 7.16.15 → 7.17.1
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 +91 -95
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +91 -95
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +100 -106
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +100 -106
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +295 -335
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +83 -2
- package/dist/index.mjs +273 -314
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +11 -3
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +11 -3
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +169 -185
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +169 -185
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +14 -12
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.mjs +14 -12
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +133 -128
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +133 -128
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +4 -4
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 => {
|
|
@@ -86,6 +84,14 @@ function getTokensSource(posthogProperties) {
|
|
|
86
84
|
}
|
|
87
85
|
return 'sdk';
|
|
88
86
|
}
|
|
87
|
+
const STRING_FORMAT = 'utf8';
|
|
88
|
+
|
|
89
|
+
// Reused across calls to avoid per-invocation allocation; truncate() runs
|
|
90
|
+
// hundreds of times for prompts with many parts.
|
|
91
|
+
new TextEncoder();
|
|
92
|
+
new TextDecoder(STRING_FORMAT, {
|
|
93
|
+
fatal: false
|
|
94
|
+
});
|
|
89
95
|
const getModelParams = params => {
|
|
90
96
|
if (!params) {
|
|
91
97
|
return {};
|
|
@@ -211,73 +217,69 @@ function addDefaults(params) {
|
|
|
211
217
|
traceId: params.traceId ?? uuid.v4()
|
|
212
218
|
};
|
|
213
219
|
}
|
|
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
|
-
}) => {
|
|
220
|
+
|
|
221
|
+
var version = "7.17.1";
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
|
|
225
|
+
* directly so that any caller — first-party SDK wrappers and external code
|
|
226
|
+
* alike — produces an identical event.
|
|
227
|
+
*/
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
|
|
231
|
+
*
|
|
232
|
+
* This is the canonical primitive that every `@posthog/ai` wrapper
|
|
233
|
+
* (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
|
|
234
|
+
* external code can use it directly to instrument LLM calls made through
|
|
235
|
+
* arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
|
|
236
|
+
* same events the SDK wrappers produce.
|
|
237
|
+
*
|
|
238
|
+
* When `error` is set, the event is captured as an error. If the error is an
|
|
239
|
+
* object, it is mutated in place to set `__posthog_previously_captured_error`
|
|
240
|
+
* so callers can re-throw the original error reference safely.
|
|
241
|
+
*/
|
|
242
|
+
const captureAiGeneration = async (client, options) => {
|
|
262
243
|
if (!client.capture) {
|
|
263
|
-
return
|
|
244
|
+
return;
|
|
264
245
|
}
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
246
|
+
const traceId = options.traceId ?? uuid.v4();
|
|
247
|
+
const eventType = options.eventType ?? AIEvent.Generation;
|
|
248
|
+
const privacyMode = options.privacyMode ?? false;
|
|
249
|
+
const usage = options.usage ?? {};
|
|
250
|
+
const safeInput = sanitizeValues(options.input);
|
|
251
|
+
const safeOutput = sanitizeValues(options.output);
|
|
252
|
+
let httpStatus = options.httpStatus;
|
|
269
253
|
let errorData = {};
|
|
270
|
-
if (error) {
|
|
254
|
+
if (options.error) {
|
|
255
|
+
if (httpStatus === undefined) {
|
|
256
|
+
if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
|
|
257
|
+
httpStatus = options.error.status;
|
|
258
|
+
} else {
|
|
259
|
+
httpStatus = 500;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
let exceptionId;
|
|
263
|
+
if (client.options?.enableExceptionAutocapture) {
|
|
264
|
+
exceptionId = core.uuidv7();
|
|
265
|
+
client.captureException(options.error, undefined, {
|
|
266
|
+
$ai_trace_id: traceId
|
|
267
|
+
}, exceptionId);
|
|
268
|
+
if (typeof options.error === 'object') {
|
|
269
|
+
options.error.__posthog_previously_captured_error = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
271
272
|
errorData = {
|
|
272
273
|
$ai_is_error: true,
|
|
273
|
-
$ai_error:
|
|
274
|
+
$ai_error: sanitizeValues(JSON.stringify(options.error)),
|
|
274
275
|
$exception_event_id: exceptionId
|
|
275
276
|
};
|
|
276
277
|
}
|
|
278
|
+
httpStatus = httpStatus ?? 200;
|
|
277
279
|
let costOverrideData = {};
|
|
278
|
-
if (
|
|
279
|
-
const inputCostUSD = (
|
|
280
|
-
const outputCostUSD = (
|
|
280
|
+
if (options.costOverride) {
|
|
281
|
+
const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
|
|
282
|
+
const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
|
|
281
283
|
costOverrideData = {
|
|
282
284
|
$ai_input_cost_usd: inputCostUSD,
|
|
283
285
|
$ai_output_cost_usd: outputCostUSD,
|
|
@@ -304,50 +306,48 @@ const sendEventToPosthog = async ({
|
|
|
304
306
|
const properties = {
|
|
305
307
|
$ai_lib: 'posthog-ai',
|
|
306
308
|
$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,
|
|
309
|
+
$ai_provider: options.providerOverride ?? options.provider,
|
|
310
|
+
$ai_model: options.modelOverride ?? options.model,
|
|
311
|
+
$ai_model_parameters: options.modelParameters ?? {},
|
|
312
|
+
$ai_input: withPrivacyMode(client, privacyMode, safeInput),
|
|
313
|
+
$ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
|
|
312
314
|
$ai_http_status: httpStatus,
|
|
313
315
|
$ai_input_tokens: usage.inputTokens ?? 0,
|
|
314
316
|
...(usage.outputTokens !== undefined ? {
|
|
315
317
|
$ai_output_tokens: usage.outputTokens
|
|
316
318
|
} : {}),
|
|
317
319
|
...additionalTokenValues,
|
|
318
|
-
$ai_latency: latency,
|
|
319
|
-
...(timeToFirstToken !== undefined ? {
|
|
320
|
-
$ai_time_to_first_token: timeToFirstToken
|
|
320
|
+
$ai_latency: options.latency ?? 0,
|
|
321
|
+
...(options.timeToFirstToken !== undefined ? {
|
|
322
|
+
$ai_time_to_first_token: options.timeToFirstToken
|
|
321
323
|
} : {}),
|
|
322
324
|
$ai_trace_id: traceId,
|
|
323
|
-
$ai_base_url: baseURL,
|
|
324
|
-
...
|
|
325
|
-
$ai_tokens_source: getTokensSource(
|
|
326
|
-
...(distinctId ? {} : {
|
|
325
|
+
$ai_base_url: options.baseURL ?? '',
|
|
326
|
+
...options.properties,
|
|
327
|
+
$ai_tokens_source: getTokensSource(options.properties),
|
|
328
|
+
...(options.distinctId ? {} : {
|
|
327
329
|
$process_person_profile: false
|
|
328
330
|
}),
|
|
329
|
-
...(stopReason ? {
|
|
330
|
-
$ai_stop_reason: stopReason
|
|
331
|
+
...(options.stopReason ? {
|
|
332
|
+
$ai_stop_reason: options.stopReason
|
|
331
333
|
} : {}),
|
|
332
|
-
...(tools ? {
|
|
333
|
-
$ai_tools: tools
|
|
334
|
+
...(options.tools ? {
|
|
335
|
+
$ai_tools: options.tools
|
|
334
336
|
} : {}),
|
|
335
337
|
...errorData,
|
|
336
338
|
...costOverrideData
|
|
337
339
|
};
|
|
338
340
|
const event = {
|
|
339
|
-
distinctId: distinctId ?? traceId,
|
|
341
|
+
distinctId: options.distinctId ?? traceId,
|
|
340
342
|
event: eventType,
|
|
341
343
|
properties,
|
|
342
|
-
groups:
|
|
344
|
+
groups: options.groups
|
|
343
345
|
};
|
|
344
|
-
if (captureImmediate) {
|
|
345
|
-
// await capture promise to send single event in serverless environments
|
|
346
|
+
if (options.captureImmediate) {
|
|
346
347
|
await client.captureImmediate(event);
|
|
347
348
|
} else {
|
|
348
349
|
client.capture(event);
|
|
349
350
|
}
|
|
350
|
-
return Promise.resolve();
|
|
351
351
|
};
|
|
352
352
|
|
|
353
353
|
class PostHogAnthropic extends AnthropicOriginal__default.default {
|
|
@@ -509,8 +509,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
509
509
|
text: accumulatedContent
|
|
510
510
|
}]
|
|
511
511
|
}];
|
|
512
|
-
await
|
|
513
|
-
client: this.phClient,
|
|
512
|
+
await captureAiGeneration(this.phClient, {
|
|
514
513
|
...posthogParams,
|
|
515
514
|
model: anthropicParams.model,
|
|
516
515
|
provider: 'anthropic',
|
|
@@ -519,15 +518,14 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
519
518
|
latency,
|
|
520
519
|
timeToFirstToken,
|
|
521
520
|
baseURL: this.baseURL,
|
|
522
|
-
|
|
521
|
+
modelParameters: getModelParams(body),
|
|
523
522
|
httpStatus: 200,
|
|
524
523
|
usage,
|
|
525
524
|
stopReason,
|
|
526
525
|
tools: availableTools
|
|
527
526
|
});
|
|
528
527
|
} catch (error) {
|
|
529
|
-
|
|
530
|
-
client: this.phClient,
|
|
528
|
+
await captureAiGeneration(this.phClient, {
|
|
531
529
|
...posthogParams,
|
|
532
530
|
model: anthropicParams.model,
|
|
533
531
|
provider: 'anthropic',
|
|
@@ -535,14 +533,14 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
535
533
|
output: [],
|
|
536
534
|
latency: 0,
|
|
537
535
|
baseURL: this.baseURL,
|
|
538
|
-
|
|
536
|
+
modelParameters: getModelParams(body),
|
|
539
537
|
usage: {
|
|
540
538
|
inputTokens: 0,
|
|
541
539
|
outputTokens: 0
|
|
542
540
|
},
|
|
543
541
|
error: error
|
|
544
542
|
});
|
|
545
|
-
throw
|
|
543
|
+
throw error;
|
|
546
544
|
}
|
|
547
545
|
})();
|
|
548
546
|
|
|
@@ -556,8 +554,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
556
554
|
if ('content' in result) {
|
|
557
555
|
const latency = (Date.now() - startTime) / 1000;
|
|
558
556
|
const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
|
|
559
|
-
await
|
|
560
|
-
client: this.phClient,
|
|
557
|
+
await captureAiGeneration(this.phClient, {
|
|
561
558
|
...posthogParams,
|
|
562
559
|
model: anthropicParams.model,
|
|
563
560
|
provider: 'anthropic',
|
|
@@ -565,7 +562,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
565
562
|
output: formatResponseAnthropic(result),
|
|
566
563
|
latency,
|
|
567
564
|
baseURL: this.baseURL,
|
|
568
|
-
|
|
565
|
+
modelParameters: getModelParams(body),
|
|
569
566
|
httpStatus: 200,
|
|
570
567
|
usage: {
|
|
571
568
|
inputTokens: result.usage.input_tokens ?? 0,
|
|
@@ -581,8 +578,7 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
581
578
|
}
|
|
582
579
|
return result;
|
|
583
580
|
}, async error => {
|
|
584
|
-
await
|
|
585
|
-
client: this.phClient,
|
|
581
|
+
await captureAiGeneration(this.phClient, {
|
|
586
582
|
...posthogParams,
|
|
587
583
|
model: anthropicParams.model,
|
|
588
584
|
provider: 'anthropic',
|
|
@@ -590,13 +586,13 @@ class WrappedMessages extends AnthropicOriginal__default.default.Messages {
|
|
|
590
586
|
output: [],
|
|
591
587
|
latency: 0,
|
|
592
588
|
baseURL: this.baseURL,
|
|
593
|
-
|
|
589
|
+
modelParameters: getModelParams(body),
|
|
594
590
|
httpStatus: error?.status ? error.status : 500,
|
|
595
591
|
usage: {
|
|
596
592
|
inputTokens: 0,
|
|
597
593
|
outputTokens: 0
|
|
598
594
|
},
|
|
599
|
-
error:
|
|
595
|
+
error: error
|
|
600
596
|
});
|
|
601
597
|
throw error;
|
|
602
598
|
});
|