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