@posthog/ai 6.5.0 → 6.6.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 +13 -3
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +13 -3
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +74 -5
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.mjs +74 -5
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +306 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +306 -28
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +48 -1
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +48 -1
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +151 -15
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.ts +1 -1
- package/dist/openai/index.mjs +151 -15
- package/dist/openai/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +156 -8
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +156 -8
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ function _interopNamespaceDefault(e) {
|
|
|
26
26
|
|
|
27
27
|
var uuid__namespace = /*#__PURE__*/_interopNamespaceDefault(uuid);
|
|
28
28
|
|
|
29
|
-
var version = "6.
|
|
29
|
+
var version = "6.6.0";
|
|
30
30
|
|
|
31
31
|
// Type guards for safer type checking
|
|
32
32
|
const isString = value => {
|
|
@@ -297,6 +297,100 @@ const truncate = input => {
|
|
|
297
297
|
}
|
|
298
298
|
return `${truncatedStr}... [truncated]`;
|
|
299
299
|
};
|
|
300
|
+
/**
|
|
301
|
+
* Calculate web search count from raw API response.
|
|
302
|
+
*
|
|
303
|
+
* Uses a two-tier detection strategy:
|
|
304
|
+
* Priority 1 (Exact Count): Count actual web search calls when available
|
|
305
|
+
* Priority 2 (Binary Detection): Return 1 if web search indicators are present, 0 otherwise
|
|
306
|
+
*
|
|
307
|
+
* @param result - Raw API response from any provider (OpenAI, Perplexity, OpenRouter, Gemini, etc.)
|
|
308
|
+
* @returns Number of web searches performed (exact count or binary 1/0)
|
|
309
|
+
*/
|
|
310
|
+
function calculateWebSearchCount(result) {
|
|
311
|
+
if (!result || typeof result !== 'object') {
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
// Priority 1: Exact Count
|
|
315
|
+
// Check for OpenAI Responses API web_search_call items
|
|
316
|
+
if ('output' in result && Array.isArray(result.output)) {
|
|
317
|
+
let count = 0;
|
|
318
|
+
for (const item of result.output) {
|
|
319
|
+
if (typeof item === 'object' && item !== null && 'type' in item && item.type === 'web_search_call') {
|
|
320
|
+
count++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (count > 0) {
|
|
324
|
+
return count;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Priority 2: Binary Detection (1 or 0)
|
|
328
|
+
// Check for citations at root level (Perplexity)
|
|
329
|
+
if ('citations' in result && Array.isArray(result.citations) && result.citations.length > 0) {
|
|
330
|
+
return 1;
|
|
331
|
+
}
|
|
332
|
+
// Check for search_results at root level (Perplexity via OpenRouter)
|
|
333
|
+
if ('search_results' in result && Array.isArray(result.search_results) && result.search_results.length > 0) {
|
|
334
|
+
return 1;
|
|
335
|
+
}
|
|
336
|
+
// Check for usage.search_context_size (Perplexity via OpenRouter)
|
|
337
|
+
if ('usage' in result && typeof result.usage === 'object' && result.usage !== null) {
|
|
338
|
+
if ('search_context_size' in result.usage && result.usage.search_context_size) {
|
|
339
|
+
return 1;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Check for annotations with url_citation in choices[].message (OpenAI/Perplexity)
|
|
343
|
+
if ('choices' in result && Array.isArray(result.choices)) {
|
|
344
|
+
for (const choice of result.choices) {
|
|
345
|
+
if (typeof choice === 'object' && choice !== null && 'message' in choice) {
|
|
346
|
+
const message = choice.message;
|
|
347
|
+
if (typeof message === 'object' && message !== null && 'annotations' in message) {
|
|
348
|
+
const annotations = message.annotations;
|
|
349
|
+
if (Array.isArray(annotations)) {
|
|
350
|
+
const hasUrlCitation = annotations.some(ann => {
|
|
351
|
+
return typeof ann === 'object' && ann !== null && 'type' in ann && ann.type === 'url_citation';
|
|
352
|
+
});
|
|
353
|
+
if (hasUrlCitation) {
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Check for annotations in output[].content[] (OpenAI Responses API)
|
|
362
|
+
if ('output' in result && Array.isArray(result.output)) {
|
|
363
|
+
for (const item of result.output) {
|
|
364
|
+
if (typeof item === 'object' && item !== null && 'content' in item) {
|
|
365
|
+
const content = item.content;
|
|
366
|
+
if (Array.isArray(content)) {
|
|
367
|
+
for (const contentItem of content) {
|
|
368
|
+
if (typeof contentItem === 'object' && contentItem !== null && 'annotations' in contentItem) {
|
|
369
|
+
const annotations = contentItem.annotations;
|
|
370
|
+
if (Array.isArray(annotations)) {
|
|
371
|
+
const hasUrlCitation = annotations.some(ann => {
|
|
372
|
+
return typeof ann === 'object' && ann !== null && 'type' in ann && ann.type === 'url_citation';
|
|
373
|
+
});
|
|
374
|
+
if (hasUrlCitation) {
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Check for grounding_metadata (Gemini)
|
|
385
|
+
if ('candidates' in result && Array.isArray(result.candidates)) {
|
|
386
|
+
for (const candidate of result.candidates) {
|
|
387
|
+
if (typeof candidate === 'object' && candidate !== null && 'grounding_metadata' in candidate && candidate.grounding_metadata) {
|
|
388
|
+
return 1;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return 0;
|
|
393
|
+
}
|
|
300
394
|
/**
|
|
301
395
|
* Extract available tool calls from the request parameters.
|
|
302
396
|
* These are the tools provided to the LLM, not the tool calls in the response.
|
|
@@ -431,6 +525,9 @@ const sendEventToPosthog = async ({
|
|
|
431
525
|
} : {}),
|
|
432
526
|
...(usage.cacheCreationInputTokens ? {
|
|
433
527
|
$ai_cache_creation_input_tokens: usage.cacheCreationInputTokens
|
|
528
|
+
} : {}),
|
|
529
|
+
...(usage.webSearchCount ? {
|
|
530
|
+
$ai_web_search_count: usage.webSearchCount
|
|
434
531
|
} : {})
|
|
435
532
|
};
|
|
436
533
|
const properties = {
|
|
@@ -741,12 +838,17 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
|
|
|
741
838
|
let accumulatedContent = '';
|
|
742
839
|
let usage = {
|
|
743
840
|
inputTokens: 0,
|
|
744
|
-
outputTokens: 0
|
|
841
|
+
outputTokens: 0,
|
|
842
|
+
webSearchCount: 0
|
|
745
843
|
};
|
|
746
844
|
// Map to track in-progress tool calls
|
|
747
845
|
const toolCallsInProgress = new Map();
|
|
748
846
|
for await (const chunk of stream1) {
|
|
749
847
|
const choice = chunk?.choices?.[0];
|
|
848
|
+
const chunkWebSearchCount = calculateWebSearchCount(chunk);
|
|
849
|
+
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
850
|
+
usage.webSearchCount = chunkWebSearchCount;
|
|
851
|
+
}
|
|
750
852
|
// Handle text content
|
|
751
853
|
const deltaContent = choice?.delta?.content;
|
|
752
854
|
if (deltaContent) {
|
|
@@ -785,6 +887,7 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
|
|
|
785
887
|
// Handle usage information
|
|
786
888
|
if (chunk.usage) {
|
|
787
889
|
usage = {
|
|
890
|
+
...usage,
|
|
788
891
|
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
789
892
|
outputTokens: chunk.usage.completion_tokens ?? 0,
|
|
790
893
|
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
@@ -836,7 +939,13 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
|
|
|
836
939
|
baseURL: this.baseURL,
|
|
837
940
|
params: body,
|
|
838
941
|
httpStatus: 200,
|
|
839
|
-
usage
|
|
942
|
+
usage: {
|
|
943
|
+
inputTokens: usage.inputTokens,
|
|
944
|
+
outputTokens: usage.outputTokens,
|
|
945
|
+
reasoningTokens: usage.reasoningTokens,
|
|
946
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
947
|
+
webSearchCount: usage.webSearchCount
|
|
948
|
+
},
|
|
840
949
|
tools: availableTools
|
|
841
950
|
});
|
|
842
951
|
} catch (error) {
|
|
@@ -871,13 +980,14 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
|
|
|
871
980
|
if ('choices' in result) {
|
|
872
981
|
const latency = (Date.now() - startTime) / 1000;
|
|
873
982
|
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
983
|
+
const formattedOutput = formatResponseOpenAI(result);
|
|
874
984
|
await sendEventToPosthog({
|
|
875
985
|
client: this.phClient,
|
|
876
986
|
...posthogParams,
|
|
877
987
|
model: openAIParams.model,
|
|
878
988
|
provider: 'openai',
|
|
879
989
|
input: sanitizeOpenAI(openAIParams.messages),
|
|
880
|
-
output:
|
|
990
|
+
output: formattedOutput,
|
|
881
991
|
latency,
|
|
882
992
|
baseURL: this.baseURL,
|
|
883
993
|
params: body,
|
|
@@ -886,7 +996,8 @@ let WrappedCompletions$1 = class WrappedCompletions extends Completions {
|
|
|
886
996
|
inputTokens: result.usage?.prompt_tokens ?? 0,
|
|
887
997
|
outputTokens: result.usage?.completion_tokens ?? 0,
|
|
888
998
|
reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
889
|
-
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
|
|
999
|
+
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
|
|
1000
|
+
webSearchCount: calculateWebSearchCount(result)
|
|
890
1001
|
},
|
|
891
1002
|
tools: availableTools
|
|
892
1003
|
});
|
|
@@ -941,14 +1052,22 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
941
1052
|
let finalContent = [];
|
|
942
1053
|
let usage = {
|
|
943
1054
|
inputTokens: 0,
|
|
944
|
-
outputTokens: 0
|
|
1055
|
+
outputTokens: 0,
|
|
1056
|
+
webSearchCount: 0
|
|
945
1057
|
};
|
|
946
1058
|
for await (const chunk of stream1) {
|
|
1059
|
+
if ('response' in chunk && chunk.response) {
|
|
1060
|
+
const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
|
|
1061
|
+
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
1062
|
+
usage.webSearchCount = chunkWebSearchCount;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
947
1065
|
if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
|
|
948
1066
|
finalContent = chunk.response.output;
|
|
949
1067
|
}
|
|
950
1068
|
if ('response' in chunk && chunk.response?.usage) {
|
|
951
1069
|
usage = {
|
|
1070
|
+
...usage,
|
|
952
1071
|
inputTokens: chunk.response.usage.input_tokens ?? 0,
|
|
953
1072
|
outputTokens: chunk.response.usage.output_tokens ?? 0,
|
|
954
1073
|
reasoningTokens: chunk.response.usage.output_tokens_details?.reasoning_tokens ?? 0,
|
|
@@ -970,7 +1089,13 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
970
1089
|
baseURL: this.baseURL,
|
|
971
1090
|
params: body,
|
|
972
1091
|
httpStatus: 200,
|
|
973
|
-
usage
|
|
1092
|
+
usage: {
|
|
1093
|
+
inputTokens: usage.inputTokens,
|
|
1094
|
+
outputTokens: usage.outputTokens,
|
|
1095
|
+
reasoningTokens: usage.reasoningTokens,
|
|
1096
|
+
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
1097
|
+
webSearchCount: usage.webSearchCount
|
|
1098
|
+
},
|
|
974
1099
|
tools: availableTools
|
|
975
1100
|
});
|
|
976
1101
|
} catch (error) {
|
|
@@ -1005,6 +1130,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
1005
1130
|
if ('output' in result) {
|
|
1006
1131
|
const latency = (Date.now() - startTime) / 1000;
|
|
1007
1132
|
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
1133
|
+
const formattedOutput = formatResponseOpenAI({
|
|
1134
|
+
output: result.output
|
|
1135
|
+
});
|
|
1008
1136
|
await sendEventToPosthog({
|
|
1009
1137
|
client: this.phClient,
|
|
1010
1138
|
...posthogParams,
|
|
@@ -1012,9 +1140,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
1012
1140
|
model: openAIParams.model,
|
|
1013
1141
|
provider: 'openai',
|
|
1014
1142
|
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
1015
|
-
output:
|
|
1016
|
-
output: result.output
|
|
1017
|
-
}),
|
|
1143
|
+
output: formattedOutput,
|
|
1018
1144
|
latency,
|
|
1019
1145
|
baseURL: this.baseURL,
|
|
1020
1146
|
params: body,
|
|
@@ -1023,7 +1149,8 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
1023
1149
|
inputTokens: result.usage?.input_tokens ?? 0,
|
|
1024
1150
|
outputTokens: result.usage?.output_tokens ?? 0,
|
|
1025
1151
|
reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
|
1026
|
-
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
|
|
1152
|
+
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
|
|
1153
|
+
webSearchCount: calculateWebSearchCount(result)
|
|
1027
1154
|
},
|
|
1028
1155
|
tools: availableTools
|
|
1029
1156
|
});
|
|
@@ -1061,9 +1188,9 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
1061
1188
|
} = extractPosthogParams(body);
|
|
1062
1189
|
const startTime = Date.now();
|
|
1063
1190
|
const originalCreate = super.create.bind(this);
|
|
1064
|
-
const
|
|
1065
|
-
const tempCreate =
|
|
1066
|
-
|
|
1191
|
+
const originalSelfRecord = this;
|
|
1192
|
+
const tempCreate = originalSelfRecord['create'];
|
|
1193
|
+
originalSelfRecord['create'] = originalCreate;
|
|
1067
1194
|
try {
|
|
1068
1195
|
const parentPromise = super.parse(openAIParams, options);
|
|
1069
1196
|
const wrappedPromise = parentPromise.then(async result => {
|
|
@@ -1112,7 +1239,7 @@ let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
|
1112
1239
|
return wrappedPromise;
|
|
1113
1240
|
} finally {
|
|
1114
1241
|
// Restore our wrapped create method
|
|
1115
|
-
|
|
1242
|
+
originalSelfRecord['create'] = tempCreate;
|
|
1116
1243
|
}
|
|
1117
1244
|
}
|
|
1118
1245
|
};
|
|
@@ -1858,11 +1985,29 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
1858
1985
|
cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
|
|
1859
1986
|
} : {})
|
|
1860
1987
|
};
|
|
1988
|
+
// Calculate web search count based on provider
|
|
1989
|
+
let webSearchCount = 0;
|
|
1990
|
+
if (providerMetadata?.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
|
|
1991
|
+
// Anthropic-specific extraction
|
|
1992
|
+
const serverToolUse = providerMetadata.anthropic.server_tool_use;
|
|
1993
|
+
if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
|
|
1994
|
+
webSearchCount = serverToolUse.web_search_requests;
|
|
1995
|
+
}
|
|
1996
|
+
} else {
|
|
1997
|
+
// For other providers through Vercel, pass available metadata to helper
|
|
1998
|
+
// Note: Vercel abstracts provider responses, so we may not have access to
|
|
1999
|
+
// raw citations/annotations unless Vercel exposes them in usage/metadata
|
|
2000
|
+
webSearchCount = calculateWebSearchCount({
|
|
2001
|
+
usage: result.usage,
|
|
2002
|
+
providerMetadata: providerMetadata
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
1861
2005
|
const usage = {
|
|
1862
2006
|
inputTokens: result.usage.inputTokens,
|
|
1863
2007
|
outputTokens: result.usage.outputTokens,
|
|
1864
2008
|
reasoningTokens: result.usage.reasoningTokens,
|
|
1865
2009
|
cacheReadInputTokens: result.usage.cachedInputTokens,
|
|
2010
|
+
webSearchCount,
|
|
1866
2011
|
...additionalTokenValues
|
|
1867
2012
|
};
|
|
1868
2013
|
await sendEventToPosthog({
|
|
@@ -1916,6 +2061,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
1916
2061
|
let generatedText = '';
|
|
1917
2062
|
let reasoningText = '';
|
|
1918
2063
|
let usage = {};
|
|
2064
|
+
let providerMetadata = undefined;
|
|
1919
2065
|
const mergedParams = {
|
|
1920
2066
|
...options,
|
|
1921
2067
|
...mapVercelParams(params),
|
|
@@ -1973,12 +2119,10 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
1973
2119
|
});
|
|
1974
2120
|
}
|
|
1975
2121
|
if (chunk.type === 'finish') {
|
|
1976
|
-
|
|
1977
|
-
const additionalTokenValues = {
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
} : {})
|
|
1981
|
-
};
|
|
2122
|
+
providerMetadata = chunk.providerMetadata;
|
|
2123
|
+
const additionalTokenValues = providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'cacheCreationInputTokens' in providerMetadata.anthropic ? {
|
|
2124
|
+
cacheCreationInputTokens: providerMetadata.anthropic.cacheCreationInputTokens
|
|
2125
|
+
} : {};
|
|
1982
2126
|
usage = {
|
|
1983
2127
|
inputTokens: chunk.usage?.inputTokens,
|
|
1984
2128
|
outputTokens: chunk.usage?.outputTokens,
|
|
@@ -2023,6 +2167,28 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
2023
2167
|
role: 'assistant',
|
|
2024
2168
|
content: content.length === 1 && content[0].type === 'text' ? content[0].text : content
|
|
2025
2169
|
}] : [];
|
|
2170
|
+
// Calculate web search count based on provider
|
|
2171
|
+
let webSearchCount = 0;
|
|
2172
|
+
if (providerMetadata && typeof providerMetadata === 'object' && 'anthropic' in providerMetadata && providerMetadata.anthropic && typeof providerMetadata.anthropic === 'object' && 'server_tool_use' in providerMetadata.anthropic) {
|
|
2173
|
+
// Anthropic-specific extraction
|
|
2174
|
+
const serverToolUse = providerMetadata.anthropic.server_tool_use;
|
|
2175
|
+
if (serverToolUse && typeof serverToolUse === 'object' && 'web_search_requests' in serverToolUse && typeof serverToolUse.web_search_requests === 'number') {
|
|
2176
|
+
webSearchCount = serverToolUse.web_search_requests;
|
|
2177
|
+
}
|
|
2178
|
+
} else {
|
|
2179
|
+
// For other providers through Vercel, pass available metadata to helper
|
|
2180
|
+
// Note: Vercel abstracts provider responses, so we may not have access to
|
|
2181
|
+
// raw citations/annotations unless Vercel exposes them in usage/metadata
|
|
2182
|
+
webSearchCount = calculateWebSearchCount({
|
|
2183
|
+
usage: usage,
|
|
2184
|
+
providerMetadata: providerMetadata
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
// Update usage with web search count
|
|
2188
|
+
const finalUsage = {
|
|
2189
|
+
...usage,
|
|
2190
|
+
webSearchCount
|
|
2191
|
+
};
|
|
2026
2192
|
await sendEventToPosthog({
|
|
2027
2193
|
client: phClient,
|
|
2028
2194
|
distinctId: options.posthogDistinctId,
|
|
@@ -2035,7 +2201,7 @@ const createInstrumentationMiddleware = (phClient, model, options) => {
|
|
|
2035
2201
|
baseURL,
|
|
2036
2202
|
params: mergedParams,
|
|
2037
2203
|
httpStatus: 200,
|
|
2038
|
-
usage,
|
|
2204
|
+
usage: finalUsage,
|
|
2039
2205
|
tools: availableTools,
|
|
2040
2206
|
captureImmediate: options.posthogCaptureImmediate
|
|
2041
2207
|
});
|
|
@@ -2121,7 +2287,8 @@ class WrappedMessages extends AnthropicOriginal.Messages {
|
|
|
2121
2287
|
inputTokens: 0,
|
|
2122
2288
|
outputTokens: 0,
|
|
2123
2289
|
cacheCreationInputTokens: 0,
|
|
2124
|
-
cacheReadInputTokens: 0
|
|
2290
|
+
cacheReadInputTokens: 0,
|
|
2291
|
+
webSearchCount: 0
|
|
2125
2292
|
};
|
|
2126
2293
|
if ('tee' in value) {
|
|
2127
2294
|
const [stream1, stream2] = value.tee();
|
|
@@ -2198,9 +2365,14 @@ class WrappedMessages extends AnthropicOriginal.Messages {
|
|
|
2198
2365
|
usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
|
|
2199
2366
|
usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
|
|
2200
2367
|
usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
|
|
2368
|
+
usage.webSearchCount = chunk.message.usage.server_tool_use?.web_search_requests ?? 0;
|
|
2201
2369
|
}
|
|
2202
2370
|
if ('usage' in chunk) {
|
|
2203
2371
|
usage.outputTokens = chunk.usage.output_tokens ?? 0;
|
|
2372
|
+
// Update web search count if present in delta
|
|
2373
|
+
if (chunk.usage.server_tool_use?.web_search_requests !== undefined) {
|
|
2374
|
+
usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
|
|
2375
|
+
}
|
|
2204
2376
|
}
|
|
2205
2377
|
}
|
|
2206
2378
|
const latency = (Date.now() - startTime) / 1000;
|
|
@@ -2277,7 +2449,8 @@ class WrappedMessages extends AnthropicOriginal.Messages {
|
|
|
2277
2449
|
inputTokens: result.usage.input_tokens ?? 0,
|
|
2278
2450
|
outputTokens: result.usage.output_tokens ?? 0,
|
|
2279
2451
|
cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
|
|
2280
|
-
cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0
|
|
2452
|
+
cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0,
|
|
2453
|
+
webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0
|
|
2281
2454
|
},
|
|
2282
2455
|
tools: availableTools
|
|
2283
2456
|
});
|
|
@@ -2351,7 +2524,8 @@ class WrappedModels {
|
|
|
2351
2524
|
inputTokens: metadata?.promptTokenCount ?? 0,
|
|
2352
2525
|
outputTokens: metadata?.candidatesTokenCount ?? 0,
|
|
2353
2526
|
reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
|
|
2354
|
-
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0
|
|
2527
|
+
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
|
|
2528
|
+
webSearchCount: calculateGoogleWebSearchCount(response)
|
|
2355
2529
|
},
|
|
2356
2530
|
tools: availableTools
|
|
2357
2531
|
});
|
|
@@ -2388,11 +2562,16 @@ class WrappedModels {
|
|
|
2388
2562
|
const accumulatedContent = [];
|
|
2389
2563
|
let usage = {
|
|
2390
2564
|
inputTokens: 0,
|
|
2391
|
-
outputTokens: 0
|
|
2565
|
+
outputTokens: 0,
|
|
2566
|
+
webSearchCount: 0
|
|
2392
2567
|
};
|
|
2393
2568
|
try {
|
|
2394
2569
|
const stream = await this.client.models.generateContentStream(geminiParams);
|
|
2395
2570
|
for await (const chunk of stream) {
|
|
2571
|
+
const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
|
|
2572
|
+
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
2573
|
+
usage.webSearchCount = chunkWebSearchCount;
|
|
2574
|
+
}
|
|
2396
2575
|
// Handle text content
|
|
2397
2576
|
if (chunk.text) {
|
|
2398
2577
|
// Find if we already have a text item to append to
|
|
@@ -2441,7 +2620,8 @@ class WrappedModels {
|
|
|
2441
2620
|
inputTokens: metadata.promptTokenCount ?? 0,
|
|
2442
2621
|
outputTokens: metadata.candidatesTokenCount ?? 0,
|
|
2443
2622
|
reasoningTokens: metadata.thoughtsTokenCount ?? 0,
|
|
2444
|
-
cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0
|
|
2623
|
+
cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0,
|
|
2624
|
+
webSearchCount: usage.webSearchCount
|
|
2445
2625
|
};
|
|
2446
2626
|
}
|
|
2447
2627
|
yield chunk;
|
|
@@ -2464,7 +2644,10 @@ class WrappedModels {
|
|
|
2464
2644
|
baseURL: 'https://generativelanguage.googleapis.com',
|
|
2465
2645
|
params: params,
|
|
2466
2646
|
httpStatus: 200,
|
|
2467
|
-
usage
|
|
2647
|
+
usage: {
|
|
2648
|
+
...usage,
|
|
2649
|
+
webSearchCount: usage.webSearchCount
|
|
2650
|
+
},
|
|
2468
2651
|
tools: availableTools
|
|
2469
2652
|
});
|
|
2470
2653
|
} catch (error) {
|
|
@@ -2607,6 +2790,57 @@ class WrappedModels {
|
|
|
2607
2790
|
return messages;
|
|
2608
2791
|
}
|
|
2609
2792
|
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Detect if Google Search grounding was used in the response.
|
|
2795
|
+
* Gemini bills per request that uses grounding, not per individual query.
|
|
2796
|
+
* Returns 1 if grounding was used, 0 otherwise.
|
|
2797
|
+
*/
|
|
2798
|
+
function calculateGoogleWebSearchCount(response) {
|
|
2799
|
+
if (!response || typeof response !== 'object' || !('candidates' in response)) {
|
|
2800
|
+
return 0;
|
|
2801
|
+
}
|
|
2802
|
+
const candidates = response.candidates;
|
|
2803
|
+
if (!Array.isArray(candidates)) {
|
|
2804
|
+
return 0;
|
|
2805
|
+
}
|
|
2806
|
+
const hasGrounding = candidates.some(candidate => {
|
|
2807
|
+
if (!candidate || typeof candidate !== 'object') {
|
|
2808
|
+
return false;
|
|
2809
|
+
}
|
|
2810
|
+
// Check for grounding metadata
|
|
2811
|
+
if ('groundingMetadata' in candidate && candidate.groundingMetadata) {
|
|
2812
|
+
const metadata = candidate.groundingMetadata;
|
|
2813
|
+
if (typeof metadata === 'object') {
|
|
2814
|
+
// Check if web_search_queries exists and is non-empty
|
|
2815
|
+
if ('webSearchQueries' in metadata && Array.isArray(metadata.webSearchQueries) && metadata.webSearchQueries.length > 0) {
|
|
2816
|
+
return true;
|
|
2817
|
+
}
|
|
2818
|
+
// Check if grounding_chunks exists and is non-empty
|
|
2819
|
+
if ('groundingChunks' in metadata && Array.isArray(metadata.groundingChunks) && metadata.groundingChunks.length > 0) {
|
|
2820
|
+
return true;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
// Check for google search in function calls
|
|
2825
|
+
if ('content' in candidate && candidate.content && typeof candidate.content === 'object') {
|
|
2826
|
+
const content = candidate.content;
|
|
2827
|
+
if ('parts' in content && Array.isArray(content.parts)) {
|
|
2828
|
+
return content.parts.some(part => {
|
|
2829
|
+
if (!part || typeof part !== 'object' || !('functionCall' in part)) {
|
|
2830
|
+
return false;
|
|
2831
|
+
}
|
|
2832
|
+
const functionCall = part.functionCall;
|
|
2833
|
+
if (functionCall && typeof functionCall === 'object' && 'name' in functionCall && typeof functionCall.name === 'string') {
|
|
2834
|
+
return functionCall.name.includes('google_search') || functionCall.name.includes('grounding');
|
|
2835
|
+
}
|
|
2836
|
+
return false;
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
return false;
|
|
2841
|
+
});
|
|
2842
|
+
return hasGrounding ? 1 : 0;
|
|
2843
|
+
}
|
|
2610
2844
|
|
|
2611
2845
|
function getDefaultExportFromCjs (x) {
|
|
2612
2846
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
@@ -3427,6 +3661,9 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
|
|
|
3427
3661
|
if (additionalTokenData.reasoningTokens) {
|
|
3428
3662
|
eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
|
|
3429
3663
|
}
|
|
3664
|
+
if (additionalTokenData.webSearchCount !== undefined) {
|
|
3665
|
+
eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
|
|
3666
|
+
}
|
|
3430
3667
|
// Handle generations/completions
|
|
3431
3668
|
let completions;
|
|
3432
3669
|
if (output.generations && Array.isArray(output.generations)) {
|
|
@@ -3592,6 +3829,47 @@ class LangChainCallbackHandler extends BaseCallbackHandler {
|
|
|
3592
3829
|
} else if (usage.reasoningTokens != null) {
|
|
3593
3830
|
additionalTokenData.reasoningTokens = usage.reasoningTokens;
|
|
3594
3831
|
}
|
|
3832
|
+
// Extract web search counts from various provider formats
|
|
3833
|
+
let webSearchCount;
|
|
3834
|
+
// Priority 1: Exact Count
|
|
3835
|
+
// Check Anthropic format (server_tool_use.web_search_requests)
|
|
3836
|
+
if (usage.server_tool_use?.web_search_requests !== undefined) {
|
|
3837
|
+
webSearchCount = usage.server_tool_use.web_search_requests;
|
|
3838
|
+
}
|
|
3839
|
+
// Priority 2: Binary Detection (1 or 0)
|
|
3840
|
+
// Check for citations array (Perplexity)
|
|
3841
|
+
else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) {
|
|
3842
|
+
webSearchCount = 1;
|
|
3843
|
+
}
|
|
3844
|
+
// Check for search_results array (Perplexity via OpenRouter)
|
|
3845
|
+
else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) {
|
|
3846
|
+
webSearchCount = 1;
|
|
3847
|
+
}
|
|
3848
|
+
// Check for search_context_size (Perplexity via OpenRouter)
|
|
3849
|
+
else if (usage.search_context_size) {
|
|
3850
|
+
webSearchCount = 1;
|
|
3851
|
+
}
|
|
3852
|
+
// Check for annotations with url_citation type
|
|
3853
|
+
else if (usage.annotations && Array.isArray(usage.annotations)) {
|
|
3854
|
+
const hasUrlCitation = usage.annotations.some(ann => {
|
|
3855
|
+
return ann && typeof ann === 'object' && 'type' in ann && ann.type === 'url_citation';
|
|
3856
|
+
});
|
|
3857
|
+
if (hasUrlCitation) {
|
|
3858
|
+
webSearchCount = 1;
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
// Check Gemini format (grounding metadata - binary 0 or 1)
|
|
3862
|
+
else if (usage.grounding_metadata?.grounding_support !== undefined || usage.grounding_metadata?.web_search_queries !== undefined) {
|
|
3863
|
+
webSearchCount = 1;
|
|
3864
|
+
}
|
|
3865
|
+
if (webSearchCount !== undefined) {
|
|
3866
|
+
additionalTokenData.webSearchCount = webSearchCount;
|
|
3867
|
+
}
|
|
3868
|
+
// In LangChain, input_tokens is the sum of input and cache read tokens.
|
|
3869
|
+
// Our cost calculation expects them to be separate, for Anthropic.
|
|
3870
|
+
if (parsedUsage.input && additionalTokenData.cacheReadInputTokens) {
|
|
3871
|
+
parsedUsage.input = Math.max(parsedUsage.input - additionalTokenData.cacheReadInputTokens, 0);
|
|
3872
|
+
}
|
|
3595
3873
|
return [parsedUsage.input, parsedUsage.output, additionalTokenData];
|
|
3596
3874
|
}
|
|
3597
3875
|
parseUsage(response) {
|