@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.mjs
CHANGED
|
@@ -8,6 +8,33 @@ const isString = value => {
|
|
|
8
8
|
return typeof value === 'string';
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/** @internal */
|
|
12
|
+
|
|
13
|
+
/** @internal */
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
function isFullAiCaptureEnabled(client) {
|
|
17
|
+
return client?.enableFullAiCapture === true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** @internal */
|
|
21
|
+
function captureAiEvent(client, event) {
|
|
22
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAi === 'function') {
|
|
23
|
+
client.captureAi(event);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
client.capture(event);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @internal */
|
|
30
|
+
async function captureAiEventImmediate(client, event) {
|
|
31
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAiImmediate === 'function') {
|
|
32
|
+
await client.captureAiImmediate(event);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await client.captureImmediate(event);
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
12
39
|
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
13
40
|
class Base64Recognizer {
|
|
@@ -113,7 +140,6 @@ class BinaryContentRedactor {
|
|
|
113
140
|
this.recognizer = recognizer;
|
|
114
141
|
}
|
|
115
142
|
redact(value, mediaType) {
|
|
116
|
-
if (this.isMultimodalEnabled()) return value;
|
|
117
143
|
this.visited = new WeakSet();
|
|
118
144
|
return this.walk(value, mediaType ? new MediaTypeContext(undefined, undefined, mediaType) : MediaTypeContext.EMPTY);
|
|
119
145
|
}
|
|
@@ -157,24 +183,26 @@ class BinaryContentRedactor {
|
|
|
157
183
|
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
158
184
|
return `[base64 ${mediaType} redacted]`;
|
|
159
185
|
}
|
|
160
|
-
isMultimodalEnabled() {
|
|
161
|
-
const val = process.env._INTERNAL_LLMA_MULTIMODAL || '';
|
|
162
|
-
return val.toLowerCase() === 'true' || val === '1' || val.toLowerCase() === 'yes';
|
|
163
|
-
}
|
|
164
186
|
}
|
|
165
187
|
|
|
166
188
|
const redactor = new BinaryContentRedactor();
|
|
167
189
|
function redactBase64DataUrl(str, mediaType) {
|
|
168
190
|
return redactor.redact(str, mediaType);
|
|
169
191
|
}
|
|
170
|
-
const
|
|
192
|
+
const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
|
|
193
|
+
const sanitizeGemini = (data, client) => sanitize(data, client);
|
|
171
194
|
|
|
172
195
|
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']);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Whether the caller supplied their own token counts, which override the ones the SDK
|
|
199
|
+
* derived from the provider response.
|
|
200
|
+
*/
|
|
201
|
+
function hasTokenOverrides(posthogProperties) {
|
|
202
|
+
return !!posthogProperties && Object.keys(posthogProperties).some(key => TOKEN_PROPERTY_KEYS.has(key));
|
|
203
|
+
}
|
|
173
204
|
function getTokensSource(posthogProperties) {
|
|
174
|
-
|
|
175
|
-
return 'passthrough';
|
|
176
|
-
}
|
|
177
|
-
return 'sdk';
|
|
205
|
+
return hasTokenOverrides(posthogProperties) ? 'passthrough' : 'sdk';
|
|
178
206
|
}
|
|
179
207
|
const STRING_FORMAT = 'utf8';
|
|
180
208
|
|
|
@@ -248,7 +276,7 @@ const buildInlineDataBlock = (mimeType, data) => {
|
|
|
248
276
|
}
|
|
249
277
|
};
|
|
250
278
|
};
|
|
251
|
-
const formatResponseGemini = response => {
|
|
279
|
+
const formatResponseGemini = (response, client) => {
|
|
252
280
|
const output = [];
|
|
253
281
|
if (response.candidates && Array.isArray(response.candidates)) {
|
|
254
282
|
for (const candidate of response.candidates) {
|
|
@@ -287,7 +315,7 @@ const formatResponseGemini = response => {
|
|
|
287
315
|
}
|
|
288
316
|
|
|
289
317
|
// Sanitize base64 data for images and other large inline data
|
|
290
|
-
data = redactBase64DataUrl(data, mimeType);
|
|
318
|
+
data = isFullAiCaptureEnabled(client) ? data : redactBase64DataUrl(data, mimeType);
|
|
291
319
|
content.push(buildInlineDataBlock(mimeType, data));
|
|
292
320
|
}
|
|
293
321
|
}
|
|
@@ -390,7 +418,7 @@ function addDefaults(params) {
|
|
|
390
418
|
};
|
|
391
419
|
}
|
|
392
420
|
|
|
393
|
-
var version = "8.
|
|
421
|
+
var version = "8.8.0";
|
|
394
422
|
|
|
395
423
|
const DEFAULT_MAX_DEPTH = 3;
|
|
396
424
|
const MAX_STACK_LINES = 20;
|
|
@@ -557,6 +585,10 @@ const captureAiGeneration = async (client, options) => {
|
|
|
557
585
|
$ai_total_cost_usd: inputCostUSD + outputCostUSD
|
|
558
586
|
};
|
|
559
587
|
}
|
|
588
|
+
|
|
589
|
+
// The caller's own token counts override the SDK-derived ones further down, via the
|
|
590
|
+
// `options.properties` spread.
|
|
591
|
+
const tokensOverridden = hasTokenOverrides(options.properties);
|
|
560
592
|
const additionalTokenValues = {
|
|
561
593
|
...(usage.reasoningTokens ? {
|
|
562
594
|
$ai_reasoning_tokens: usage.reasoningTokens
|
|
@@ -567,6 +599,18 @@ const captureAiGeneration = async (client, options) => {
|
|
|
567
599
|
...(usage.cacheCreationInputTokens ? {
|
|
568
600
|
$ai_cache_creation_input_tokens: usage.cacheCreationInputTokens
|
|
569
601
|
} : {}),
|
|
602
|
+
// Checked against undefined rather than truthiness, because false is the meaningful
|
|
603
|
+
// value here and a truthiness guard would drop it.
|
|
604
|
+
//
|
|
605
|
+
// Dropped entirely when the caller overrides the token counts: the flag describes how
|
|
606
|
+
// the SDK-derived counts relate to each other, so against passthrough counts it can be
|
|
607
|
+
// wrong in the expensive direction. Declaring inclusive over counts that are actually
|
|
608
|
+
// exclusive makes ingestion subtract the cache pool that was never in the input. A
|
|
609
|
+
// caller who knows their own accounting model can still pass
|
|
610
|
+
// `$ai_cache_reporting_exclusive` themselves, and that value wins.
|
|
611
|
+
...(usage.cacheReportingExclusive !== undefined && !tokensOverridden ? {
|
|
612
|
+
$ai_cache_reporting_exclusive: usage.cacheReportingExclusive
|
|
613
|
+
} : {}),
|
|
570
614
|
...(usage.webSearchCount ? {
|
|
571
615
|
$ai_web_search_count: usage.webSearchCount
|
|
572
616
|
} : {}),
|
|
@@ -623,9 +667,9 @@ const captureAiGeneration = async (client, options) => {
|
|
|
623
667
|
groups: options.groups
|
|
624
668
|
};
|
|
625
669
|
if (options.captureImmediate) {
|
|
626
|
-
await client
|
|
670
|
+
await captureAiEventImmediate(client, event);
|
|
627
671
|
} else {
|
|
628
|
-
client
|
|
672
|
+
captureAiEvent(client, event);
|
|
629
673
|
}
|
|
630
674
|
} catch (error) {
|
|
631
675
|
// Telemetry failures must never affect the instrumented provider call.
|
|
@@ -666,7 +710,7 @@ class WrappedModels {
|
|
|
666
710
|
model: geminiParams.model,
|
|
667
711
|
provider: 'gemini',
|
|
668
712
|
input: this.formatInputForPostHog(geminiParams),
|
|
669
|
-
output: formatResponseGemini(response),
|
|
713
|
+
output: formatResponseGemini(response, this.phClient),
|
|
670
714
|
latency,
|
|
671
715
|
baseURL: 'https://generativelanguage.googleapis.com',
|
|
672
716
|
modelParameters: getModelParams(params),
|
|
@@ -676,6 +720,13 @@ class WrappedModels {
|
|
|
676
720
|
outputTokens: metadata?.candidatesTokenCount ?? 0,
|
|
677
721
|
reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
|
|
678
722
|
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
|
|
723
|
+
// Gemini counts cachedContentTokenCount inside promptTokenCount, so declare the
|
|
724
|
+
// accounting model rather than leaving ingestion to infer it. Under explicit
|
|
725
|
+
// context caching the two counts come from separate measurements and can disagree
|
|
726
|
+
// by a few percent, which makes inference from the counts alone unreliable.
|
|
727
|
+
...(metadata?.cachedContentTokenCount ? {
|
|
728
|
+
cacheReportingExclusive: false
|
|
729
|
+
} : {}),
|
|
679
730
|
webSearchCount: calculateGoogleWebSearchCount(response),
|
|
680
731
|
rawUsage: metadata
|
|
681
732
|
},
|
|
@@ -789,6 +840,11 @@ class WrappedModels {
|
|
|
789
840
|
outputTokens: metadata.candidatesTokenCount ?? 0,
|
|
790
841
|
reasoningTokens: metadata.thoughtsTokenCount ?? 0,
|
|
791
842
|
cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0,
|
|
843
|
+
// See the non-streaming path: Gemini counts cachedContentTokenCount inside
|
|
844
|
+
// promptTokenCount, so the accounting model is declared rather than inferred.
|
|
845
|
+
...(metadata.cachedContentTokenCount ? {
|
|
846
|
+
cacheReportingExclusive: false
|
|
847
|
+
} : {}),
|
|
792
848
|
webSearchCount: usage.webSearchCount,
|
|
793
849
|
rawUsage: metadata
|
|
794
850
|
};
|
|
@@ -1021,7 +1077,7 @@ class WrappedModels {
|
|
|
1021
1077
|
return null;
|
|
1022
1078
|
}
|
|
1023
1079
|
formatInputForPostHog(params) {
|
|
1024
|
-
const sanitized = sanitizeGemini(params.contents);
|
|
1080
|
+
const sanitized = sanitizeGemini(params.contents, this.phClient);
|
|
1025
1081
|
const messages = this.formatInput(sanitized);
|
|
1026
1082
|
const systemInstruction = this.extractSystemInstruction(params);
|
|
1027
1083
|
if (systemInstruction) {
|