@posthog/ai 8.7.0 → 8.8.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/dist/anthropic/index.cjs +61 -17
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +61 -17
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +73 -17
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +73 -17
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +83 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +83 -39
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +27 -13
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +27 -13
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +91 -47
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.mjs +91 -47
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +25 -4
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.mjs +25 -4
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/otel/index.cjs +1 -5
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.mjs +1 -5
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +92 -39
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +92 -39
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/gemini/index.cjs
CHANGED
|
@@ -12,6 +12,33 @@ const isString = value => {
|
|
|
12
12
|
return typeof value === 'string';
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
/** @internal */
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
function isFullAiCaptureEnabled(client) {
|
|
21
|
+
return client?.enableFullAiCapture === true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @internal */
|
|
25
|
+
function captureAiEvent(client, event) {
|
|
26
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
|
|
27
|
+
client.captureAi(event);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
client.capture(event);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @internal */
|
|
34
|
+
async function captureAiEventImmediate(client, event) {
|
|
35
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === 'function') {
|
|
36
|
+
await client.captureAiImmediate(event);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await client.captureImmediate(event);
|
|
40
|
+
}
|
|
41
|
+
|
|
15
42
|
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
16
43
|
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
17
44
|
class Base64Recognizer {
|
|
@@ -117,7 +144,6 @@ class BinaryContentRedactor {
|
|
|
117
144
|
this.recognizer = recognizer;
|
|
118
145
|
}
|
|
119
146
|
redact(value, mediaType) {
|
|
120
|
-
if (this.isMultimodalEnabled()) return value;
|
|
121
147
|
this.visited = new WeakSet();
|
|
122
148
|
return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
|
|
123
149
|
}
|
|
@@ -161,24 +187,26 @@ class BinaryContentRedactor {
|
|
|
161
187
|
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
162
188
|
return `[base64 ${mediaType} redacted]`;
|
|
163
189
|
}
|
|
164
|
-
isMultimodalEnabled() {
|
|
165
|
-
const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
|
|
166
|
-
return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
|
|
167
|
-
}
|
|
168
190
|
}
|
|
169
191
|
|
|
170
192
|
const redactor = new BinaryContentRedactor();
|
|
171
193
|
function redactBase64DataUrl(str, mediaType) {
|
|
172
194
|
return redactor.redact(str, mediaType);
|
|
173
195
|
}
|
|
174
|
-
const
|
|
196
|
+
const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
|
|
197
|
+
const sanitizeGemini = (data, client) => sanitize(data, client);
|
|
175
198
|
|
|
176
199
|
const TOKEN_PROPERTY_KEYS = new Set(['$ai_input_tokens', '$ai_output_tokens', '$ai_cache_read_input_tokens', '$ai_cache_creation_input_tokens', '$ai_total_tokens', '$ai_reasoning_tokens']);
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Whether the caller supplied their own token counts, which override the ones the SDK
|
|
203
|
+
* derived from the provider response.
|
|
204
|
+
*/
|
|
205
|
+
function hasTokenOverrides(posthogProperties) {
|
|
206
|
+
return !!posthogProperties && Object.keys(posthogProperties).some(key => TOKEN_PROPERTY_KEYS.has(key));
|
|
207
|
+
}
|
|
177
208
|
function getTokensSource(posthogProperties) {
|
|
178
|
-
|
|
179
|
-
return 'passthrough';
|
|
180
|
-
}
|
|
181
|
-
return 'sdk';
|
|
209
|
+
return hasTokenOverrides(posthogProperties) ? 'passthrough' : 'sdk';
|
|
182
210
|
}
|
|
183
211
|
const STRING_FORMAT = 'utf8';
|
|
184
212
|
|
|
@@ -252,7 +280,7 @@ const buildInlineDataBlock = (mimeType, data) => {
|
|
|
252
280
|
}
|
|
253
281
|
};
|
|
254
282
|
};
|
|
255
|
-
const formatResponseGemini = response => {
|
|
283
|
+
const formatResponseGemini = (response, client) => {
|
|
256
284
|
const output = [];
|
|
257
285
|
if (response.candidates && Array.isArray(response.candidates)) {
|
|
258
286
|
for (const candidate of response.candidates) {
|
|
@@ -291,7 +319,7 @@ const formatResponseGemini = response => {
|
|
|
291
319
|
}
|
|
292
320
|
|
|
293
321
|
// Sanitize base64 data for images and other large inline data
|
|
294
|
-
data = redactBase64DataUrl(data, mimeType);
|
|
322
|
+
data = isFullAiCaptureEnabled(client) ? data : redactBase64DataUrl(data, mimeType);
|
|
295
323
|
content.push(buildInlineDataBlock(mimeType, data));
|
|
296
324
|
}
|
|
297
325
|
}
|
|
@@ -394,7 +422,7 @@ function addDefaults(params) {
|
|
|
394
422
|
};
|
|
395
423
|
}
|
|
396
424
|
|
|
397
|
-
var version = "8.
|
|
425
|
+
var version = "8.8.0";
|
|
398
426
|
|
|
399
427
|
const DEFAULT_MAX_DEPTH = 3;
|
|
400
428
|
const MAX_STACK_LINES = 20;
|
|
@@ -561,6 +589,10 @@ const captureAiGeneration = async (client, options) => {
|
|
|
561
589
|
$ai_total_cost_usd: inputCostUSD + outputCostUSD
|
|
562
590
|
};
|
|
563
591
|
}
|
|
592
|
+
|
|
593
|
+
// The caller's own token counts override the SDK-derived ones further down, via the
|
|
594
|
+
// `options.properties` spread.
|
|
595
|
+
const tokensOverridden = hasTokenOverrides(options.properties);
|
|
564
596
|
const additionalTokenValues = {
|
|
565
597
|
...(usage.reasoningTokens ? {
|
|
566
598
|
$ai_reasoning_tokens: usage.reasoningTokens
|
|
@@ -571,6 +603,18 @@ const captureAiGeneration = async (client, options) => {
|
|
|
571
603
|
...(usage.cacheCreationInputTokens ? {
|
|
572
604
|
$ai_cache_creation_input_tokens: usage.cacheCreationInputTokens
|
|
573
605
|
} : {}),
|
|
606
|
+
// Checked against undefined rather than truthiness, because false is the meaningful
|
|
607
|
+
// value here and a truthiness guard would drop it.
|
|
608
|
+
//
|
|
609
|
+
// Dropped entirely when the caller overrides the token counts: the flag describes how
|
|
610
|
+
// the SDK-derived counts relate to each other, so against passthrough counts it can be
|
|
611
|
+
// wrong in the expensive direction. Declaring inclusive over counts that are actually
|
|
612
|
+
// exclusive makes ingestion subtract the cache pool that was never in the input. A
|
|
613
|
+
// caller who knows their own accounting model can still pass
|
|
614
|
+
// `$ai_cache_reporting_exclusive` themselves, and that value wins.
|
|
615
|
+
...(usage.cacheReportingExclusive !== undefined && !tokensOverridden ? {
|
|
616
|
+
$ai_cache_reporting_exclusive: usage.cacheReportingExclusive
|
|
617
|
+
} : {}),
|
|
574
618
|
...(usage.webSearchCount ? {
|
|
575
619
|
$ai_web_search_count: usage.webSearchCount
|
|
576
620
|
} : {}),
|
|
@@ -627,9 +671,9 @@ const captureAiGeneration = async (client, options) => {
|
|
|
627
671
|
groups: options.groups
|
|
628
672
|
};
|
|
629
673
|
if (options.captureImmediate) {
|
|
630
|
-
await client
|
|
674
|
+
await captureAiEventImmediate(client, event);
|
|
631
675
|
} else {
|
|
632
|
-
client
|
|
676
|
+
captureAiEvent(client, event);
|
|
633
677
|
}
|
|
634
678
|
} catch (error) {
|
|
635
679
|
// Telemetry failures must never affect the instrumented provider call.
|
|
@@ -670,7 +714,7 @@ class WrappedModels {
|
|
|
670
714
|
model: geminiParams.model,
|
|
671
715
|
provider: 'gemini',
|
|
672
716
|
input: this.formatInputForPostHog(geminiParams),
|
|
673
|
-
output: formatResponseGemini(response),
|
|
717
|
+
output: formatResponseGemini(response, this.phClient),
|
|
674
718
|
latency,
|
|
675
719
|
baseURL: 'https://generativelanguage.googleapis.com',
|
|
676
720
|
modelParameters: getModelParams(params),
|
|
@@ -680,6 +724,13 @@ class WrappedModels {
|
|
|
680
724
|
outputTokens: metadata?.candidatesTokenCount ?? 0,
|
|
681
725
|
reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
|
|
682
726
|
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
|
|
727
|
+
// Gemini counts cachedContentTokenCount inside promptTokenCount, so declare the
|
|
728
|
+
// accounting model rather than leaving ingestion to infer it. Under explicit
|
|
729
|
+
// context caching the two counts come from separate measurements and can disagree
|
|
730
|
+
// by a few percent, which makes inference from the counts alone unreliable.
|
|
731
|
+
...(metadata?.cachedContentTokenCount ? {
|
|
732
|
+
cacheReportingExclusive: false
|
|
733
|
+
} : {}),
|
|
683
734
|
webSearchCount: calculateGoogleWebSearchCount(response),
|
|
684
735
|
rawUsage: metadata
|
|
685
736
|
},
|
|
@@ -793,6 +844,11 @@ class WrappedModels {
|
|
|
793
844
|
outputTokens: metadata.candidatesTokenCount ?? 0,
|
|
794
845
|
reasoningTokens: metadata.thoughtsTokenCount ?? 0,
|
|
795
846
|
cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0,
|
|
847
|
+
// See the non-streaming path: Gemini counts cachedContentTokenCount inside
|
|
848
|
+
// promptTokenCount, so the accounting model is declared rather than inferred.
|
|
849
|
+
...(metadata.cachedContentTokenCount ? {
|
|
850
|
+
cacheReportingExclusive: false
|
|
851
|
+
} : {}),
|
|
796
852
|
webSearchCount: usage.webSearchCount,
|
|
797
853
|
rawUsage: metadata
|
|
798
854
|
};
|
|
@@ -1025,7 +1081,7 @@ class WrappedModels {
|
|
|
1025
1081
|
return null;
|
|
1026
1082
|
}
|
|
1027
1083
|
formatInputForPostHog(params) {
|
|
1028
|
-
const sanitized = sanitizeGemini(params.contents);
|
|
1084
|
+
const sanitized = sanitizeGemini(params.contents, this.phClient);
|
|
1029
1085
|
const messages = this.formatInput(sanitized);
|
|
1030
1086
|
const systemInstruction = this.extractSystemInstruction(params);
|
|
1031
1087
|
if (systemInstruction) {
|