@posthog/ai 8.6.7 → 8.7.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.
@@ -1,6 +1,12 @@
1
1
  import 'uuid';
2
2
  import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
3
3
 
4
+ // Type guards for safer type checking
5
+
6
+ const isObject = value => {
7
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
8
+ };
9
+
4
10
  const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
5
11
  const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
6
12
  class Base64Recognizer {
@@ -225,7 +231,7 @@ function sanitizeValues(obj) {
225
231
  return jsonSafe;
226
232
  }
227
233
 
228
- var version = "8.6.7";
234
+ var version = "8.7.0";
229
235
 
230
236
  const DEFAULT_MAX_DEPTH = 3;
231
237
  const MAX_STACK_LINES = 20;
@@ -655,6 +661,10 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
655
661
  if (additionalTokenData.cacheWriteInputTokens) {
656
662
  eventProperties['$ai_cache_creation_input_tokens'] = additionalTokenData.cacheWriteInputTokens;
657
663
  }
664
+ if (additionalTokenData.cacheWrite5mInputTokens !== undefined && additionalTokenData.cacheWrite1hInputTokens !== undefined) {
665
+ eventProperties['$ai_cache_creation_5m_input_tokens'] = additionalTokenData.cacheWrite5mInputTokens;
666
+ eventProperties['$ai_cache_creation_1h_input_tokens'] = additionalTokenData.cacheWrite1hInputTokens;
667
+ }
658
668
  if (additionalTokenData.reasoningTokens) {
659
669
  eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
660
670
  }
@@ -824,7 +834,52 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
824
834
  const stopReason = messageResponseMetadata?.finish_reason || messageResponseMetadata?.stop_reason || gen.generationInfo?.finish_reason || generationResponseMetadata?.stop_reason || generationResponseMetadata?.finish_reason || gen.generationInfo?.stop_reason;
825
835
  return stopReason != null ? String(stopReason) : undefined;
826
836
  }
