@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/dist/vercel/index.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { v4 } from 'uuid';
|
|
2
2
|
import { uuidv7 } from '@posthog/core';
|
|
3
3
|
|
|
4
|
-
var version = "7.16.15";
|
|
5
|
-
|
|
6
4
|
// Type guards for safer type checking
|
|
7
5
|
|
|
8
6
|
const isString = value => {
|
|
@@ -74,6 +72,14 @@ function getTokensSource(posthogProperties) {
|
|
|
74
72
|
const MAX_OUTPUT_SIZE = 200000;
|
|
75
73
|
const STRING_FORMAT = 'utf8';
|
|
76
74
|
|
|
75
|
+
// Reused across calls to avoid per-invocation allocation; truncate() runs
|
|
76
|
+
// hundreds of times for prompts with many parts.
|
|
77
|
+
const sharedTextEncoder = new TextEncoder();
|
|
78
|
+
const sharedTextDecoder = new TextDecoder(STRING_FORMAT, {
|
|
79
|
+
fatal: false
|
|
80
|
+
});
|
|
81
|
+
const utf8ByteLength = str => sharedTextEncoder.encode(str).byteLength;
|
|
82
|
+
|
|
77
83
|
/**
|
|
78
84
|
* Safely converts content to a string, preserving structure for objects/arrays.
|
|
79
85
|
* - If content is already a string, returns it as-is
|
|
@@ -136,20 +142,16 @@ const truncate = input => {
|
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
// Check if we need to truncate and ensure STRING_FORMAT is respected
|
|
139
|
-
const
|
|
140
|
-
const buffer = encoder.encode(str);
|
|
145
|
+
const buffer = sharedTextEncoder.encode(str);
|
|
141
146
|
if (buffer.length <= MAX_OUTPUT_SIZE) {
|
|
142
147
|
// Ensure STRING_FORMAT is respected
|
|
143
|
-
return
|
|
148
|
+
return sharedTextDecoder.decode(buffer);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
// Truncate the buffer and ensure a valid string is returned
|
|
151
|
+
// Truncate the buffer and ensure a valid string is returned.
|
|
152
|
+
// fatal: false means we get U+FFFD at the end if truncation broke the encoding.
|
|
147
153
|
const truncatedBuffer = buffer.slice(0, MAX_OUTPUT_SIZE);
|
|
148
|
-
|
|
149
|
-
const decoder = new TextDecoder(STRING_FORMAT, {
|
|
150
|
-
fatal: false
|
|
151
|
-
});
|
|
152
|
-
let truncatedStr = decoder.decode(truncatedBuffer);
|
|
154
|
+
let truncatedStr = sharedTextDecoder.decode(truncatedBuffer);
|
|
153
155
|
if (truncatedStr.endsWith('\uFFFD')) {
|
|
154
156
|
truncatedStr = truncatedStr.slice(0, -1);
|
|
155
157
|
}
|
|
@@ -292,73 +294,69 @@ function sanitizeValues(obj) {
|
|
|
292
294
|
}
|
|
293
295
|
return jsonSafe;
|
|
294
296
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
await sendEventToPosthog(properties);
|
|
320
|
-
return enrichedError;
|
|
321
|
-
};
|
|
322
|
-
const sendEventToPosthog = async ({
|
|
323
|
-
client,
|
|
324
|
-
eventType = AIEvent.Generation,
|
|
325
|
-
distinctId,
|
|
326
|
-
traceId,
|
|
327
|
-
model,
|
|
328
|
-
provider,
|
|
329
|
-
input,
|
|
330
|
-
output,
|
|
331
|
-
latency,
|
|
332
|
-
timeToFirstToken,
|
|
333
|
-
baseURL,
|
|
334
|
-
params,
|
|
335
|
-
httpStatus = 200,
|
|
336
|
-
usage = {},
|
|
337
|
-
error,
|
|
338
|
-
exceptionId,
|
|
339
|
-
stopReason,
|
|
340
|
-
tools,
|
|
341
|
-
captureImmediate = false
|
|
342
|
-
}) => {
|
|
297
|
+
|
|
298
|
+
var version = "7.17.1";
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Options for `captureAiGeneration`. Mirrors the `$ai_generation` event shape
|
|
302
|
+
* directly so that any caller — first-party SDK wrappers and external code
|
|
303
|
+
* alike — produces an identical event.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Capture an `$ai_generation` (or `$ai_embedding`) event to PostHog.
|
|
308
|
+
*
|
|
309
|
+
* This is the canonical primitive that every `@posthog/ai` wrapper
|
|
310
|
+
* (`withTracing`, `OpenAI`, `Anthropic`, `GoogleGenAI`, …) funnels through, so
|
|
311
|
+
* external code can use it directly to instrument LLM calls made through
|
|
312
|
+
* arbitrary clients (Cloudflare Workers AI, custom HTTP, etc.) and get the
|
|
313
|
+
* same events the SDK wrappers produce.
|
|
314
|
+
*
|
|
315
|
+
* When `error` is set, the event is captured as an error. If the error is an
|
|
316
|
+
* object, it is mutated in place to set `__posthog_previously_captured_error`
|
|
317
|
+
* so callers can re-throw the original error reference safely.
|
|
318
|
+
*/
|
|
319
|
+
const captureAiGeneration = async (client, options) => {
|
|
343
320
|
if (!client.capture) {
|
|
344
|
-
return
|
|
321
|
+
return;
|
|
345
322
|
}
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
const
|
|
349
|
-
const
|
|
323
|
+
const traceId = options.traceId ?? v4();
|
|
324
|
+
const eventType = options.eventType ?? AIEvent.Generation;
|
|
325
|
+
const privacyMode = options.privacyMode ?? false;
|
|
326
|
+
const usage = options.usage ?? {};
|
|
327
|
+
const safeInput = sanitizeValues(options.input);
|
|
328
|
+
const safeOutput = sanitizeValues(options.output);
|
|
329
|
+
let httpStatus = options.httpStatus;
|
|
350
330
|
let errorData = {};
|
|
351
|
-
if (error) {
|
|
331
|
+
if (options.error) {
|
|
332
|
+
if (httpStatus === undefined) {
|
|
333
|
+
if (typeof options.error === 'object' && 'status' in options.error && typeof options.error.status === 'number') {
|
|
334
|
+
httpStatus = options.error.status;
|
|
335
|
+
} else {
|
|
336
|
+
httpStatus = 500;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
let exceptionId;
|
|
340
|
+
if (client.options?.enableExceptionAutocapture) {
|
|
341
|
+
exceptionId = uuidv7();
|
|
342
|
+
client.captureException(options.error, undefined, {
|
|
343
|
+
$ai_trace_id: traceId
|
|
344
|
+
}, exceptionId);
|
|
345
|
+
if (typeof options.error === 'object') {
|
|
346
|
+
options.error.__posthog_previously_captured_error = true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
352
349
|
errorData = {
|
|
353
350
|
$ai_is_error: true,
|
|
354
|
-
$ai_error:
|
|
351
|
+
$ai_error: sanitizeValues(JSON.stringify(options.error)),
|
|
355
352
|
$exception_event_id: exceptionId
|
|
356
353
|
};
|
|
357
354
|
}
|
|
355
|
+
httpStatus = httpStatus ?? 200;
|
|
358
356
|
let costOverrideData = {};
|
|
359
|
-
if (
|
|
360
|
-
const inputCostUSD = (
|
|
361
|
-
const outputCostUSD = (
|
|
357
|
+
if (options.costOverride) {
|
|
358
|
+
const inputCostUSD = (options.costOverride.inputCost ?? 0) * (usage.inputTokens ?? 0);
|
|
359
|
+
const outputCostUSD = (options.costOverride.outputCost ?? 0) * (usage.outputTokens ?? 0);
|
|
362
360
|
costOverrideData = {
|
|
363
361
|
$ai_input_cost_usd: inputCostUSD,
|
|
364
362
|
$ai_output_cost_usd: outputCostUSD,
|
|
@@ -385,50 +383,48 @@ const sendEventToPosthog = async ({
|
|
|
385
383
|
const properties = {
|
|
386
384
|
$ai_lib: 'posthog-ai',
|
|
387
385
|
$ai_lib_version: version,
|
|
388
|
-
$ai_provider:
|
|
389
|
-
$ai_model:
|
|
390
|
-
$ai_model_parameters:
|
|
391
|
-
$ai_input: withPrivacyMode(client,
|
|
392
|
-
$ai_output_choices: withPrivacyMode(client,
|
|
386
|
+
$ai_provider: options.providerOverride ?? options.provider,
|
|
387
|
+
$ai_model: options.modelOverride ?? options.model,
|
|
388
|
+
$ai_model_parameters: options.modelParameters ?? {},
|
|
389
|
+
$ai_input: withPrivacyMode(client, privacyMode, safeInput),
|
|
390
|
+
$ai_output_choices: withPrivacyMode(client, privacyMode, safeOutput),
|
|
393
391
|
$ai_http_status: httpStatus,
|
|
394
392
|
$ai_input_tokens: usage.inputTokens ?? 0,
|
|
395
393
|
...(usage.outputTokens !== undefined ? {
|
|
396
394
|
$ai_output_tokens: usage.outputTokens
|
|
397
395
|
} : {}),
|
|
398
396
|
...additionalTokenValues,
|
|
399
|
-
$ai_latency: latency,
|
|
400
|
-
...(timeToFirstToken !== undefined ? {
|
|
401
|
-
$ai_time_to_first_token: timeToFirstToken
|
|
397
|
+
$ai_latency: options.latency ?? 0,
|
|
398
|
+
...(options.timeToFirstToken !== undefined ? {
|
|
399
|
+
$ai_time_to_first_token: options.timeToFirstToken
|
|
402
400
|
} : {}),
|
|
403
401
|
$ai_trace_id: traceId,
|
|
404
|
-
$ai_base_url: baseURL,
|
|
405
|
-
...
|
|
406
|
-
$ai_tokens_source: getTokensSource(
|
|
407
|
-
...(distinctId ? {} : {
|
|
402
|
+
$ai_base_url: options.baseURL ?? '',
|
|
403
|
+
...options.properties,
|
|
404
|
+
$ai_tokens_source: getTokensSource(options.properties),
|
|
405
|
+
...(options.distinctId ? {} : {
|
|
408
406
|
$process_person_profile: false
|
|
409
407
|
}),
|
|
410
|
-
...(stopReason ? {
|
|
411
|
-
$ai_stop_reason: stopReason
|
|
408
|
+
...(options.stopReason ? {
|
|
409
|
+
$ai_stop_reason: options.stopReason
|
|
412
410
|
} : {}),
|
|
413
|
-
...(tools ? {
|
|
414
|
-
$ai_tools: tools
|
|
411
|
+
...(options.tools ? {
|
|
412
|
+
$ai_tools: options.tools
|
|
415
413
|
} : {}),
|
|
416
414
|
...errorData,
|
|
417
415
|
...costOverrideData
|
|
418
416
|
};
|
|
419
417
|
const event = {
|
|
420
|
-
distinctId: distinctId ?? traceId,
|
|
418
|
+
distinctId: options.distinctId ?? traceId,
|
|
421
419
|
event: eventType,
|
|
422
420
|
properties,
|
|
423
|
-
groups:
|
|
421
|
+
groups: options.groups
|
|
424
422
|
};
|
|
425
|
-
if (captureImmediate) {
|
|
426
|
-
// await capture promise to send single event in serverless environments
|
|
423
|
+
if (options.captureImmediate) {
|
|
427
424
|
await client.captureImmediate(event);
|
|
428
425
|
} else {
|
|
429
426
|
client.capture(event);
|
|
430
427
|
}
|
|
431
|
-
return Promise.resolve();
|
|
432
428
|
};
|
|
433
429
|
|
|
434
430
|
// Union types for dual version support
|
|
@@ -528,18 +524,26 @@ const mapVercelPrompt = messages => {
|
|
|
528
524
|
};
|
|
529
525
|
});
|
|
530
526
|
try {
|
|
531
|
-
// Trim the inputs array until its JSON size fits within MAX_OUTPUT_SIZE
|
|
532
|
-
|
|
533
|
-
|
|
527
|
+
// Trim the inputs array until its serialized JSON size fits within MAX_OUTPUT_SIZE.
|
|
528
|
+
// Pre-compute each message's byte size once so we can shift by accumulated budget
|
|
529
|
+
// in a single linear pass, instead of re-stringifying the whole array per iteration.
|
|
530
|
+
const messageSizes = inputs.map(m => utf8ByteLength(JSON.stringify(m)));
|
|
531
|
+
// Account for the surrounding `[` `]` plus a comma between each pair of elements.
|
|
532
|
+
let totalBytes = 2 + Math.max(0, messageSizes.length - 1);
|
|
533
|
+
for (const size of messageSizes) {
|
|
534
|
+
totalBytes += size;
|
|
535
|
+
}
|
|
534
536
|
let removedCount = 0;
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
537
|
+
while (totalBytes > MAX_OUTPUT_SIZE && removedCount < messageSizes.length) {
|
|
538
|
+
totalBytes -= messageSizes[removedCount];
|
|
539
|
+
// Each removed message past the first also drops the comma that joined it.
|
|
540
|
+
if (removedCount < messageSizes.length - 1) {
|
|
541
|
+
totalBytes -= 1;
|
|
542
|
+
}
|
|
539
543
|
removedCount++;
|
|
540
|
-
serialized = JSON.stringify(inputs);
|
|
541
544
|
}
|
|
542
545
|
if (removedCount > 0) {
|
|
546
|
+
inputs.splice(0, removedCount);
|
|
543
547
|
// Add one placeholder to indicate how many were removed
|
|
544
548
|
inputs.unshift({
|
|
545
549
|
role: 'posthog',
|
|
@@ -770,6 +774,19 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
770
774
|
}
|
|
771
775
|
};
|
|
772
776
|
|
|
777
|
+
// Shared `captureAiGeneration` options for every call site in this wrapper.
|
|
778
|
+
const baseOptions = {
|
|
779
|
+
distinctId: mergedOptions.posthogDistinctId,
|
|
780
|
+
traceId,
|
|
781
|
+
properties: mergedOptions.posthogProperties,
|
|
782
|
+
groups: mergedOptions.posthogGroups,
|
|
783
|
+
privacyMode: mergedOptions.posthogPrivacyMode,
|
|
784
|
+
modelOverride: mergedOptions.posthogModelOverride,
|
|
785
|
+
providerOverride: mergedOptions.posthogProviderOverride,
|
|
786
|
+
costOverride: mergedOptions.posthogCostOverride,
|
|
787
|
+
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
788
|
+
};
|
|
789
|
+
|
|
773
790
|
// Create wrapped model using Object.create to preserve the prototype chain
|
|
774
791
|
// This automatically inherits all properties (including getters) from the model
|
|
775
792
|
const wrappedModel = Object.create(model, {
|
|
@@ -826,46 +843,40 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
826
843
|
// Extract finish reason - V2 returns a string, V3 returns an object with .unified
|
|
827
844
|
const rawFinishReason = result.finishReason;
|
|
828
845
|
const finishReasonStr = typeof rawFinishReason === 'string' ? rawFinishReason : rawFinishReason && typeof rawFinishReason === 'object' && 'unified' in rawFinishReason ? String(rawFinishReason.unified) : undefined;
|
|
829
|
-
await
|
|
830
|
-
|
|
831
|
-
distinctId: mergedOptions.posthogDistinctId,
|
|
832
|
-
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
846
|
+
await captureAiGeneration(phClient, {
|
|
847
|
+
...baseOptions,
|
|
833
848
|
model: modelId,
|
|
834
849
|
provider: provider,
|
|
835
850
|
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
836
851
|
output: content,
|
|
837
852
|
latency,
|
|
838
853
|
baseURL,
|
|
839
|
-
|
|
854
|
+
modelParameters: getModelParams(mergedParams),
|
|
840
855
|
httpStatus: 200,
|
|
841
856
|
usage,
|
|
842
857
|
stopReason: finishReasonStr,
|
|
843
|
-
tools: availableTools
|
|
844
|
-
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
858
|
+
tools: availableTools
|
|
845
859
|
});
|
|
846
860
|
return result;
|
|
847
861
|
} catch (error) {
|
|
848
862
|
const modelId = model.modelId;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
distinctId: mergedOptions.posthogDistinctId,
|
|
852
|
-
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
863
|
+
await captureAiGeneration(phClient, {
|
|
864
|
+
...baseOptions,
|
|
853
865
|
model: modelId,
|
|
854
866
|
provider: model.provider,
|
|
855
867
|
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
856
868
|
output: [],
|
|
857
869
|
latency: 0,
|
|
858
870
|
baseURL: '',
|
|
859
|
-
|
|
871
|
+
modelParameters: getModelParams(mergedParams),
|
|
860
872
|
usage: {
|
|
861
873
|
inputTokens: 0,
|
|
862
874
|
outputTokens: 0
|
|
863
875
|
},
|
|
864
876
|
error: error,
|
|
865
|
-
tools: availableTools
|
|
866
|
-
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
877
|
+
tools: availableTools
|
|
867
878
|
});
|
|
868
|
-
throw
|
|
879
|
+
throw error;
|
|
869
880
|
}
|
|
870
881
|
},
|
|
871
882
|
writable: true,
|
|
@@ -1017,10 +1028,8 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
1017
1028
|
}
|
|
1018
1029
|
};
|
|
1019
1030
|
adjustAnthropicV3CacheTokens(model, modelId, provider, finalUsage);
|
|
1020
|
-
await
|
|
1021
|
-
|
|
1022
|
-
distinctId: mergedOptions.posthogDistinctId,
|
|
1023
|
-
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
1031
|
+
await captureAiGeneration(phClient, {
|
|
1032
|
+
...baseOptions,
|
|
1024
1033
|
model: modelId,
|
|
1025
1034
|
provider: provider,
|
|
1026
1035
|
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
@@ -1028,12 +1037,11 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
1028
1037
|
latency,
|
|
1029
1038
|
timeToFirstToken,
|
|
1030
1039
|
baseURL,
|
|
1031
|
-
|
|
1040
|
+
modelParameters: getModelParams(mergedParams),
|
|
1032
1041
|
httpStatus: 200,
|
|
1033
1042
|
usage: finalUsage,
|
|
1034
1043
|
stopReason,
|
|
1035
|
-
tools: availableTools
|
|
1036
|
-
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
1044
|
+
tools: availableTools
|
|
1037
1045
|
});
|
|
1038
1046
|
}
|
|
1039
1047
|
});
|
|
@@ -1042,26 +1050,23 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
1042
1050
|
...rest
|
|
1043
1051
|
};
|
|
1044
1052
|
} catch (error) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
distinctId: mergedOptions.posthogDistinctId,
|
|
1048
|
-
traceId: mergedOptions.posthogTraceId ?? v4(),
|
|
1053
|
+
await captureAiGeneration(phClient, {
|
|
1054
|
+
...baseOptions,
|
|
1049
1055
|
model: modelId,
|
|
1050
1056
|
provider: provider,
|
|
1051
1057
|
input: mergedOptions.posthogPrivacyMode ? '' : mapVercelPrompt(params.prompt),
|
|
1052
1058
|
output: [],
|
|
1053
1059
|
latency: 0,
|
|
1054
1060
|
baseURL: '',
|
|
1055
|
-
|
|
1061
|
+
modelParameters: getModelParams(mergedParams),
|
|
1056
1062
|
usage: {
|
|
1057
1063
|
inputTokens: 0,
|
|
1058
1064
|
outputTokens: 0
|
|
1059
1065
|
},
|
|
1060
1066
|
error: error,
|
|
1061
|
-
tools: availableTools
|
|
1062
|
-
captureImmediate: mergedOptions.posthogCaptureImmediate
|
|
1067
|
+
tools: availableTools
|
|
1063
1068
|
});
|
|
1064
|
-
throw
|
|
1069
|
+
throw error;
|
|
1065
1070
|
}
|
|
1066
1071
|
},
|
|
1067
1072
|
writable: true,
|