827
- _parseUsageModel(usage, provider, model) {
837
+ _extractCacheCreationTtlBreakdown(cacheCreation, aggregateValues) {
838
+ if (!isObject(cacheCreation)) {
839
+ return undefined;
840
+ }
841
+ const {
842
+ ephemeral_5m_input_tokens: cache5m,
843
+ ephemeral_1h_input_tokens: cache1h
844
+ } = cacheCreation;
845
+ const providedValues = [cache5m, cache1h].filter(value => value != null);
846
+ if (providedValues.length === 0 || !providedValues.every(value => typeof value === 'number' && Number.isFinite(value) && value >= 0)) {
847
+ return undefined;
848
+ }
849
+ const breakdown = [typeof cache5m === 'number' ? cache5m : 0, typeof cache1h === 'number' ? cache1h : 0];
850
+ const total = breakdown[0] + breakdown[1];
851
+ const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
852
+ return total > 0 && !validAggregates.some(aggregate => aggregate !== total) ? breakdown : undefined;
853
+ }
854
+ _extractBedrockCacheCreationTtlBreakdown(cacheDetails, aggregateValues) {
855
+ if (!Array.isArray(cacheDetails)) {
856
+ return undefined;
857
+ }
858
+ let cache5m = 0;
859
+ let cache1h = 0;
860
+ for (const detail of cacheDetails) {
861
+ if (!isObject(detail)) {
862
+ continue;
863
+ }
864
+ const ttl = typeof detail.ttl === 'string' ? detail.ttl.toLowerCase() : undefined;
865
+ const inputTokens = detail.inputTokens;
866
+ if (ttl !== '5m' && ttl !== 't5m' && ttl !== '1h' && ttl !== 't1h' || typeof inputTokens !== 'number' || !Number.isFinite(inputTokens) || inputTokens < 0) {
867
+ continue;
868
+ }
869
+ if (ttl === '5m' || ttl === 't5m') {
870
+ cache5m += inputTokens;
871
+ } else {
872
+ cache1h += inputTokens;
873
+ }
874
+ }
875
+ const total = cache5m + cache1h;
876
+ const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
877
+ if (total === 0 || validAggregates.some(aggregate => aggregate !== total)) {
878
+ return undefined;
879
+ }
880
+ return [cache5m, cache1h];
881
+ }
882
+ _parseUsageModel(usage, provider, model, inputIncludesCacheTokens = true, rawUsage) {
828
883
  const conversionList = [['promptTokens', 'input'], ['completionTokens', 'output'], ['input_tokens', 'input'], ['output_tokens', 'output'], ['prompt_token_count', 'input'], ['candidates_token_count', 'output'], ['inputTokenCount', 'input'], ['outputTokenCount', 'output'], ['input_token_count', 'input'], ['generated_token_count', 'output']];
829
884
  const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {
830
885
  const value = usage[modelKey];
@@ -858,6 +913,14 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
858
913
  } else if (usage.input_token_details?.cache_creation != null) {
859
914
  additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
860
915
  }
916
+ const directCacheCreationAggregates = [usage.cache_creation_input_tokens, usage.input_token_details?.cache_creation, usage.cacheWriteInputTokens, rawUsage?.cache_creation_input_tokens, rawUsage?.input_token_details?.cache_creation, rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens];
917
+ const cacheCreationTtl = this._extractCacheCreationTtlBreakdown(usage.cache_creation, directCacheCreationAggregates) ?? this._extractCacheCreationTtlBreakdown(rawUsage?.cache_creation, directCacheCreationAggregates) ?? this._extractBedrockCacheCreationTtlBreakdown(usage.cacheDetails, [usage.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]) ?? this._extractBedrockCacheCreationTtlBreakdown(rawUsage?.cacheDetails, [rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]);
918
+ if (cacheCreationTtl) {
919
+ const [cacheWrite5mInputTokens, cacheWrite1hInputTokens] = cacheCreationTtl;
920
+ additionalTokenData.cacheWrite5mInputTokens = cacheWrite5mInputTokens;
921
+ additionalTokenData.cacheWrite1hInputTokens = cacheWrite1hInputTokens;
922
+ additionalTokenData.cacheWriteInputTokens = cacheWrite5mInputTokens + cacheWrite1hInputTokens;
923
+ }
861
924
 
862
925
  // Check for reasoning tokens in various formats
863
926
  if (usage.completion_tokens_details?.reasoning_tokens != null) {
@@ -918,7 +981,7 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
918
981
  } else if (model && model.toLowerCase().includes('anthropic')) {
919
982
  isAnthropic = true;
920
983
  }
921
- if (isAnthropic && parsedUsage.input) {
984
+ if (isAnthropic && inputIncludesCacheTokens && parsedUsage.input) {
922
985
  const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
923
986
  if (cacheTokens > 0) {
924
987
  parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
@@ -927,32 +990,49 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
927
990
  return [parsedUsage.input, parsedUsage.output, additionalTokenData];
928
991
  }
929
992
  parseUsage(response, provider, model) {
930
- let llmUsage = [0, 0, {}];
931
- const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
932
- if (response.llmOutput != null) {
933
- const key = llmUsageKeys.find(k => response.llmOutput?.[k] != null);
934
- if (key) {
935
- llmUsage = this._parseUsageModel(response.llmOutput[key], provider, model);
993
+ const isNonEmptyUsage = usage => isObject(usage) && Object.keys(usage).length > 0;
994
+ const firstNonEmptyUsage = (...candidates) => candidates.find(isNonEmptyUsage);
995
+ let normalizedGenerationUsage;
996
+ let rawGenerationUsage;
997
+ let fallbackGenerationUsage;
998
+ for (const generation of response.generations ?? []) {
999
+ for (const genChunk of generation) {
1000
+ const generationInfo = genChunk.generationInfo ?? {};
1001
+ const message = 'message' in genChunk ? genChunk.message : undefined;
1002
+ const messageUsage = message && typeof message === 'object' && 'usage_metadata' in message ? message.usage_metadata : undefined;
1003
+ normalizedGenerationUsage = firstNonEmptyUsage(normalizedGenerationUsage, messageUsage, generationInfo.usage_metadata);
1004
+ const messageResponseMetadata = message && typeof message === 'object' && 'response_metadata' in message && isObject(message.response_metadata) ? message.response_metadata : undefined;
1005
+ const generationResponseMetadata = isObject(generationInfo.response_metadata) ? generationInfo.response_metadata : undefined;
1006
+ const messageStreamMetadata = isObject(messageResponseMetadata?.metadata) ? messageResponseMetadata.metadata : undefined;
1007
+ const generationStreamMetadata = isObject(generationResponseMetadata?.metadata) ? generationResponseMetadata.metadata : undefined;
1008
+ rawGenerationUsage = firstNonEmptyUsage(rawGenerationUsage, messageResponseMetadata?.usage, messageStreamMetadata?.usage, generationResponseMetadata?.usage, generationStreamMetadata?.usage);
1009
+ fallbackGenerationUsage = firstNonEmptyUsage(fallbackGenerationUsage, messageResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationInfo.usage_metadata);
936
1010
  }
937
1011
  }
938
-
939
- // If top-level usage info was not found, try checking the generations.
940
- if (llmUsage[0] === 0 && llmUsage[1] === 0 && response.generations) {
941
- for (const generation of response.generations) {
942
- for (const genChunk of generation) {
943
- const message = genChunk.message ?? {};
944
- const messageResponseMetadata = message.response_metadata ?? {};
945
- const generationInfo = genChunk.generationInfo ?? {};
946
- const generationResponseMetadata = generationInfo.response_metadata ?? {};
947
- const chunkUsage = message.usage_metadata ?? messageResponseMetadata['usage'] ?? messageResponseMetadata['amazon-bedrock-invocationMetrics'] ?? generationInfo.usage_metadata ?? generationResponseMetadata['usage'] ?? generationResponseMetadata['amazon-bedrock-invocationMetrics'];
948
- if (chunkUsage) {
949
- llmUsage = this._parseUsageModel(chunkUsage, provider, model);
950
- return llmUsage;
951
- }
1012
+ const isAnthropic = provider?.toLowerCase() === 'anthropic' || model?.toLowerCase().includes('anthropic') === true;
1013
+ if (isAnthropic && isNonEmptyUsage(normalizedGenerationUsage)) {
1014
+ return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
1015
+ }
1016
+ const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
1017
+ if (response.llmOutput != null) {
1018
+ for (const key of llmUsageKeys) {
1019
+ const llmUsage = response.llmOutput[key];
1020
+ if (!isNonEmptyUsage(llmUsage)) {
1021
+ continue;
952
1022
  }
1023
+ return this._parseUsageModel(llmUsage, provider, model, key !== 'usage', llmUsage);
953
1024
  }
954
1025
  }
955
- return llmUsage;
1026
+ if (isNonEmptyUsage(normalizedGenerationUsage)) {
1027
+ return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
1028
+ }
1029
+ if (isNonEmptyUsage(rawGenerationUsage)) {
1030
+ return this._parseUsageModel(rawGenerationUsage, provider, model, false, rawGenerationUsage);
1031
+ }
1032
+ if (isNonEmptyUsage(fallbackGenerationUsage)) {
1033
+ return this._parseUsageModel(fallbackGenerationUsage, provider, model);
1034
+ }
1035
+ return [0, 0, {}];
956
1036
  }
957
1037
  }
958
1038