@posthog/ai 7.20.14 → 8.0.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/README.md +21 -4
- package/dist/anthropic/index.cjs +1 -1
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.mjs +1 -1
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +2 -1
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.d.ts +1 -1
- package/dist/gemini/index.mjs +2 -2
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +72 -3500
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -276
- package/dist/index.mjs +72 -3473
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +4 -486
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.mjs +3 -465
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/openai/index.cjs +529 -1
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.ts +40 -2
- package/dist/openai/index.mjs +530 -3
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +1 -1
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.mjs +1 -1
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/otel/index.cjs +261 -16
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +11 -16
- package/dist/otel/index.mjs +261 -16
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +1 -1
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.mjs +1 -1
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +24 -9
package/dist/index.cjs
CHANGED
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var openai = require('openai');
|
|
4
3
|
var uuid = require('uuid');
|
|
5
4
|
var core = require('@posthog/core');
|
|
6
|
-
var AnthropicOriginal = require('@anthropic-ai/sdk');
|
|
7
|
-
var genai = require('@google/genai');
|
|
8
|
-
|
|
9
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
|
|
11
|
-
function _interopNamespace(e) {
|
|
12
|
-
if (e && e.__esModule) return e;
|
|
13
|
-
var n = Object.create(null);
|
|
14
|
-
if (e) {
|
|
15
|
-
Object.keys(e).forEach(function (k) {
|
|
16
|
-
if (k !== 'default') {
|
|
17
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () { return e[k]; }
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
n.default = e;
|
|
26
|
-
return Object.freeze(n);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
var uuid__namespace = /*#__PURE__*/_interopNamespace(uuid);
|
|
30
|
-
var AnthropicOriginal__default = /*#__PURE__*/_interopDefault(AnthropicOriginal);
|
|
31
5
|
|
|
32
6
|
// Type guards for safer type checking
|
|
33
7
|
const isString = value => {
|
|
@@ -182,11 +156,6 @@ const redactor = new BinaryContentRedactor();
|
|
|
182
156
|
function redactBase64DataUrl(str) {
|
|
183
157
|
return redactor.redact(str);
|
|
184
158
|
}
|
|
185
|
-
const sanitizeOpenAI = data => redactor.redact(data);
|
|
186
|
-
const sanitizeOpenAIResponse = data => redactor.redact(data);
|
|
187
|
-
const sanitizeAnthropic = data => redactor.redact(data);
|
|
188
|
-
const sanitizeGemini = data => redactor.redact(data);
|
|
189
|
-
const sanitizeLangChain = data => redactor.redact(data);
|
|
190
159
|
|
|
191
160
|
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']);
|
|
192
161
|
function getTokensSource(posthogProperties) {
|
|
@@ -243,236 +212,6 @@ const getModelParams = params => {
|
|
|
243
212
|
}
|
|
244
213
|
return modelParams;
|
|
245
214
|
};
|
|
246
|
-
const formatResponseAnthropic = response => {
|
|
247
|
-
const output = [];
|
|
248
|
-
const content = [];
|
|
249
|
-
for (const choice of response.content ?? []) {
|
|
250
|
-
if (choice?.type === 'text' && choice?.text) {
|
|
251
|
-
content.push({
|
|
252
|
-
type: 'text',
|
|
253
|
-
text: choice.text
|
|
254
|
-
});
|
|
255
|
-
} else if (choice?.type === 'tool_use' && choice?.name && choice?.id) {
|
|
256
|
-
content.push({
|
|
257
|
-
type: 'function',
|
|
258
|
-
id: choice.id,
|
|
259
|
-
function: {
|
|
260
|
-
name: choice.name,
|
|
261
|
-
arguments: choice.input || {}
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (content.length > 0) {
|
|
267
|
-
output.push({
|
|
268
|
-
role: 'assistant',
|
|
269
|
-
content
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
return output;
|
|
273
|
-
};
|
|
274
|
-
const formatResponseOpenAI = response => {
|
|
275
|
-
const output = [];
|
|
276
|
-
if (response.choices) {
|
|
277
|
-
for (const choice of response.choices) {
|
|
278
|
-
const content = [];
|
|
279
|
-
let role = 'assistant';
|
|
280
|
-
if (choice.message) {
|
|
281
|
-
if (choice.message.role) {
|
|
282
|
-
role = choice.message.role;
|
|
283
|
-
}
|
|
284
|
-
if (choice.message.content) {
|
|
285
|
-
content.push({
|
|
286
|
-
type: 'text',
|
|
287
|
-
text: choice.message.content
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (choice.message.tool_calls) {
|
|
291
|
-
for (const toolCall of choice.message.tool_calls) {
|
|
292
|
-
content.push({
|
|
293
|
-
type: 'function',
|
|
294
|
-
id: toolCall.id,
|
|
295
|
-
function: {
|
|
296
|
-
name: toolCall.function.name,
|
|
297
|
-
arguments: toolCall.function.arguments
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// Handle audio output (gpt-4o-audio-preview)
|
|
303
|
-
if (choice.message.audio) {
|
|
304
|
-
content.push({
|
|
305
|
-
type: 'audio',
|
|
306
|
-
...choice.message.audio
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
if (content.length > 0) {
|
|
311
|
-
output.push({
|
|
312
|
-
role,
|
|
313
|
-
content
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
// Handle Responses API format
|
|
319
|
-
if (response.output) {
|
|
320
|
-
const content = [];
|
|
321
|
-
let role = 'assistant';
|
|
322
|
-
for (const item of response.output) {
|
|
323
|
-
if (item.type === 'message') {
|
|
324
|
-
role = item.role;
|
|
325
|
-
if (item.content && Array.isArray(item.content)) {
|
|
326
|
-
for (const contentItem of item.content) {
|
|
327
|
-
if (contentItem.type === 'output_text' && contentItem.text) {
|
|
328
|
-
content.push({
|
|
329
|
-
type: 'text',
|
|
330
|
-
text: contentItem.text
|
|
331
|
-
});
|
|
332
|
-
} else if (contentItem.text) {
|
|
333
|
-
content.push({
|
|
334
|
-
type: 'text',
|
|
335
|
-
text: contentItem.text
|
|
336
|
-
});
|
|
337
|
-
} else if (contentItem.type === 'input_image' && contentItem.image_url) {
|
|
338
|
-
content.push({
|
|
339
|
-
type: 'image',
|
|
340
|
-
image: contentItem.image_url
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} else if (item.content) {
|
|
345
|
-
content.push({
|
|
346
|
-
type: 'text',
|
|
347
|
-
text: String(item.content)
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
} else if (item.type === 'function_call') {
|
|
351
|
-
content.push({
|
|
352
|
-
type: 'function',
|
|
353
|
-
id: item.call_id || item.id || '',
|
|
354
|
-
function: {
|
|
355
|
-
name: item.name,
|
|
356
|
-
arguments: item.arguments || {}
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (content.length > 0) {
|
|
362
|
-
output.push({
|
|
363
|
-
role,
|
|
364
|
-
content
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
return output;
|
|
369
|
-
};
|
|
370
|
-
const buildInlineDataBlock = (mimeType, data) => {
|
|
371
|
-
if (mimeType.startsWith('audio/')) {
|
|
372
|
-
return {
|
|
373
|
-
type: 'audio',
|
|
374
|
-
mime_type: mimeType,
|
|
375
|
-
data
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
if (mimeType.startsWith('image/')) {
|
|
379
|
-
return {
|
|
380
|
-
type: 'image',
|
|
381
|
-
inline_data: {
|
|
382
|
-
mime_type: mimeType,
|
|
383
|
-
data
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
return {
|
|
388
|
-
type: 'document',
|
|
389
|
-
inline_data: {
|
|
390
|
-
mime_type: mimeType,
|
|
391
|
-
data
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
};
|
|
395
|
-
const formatResponseGemini = response => {
|
|
396
|
-
const output = [];
|
|
397
|
-
if (response.candidates && Array.isArray(response.candidates)) {
|
|
398
|
-
for (const candidate of response.candidates) {
|
|
399
|
-
if (candidate.content && candidate.content.parts) {
|
|
400
|
-
const content = [];
|
|
401
|
-
for (const part of candidate.content.parts) {
|
|
402
|
-
if (part.text) {
|
|
403
|
-
content.push({
|
|
404
|
-
type: 'text',
|
|
405
|
-
text: part.text
|
|
406
|
-
});
|
|
407
|
-
} else if (part.functionCall) {
|
|
408
|
-
content.push({
|
|
409
|
-
type: 'function',
|
|
410
|
-
function: {
|
|
411
|
-
name: part.functionCall.name,
|
|
412
|
-
arguments: part.functionCall.args
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
} else if (part.inlineData) {
|
|
416
|
-
// Handle inline data (images, audio, documents)
|
|
417
|
-
const mimeType = part.inlineData.mimeType || part.inlineData.mime_type || 'application/octet-stream';
|
|
418
|
-
let data = part.inlineData.data;
|
|
419
|
-
// Handle binary data (Uint8Array/Buffer -> base64)
|
|
420
|
-
if (data instanceof Uint8Array) {
|
|
421
|
-
if (typeof Buffer !== 'undefined') {
|
|
422
|
-
data = Buffer.from(data).toString('base64');
|
|
423
|
-
} else {
|
|
424
|
-
let binary = '';
|
|
425
|
-
for (let i = 0; i < data.length; i++) {
|
|
426
|
-
binary += String.fromCharCode(data[i]);
|
|
427
|
-
}
|
|
428
|
-
data = btoa(binary);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
// Sanitize base64 data for images and other large inline data
|
|
432
|
-
data = redactBase64DataUrl(data);
|
|
433
|
-
content.push(buildInlineDataBlock(mimeType, data));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
if (content.length > 0) {
|
|
437
|
-
output.push({
|
|
438
|
-
role: 'assistant',
|
|
439
|
-
content
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
} else if (candidate.text) {
|
|
443
|
-
output.push({
|
|
444
|
-
role: 'assistant',
|
|
445
|
-
content: [{
|
|
446
|
-
type: 'text',
|
|
447
|
-
text: candidate.text
|
|
448
|
-
}]
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
} else if (response.text) {
|
|
453
|
-
output.push({
|
|
454
|
-
role: 'assistant',
|
|
455
|
-
content: [{
|
|
456
|
-
type: 'text',
|
|
457
|
-
text: response.text
|
|
458
|
-
}]
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
return output;
|
|
462
|
-
};
|
|
463
|
-
const mergeSystemPrompt = (params, provider) => {
|
|
464
|
-
{
|
|
465
|
-
const messages = params.messages || [];
|
|
466
|
-
if (!params.system) {
|
|
467
|
-
return messages;
|
|
468
|
-
}
|
|
469
|
-
const systemMessage = params.system;
|
|
470
|
-
return [{
|
|
471
|
-
role: 'system',
|
|
472
|
-
content: systemMessage
|
|
473
|
-
}, ...messages];
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
215
|
const withPrivacyMode = (client, privacyMode, input) => {
|
|
477
216
|
return client.privacy_mode || privacyMode ? null : input;
|
|
478
217
|
};
|
|
@@ -610,28 +349,12 @@ function calculateWebSearchCount(result) {
|
|
|
610
349
|
* These are the tools provided to the LLM, not the tool calls in the response.
|
|
611
350
|
*/
|
|
612
351
|
const extractAvailableToolCalls = (provider, params) => {
|
|
613
|
-
|
|
614
|
-
if (params.tools) {
|
|
615
|
-
return params.tools;
|
|
616
|
-
}
|
|
617
|
-
return null;
|
|
618
|
-
} else if (provider === 'gemini') {
|
|
619
|
-
if (params.config && params.config.tools) {
|
|
620
|
-
return params.config.tools;
|
|
621
|
-
}
|
|
622
|
-
return null;
|
|
623
|
-
} else if (provider === 'openai') {
|
|
624
|
-
if (params.tools) {
|
|
625
|
-
return params.tools;
|
|
626
|
-
}
|
|
627
|
-
return null;
|
|
628
|
-
} else if (provider === 'vercel') {
|
|
352
|
+
{
|
|
629
353
|
if (params.tools) {
|
|
630
354
|
return params.tools;
|
|
631
355
|
}
|
|
632
356
|
return null;
|
|
633
357
|
}
|
|
634
|
-
return null;
|
|
635
358
|
};
|
|
636
359
|
exports.AIEvent = void 0;
|
|
637
360
|
(function (AIEvent) {
|
|
@@ -653,87 +376,8 @@ function sanitizeValues(obj) {
|
|
|
653
376
|
}
|
|
654
377
|
return jsonSafe;
|
|
655
378
|
}
|
|
656
|
-
const POSTHOG_PARAMS_MAP = {
|
|
657
|
-
posthogDistinctId: 'distinctId',
|
|
658
|
-
posthogTraceId: 'traceId',
|
|
659
|
-
posthogProperties: 'properties',
|
|
660
|
-
posthogPrivacyMode: 'privacyMode',
|
|
661
|
-
posthogGroups: 'groups',
|
|
662
|
-
posthogModelOverride: 'modelOverride',
|
|
663
|
-
posthogProviderOverride: 'providerOverride',
|
|
664
|
-
posthogCostOverride: 'costOverride',
|
|
665
|
-
posthogCaptureImmediate: 'captureImmediate'
|
|
666
|
-
};
|
|
667
|
-
function extractPosthogParams(body) {
|
|
668
|
-
const providerParams = {};
|
|
669
|
-
const posthogParams = {};
|
|
670
|
-
for (const [key, value] of Object.entries(body)) {
|
|
671
|
-
if (POSTHOG_PARAMS_MAP[key]) {
|
|
672
|
-
posthogParams[POSTHOG_PARAMS_MAP[key]] = value;
|
|
673
|
-
} else if (key.startsWith('posthog')) {
|
|
674
|
-
console.warn(`Unknown Posthog parameter ${key}`);
|
|
675
|
-
} else {
|
|
676
|
-
providerParams[key] = value;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
providerParams: providerParams,
|
|
681
|
-
posthogParams: addDefaults(posthogParams)
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
function addDefaults(params) {
|
|
685
|
-
return {
|
|
686
|
-
...params,
|
|
687
|
-
privacyMode: params.privacyMode ?? false,
|
|
688
|
-
traceId: params.traceId ?? uuid.v4()
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
function formatOpenAIResponsesInput(input, instructions) {
|
|
692
|
-
const messages = [];
|
|
693
|
-
if (instructions) {
|
|
694
|
-
messages.push({
|
|
695
|
-
role: 'system',
|
|
696
|
-
content: instructions
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
if (Array.isArray(input)) {
|
|
700
|
-
for (const item of input) {
|
|
701
|
-
if (typeof item === 'string') {
|
|
702
|
-
messages.push({
|
|
703
|
-
role: 'user',
|
|
704
|
-
content: item
|
|
705
|
-
});
|
|
706
|
-
} else if (item && typeof item === 'object') {
|
|
707
|
-
const obj = item;
|
|
708
|
-
const role = isString(obj.role) ? obj.role : 'user';
|
|
709
|
-
// Handle content properly - preserve structure for objects/arrays
|
|
710
|
-
const content = obj.content ?? obj.text ?? item;
|
|
711
|
-
messages.push({
|
|
712
|
-
role,
|
|
713
|
-
content: toContentString(content)
|
|
714
|
-
});
|
|
715
|
-
} else {
|
|
716
|
-
messages.push({
|
|
717
|
-
role: 'user',
|
|
718
|
-
content: toContentString(item)
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
} else if (typeof input === 'string') {
|
|
723
|
-
messages.push({
|
|
724
|
-
role: 'user',
|
|
725
|
-
content: input
|
|
726
|
-
});
|
|
727
|
-
} else if (input) {
|
|
728
|
-
messages.push({
|
|
729
|
-
role: 'user',
|
|
730
|
-
content: toContentString(input)
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
return messages;
|
|
734
|
-
}
|
|
735
379
|
|
|
736
|
-
var version = "
|
|
380
|
+
var version = "8.0.0";
|
|
737
381
|
|
|
738
382
|
const DEFAULT_MAX_DEPTH = 3;
|
|
739
383
|
const MAX_STACK_LINES = 20;
|
|
@@ -916,1336 +560,51 @@ const captureAiGeneration = async (client, options) => {
|
|
|
916
560
|
}
|
|
917
561
|
};
|
|
918
562
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
*/
|
|
923
|
-
function isResponseTokenChunk(chunk) {
|
|
924
|
-
return chunk.type === 'response.output_item.added' || chunk.type === 'response.content_part.added' || chunk.type === 'response.output_text.delta' || chunk.type === 'response.reasoning_text.delta' || chunk.type === 'response.reasoning_summary_text.delta' || chunk.type === 'response.audio.delta' || chunk.type === 'response.audio.transcript.delta' || chunk.type === 'response.refusal.delta';
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Reads the OpenAI SDK's `_request_id` field from a response object. The SDK
|
|
928
|
-
* attaches the `x-request-id` response header here, but it is not part of the
|
|
929
|
-
* public response types, so it has to be read through a cast. Used to populate
|
|
930
|
-
* `$ai_provider_metadata.request_id`.
|
|
931
|
-
*/
|
|
932
|
-
function extractRequestId(result) {
|
|
933
|
-
return result?._request_id ?? undefined;
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Assembles the `$ai_provider_metadata` blob for OpenAI / Azure OpenAI events.
|
|
937
|
-
* Provider-specific fields (system fingerprint, request id) live here rather
|
|
938
|
-
* than in the shared, provider-agnostic `$ai_*` namespace. Only keys with a
|
|
939
|
-
* truthy value are included, and `undefined` is returned when there is nothing
|
|
940
|
-
* to report so the property can be omitted from the event entirely.
|
|
941
|
-
*/
|
|
942
|
-
function buildProviderMetadata(fields) {
|
|
943
|
-
const metadata = {};
|
|
944
|
-
if (fields.systemFingerprint) {
|
|
945
|
-
metadata.system_fingerprint = fields.systemFingerprint;
|
|
946
|
-
}
|
|
947
|
-
if (fields.requestId) {
|
|
948
|
-
metadata.request_id = fields.requestId;
|
|
949
|
-
}
|
|
950
|
-
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const Chat = openai.OpenAI.Chat;
|
|
954
|
-
const Completions = Chat.Completions;
|
|
955
|
-
const Responses = openai.OpenAI.Responses;
|
|
956
|
-
const Embeddings = openai.OpenAI.Embeddings;
|
|
957
|
-
const Audio = openai.OpenAI.Audio;
|
|
958
|
-
const Transcriptions = openai.OpenAI.Audio.Transcriptions;
|
|
959
|
-
function captureAiGenerationInBackground(...args) {
|
|
960
|
-
void captureAiGeneration(...args).catch(() => undefined);
|
|
961
|
-
}
|
|
962
|
-
async function captureAiGenerationAfterSuccess(...args) {
|
|
963
|
-
const [, options] = args;
|
|
964
|
-
if (options.captureImmediate) {
|
|
965
|
-
await captureAiGeneration(...args);
|
|
966
|
-
} else {
|
|
967
|
-
captureAiGenerationInBackground(...args);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
function preserveAPIPromiseHelpers(parentPromise, wrappedPromise) {
|
|
971
|
-
const apiPromise = wrappedPromise;
|
|
972
|
-
if (typeof parentPromise.asResponse === 'function') {
|
|
973
|
-
apiPromise.asResponse = () => parentPromise.asResponse();
|
|
974
|
-
}
|
|
975
|
-
if (typeof parentPromise.withResponse === 'function') {
|
|
976
|
-
apiPromise.withResponse = async () => {
|
|
977
|
-
const [response, data] = await Promise.all([parentPromise.withResponse(), wrappedPromise]);
|
|
978
|
-
return {
|
|
979
|
-
...response,
|
|
980
|
-
data
|
|
981
|
-
};
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
return apiPromise;
|
|
985
|
-
}
|
|
986
|
-
class PostHogOpenAI extends openai.OpenAI {
|
|
987
|
-
constructor(config) {
|
|
988
|
-
const {
|
|
989
|
-
posthog,
|
|
990
|
-
...openAIConfig
|
|
991
|
-
} = config;
|
|
992
|
-
super(openAIConfig);
|
|
993
|
-
this.phClient = posthog;
|
|
994
|
-
this.chat = new WrappedChat$1(this, this.phClient);
|
|
995
|
-
this.responses = new WrappedResponses$1(this, this.phClient);
|
|
996
|
-
this.embeddings = new WrappedEmbeddings$1(this, this.phClient);
|
|
997
|
-
this.audio = new WrappedAudio(this, this.phClient);
|
|
998
|
-
}
|
|
563
|
+
// Type guards
|
|
564
|
+
function isV3Model(model) {
|
|
565
|
+
return model.specificationVersion === 'v3';
|
|
999
566
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
567
|
+
const mapVercelParams = params => {
|
|
568
|
+
return {
|
|
569
|
+
temperature: params.temperature,
|
|
570
|
+
max_output_tokens: params.maxOutputTokens,
|
|
571
|
+
top_p: params.topP,
|
|
572
|
+
frequency_penalty: params.frequencyPenalty,
|
|
573
|
+
presence_penalty: params.presencePenalty,
|
|
574
|
+
stop: params.stopSequences,
|
|
575
|
+
stream: params.stream
|
|
576
|
+
};
|
|
1005
577
|
};
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
posthogParams
|
|
1017
|
-
} = extractPosthogParams(body);
|
|
1018
|
-
const startTime = Date.now();
|
|
1019
|
-
const parentPromise = super.create(openAIParams, options);
|
|
1020
|
-
if (openAIParams.stream) {
|
|
1021
|
-
const wrappedPromise = parentPromise.then(value => {
|
|
1022
|
-
if ('tee' in value) {
|
|
1023
|
-
const [stream1, stream2] = value.tee();
|
|
1024
|
-
(async () => {
|
|
1025
|
-
// Hoisted so the catch block can surface whatever was accumulated
|
|
1026
|
-
// from the streamed chunks before the failure.
|
|
1027
|
-
let completionIdFromResponse;
|
|
1028
|
-
let systemFingerprintFromResponse;
|
|
1029
|
-
try {
|
|
1030
|
-
const contentBlocks = [];
|
|
1031
|
-
let accumulatedContent = '';
|
|
1032
|
-
let modelFromResponse;
|
|
1033
|
-
let firstTokenTime;
|
|
1034
|
-
let stopReason;
|
|
1035
|
-
let usage = {
|
|
1036
|
-
inputTokens: 0,
|
|
1037
|
-
outputTokens: 0,
|
|
1038
|
-
webSearchCount: 0
|
|
1039
|
-
};
|
|
1040
|
-
// Map to track in-progress tool calls
|
|
1041
|
-
const toolCallsInProgress = new Map();
|
|
1042
|
-
let rawUsageData;
|
|
1043
|
-
for await (const chunk of stream1) {
|
|
1044
|
-
// Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
|
|
1045
|
-
if (!modelFromResponse && chunk.model) {
|
|
1046
|
-
modelFromResponse = chunk.model;
|
|
1047
|
-
}
|
|
1048
|
-
if (!completionIdFromResponse && chunk.id) {
|
|
1049
|
-
completionIdFromResponse = chunk.id;
|
|
1050
|
-
}
|
|
1051
|
-
if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
|
|
1052
|
-
systemFingerprintFromResponse = chunk.system_fingerprint;
|
|
1053
|
-
}
|
|
1054
|
-
const choice = chunk?.choices?.[0];
|
|
1055
|
-
if (choice?.finish_reason) {
|
|
1056
|
-
stopReason = choice.finish_reason;
|
|
1057
|
-
}
|
|
1058
|
-
const chunkWebSearchCount = calculateWebSearchCount(chunk);
|
|
1059
|
-
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
1060
|
-
usage.webSearchCount = chunkWebSearchCount;
|
|
1061
|
-
}
|
|
1062
|
-
// Handle text content
|
|
1063
|
-
const deltaContent = choice?.delta?.content;
|
|
1064
|
-
if (deltaContent) {
|
|
1065
|
-
if (firstTokenTime === undefined) {
|
|
1066
|
-
firstTokenTime = Date.now();
|
|
1067
|
-
}
|
|
1068
|
-
accumulatedContent += deltaContent;
|
|
1069
|
-
}
|
|
1070
|
-
// Handle tool calls
|
|
1071
|
-
const deltaToolCalls = choice?.delta?.tool_calls;
|
|
1072
|
-
if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
|
|
1073
|
-
if (firstTokenTime === undefined) {
|
|
1074
|
-
firstTokenTime = Date.now();
|
|
1075
|
-
}
|
|
1076
|
-
for (const toolCall of deltaToolCalls) {
|
|
1077
|
-
const index = toolCall.index;
|
|
1078
|
-
if (index !== undefined) {
|
|
1079
|
-
if (!toolCallsInProgress.has(index)) {
|
|
1080
|
-
// New tool call
|
|
1081
|
-
toolCallsInProgress.set(index, {
|
|
1082
|
-
id: toolCall.id || '',
|
|
1083
|
-
name: toolCall.function?.name || '',
|
|
1084
|
-
arguments: ''
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
const inProgressCall = toolCallsInProgress.get(index);
|
|
1088
|
-
if (inProgressCall) {
|
|
1089
|
-
// Update tool call data
|
|
1090
|
-
if (toolCall.id) {
|
|
1091
|
-
inProgressCall.id = toolCall.id;
|
|
1092
|
-
}
|
|
1093
|
-
if (toolCall.function?.name) {
|
|
1094
|
-
inProgressCall.name = toolCall.function.name;
|
|
1095
|
-
}
|
|
1096
|
-
if (toolCall.function?.arguments) {
|
|
1097
|
-
inProgressCall.arguments += toolCall.function.arguments;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
// Handle usage information
|
|
1104
|
-
if (chunk.usage) {
|
|
1105
|
-
rawUsageData = chunk.usage;
|
|
1106
|
-
usage = {
|
|
1107
|
-
...usage,
|
|
1108
|
-
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1109
|
-
outputTokens: chunk.usage.completion_tokens ?? 0,
|
|
1110
|
-
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
1111
|
-
cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
// Build final content blocks
|
|
1116
|
-
if (accumulatedContent) {
|
|
1117
|
-
contentBlocks.push({
|
|
1118
|
-
type: 'text',
|
|
1119
|
-
text: accumulatedContent
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
// Add completed tool calls to content blocks
|
|
1123
|
-
for (const toolCall of toolCallsInProgress.values()) {
|
|
1124
|
-
if (toolCall.name) {
|
|
1125
|
-
contentBlocks.push({
|
|
1126
|
-
type: 'function',
|
|
1127
|
-
id: toolCall.id,
|
|
1128
|
-
function: {
|
|
1129
|
-
name: toolCall.name,
|
|
1130
|
-
arguments: toolCall.arguments
|
|
1131
|
-
}
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
// Format output to match non-streaming version
|
|
1136
|
-
const formattedOutput = contentBlocks.length > 0 ? [{
|
|
1137
|
-
role: 'assistant',
|
|
1138
|
-
content: contentBlocks
|
|
1139
|
-
}] : [{
|
|
1140
|
-
role: 'assistant',
|
|
1141
|
-
content: [{
|
|
1142
|
-
type: 'text',
|
|
1143
|
-
text: ''
|
|
1144
|
-
}]
|
|
1145
|
-
}];
|
|
1146
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1147
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
1148
|
-
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
1149
|
-
await captureAiGeneration(this.phClient, {
|
|
1150
|
-
...posthogParams,
|
|
1151
|
-
model: openAIParams.model ?? modelFromResponse,
|
|
1152
|
-
provider: 'openai',
|
|
1153
|
-
input: sanitizeOpenAI(openAIParams.messages),
|
|
1154
|
-
output: formattedOutput,
|
|
1155
|
-
latency,
|
|
1156
|
-
timeToFirstToken,
|
|
1157
|
-
baseURL: this.baseURL,
|
|
1158
|
-
modelParameters: getModelParams(body),
|
|
1159
|
-
httpStatus: 200,
|
|
1160
|
-
usage: {
|
|
1161
|
-
inputTokens: usage.inputTokens,
|
|
1162
|
-
outputTokens: usage.outputTokens,
|
|
1163
|
-
reasoningTokens: usage.reasoningTokens,
|
|
1164
|
-
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
1165
|
-
webSearchCount: usage.webSearchCount,
|
|
1166
|
-
rawUsage: rawUsageData
|
|
1167
|
-
},
|
|
1168
|
-
stopReason,
|
|
1169
|
-
tools: availableTools,
|
|
1170
|
-
completionId: completionIdFromResponse,
|
|
1171
|
-
providerMetadata: buildProviderMetadata({
|
|
1172
|
-
systemFingerprint: systemFingerprintFromResponse
|
|
1173
|
-
})
|
|
1174
|
-
});
|
|
1175
|
-
} catch (error) {
|
|
1176
|
-
await captureAiGeneration(this.phClient, {
|
|
1177
|
-
...posthogParams,
|
|
1178
|
-
model: openAIParams.model,
|
|
1179
|
-
provider: 'openai',
|
|
1180
|
-
input: sanitizeOpenAI(openAIParams.messages),
|
|
1181
|
-
output: [],
|
|
1182
|
-
latency: 0,
|
|
1183
|
-
baseURL: this.baseURL,
|
|
1184
|
-
modelParameters: getModelParams(body),
|
|
1185
|
-
usage: {
|
|
1186
|
-
inputTokens: 0,
|
|
1187
|
-
outputTokens: 0
|
|
1188
|
-
},
|
|
1189
|
-
// If the stream fails mid-flight, surface whatever completion
|
|
1190
|
-
// metadata the consumed chunks already provided so the error
|
|
1191
|
-
// event can still be correlated to OpenAI's Logs dashboard.
|
|
1192
|
-
completionId: completionIdFromResponse,
|
|
1193
|
-
providerMetadata: buildProviderMetadata({
|
|
1194
|
-
systemFingerprint: systemFingerprintFromResponse
|
|
1195
|
-
}),
|
|
1196
|
-
error
|
|
1197
|
-
});
|
|
1198
|
-
throw error;
|
|
1199
|
-
}
|
|
1200
|
-
})();
|
|
1201
|
-
// Return the other stream to the user
|
|
1202
|
-
return stream2;
|
|
1203
|
-
}
|
|
1204
|
-
return value;
|
|
1205
|
-
});
|
|
1206
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
578
|
+
const mapVercelPrompt = messages => {
|
|
579
|
+
// Map and truncate individual content
|
|
580
|
+
const inputs = messages.map(message => {
|
|
581
|
+
let content;
|
|
582
|
+
// Handle system role which has string content
|
|
583
|
+
if (message.role === 'system') {
|
|
584
|
+
content = [{
|
|
585
|
+
type: 'text',
|
|
586
|
+
text: truncate(toContentString(message.content))
|
|
587
|
+
}];
|
|
1207
588
|
} else {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0,
|
|
1228
|
-
webSearchCount: calculateWebSearchCount(result),
|
|
1229
|
-
rawUsage: result.usage
|
|
1230
|
-
},
|
|
1231
|
-
stopReason: result.choices[0]?.finish_reason ?? undefined,
|
|
1232
|
-
tools: availableTools,
|
|
1233
|
-
completionId: result.id,
|
|
1234
|
-
providerMetadata: buildProviderMetadata({
|
|
1235
|
-
systemFingerprint: result.system_fingerprint,
|
|
1236
|
-
requestId: extractRequestId(result)
|
|
1237
|
-
})
|
|
1238
|
-
});
|
|
1239
|
-
}
|
|
1240
|
-
return result;
|
|
1241
|
-
}, async error => {
|
|
1242
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
1243
|
-
await captureAiGeneration(this.phClient, {
|
|
1244
|
-
...posthogParams,
|
|
1245
|
-
model: openAIParams.model,
|
|
1246
|
-
provider: 'openai',
|
|
1247
|
-
input: sanitizeOpenAI(openAIParams.messages),
|
|
1248
|
-
output: [],
|
|
1249
|
-
latency: 0,
|
|
1250
|
-
baseURL: this.baseURL,
|
|
1251
|
-
modelParameters: getModelParams(body),
|
|
1252
|
-
httpStatus,
|
|
1253
|
-
usage: {
|
|
1254
|
-
inputTokens: 0,
|
|
1255
|
-
outputTokens: 0
|
|
1256
|
-
},
|
|
1257
|
-
error
|
|
1258
|
-
});
|
|
1259
|
-
throw error;
|
|
1260
|
-
});
|
|
1261
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
};
|
|
1265
|
-
let WrappedResponses$1 = class WrappedResponses extends Responses {
|
|
1266
|
-
constructor(client, phClient) {
|
|
1267
|
-
super(client);
|
|
1268
|
-
this.phClient = phClient;
|
|
1269
|
-
this.baseURL = client.baseURL;
|
|
1270
|
-
}
|
|
1271
|
-
// --- Implementation Signature
|
|
1272
|
-
create(body, options) {
|
|
1273
|
-
const {
|
|
1274
|
-
providerParams: openAIParams,
|
|
1275
|
-
posthogParams
|
|
1276
|
-
} = extractPosthogParams(body);
|
|
1277
|
-
const startTime = Date.now();
|
|
1278
|
-
const parentPromise = super.create(openAIParams, options);
|
|
1279
|
-
if (openAIParams.stream) {
|
|
1280
|
-
const wrappedPromise = parentPromise.then(value => {
|
|
1281
|
-
if ('tee' in value && typeof value.tee === 'function') {
|
|
1282
|
-
const [stream1, stream2] = value.tee();
|
|
1283
|
-
(async () => {
|
|
1284
|
-
// Hoisted so the catch block can surface the completion ID that
|
|
1285
|
-
// was accumulated from the streamed chunks before the failure.
|
|
1286
|
-
let completionIdFromResponse;
|
|
1287
|
-
try {
|
|
1288
|
-
let finalContent = [];
|
|
1289
|
-
let modelFromResponse;
|
|
1290
|
-
let firstTokenTime;
|
|
1291
|
-
let stopReason;
|
|
1292
|
-
let usage = {
|
|
1293
|
-
inputTokens: 0,
|
|
1294
|
-
outputTokens: 0,
|
|
1295
|
-
webSearchCount: 0
|
|
1296
|
-
};
|
|
1297
|
-
let rawUsageData;
|
|
1298
|
-
for await (const chunk of stream1) {
|
|
1299
|
-
// Track first token time on content delta events
|
|
1300
|
-
if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
|
|
1301
|
-
firstTokenTime = Date.now();
|
|
1302
|
-
}
|
|
1303
|
-
if ('response' in chunk && chunk.response) {
|
|
1304
|
-
// Extract model and completion ID from the response object in the chunk (for stored prompts)
|
|
1305
|
-
if (!modelFromResponse && chunk.response.model) {
|
|
1306
|
-
modelFromResponse = chunk.response.model;
|
|
1307
|
-
}
|
|
1308
|
-
if (!completionIdFromResponse && chunk.response.id) {
|
|
1309
|
-
completionIdFromResponse = chunk.response.id;
|
|
1310
|
-
}
|
|
1311
|
-
const chunkWebSearchCount = calculateWebSearchCount(chunk.response);
|
|
1312
|
-
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
1313
|
-
usage.webSearchCount = chunkWebSearchCount;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
|
|
1317
|
-
finalContent = chunk.response.output;
|
|
1318
|
-
if (chunk.response.status) {
|
|
1319
|
-
stopReason = chunk.response.status;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
if ('response' in chunk && chunk.response?.usage) {
|
|
1323
|
-
rawUsageData = chunk.response.usage;
|
|
1324
|
-
usage = {
|
|
1325
|
-
...usage,
|
|
1326
|
-
inputTokens: chunk.response.usage.input_tokens ?? 0,
|
|
1327
|
-
outputTokens: chunk.response.usage.output_tokens ?? 0,
|
|
1328
|
-
reasoningTokens: chunk.response.usage.output_tokens_details?.reasoning_tokens ?? 0,
|
|
1329
|
-
cacheReadInputTokens: chunk.response.usage.input_tokens_details?.cached_tokens ?? 0
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1334
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
1335
|
-
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
1336
|
-
await captureAiGeneration(this.phClient, {
|
|
1337
|
-
...posthogParams,
|
|
1338
|
-
model: openAIParams.model ?? modelFromResponse,
|
|
1339
|
-
provider: 'openai',
|
|
1340
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1341
|
-
output: finalContent,
|
|
1342
|
-
latency,
|
|
1343
|
-
timeToFirstToken,
|
|
1344
|
-
baseURL: this.baseURL,
|
|
1345
|
-
modelParameters: getModelParams(body),
|
|
1346
|
-
httpStatus: 200,
|
|
1347
|
-
usage: {
|
|
1348
|
-
inputTokens: usage.inputTokens,
|
|
1349
|
-
outputTokens: usage.outputTokens,
|
|
1350
|
-
reasoningTokens: usage.reasoningTokens,
|
|
1351
|
-
cacheReadInputTokens: usage.cacheReadInputTokens,
|
|
1352
|
-
webSearchCount: usage.webSearchCount,
|
|
1353
|
-
rawUsage: rawUsageData
|
|
1354
|
-
},
|
|
1355
|
-
stopReason,
|
|
1356
|
-
tools: availableTools,
|
|
1357
|
-
completionId: completionIdFromResponse
|
|
1358
|
-
});
|
|
1359
|
-
} catch (error) {
|
|
1360
|
-
await captureAiGeneration(this.phClient, {
|
|
1361
|
-
...posthogParams,
|
|
1362
|
-
model: openAIParams.model,
|
|
1363
|
-
provider: 'openai',
|
|
1364
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1365
|
-
output: [],
|
|
1366
|
-
latency: 0,
|
|
1367
|
-
baseURL: this.baseURL,
|
|
1368
|
-
modelParameters: getModelParams(body),
|
|
1369
|
-
usage: {
|
|
1370
|
-
inputTokens: 0,
|
|
1371
|
-
outputTokens: 0
|
|
1372
|
-
},
|
|
1373
|
-
// Surface the completion ID from any chunks consumed before
|
|
1374
|
-
// the stream failed so the error event remains correlatable.
|
|
1375
|
-
completionId: completionIdFromResponse,
|
|
1376
|
-
error
|
|
1377
|
-
});
|
|
1378
|
-
throw error;
|
|
1379
|
-
}
|
|
1380
|
-
})();
|
|
1381
|
-
return stream2;
|
|
1382
|
-
}
|
|
1383
|
-
return value;
|
|
1384
|
-
});
|
|
1385
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1386
|
-
} else {
|
|
1387
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
1388
|
-
if ('output' in result) {
|
|
1389
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1390
|
-
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
1391
|
-
const formattedOutput = formatResponseOpenAI({
|
|
1392
|
-
output: result.output
|
|
1393
|
-
});
|
|
1394
|
-
await captureAiGenerationAfterSuccess(this.phClient, {
|
|
1395
|
-
...posthogParams,
|
|
1396
|
-
model: openAIParams.model ?? result.model,
|
|
1397
|
-
provider: 'openai',
|
|
1398
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1399
|
-
output: formattedOutput,
|
|
1400
|
-
latency,
|
|
1401
|
-
baseURL: this.baseURL,
|
|
1402
|
-
modelParameters: getModelParams(body),
|
|
1403
|
-
httpStatus: 200,
|
|
1404
|
-
usage: {
|
|
1405
|
-
inputTokens: result.usage?.input_tokens ?? 0,
|
|
1406
|
-
outputTokens: result.usage?.output_tokens ?? 0,
|
|
1407
|
-
reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
|
1408
|
-
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
|
|
1409
|
-
webSearchCount: calculateWebSearchCount(result),
|
|
1410
|
-
rawUsage: result.usage
|
|
1411
|
-
},
|
|
1412
|
-
stopReason: result.status ?? undefined,
|
|
1413
|
-
tools: availableTools,
|
|
1414
|
-
completionId: result.id,
|
|
1415
|
-
providerMetadata: buildProviderMetadata({
|
|
1416
|
-
requestId: extractRequestId(result)
|
|
1417
|
-
})
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
return result;
|
|
1421
|
-
}, async error => {
|
|
1422
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
1423
|
-
await captureAiGeneration(this.phClient, {
|
|
1424
|
-
...posthogParams,
|
|
1425
|
-
model: openAIParams.model,
|
|
1426
|
-
provider: 'openai',
|
|
1427
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1428
|
-
output: [],
|
|
1429
|
-
latency: 0,
|
|
1430
|
-
baseURL: this.baseURL,
|
|
1431
|
-
modelParameters: getModelParams(body),
|
|
1432
|
-
httpStatus,
|
|
1433
|
-
usage: {
|
|
1434
|
-
inputTokens: 0,
|
|
1435
|
-
outputTokens: 0
|
|
1436
|
-
},
|
|
1437
|
-
error
|
|
1438
|
-
});
|
|
1439
|
-
throw error;
|
|
1440
|
-
});
|
|
1441
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
parse(body, options) {
|
|
1445
|
-
const {
|
|
1446
|
-
providerParams: openAIParams,
|
|
1447
|
-
posthogParams
|
|
1448
|
-
} = extractPosthogParams(body);
|
|
1449
|
-
const startTime = Date.now();
|
|
1450
|
-
const originalCreate = super.create.bind(this);
|
|
1451
|
-
const originalSelfRecord = this;
|
|
1452
|
-
const tempCreate = originalSelfRecord['create'];
|
|
1453
|
-
originalSelfRecord['create'] = originalCreate;
|
|
1454
|
-
try {
|
|
1455
|
-
const parentPromise = super.parse(openAIParams, options);
|
|
1456
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
1457
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1458
|
-
await captureAiGeneration(this.phClient, {
|
|
1459
|
-
...posthogParams,
|
|
1460
|
-
model: openAIParams.model ?? result.model,
|
|
1461
|
-
provider: 'openai',
|
|
1462
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1463
|
-
output: result.output,
|
|
1464
|
-
latency,
|
|
1465
|
-
baseURL: this.baseURL,
|
|
1466
|
-
modelParameters: getModelParams(body),
|
|
1467
|
-
httpStatus: 200,
|
|
1468
|
-
usage: {
|
|
1469
|
-
inputTokens: result.usage?.input_tokens ?? 0,
|
|
1470
|
-
outputTokens: result.usage?.output_tokens ?? 0,
|
|
1471
|
-
reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
|
1472
|
-
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0,
|
|
1473
|
-
rawUsage: result.usage
|
|
1474
|
-
},
|
|
1475
|
-
stopReason: result.status ?? undefined,
|
|
1476
|
-
completionId: result.id,
|
|
1477
|
-
providerMetadata: buildProviderMetadata({
|
|
1478
|
-
requestId: extractRequestId(result)
|
|
1479
|
-
})
|
|
1480
|
-
});
|
|
1481
|
-
return result;
|
|
1482
|
-
}, async error => {
|
|
1483
|
-
await captureAiGeneration(this.phClient, {
|
|
1484
|
-
...posthogParams,
|
|
1485
|
-
model: openAIParams.model,
|
|
1486
|
-
provider: 'openai',
|
|
1487
|
-
input: formatOpenAIResponsesInput(sanitizeOpenAIResponse(openAIParams.input), openAIParams.instructions),
|
|
1488
|
-
output: [],
|
|
1489
|
-
latency: 0,
|
|
1490
|
-
baseURL: this.baseURL,
|
|
1491
|
-
modelParameters: getModelParams(body),
|
|
1492
|
-
usage: {
|
|
1493
|
-
inputTokens: 0,
|
|
1494
|
-
outputTokens: 0
|
|
1495
|
-
},
|
|
1496
|
-
error
|
|
1497
|
-
});
|
|
1498
|
-
throw error;
|
|
1499
|
-
});
|
|
1500
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1501
|
-
} finally {
|
|
1502
|
-
// Restore our wrapped create method
|
|
1503
|
-
originalSelfRecord['create'] = tempCreate;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
};
|
|
1507
|
-
let WrappedEmbeddings$1 = class WrappedEmbeddings extends Embeddings {
|
|
1508
|
-
constructor(client, phClient) {
|
|
1509
|
-
super(client);
|
|
1510
|
-
this.phClient = phClient;
|
|
1511
|
-
this.baseURL = client.baseURL;
|
|
1512
|
-
}
|
|
1513
|
-
create(body, options) {
|
|
1514
|
-
const {
|
|
1515
|
-
providerParams: openAIParams,
|
|
1516
|
-
posthogParams
|
|
1517
|
-
} = extractPosthogParams(body);
|
|
1518
|
-
const startTime = Date.now();
|
|
1519
|
-
const parentPromise = super.create(openAIParams, options);
|
|
1520
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
1521
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1522
|
-
await captureAiGeneration(this.phClient, {
|
|
1523
|
-
...posthogParams,
|
|
1524
|
-
eventType: exports.AIEvent.Embedding,
|
|
1525
|
-
model: openAIParams.model,
|
|
1526
|
-
provider: 'openai',
|
|
1527
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
|
|
1528
|
-
output: null,
|
|
1529
|
-
// Embeddings don't have output content
|
|
1530
|
-
latency,
|
|
1531
|
-
baseURL: this.baseURL,
|
|
1532
|
-
modelParameters: getModelParams(body),
|
|
1533
|
-
httpStatus: 200,
|
|
1534
|
-
usage: {
|
|
1535
|
-
inputTokens: result.usage?.prompt_tokens ?? 0,
|
|
1536
|
-
rawUsage: result.usage
|
|
1537
|
-
}
|
|
1538
|
-
});
|
|
1539
|
-
return result;
|
|
1540
|
-
}, async error => {
|
|
1541
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
1542
|
-
await captureAiGeneration(this.phClient, {
|
|
1543
|
-
eventType: exports.AIEvent.Embedding,
|
|
1544
|
-
...posthogParams,
|
|
1545
|
-
model: openAIParams.model,
|
|
1546
|
-
provider: 'openai',
|
|
1547
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
|
|
1548
|
-
output: null,
|
|
1549
|
-
// Embeddings don't have output content
|
|
1550
|
-
latency: 0,
|
|
1551
|
-
baseURL: this.baseURL,
|
|
1552
|
-
modelParameters: getModelParams(body),
|
|
1553
|
-
httpStatus,
|
|
1554
|
-
usage: {
|
|
1555
|
-
inputTokens: 0
|
|
1556
|
-
},
|
|
1557
|
-
error
|
|
1558
|
-
});
|
|
1559
|
-
throw error;
|
|
1560
|
-
});
|
|
1561
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1562
|
-
}
|
|
1563
|
-
};
|
|
1564
|
-
class WrappedAudio extends Audio {
|
|
1565
|
-
constructor(parentClient, phClient) {
|
|
1566
|
-
super(parentClient);
|
|
1567
|
-
this.transcriptions = new WrappedTranscriptions(parentClient, phClient);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
class WrappedTranscriptions extends Transcriptions {
|
|
1571
|
-
constructor(client, phClient) {
|
|
1572
|
-
super(client);
|
|
1573
|
-
this.phClient = phClient;
|
|
1574
|
-
this.baseURL = client.baseURL;
|
|
1575
|
-
}
|
|
1576
|
-
// --- Implementation Signature
|
|
1577
|
-
create(body, options) {
|
|
1578
|
-
const {
|
|
1579
|
-
providerParams: openAIParams,
|
|
1580
|
-
posthogParams
|
|
1581
|
-
} = extractPosthogParams(body);
|
|
1582
|
-
const startTime = Date.now();
|
|
1583
|
-
const parentPromise = openAIParams.stream ? super.create(openAIParams, options) : super.create(openAIParams, options);
|
|
1584
|
-
if (openAIParams.stream) {
|
|
1585
|
-
const wrappedPromise = parentPromise.then(value => {
|
|
1586
|
-
if ('tee' in value && typeof value.tee === 'function') {
|
|
1587
|
-
const [stream1, stream2] = value.tee();
|
|
1588
|
-
(async () => {
|
|
1589
|
-
try {
|
|
1590
|
-
let finalContent = '';
|
|
1591
|
-
let firstTokenTime;
|
|
1592
|
-
let usage = {
|
|
1593
|
-
inputTokens: 0,
|
|
1594
|
-
outputTokens: 0
|
|
1595
|
-
};
|
|
1596
|
-
const doneEvent = 'transcript.text.done';
|
|
1597
|
-
for await (const chunk of stream1) {
|
|
1598
|
-
// Track first token on text delta events
|
|
1599
|
-
if (firstTokenTime === undefined && chunk.type === 'transcript.text.delta') {
|
|
1600
|
-
firstTokenTime = Date.now();
|
|
1601
|
-
}
|
|
1602
|
-
if (chunk.type === doneEvent && 'text' in chunk && chunk.text && chunk.text.length > 0) {
|
|
1603
|
-
finalContent = chunk.text;
|
|
1604
|
-
}
|
|
1605
|
-
if ('usage' in chunk && chunk.usage) {
|
|
1606
|
-
usage = {
|
|
1607
|
-
inputTokens: chunk.usage?.type === 'tokens' ? chunk.usage.input_tokens ?? 0 : 0,
|
|
1608
|
-
outputTokens: chunk.usage?.type === 'tokens' ? chunk.usage.output_tokens ?? 0 : 0,
|
|
1609
|
-
rawUsage: chunk.usage
|
|
1610
|
-
};
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1614
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
1615
|
-
const availableTools = extractAvailableToolCalls('openai', openAIParams);
|
|
1616
|
-
await captureAiGeneration(this.phClient, {
|
|
1617
|
-
...posthogParams,
|
|
1618
|
-
model: openAIParams.model,
|
|
1619
|
-
provider: 'openai',
|
|
1620
|
-
input: openAIParams.prompt,
|
|
1621
|
-
output: finalContent,
|
|
1622
|
-
latency,
|
|
1623
|
-
timeToFirstToken,
|
|
1624
|
-
baseURL: this.baseURL,
|
|
1625
|
-
modelParameters: getModelParams(body),
|
|
1626
|
-
httpStatus: 200,
|
|
1627
|
-
usage,
|
|
1628
|
-
tools: availableTools
|
|
1629
|
-
});
|
|
1630
|
-
} catch (error) {
|
|
1631
|
-
await captureAiGeneration(this.phClient, {
|
|
1632
|
-
...posthogParams,
|
|
1633
|
-
model: openAIParams.model,
|
|
1634
|
-
provider: 'openai',
|
|
1635
|
-
input: openAIParams.prompt,
|
|
1636
|
-
output: [],
|
|
1637
|
-
latency: 0,
|
|
1638
|
-
baseURL: this.baseURL,
|
|
1639
|
-
modelParameters: getModelParams(body),
|
|
1640
|
-
usage: {
|
|
1641
|
-
inputTokens: 0,
|
|
1642
|
-
outputTokens: 0
|
|
1643
|
-
},
|
|
1644
|
-
error
|
|
1645
|
-
});
|
|
1646
|
-
throw error;
|
|
1647
|
-
}
|
|
1648
|
-
})();
|
|
1649
|
-
return stream2;
|
|
1650
|
-
}
|
|
1651
|
-
return value;
|
|
1652
|
-
});
|
|
1653
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1654
|
-
} else {
|
|
1655
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
1656
|
-
if (result && typeof result === 'object' && 'text' in result) {
|
|
1657
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1658
|
-
await captureAiGenerationAfterSuccess(this.phClient, {
|
|
1659
|
-
...posthogParams,
|
|
1660
|
-
model: openAIParams.model,
|
|
1661
|
-
provider: 'openai',
|
|
1662
|
-
input: openAIParams.prompt,
|
|
1663
|
-
output: result.text,
|
|
1664
|
-
latency,
|
|
1665
|
-
baseURL: this.baseURL,
|
|
1666
|
-
modelParameters: getModelParams(body),
|
|
1667
|
-
httpStatus: 200,
|
|
1668
|
-
usage: {
|
|
1669
|
-
inputTokens: result.usage?.type === 'tokens' ? result.usage.input_tokens ?? 0 : 0,
|
|
1670
|
-
outputTokens: result.usage?.type === 'tokens' ? result.usage.output_tokens ?? 0 : 0,
|
|
1671
|
-
rawUsage: result.usage
|
|
1672
|
-
}
|
|
1673
|
-
});
|
|
1674
|
-
}
|
|
1675
|
-
return result;
|
|
1676
|
-
}, async error => {
|
|
1677
|
-
await captureAiGeneration(this.phClient, {
|
|
1678
|
-
...posthogParams,
|
|
1679
|
-
model: openAIParams.model,
|
|
1680
|
-
provider: 'openai',
|
|
1681
|
-
input: openAIParams.prompt,
|
|
1682
|
-
output: [],
|
|
1683
|
-
latency: 0,
|
|
1684
|
-
baseURL: this.baseURL,
|
|
1685
|
-
modelParameters: getModelParams(body),
|
|
1686
|
-
usage: {
|
|
1687
|
-
inputTokens: 0,
|
|
1688
|
-
outputTokens: 0
|
|
1689
|
-
},
|
|
1690
|
-
error
|
|
1691
|
-
});
|
|
1692
|
-
throw error;
|
|
1693
|
-
});
|
|
1694
|
-
return preserveAPIPromiseHelpers(parentPromise, wrappedPromise);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
class PostHogAzureOpenAI extends openai.AzureOpenAI {
|
|
1700
|
-
constructor(config) {
|
|
1701
|
-
const {
|
|
1702
|
-
posthog,
|
|
1703
|
-
...openAIConfig
|
|
1704
|
-
} = config;
|
|
1705
|
-
super(openAIConfig);
|
|
1706
|
-
this.phClient = posthog;
|
|
1707
|
-
this.chat = new WrappedChat(this, this.phClient);
|
|
1708
|
-
this.embeddings = new WrappedEmbeddings(this, this.phClient);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
class WrappedChat extends openai.AzureOpenAI.Chat {
|
|
1712
|
-
constructor(parentClient, phClient) {
|
|
1713
|
-
super(parentClient);
|
|
1714
|
-
this.completions = new WrappedCompletions(parentClient, phClient);
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
class WrappedCompletions extends openai.AzureOpenAI.Chat.Completions {
|
|
1718
|
-
constructor(client, phClient) {
|
|
1719
|
-
super(client);
|
|
1720
|
-
this.phClient = phClient;
|
|
1721
|
-
this.baseURL = client.baseURL;
|
|
1722
|
-
}
|
|
1723
|
-
// --- Implementation Signature
|
|
1724
|
-
create(body, options) {
|
|
1725
|
-
const {
|
|
1726
|
-
providerParams: openAIParams,
|
|
1727
|
-
posthogParams
|
|
1728
|
-
} = extractPosthogParams(body);
|
|
1729
|
-
const startTime = Date.now();
|
|
1730
|
-
const parentPromise = super.create(openAIParams, options);
|
|
1731
|
-
if (openAIParams.stream) {
|
|
1732
|
-
return parentPromise.then(value => {
|
|
1733
|
-
if ('tee' in value) {
|
|
1734
|
-
const [stream1, stream2] = value.tee();
|
|
1735
|
-
(async () => {
|
|
1736
|
-
// Hoisted so the catch block can surface whatever was accumulated
|
|
1737
|
-
// from the streamed chunks before the failure.
|
|
1738
|
-
let completionIdFromResponse;
|
|
1739
|
-
let systemFingerprintFromResponse;
|
|
1740
|
-
try {
|
|
1741
|
-
const contentBlocks = [];
|
|
1742
|
-
let accumulatedContent = '';
|
|
1743
|
-
let modelFromResponse;
|
|
1744
|
-
let firstTokenTime;
|
|
1745
|
-
let usage = {
|
|
1746
|
-
inputTokens: 0,
|
|
1747
|
-
outputTokens: 0
|
|
1748
|
-
};
|
|
1749
|
-
// Map to track in-progress tool calls
|
|
1750
|
-
const toolCallsInProgress = new Map();
|
|
1751
|
-
for await (const chunk of stream1) {
|
|
1752
|
-
// Extract model and completion metadata from chunk (Chat Completions chunks carry these fields)
|
|
1753
|
-
if (!modelFromResponse && chunk.model) {
|
|
1754
|
-
modelFromResponse = chunk.model;
|
|
1755
|
-
}
|
|
1756
|
-
if (!completionIdFromResponse && chunk.id) {
|
|
1757
|
-
completionIdFromResponse = chunk.id;
|
|
1758
|
-
}
|
|
1759
|
-
if (!systemFingerprintFromResponse && chunk.system_fingerprint) {
|
|
1760
|
-
systemFingerprintFromResponse = chunk.system_fingerprint;
|
|
1761
|
-
}
|
|
1762
|
-
const choice = chunk?.choices?.[0];
|
|
1763
|
-
// Handle text content
|
|
1764
|
-
const deltaContent = choice?.delta?.content;
|
|
1765
|
-
if (deltaContent) {
|
|
1766
|
-
if (firstTokenTime === undefined) {
|
|
1767
|
-
firstTokenTime = Date.now();
|
|
1768
|
-
}
|
|
1769
|
-
accumulatedContent += deltaContent;
|
|
1770
|
-
}
|
|
1771
|
-
// Handle tool calls
|
|
1772
|
-
const deltaToolCalls = choice?.delta?.tool_calls;
|
|
1773
|
-
if (deltaToolCalls && Array.isArray(deltaToolCalls)) {
|
|
1774
|
-
if (firstTokenTime === undefined) {
|
|
1775
|
-
firstTokenTime = Date.now();
|
|
1776
|
-
}
|
|
1777
|
-
for (const toolCall of deltaToolCalls) {
|
|
1778
|
-
const index = toolCall.index;
|
|
1779
|
-
if (index !== undefined) {
|
|
1780
|
-
if (!toolCallsInProgress.has(index)) {
|
|
1781
|
-
// New tool call
|
|
1782
|
-
toolCallsInProgress.set(index, {
|
|
1783
|
-
id: toolCall.id || '',
|
|
1784
|
-
name: toolCall.function?.name || '',
|
|
1785
|
-
arguments: ''
|
|
1786
|
-
});
|
|
1787
|
-
}
|
|
1788
|
-
const inProgressCall = toolCallsInProgress.get(index);
|
|
1789
|
-
if (inProgressCall) {
|
|
1790
|
-
// Update tool call data
|
|
1791
|
-
if (toolCall.id) {
|
|
1792
|
-
inProgressCall.id = toolCall.id;
|
|
1793
|
-
}
|
|
1794
|
-
if (toolCall.function?.name) {
|
|
1795
|
-
inProgressCall.name = toolCall.function.name;
|
|
1796
|
-
}
|
|
1797
|
-
if (toolCall.function?.arguments) {
|
|
1798
|
-
inProgressCall.arguments += toolCall.function.arguments;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
// Handle usage information
|
|
1805
|
-
if (chunk.usage) {
|
|
1806
|
-
usage = {
|
|
1807
|
-
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1808
|
-
outputTokens: chunk.usage.completion_tokens ?? 0,
|
|
1809
|
-
reasoningTokens: chunk.usage.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
1810
|
-
cacheReadInputTokens: chunk.usage.prompt_tokens_details?.cached_tokens ?? 0
|
|
1811
|
-
};
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
// Build final content blocks
|
|
1815
|
-
if (accumulatedContent) {
|
|
1816
|
-
contentBlocks.push({
|
|
1817
|
-
type: 'text',
|
|
1818
|
-
text: accumulatedContent
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
1821
|
-
// Add completed tool calls to content blocks
|
|
1822
|
-
for (const toolCall of toolCallsInProgress.values()) {
|
|
1823
|
-
if (toolCall.name) {
|
|
1824
|
-
contentBlocks.push({
|
|
1825
|
-
type: 'function',
|
|
1826
|
-
id: toolCall.id,
|
|
1827
|
-
function: {
|
|
1828
|
-
name: toolCall.name,
|
|
1829
|
-
arguments: toolCall.arguments
|
|
1830
|
-
}
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
// Format output to match non-streaming version
|
|
1835
|
-
const formattedOutput = contentBlocks.length > 0 ? [{
|
|
1836
|
-
role: 'assistant',
|
|
1837
|
-
content: contentBlocks
|
|
1838
|
-
}] : [{
|
|
1839
|
-
role: 'assistant',
|
|
1840
|
-
content: [{
|
|
1841
|
-
type: 'text',
|
|
1842
|
-
text: ''
|
|
1843
|
-
}]
|
|
1844
|
-
}];
|
|
1845
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1846
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
1847
|
-
await captureAiGeneration(this.phClient, {
|
|
1848
|
-
...posthogParams,
|
|
1849
|
-
model: openAIParams.model ?? modelFromResponse,
|
|
1850
|
-
provider: 'azure',
|
|
1851
|
-
input: sanitizeOpenAI(openAIParams.messages),
|
|
1852
|
-
output: formattedOutput,
|
|
1853
|
-
latency,
|
|
1854
|
-
timeToFirstToken,
|
|
1855
|
-
baseURL: this.baseURL,
|
|
1856
|
-
modelParameters: getModelParams(body),
|
|
1857
|
-
httpStatus: 200,
|
|
1858
|
-
usage,
|
|
1859
|
-
completionId: completionIdFromResponse,
|
|
1860
|
-
providerMetadata: buildProviderMetadata({
|
|
1861
|
-
systemFingerprint: systemFingerprintFromResponse
|
|
1862
|
-
})
|
|
1863
|
-
});
|
|
1864
|
-
} catch (error) {
|
|
1865
|
-
await captureAiGeneration(this.phClient, {
|
|
1866
|
-
...posthogParams,
|
|
1867
|
-
model: openAIParams.model,
|
|
1868
|
-
provider: 'azure',
|
|
1869
|
-
input: sanitizeOpenAI(openAIParams.messages),
|
|
1870
|
-
output: [],
|
|
1871
|
-
latency: 0,
|
|
1872
|
-
baseURL: this.baseURL,
|
|
1873
|
-
modelParameters: getModelParams(body),
|
|
1874
|
-
usage: {
|
|
1875
|
-
inputTokens: 0,
|
|
1876
|
-
outputTokens: 0
|
|
1877
|
-
},
|
|
1878
|
-
// If the stream fails mid-flight, surface whatever completion
|
|
1879
|
-
// metadata the consumed chunks already provided so the error
|
|
1880
|
-
// event can still be correlated to OpenAI's Logs dashboard.
|
|
1881
|
-
completionId: completionIdFromResponse,
|
|
1882
|
-
providerMetadata: buildProviderMetadata({
|
|
1883
|
-
systemFingerprint: systemFingerprintFromResponse
|
|
1884
|
-
}),
|
|
1885
|
-
error: error
|
|
1886
|
-
});
|
|
1887
|
-
throw error;
|
|
1888
|
-
}
|
|
1889
|
-
})();
|
|
1890
|
-
// Return the other stream to the user
|
|
1891
|
-
return stream2;
|
|
1892
|
-
}
|
|
1893
|
-
return value;
|
|
1894
|
-
});
|
|
1895
|
-
} else {
|
|
1896
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
1897
|
-
if ('choices' in result) {
|
|
1898
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
1899
|
-
await captureAiGeneration(this.phClient, {
|
|
1900
|
-
...posthogParams,
|
|
1901
|
-
model: openAIParams.model ?? result.model,
|
|
1902
|
-
provider: 'azure',
|
|
1903
|
-
input: openAIParams.messages,
|
|
1904
|
-
output: formatResponseOpenAI(result),
|
|
1905
|
-
latency,
|
|
1906
|
-
baseURL: this.baseURL,
|
|
1907
|
-
modelParameters: getModelParams(body),
|
|
1908
|
-
httpStatus: 200,
|
|
1909
|
-
usage: {
|
|
1910
|
-
inputTokens: result.usage?.prompt_tokens ?? 0,
|
|
1911
|
-
outputTokens: result.usage?.completion_tokens ?? 0,
|
|
1912
|
-
reasoningTokens: result.usage?.completion_tokens_details?.reasoning_tokens ?? 0,
|
|
1913
|
-
cacheReadInputTokens: result.usage?.prompt_tokens_details?.cached_tokens ?? 0
|
|
1914
|
-
},
|
|
1915
|
-
completionId: result.id,
|
|
1916
|
-
providerMetadata: buildProviderMetadata({
|
|
1917
|
-
systemFingerprint: result.system_fingerprint,
|
|
1918
|
-
requestId: extractRequestId(result)
|
|
1919
|
-
})
|
|
1920
|
-
});
|
|
1921
|
-
}
|
|
1922
|
-
return result;
|
|
1923
|
-
}, async error => {
|
|
1924
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
1925
|
-
await captureAiGeneration(this.phClient, {
|
|
1926
|
-
...posthogParams,
|
|
1927
|
-
model: openAIParams.model,
|
|
1928
|
-
provider: 'azure',
|
|
1929
|
-
input: openAIParams.messages,
|
|
1930
|
-
output: [],
|
|
1931
|
-
latency: 0,
|
|
1932
|
-
baseURL: this.baseURL,
|
|
1933
|
-
modelParameters: getModelParams(body),
|
|
1934
|
-
httpStatus,
|
|
1935
|
-
usage: {
|
|
1936
|
-
inputTokens: 0,
|
|
1937
|
-
outputTokens: 0
|
|
1938
|
-
},
|
|
1939
|
-
error
|
|
1940
|
-
});
|
|
1941
|
-
throw error;
|
|
1942
|
-
});
|
|
1943
|
-
return wrappedPromise;
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
class WrappedResponses extends openai.AzureOpenAI.Responses {
|
|
1948
|
-
constructor(client, phClient) {
|
|
1949
|
-
super(client);
|
|
1950
|
-
this.phClient = phClient;
|
|
1951
|
-
this.baseURL = client.baseURL;
|
|
1952
|
-
}
|
|
1953
|
-
// --- Implementation Signature
|
|
1954
|
-
create(body, options) {
|
|
1955
|
-
const {
|
|
1956
|
-
providerParams: openAIParams,
|
|
1957
|
-
posthogParams
|
|
1958
|
-
} = extractPosthogParams(body);
|
|
1959
|
-
const startTime = Date.now();
|
|
1960
|
-
const parentPromise = super.create(openAIParams, options);
|
|
1961
|
-
if (openAIParams.stream) {
|
|
1962
|
-
return parentPromise.then(value => {
|
|
1963
|
-
if ('tee' in value && typeof value.tee === 'function') {
|
|
1964
|
-
const [stream1, stream2] = value.tee();
|
|
1965
|
-
(async () => {
|
|
1966
|
-
// Hoisted so the catch block can surface the completion ID that
|
|
1967
|
-
// was accumulated from the streamed chunks before the failure.
|
|
1968
|
-
let completionIdFromResponse;
|
|
1969
|
-
try {
|
|
1970
|
-
let finalContent = [];
|
|
1971
|
-
let modelFromResponse;
|
|
1972
|
-
let firstTokenTime;
|
|
1973
|
-
let usage = {
|
|
1974
|
-
inputTokens: 0,
|
|
1975
|
-
outputTokens: 0
|
|
1976
|
-
};
|
|
1977
|
-
for await (const chunk of stream1) {
|
|
1978
|
-
// Track first token time on content delta events
|
|
1979
|
-
if (firstTokenTime === undefined && isResponseTokenChunk(chunk)) {
|
|
1980
|
-
firstTokenTime = Date.now();
|
|
1981
|
-
}
|
|
1982
|
-
if ('response' in chunk && chunk.response) {
|
|
1983
|
-
// Extract model and completion ID from the response object in the chunk (for stored prompts)
|
|
1984
|
-
if (!modelFromResponse && chunk.response.model) {
|
|
1985
|
-
modelFromResponse = chunk.response.model;
|
|
1986
|
-
}
|
|
1987
|
-
if (!completionIdFromResponse && chunk.response.id) {
|
|
1988
|
-
completionIdFromResponse = chunk.response.id;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
if (chunk.type === 'response.completed' && 'response' in chunk && chunk.response?.output && chunk.response.output.length > 0) {
|
|
1992
|
-
finalContent = chunk.response.output;
|
|
1993
|
-
}
|
|
1994
|
-
if ('usage' in chunk && chunk.usage) {
|
|
1995
|
-
usage = {
|
|
1996
|
-
inputTokens: chunk.usage.input_tokens ?? 0,
|
|
1997
|
-
outputTokens: chunk.usage.output_tokens ?? 0,
|
|
1998
|
-
reasoningTokens: chunk.usage.output_tokens_details?.reasoning_tokens ?? 0,
|
|
1999
|
-
cacheReadInputTokens: chunk.usage.input_tokens_details?.cached_tokens ?? 0
|
|
2000
|
-
};
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
2004
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
2005
|
-
await captureAiGeneration(this.phClient, {
|
|
2006
|
-
...posthogParams,
|
|
2007
|
-
model: openAIParams.model ?? modelFromResponse,
|
|
2008
|
-
provider: 'azure',
|
|
2009
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2010
|
-
output: finalContent,
|
|
2011
|
-
latency,
|
|
2012
|
-
timeToFirstToken,
|
|
2013
|
-
baseURL: this.baseURL,
|
|
2014
|
-
modelParameters: getModelParams(body),
|
|
2015
|
-
httpStatus: 200,
|
|
2016
|
-
usage,
|
|
2017
|
-
completionId: completionIdFromResponse
|
|
2018
|
-
});
|
|
2019
|
-
} catch (error) {
|
|
2020
|
-
await captureAiGeneration(this.phClient, {
|
|
2021
|
-
...posthogParams,
|
|
2022
|
-
model: openAIParams.model,
|
|
2023
|
-
provider: 'azure',
|
|
2024
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2025
|
-
output: [],
|
|
2026
|
-
latency: 0,
|
|
2027
|
-
baseURL: this.baseURL,
|
|
2028
|
-
modelParameters: getModelParams(body),
|
|
2029
|
-
usage: {
|
|
2030
|
-
inputTokens: 0,
|
|
2031
|
-
outputTokens: 0
|
|
2032
|
-
},
|
|
2033
|
-
// Surface the completion ID from any chunks consumed before
|
|
2034
|
-
// the stream failed so the error event remains correlatable.
|
|
2035
|
-
completionId: completionIdFromResponse,
|
|
2036
|
-
error: error
|
|
2037
|
-
});
|
|
2038
|
-
throw error;
|
|
2039
|
-
}
|
|
2040
|
-
})();
|
|
2041
|
-
return stream2;
|
|
2042
|
-
}
|
|
2043
|
-
return value;
|
|
2044
|
-
});
|
|
2045
|
-
} else {
|
|
2046
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
2047
|
-
if ('output' in result) {
|
|
2048
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
2049
|
-
await captureAiGeneration(this.phClient, {
|
|
2050
|
-
...posthogParams,
|
|
2051
|
-
model: openAIParams.model ?? result.model,
|
|
2052
|
-
provider: 'azure',
|
|
2053
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2054
|
-
output: result.output,
|
|
2055
|
-
latency,
|
|
2056
|
-
baseURL: this.baseURL,
|
|
2057
|
-
modelParameters: getModelParams(body),
|
|
2058
|
-
httpStatus: 200,
|
|
2059
|
-
usage: {
|
|
2060
|
-
inputTokens: result.usage?.input_tokens ?? 0,
|
|
2061
|
-
outputTokens: result.usage?.output_tokens ?? 0,
|
|
2062
|
-
reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
|
2063
|
-
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
|
|
2064
|
-
},
|
|
2065
|
-
completionId: result.id,
|
|
2066
|
-
providerMetadata: buildProviderMetadata({
|
|
2067
|
-
requestId: extractRequestId(result)
|
|
2068
|
-
})
|
|
2069
|
-
});
|
|
2070
|
-
}
|
|
2071
|
-
return result;
|
|
2072
|
-
}, async error => {
|
|
2073
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
2074
|
-
await captureAiGeneration(this.phClient, {
|
|
2075
|
-
...posthogParams,
|
|
2076
|
-
model: openAIParams.model,
|
|
2077
|
-
provider: 'azure',
|
|
2078
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2079
|
-
output: [],
|
|
2080
|
-
latency: 0,
|
|
2081
|
-
baseURL: this.baseURL,
|
|
2082
|
-
modelParameters: getModelParams(body),
|
|
2083
|
-
httpStatus,
|
|
2084
|
-
usage: {
|
|
2085
|
-
inputTokens: 0,
|
|
2086
|
-
outputTokens: 0
|
|
2087
|
-
},
|
|
2088
|
-
error
|
|
2089
|
-
});
|
|
2090
|
-
throw error;
|
|
2091
|
-
});
|
|
2092
|
-
return wrappedPromise;
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
parse(body, options) {
|
|
2096
|
-
const {
|
|
2097
|
-
providerParams: openAIParams,
|
|
2098
|
-
posthogParams
|
|
2099
|
-
} = extractPosthogParams(body);
|
|
2100
|
-
const startTime = Date.now();
|
|
2101
|
-
const parentPromise = super.parse(openAIParams, options);
|
|
2102
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
2103
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
2104
|
-
await captureAiGeneration(this.phClient, {
|
|
2105
|
-
...posthogParams,
|
|
2106
|
-
model: openAIParams.model ?? result.model,
|
|
2107
|
-
provider: 'azure',
|
|
2108
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2109
|
-
output: result.output,
|
|
2110
|
-
latency,
|
|
2111
|
-
baseURL: this.baseURL,
|
|
2112
|
-
modelParameters: getModelParams(body),
|
|
2113
|
-
httpStatus: 200,
|
|
2114
|
-
usage: {
|
|
2115
|
-
inputTokens: result.usage?.input_tokens ?? 0,
|
|
2116
|
-
outputTokens: result.usage?.output_tokens ?? 0,
|
|
2117
|
-
reasoningTokens: result.usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
|
2118
|
-
cacheReadInputTokens: result.usage?.input_tokens_details?.cached_tokens ?? 0
|
|
2119
|
-
},
|
|
2120
|
-
completionId: result.id,
|
|
2121
|
-
providerMetadata: buildProviderMetadata({
|
|
2122
|
-
requestId: extractRequestId(result)
|
|
2123
|
-
})
|
|
2124
|
-
});
|
|
2125
|
-
return result;
|
|
2126
|
-
}, async error => {
|
|
2127
|
-
await captureAiGeneration(this.phClient, {
|
|
2128
|
-
...posthogParams,
|
|
2129
|
-
model: openAIParams.model,
|
|
2130
|
-
provider: 'azure',
|
|
2131
|
-
input: formatOpenAIResponsesInput(openAIParams.input, openAIParams.instructions),
|
|
2132
|
-
output: [],
|
|
2133
|
-
latency: 0,
|
|
2134
|
-
baseURL: this.baseURL,
|
|
2135
|
-
modelParameters: getModelParams(body),
|
|
2136
|
-
httpStatus: error?.status ? error.status : 500,
|
|
2137
|
-
usage: {
|
|
2138
|
-
inputTokens: 0,
|
|
2139
|
-
outputTokens: 0
|
|
2140
|
-
},
|
|
2141
|
-
error
|
|
2142
|
-
});
|
|
2143
|
-
throw error;
|
|
2144
|
-
});
|
|
2145
|
-
return wrappedPromise;
|
|
2146
|
-
}
|
|
2147
|
-
}
|
|
2148
|
-
class WrappedEmbeddings extends openai.AzureOpenAI.Embeddings {
|
|
2149
|
-
constructor(client, phClient) {
|
|
2150
|
-
super(client);
|
|
2151
|
-
this.phClient = phClient;
|
|
2152
|
-
this.baseURL = client.baseURL;
|
|
2153
|
-
}
|
|
2154
|
-
create(body, options) {
|
|
2155
|
-
const {
|
|
2156
|
-
providerParams: openAIParams,
|
|
2157
|
-
posthogParams
|
|
2158
|
-
} = extractPosthogParams(body);
|
|
2159
|
-
const startTime = Date.now();
|
|
2160
|
-
const parentPromise = super.create(openAIParams, options);
|
|
2161
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
2162
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
2163
|
-
await captureAiGeneration(this.phClient, {
|
|
2164
|
-
eventType: exports.AIEvent.Embedding,
|
|
2165
|
-
...posthogParams,
|
|
2166
|
-
model: openAIParams.model,
|
|
2167
|
-
provider: 'azure',
|
|
2168
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
|
|
2169
|
-
output: null,
|
|
2170
|
-
// Embeddings don't have output content
|
|
2171
|
-
latency,
|
|
2172
|
-
baseURL: this.baseURL,
|
|
2173
|
-
modelParameters: getModelParams(body),
|
|
2174
|
-
httpStatus: 200,
|
|
2175
|
-
usage: {
|
|
2176
|
-
inputTokens: result.usage?.prompt_tokens ?? 0
|
|
2177
|
-
}
|
|
2178
|
-
});
|
|
2179
|
-
return result;
|
|
2180
|
-
}, async error => {
|
|
2181
|
-
const httpStatus = error && typeof error === 'object' && 'status' in error ? error.status ?? 500 : 500;
|
|
2182
|
-
await captureAiGeneration(this.phClient, {
|
|
2183
|
-
eventType: exports.AIEvent.Embedding,
|
|
2184
|
-
...posthogParams,
|
|
2185
|
-
model: openAIParams.model,
|
|
2186
|
-
provider: 'azure',
|
|
2187
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode, openAIParams.input),
|
|
2188
|
-
output: null,
|
|
2189
|
-
latency: 0,
|
|
2190
|
-
baseURL: this.baseURL,
|
|
2191
|
-
modelParameters: getModelParams(body),
|
|
2192
|
-
httpStatus,
|
|
2193
|
-
usage: {
|
|
2194
|
-
inputTokens: 0
|
|
2195
|
-
},
|
|
2196
|
-
error
|
|
2197
|
-
});
|
|
2198
|
-
throw error;
|
|
2199
|
-
});
|
|
2200
|
-
return wrappedPromise;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
// Type guards
|
|
2205
|
-
function isV3Model(model) {
|
|
2206
|
-
return model.specificationVersion === 'v3';
|
|
2207
|
-
}
|
|
2208
|
-
const mapVercelParams = params => {
|
|
2209
|
-
return {
|
|
2210
|
-
temperature: params.temperature,
|
|
2211
|
-
max_output_tokens: params.maxOutputTokens,
|
|
2212
|
-
top_p: params.topP,
|
|
2213
|
-
frequency_penalty: params.frequencyPenalty,
|
|
2214
|
-
presence_penalty: params.presencePenalty,
|
|
2215
|
-
stop: params.stopSequences,
|
|
2216
|
-
stream: params.stream
|
|
2217
|
-
};
|
|
2218
|
-
};
|
|
2219
|
-
const mapVercelPrompt = messages => {
|
|
2220
|
-
// Map and truncate individual content
|
|
2221
|
-
const inputs = messages.map(message => {
|
|
2222
|
-
let content;
|
|
2223
|
-
// Handle system role which has string content
|
|
2224
|
-
if (message.role === 'system') {
|
|
2225
|
-
content = [{
|
|
2226
|
-
type: 'text',
|
|
2227
|
-
text: truncate(toContentString(message.content))
|
|
2228
|
-
}];
|
|
2229
|
-
} else {
|
|
2230
|
-
// Handle other roles which have array content
|
|
2231
|
-
if (Array.isArray(message.content)) {
|
|
2232
|
-
content = message.content.map(c => {
|
|
2233
|
-
if (c.type === 'text') {
|
|
2234
|
-
return {
|
|
2235
|
-
type: 'text',
|
|
2236
|
-
text: truncate(c.text)
|
|
2237
|
-
};
|
|
2238
|
-
} else if (c.type === 'file') {
|
|
2239
|
-
// For file type, check if it's a data URL and redact if needed
|
|
2240
|
-
let fileData;
|
|
2241
|
-
const contentData = c.data;
|
|
2242
|
-
if (contentData instanceof URL) {
|
|
2243
|
-
fileData = contentData.toString();
|
|
2244
|
-
} else if (isString(contentData)) {
|
|
2245
|
-
// Redact base64 data URLs and raw base64 to prevent oversized events
|
|
2246
|
-
fileData = redactBase64DataUrl(contentData);
|
|
2247
|
-
} else {
|
|
2248
|
-
fileData = 'raw files not supported';
|
|
589
|
+
// Handle other roles which have array content
|
|
590
|
+
if (Array.isArray(message.content)) {
|
|
591
|
+
content = message.content.map(c => {
|
|
592
|
+
if (c.type === 'text') {
|
|
593
|
+
return {
|
|
594
|
+
type: 'text',
|
|
595
|
+
text: truncate(c.text)
|
|
596
|
+
};
|
|
597
|
+
} else if (c.type === 'file') {
|
|
598
|
+
// For file type, check if it's a data URL and redact if needed
|
|
599
|
+
let fileData;
|
|
600
|
+
const contentData = c.data;
|
|
601
|
+
if (contentData instanceof URL) {
|
|
602
|
+
fileData = contentData.toString();
|
|
603
|
+
} else if (isString(contentData)) {
|
|
604
|
+
// Redact base64 data URLs and raw base64 to prevent oversized events
|
|
605
|
+
fileData = redactBase64DataUrl(contentData);
|
|
606
|
+
} else {
|
|
607
|
+
fileData = 'raw files not supported';
|
|
2249
608
|
}
|
|
2250
609
|
return {
|
|
2251
610
|
type: 'file',
|
|
@@ -2822,1794 +1181,19 @@ const wrapVercelLanguageModel = (model, phClient, options) => {
|
|
|
2822
1181
|
return wrappedModel;
|
|
2823
1182
|
};
|
|
2824
1183
|
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
}
|
|
1184
|
+
/// <reference lib="dom" />
|
|
1185
|
+
const DEFAULT_CACHE_TTL_SECONDS = 300; // 5 minutes
|
|
1186
|
+
const DEFAULT_PROMPTS_HOST = 'https://us.posthog.com';
|
|
1187
|
+
function normalizeApiKey(value) {
|
|
1188
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
1189
|
+
}
|
|
1190
|
+
function normalizeHost(value) {
|
|
1191
|
+
const normalizedHost = typeof value === 'string' ? value.trim() : '';
|
|
1192
|
+
return (normalizedHost || DEFAULT_PROMPTS_HOST).replace(/\/+$/, '');
|
|
2835
1193
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
this.phClient = phClient;
|
|
2840
|
-
this.baseURL = parentClient.baseURL;
|
|
2841
|
-
}
|
|
2842
|
-
create(body, options) {
|
|
2843
|
-
const {
|
|
2844
|
-
providerParams: anthropicParams,
|
|
2845
|
-
posthogParams
|
|
2846
|
-
} = extractPosthogParams(body);
|
|
2847
|
-
const startTime = Date.now();
|
|
2848
|
-
const parentPromise = super.create(anthropicParams, options);
|
|
2849
|
-
if (anthropicParams.stream) {
|
|
2850
|
-
return parentPromise.then(value => {
|
|
2851
|
-
let accumulatedContent = '';
|
|
2852
|
-
const contentBlocks = [];
|
|
2853
|
-
const toolsInProgress = new Map();
|
|
2854
|
-
let currentTextBlock = null;
|
|
2855
|
-
let firstTokenTime;
|
|
2856
|
-
let stopReason;
|
|
2857
|
-
const usage = {
|
|
2858
|
-
inputTokens: 0,
|
|
2859
|
-
outputTokens: 0,
|
|
2860
|
-
cacheCreationInputTokens: 0,
|
|
2861
|
-
cacheReadInputTokens: 0,
|
|
2862
|
-
webSearchCount: 0
|
|
2863
|
-
};
|
|
2864
|
-
let lastRawUsage;
|
|
2865
|
-
if ('tee' in value) {
|
|
2866
|
-
const [stream1, stream2] = value.tee();
|
|
2867
|
-
(async () => {
|
|
2868
|
-
try {
|
|
2869
|
-
for await (const chunk of stream1) {
|
|
2870
|
-
// Handle content block start events
|
|
2871
|
-
if (chunk.type === 'content_block_start') {
|
|
2872
|
-
if (chunk.content_block?.type === 'text') {
|
|
2873
|
-
currentTextBlock = {
|
|
2874
|
-
type: 'text',
|
|
2875
|
-
text: ''
|
|
2876
|
-
};
|
|
2877
|
-
contentBlocks.push(currentTextBlock);
|
|
2878
|
-
} else if (chunk.content_block?.type === 'tool_use') {
|
|
2879
|
-
if (firstTokenTime === undefined) {
|
|
2880
|
-
firstTokenTime = Date.now();
|
|
2881
|
-
}
|
|
2882
|
-
const toolBlock = {
|
|
2883
|
-
type: 'function',
|
|
2884
|
-
id: chunk.content_block.id,
|
|
2885
|
-
function: {
|
|
2886
|
-
name: chunk.content_block.name,
|
|
2887
|
-
arguments: {}
|
|
2888
|
-
}
|
|
2889
|
-
};
|
|
2890
|
-
contentBlocks.push(toolBlock);
|
|
2891
|
-
toolsInProgress.set(chunk.content_block.id, {
|
|
2892
|
-
block: toolBlock,
|
|
2893
|
-
inputString: ''
|
|
2894
|
-
});
|
|
2895
|
-
currentTextBlock = null;
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
// Handle text delta events
|
|
2899
|
-
if ('delta' in chunk) {
|
|
2900
|
-
if ('text' in chunk.delta) {
|
|
2901
|
-
const delta = chunk.delta.text;
|
|
2902
|
-
if (firstTokenTime === undefined) {
|
|
2903
|
-
firstTokenTime = Date.now();
|
|
2904
|
-
}
|
|
2905
|
-
accumulatedContent += delta;
|
|
2906
|
-
if (currentTextBlock) {
|
|
2907
|
-
currentTextBlock.text += delta;
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
// Handle tool input delta events
|
|
2912
|
-
if (chunk.type === 'content_block_delta' && chunk.delta?.type === 'input_json_delta') {
|
|
2913
|
-
const block = chunk.index !== undefined ? contentBlocks[chunk.index] : undefined;
|
|
2914
|
-
const toolId = block?.type === 'function' ? block.id : undefined;
|
|
2915
|
-
if (toolId && toolsInProgress.has(toolId)) {
|
|
2916
|
-
const tool = toolsInProgress.get(toolId);
|
|
2917
|
-
if (tool) {
|
|
2918
|
-
tool.inputString += chunk.delta.partial_json || '';
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
// Handle content block stop events
|
|
2923
|
-
if (chunk.type === 'content_block_stop') {
|
|
2924
|
-
currentTextBlock = null;
|
|
2925
|
-
// Parse accumulated tool input
|
|
2926
|
-
if (chunk.index !== undefined) {
|
|
2927
|
-
const block = contentBlocks[chunk.index];
|
|
2928
|
-
if (block?.type === 'function' && block.id && toolsInProgress.has(block.id)) {
|
|
2929
|
-
const tool = toolsInProgress.get(block.id);
|
|
2930
|
-
if (tool) {
|
|
2931
|
-
try {
|
|
2932
|
-
block.function.arguments = JSON.parse(tool.inputString);
|
|
2933
|
-
} catch (e) {
|
|
2934
|
-
// Keep empty object if parsing fails
|
|
2935
|
-
console.error('Error parsing tool input:', e);
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
toolsInProgress.delete(block.id);
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
if (chunk.type == 'message_start') {
|
|
2943
|
-
lastRawUsage = chunk.message.usage;
|
|
2944
|
-
usage.inputTokens = chunk.message.usage.input_tokens ?? 0;
|
|
2945
|
-
usage.cacheCreationInputTokens = chunk.message.usage.cache_creation_input_tokens ?? 0;
|
|
2946
|
-
usage.cacheReadInputTokens = chunk.message.usage.cache_read_input_tokens ?? 0;
|
|
2947
|
-
usage.webSearchCount = chunk.message.usage.server_tool_use?.web_search_requests ?? 0;
|
|
2948
|
-
}
|
|
2949
|
-
if ('usage' in chunk) {
|
|
2950
|
-
lastRawUsage = chunk.usage;
|
|
2951
|
-
usage.outputTokens = chunk.usage.output_tokens ?? 0;
|
|
2952
|
-
// Update web search count if present in delta
|
|
2953
|
-
if (chunk.usage.server_tool_use?.web_search_requests !== undefined) {
|
|
2954
|
-
usage.webSearchCount = chunk.usage.server_tool_use.web_search_requests;
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
if (chunk.type === 'message_delta' && 'delta' in chunk) {
|
|
2958
|
-
const delta = chunk.delta;
|
|
2959
|
-
if ('stop_reason' in delta && typeof delta.stop_reason === 'string' && delta.stop_reason) {
|
|
2960
|
-
stopReason = delta.stop_reason;
|
|
2961
|
-
}
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
usage.rawUsage = lastRawUsage;
|
|
2965
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
2966
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
2967
|
-
const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
|
|
2968
|
-
// Format output to match non-streaming version
|
|
2969
|
-
const formattedOutput = contentBlocks.length > 0 ? [{
|
|
2970
|
-
role: 'assistant',
|
|
2971
|
-
content: contentBlocks
|
|
2972
|
-
}] : [{
|
|
2973
|
-
role: 'assistant',
|
|
2974
|
-
content: [{
|
|
2975
|
-
type: 'text',
|
|
2976
|
-
text: accumulatedContent
|
|
2977
|
-
}]
|
|
2978
|
-
}];
|
|
2979
|
-
await captureAiGeneration(this.phClient, {
|
|
2980
|
-
...posthogParams,
|
|
2981
|
-
model: anthropicParams.model,
|
|
2982
|
-
provider: 'anthropic',
|
|
2983
|
-
input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams, 'anthropic')),
|
|
2984
|
-
output: formattedOutput,
|
|
2985
|
-
latency,
|
|
2986
|
-
timeToFirstToken,
|
|
2987
|
-
baseURL: this.baseURL,
|
|
2988
|
-
modelParameters: getModelParams(body),
|
|
2989
|
-
httpStatus: 200,
|
|
2990
|
-
usage,
|
|
2991
|
-
stopReason,
|
|
2992
|
-
tools: availableTools
|
|
2993
|
-
});
|
|
2994
|
-
} catch (error) {
|
|
2995
|
-
await captureAiGeneration(this.phClient, {
|
|
2996
|
-
...posthogParams,
|
|
2997
|
-
model: anthropicParams.model,
|
|
2998
|
-
provider: 'anthropic',
|
|
2999
|
-
input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
|
|
3000
|
-
output: [],
|
|
3001
|
-
latency: 0,
|
|
3002
|
-
baseURL: this.baseURL,
|
|
3003
|
-
modelParameters: getModelParams(body),
|
|
3004
|
-
usage: {
|
|
3005
|
-
inputTokens: 0,
|
|
3006
|
-
outputTokens: 0
|
|
3007
|
-
},
|
|
3008
|
-
error: error
|
|
3009
|
-
});
|
|
3010
|
-
throw error;
|
|
3011
|
-
}
|
|
3012
|
-
})();
|
|
3013
|
-
// Return the other stream to the user
|
|
3014
|
-
return stream2;
|
|
3015
|
-
}
|
|
3016
|
-
return value;
|
|
3017
|
-
});
|
|
3018
|
-
} else {
|
|
3019
|
-
const wrappedPromise = parentPromise.then(async result => {
|
|
3020
|
-
if ('content' in result) {
|
|
3021
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3022
|
-
const availableTools = extractAvailableToolCalls('anthropic', anthropicParams);
|
|
3023
|
-
await captureAiGeneration(this.phClient, {
|
|
3024
|
-
...posthogParams,
|
|
3025
|
-
model: anthropicParams.model,
|
|
3026
|
-
provider: 'anthropic',
|
|
3027
|
-
input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
|
|
3028
|
-
output: formatResponseAnthropic(result),
|
|
3029
|
-
latency,
|
|
3030
|
-
baseURL: this.baseURL,
|
|
3031
|
-
modelParameters: getModelParams(body),
|
|
3032
|
-
httpStatus: 200,
|
|
3033
|
-
usage: {
|
|
3034
|
-
inputTokens: result.usage.input_tokens ?? 0,
|
|
3035
|
-
outputTokens: result.usage.output_tokens ?? 0,
|
|
3036
|
-
cacheCreationInputTokens: result.usage.cache_creation_input_tokens ?? 0,
|
|
3037
|
-
cacheReadInputTokens: result.usage.cache_read_input_tokens ?? 0,
|
|
3038
|
-
webSearchCount: result.usage.server_tool_use?.web_search_requests ?? 0,
|
|
3039
|
-
rawUsage: result.usage
|
|
3040
|
-
},
|
|
3041
|
-
stopReason: result.stop_reason ?? undefined,
|
|
3042
|
-
tools: availableTools
|
|
3043
|
-
});
|
|
3044
|
-
}
|
|
3045
|
-
return result;
|
|
3046
|
-
}, async error => {
|
|
3047
|
-
await captureAiGeneration(this.phClient, {
|
|
3048
|
-
...posthogParams,
|
|
3049
|
-
model: anthropicParams.model,
|
|
3050
|
-
provider: 'anthropic',
|
|
3051
|
-
input: sanitizeAnthropic(mergeSystemPrompt(anthropicParams)),
|
|
3052
|
-
output: [],
|
|
3053
|
-
latency: 0,
|
|
3054
|
-
baseURL: this.baseURL,
|
|
3055
|
-
modelParameters: getModelParams(body),
|
|
3056
|
-
httpStatus: error?.status ? error.status : 500,
|
|
3057
|
-
usage: {
|
|
3058
|
-
inputTokens: 0,
|
|
3059
|
-
outputTokens: 0
|
|
3060
|
-
},
|
|
3061
|
-
error: error
|
|
3062
|
-
});
|
|
3063
|
-
throw error;
|
|
3064
|
-
});
|
|
3065
|
-
return wrappedPromise;
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
class PostHogGoogleGenAI {
|
|
3071
|
-
constructor(config) {
|
|
3072
|
-
const {
|
|
3073
|
-
posthog,
|
|
3074
|
-
...geminiConfig
|
|
3075
|
-
} = config;
|
|
3076
|
-
this.phClient = posthog;
|
|
3077
|
-
this.client = new genai.GoogleGenAI(geminiConfig);
|
|
3078
|
-
this.models = new WrappedModels(this.client, this.phClient);
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
class WrappedModels {
|
|
3082
|
-
constructor(client, phClient) {
|
|
3083
|
-
this.client = client;
|
|
3084
|
-
this.phClient = phClient;
|
|
3085
|
-
}
|
|
3086
|
-
async generateContent(params) {
|
|
3087
|
-
const {
|
|
3088
|
-
providerParams: geminiParams,
|
|
3089
|
-
posthogParams
|
|
3090
|
-
} = extractPosthogParams(params);
|
|
3091
|
-
const startTime = Date.now();
|
|
3092
|
-
try {
|
|
3093
|
-
const response = await this.client.models.generateContent(geminiParams);
|
|
3094
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3095
|
-
const availableTools = extractAvailableToolCalls('gemini', geminiParams);
|
|
3096
|
-
const metadata = response.usageMetadata;
|
|
3097
|
-
const finishReason = response.candidates?.[0]?.finishReason;
|
|
3098
|
-
await captureAiGeneration(this.phClient, {
|
|
3099
|
-
...posthogParams,
|
|
3100
|
-
model: geminiParams.model,
|
|
3101
|
-
provider: 'gemini',
|
|
3102
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
3103
|
-
output: formatResponseGemini(response),
|
|
3104
|
-
latency,
|
|
3105
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3106
|
-
modelParameters: getModelParams(params),
|
|
3107
|
-
httpStatus: 200,
|
|
3108
|
-
usage: {
|
|
3109
|
-
inputTokens: metadata?.promptTokenCount ?? 0,
|
|
3110
|
-
outputTokens: metadata?.candidatesTokenCount ?? 0,
|
|
3111
|
-
reasoningTokens: metadata?.thoughtsTokenCount ?? 0,
|
|
3112
|
-
cacheReadInputTokens: metadata?.cachedContentTokenCount ?? 0,
|
|
3113
|
-
webSearchCount: calculateGoogleWebSearchCount(response),
|
|
3114
|
-
rawUsage: metadata
|
|
3115
|
-
},
|
|
3116
|
-
stopReason: finishReason ?? undefined,
|
|
3117
|
-
tools: availableTools
|
|
3118
|
-
});
|
|
3119
|
-
return response;
|
|
3120
|
-
} catch (error) {
|
|
3121
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3122
|
-
await captureAiGeneration(this.phClient, {
|
|
3123
|
-
...posthogParams,
|
|
3124
|
-
model: geminiParams.model,
|
|
3125
|
-
provider: 'gemini',
|
|
3126
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
3127
|
-
output: [],
|
|
3128
|
-
latency,
|
|
3129
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3130
|
-
modelParameters: getModelParams(params),
|
|
3131
|
-
usage: {
|
|
3132
|
-
inputTokens: 0,
|
|
3133
|
-
outputTokens: 0
|
|
3134
|
-
},
|
|
3135
|
-
error
|
|
3136
|
-
});
|
|
3137
|
-
throw error;
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
async *generateContentStream(params) {
|
|
3141
|
-
const {
|
|
3142
|
-
providerParams: geminiParams,
|
|
3143
|
-
posthogParams
|
|
3144
|
-
} = extractPosthogParams(params);
|
|
3145
|
-
const startTime = Date.now();
|
|
3146
|
-
const accumulatedContent = [];
|
|
3147
|
-
let firstTokenTime;
|
|
3148
|
-
let stopReason;
|
|
3149
|
-
let usage = {
|
|
3150
|
-
inputTokens: 0,
|
|
3151
|
-
outputTokens: 0,
|
|
3152
|
-
webSearchCount: 0,
|
|
3153
|
-
rawUsage: undefined
|
|
3154
|
-
};
|
|
3155
|
-
try {
|
|
3156
|
-
const stream = await this.client.models.generateContentStream(geminiParams);
|
|
3157
|
-
for await (const chunk of stream) {
|
|
3158
|
-
// Track first token time when we get text content
|
|
3159
|
-
if (firstTokenTime === undefined && chunk.text) {
|
|
3160
|
-
firstTokenTime = Date.now();
|
|
3161
|
-
}
|
|
3162
|
-
const chunkWebSearchCount = calculateGoogleWebSearchCount(chunk);
|
|
3163
|
-
if (chunkWebSearchCount > 0 && chunkWebSearchCount > (usage.webSearchCount ?? 0)) {
|
|
3164
|
-
usage.webSearchCount = chunkWebSearchCount;
|
|
3165
|
-
}
|
|
3166
|
-
// Handle text content
|
|
3167
|
-
if (chunk.text) {
|
|
3168
|
-
// Find if we already have a text item to append to
|
|
3169
|
-
let lastTextItem;
|
|
3170
|
-
for (let i = accumulatedContent.length - 1; i >= 0; i--) {
|
|
3171
|
-
if (accumulatedContent[i].type === 'text') {
|
|
3172
|
-
lastTextItem = accumulatedContent[i];
|
|
3173
|
-
break;
|
|
3174
|
-
}
|
|
3175
|
-
}
|
|
3176
|
-
if (lastTextItem && lastTextItem.type === 'text') {
|
|
3177
|
-
lastTextItem.text += chunk.text;
|
|
3178
|
-
} else {
|
|
3179
|
-
accumulatedContent.push({
|
|
3180
|
-
type: 'text',
|
|
3181
|
-
text: chunk.text
|
|
3182
|
-
});
|
|
3183
|
-
}
|
|
3184
|
-
}
|
|
3185
|
-
// Track finish reason from candidates
|
|
3186
|
-
if (chunk.candidates?.[0]?.finishReason) {
|
|
3187
|
-
stopReason = chunk.candidates[0].finishReason;
|
|
3188
|
-
}
|
|
3189
|
-
// Handle function calls from candidates
|
|
3190
|
-
if (chunk.candidates && Array.isArray(chunk.candidates)) {
|
|
3191
|
-
for (const candidate of chunk.candidates) {
|
|
3192
|
-
if (candidate.content && candidate.content.parts) {
|
|
3193
|
-
for (const part of candidate.content.parts) {
|
|
3194
|
-
// Type-safe check for functionCall
|
|
3195
|
-
if ('functionCall' in part) {
|
|
3196
|
-
if (firstTokenTime === undefined) {
|
|
3197
|
-
firstTokenTime = Date.now();
|
|
3198
|
-
}
|
|
3199
|
-
const funcCall = part.functionCall;
|
|
3200
|
-
if (funcCall?.name) {
|
|
3201
|
-
accumulatedContent.push({
|
|
3202
|
-
type: 'function',
|
|
3203
|
-
function: {
|
|
3204
|
-
name: funcCall.name,
|
|
3205
|
-
arguments: funcCall.args || {}
|
|
3206
|
-
}
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
// Update usage metadata - handle both old and new field names
|
|
3215
|
-
if (chunk.usageMetadata) {
|
|
3216
|
-
const metadata = chunk.usageMetadata;
|
|
3217
|
-
usage = {
|
|
3218
|
-
inputTokens: metadata.promptTokenCount ?? 0,
|
|
3219
|
-
outputTokens: metadata.candidatesTokenCount ?? 0,
|
|
3220
|
-
reasoningTokens: metadata.thoughtsTokenCount ?? 0,
|
|
3221
|
-
cacheReadInputTokens: metadata.cachedContentTokenCount ?? 0,
|
|
3222
|
-
webSearchCount: usage.webSearchCount,
|
|
3223
|
-
rawUsage: metadata
|
|
3224
|
-
};
|
|
3225
|
-
}
|
|
3226
|
-
yield chunk;
|
|
3227
|
-
}
|
|
3228
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3229
|
-
const timeToFirstToken = firstTokenTime !== undefined ? (firstTokenTime - startTime) / 1000 : undefined;
|
|
3230
|
-
const availableTools = extractAvailableToolCalls('gemini', geminiParams);
|
|
3231
|
-
// Format output similar to formatResponseGemini
|
|
3232
|
-
const output = accumulatedContent.length > 0 ? [{
|
|
3233
|
-
role: 'assistant',
|
|
3234
|
-
content: accumulatedContent
|
|
3235
|
-
}] : [];
|
|
3236
|
-
await captureAiGeneration(this.phClient, {
|
|
3237
|
-
...posthogParams,
|
|
3238
|
-
model: geminiParams.model,
|
|
3239
|
-
provider: 'gemini',
|
|
3240
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
3241
|
-
output,
|
|
3242
|
-
latency,
|
|
3243
|
-
timeToFirstToken,
|
|
3244
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3245
|
-
modelParameters: getModelParams(params),
|
|
3246
|
-
httpStatus: 200,
|
|
3247
|
-
usage: {
|
|
3248
|
-
...usage,
|
|
3249
|
-
webSearchCount: usage.webSearchCount,
|
|
3250
|
-
rawUsage: usage.rawUsage
|
|
3251
|
-
},
|
|
3252
|
-
stopReason,
|
|
3253
|
-
tools: availableTools
|
|
3254
|
-
});
|
|
3255
|
-
} catch (error) {
|
|
3256
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3257
|
-
await captureAiGeneration(this.phClient, {
|
|
3258
|
-
...posthogParams,
|
|
3259
|
-
model: geminiParams.model,
|
|
3260
|
-
provider: 'gemini',
|
|
3261
|
-
input: this.formatInputForPostHog(geminiParams),
|
|
3262
|
-
output: [],
|
|
3263
|
-
latency,
|
|
3264
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3265
|
-
modelParameters: getModelParams(params),
|
|
3266
|
-
usage: {
|
|
3267
|
-
inputTokens: 0,
|
|
3268
|
-
outputTokens: 0
|
|
3269
|
-
},
|
|
3270
|
-
error
|
|
3271
|
-
});
|
|
3272
|
-
throw error;
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
async embedContent(params) {
|
|
3276
|
-
const {
|
|
3277
|
-
providerParams: geminiParams,
|
|
3278
|
-
posthogParams
|
|
3279
|
-
} = extractPosthogParams(params);
|
|
3280
|
-
const startTime = Date.now();
|
|
3281
|
-
try {
|
|
3282
|
-
const response = await this.client.models.embedContent(geminiParams);
|
|
3283
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3284
|
-
const inputTokens = extractEmbeddingTokenCount(response);
|
|
3285
|
-
await captureAiGeneration(this.phClient, {
|
|
3286
|
-
...posthogParams,
|
|
3287
|
-
eventType: exports.AIEvent.Embedding,
|
|
3288
|
-
model: geminiParams.model,
|
|
3289
|
-
provider: 'gemini',
|
|
3290
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
3291
|
-
output: null,
|
|
3292
|
-
latency,
|
|
3293
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3294
|
-
modelParameters: getModelParams(params),
|
|
3295
|
-
httpStatus: 200,
|
|
3296
|
-
usage: {
|
|
3297
|
-
inputTokens
|
|
3298
|
-
}
|
|
3299
|
-
});
|
|
3300
|
-
return response;
|
|
3301
|
-
} catch (error) {
|
|
3302
|
-
const latency = (Date.now() - startTime) / 1000;
|
|
3303
|
-
await captureAiGeneration(this.phClient, {
|
|
3304
|
-
...posthogParams,
|
|
3305
|
-
eventType: exports.AIEvent.Embedding,
|
|
3306
|
-
model: geminiParams.model,
|
|
3307
|
-
provider: 'gemini',
|
|
3308
|
-
input: withPrivacyMode(this.phClient, posthogParams.privacyMode ?? false, geminiParams.contents),
|
|
3309
|
-
output: null,
|
|
3310
|
-
latency,
|
|
3311
|
-
baseURL: 'https://generativelanguage.googleapis.com',
|
|
3312
|
-
modelParameters: getModelParams(params),
|
|
3313
|
-
usage: {
|
|
3314
|
-
inputTokens: 0
|
|
3315
|
-
},
|
|
3316
|
-
error
|
|
3317
|
-
});
|
|
3318
|
-
throw error;
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
formatPartsAsContentBlocks(parts) {
|
|
3322
|
-
const blocks = [];
|
|
3323
|
-
for (const part of parts) {
|
|
3324
|
-
// Handle dict/object with text field
|
|
3325
|
-
if (part && typeof part === 'object' && 'text' in part && part.text) {
|
|
3326
|
-
blocks.push({
|
|
3327
|
-
type: 'text',
|
|
3328
|
-
text: String(part.text)
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
// Handle string parts
|
|
3332
|
-
else if (typeof part === 'string') {
|
|
3333
|
-
blocks.push({
|
|
3334
|
-
type: 'text',
|
|
3335
|
-
text: part
|
|
3336
|
-
});
|
|
3337
|
-
}
|
|
3338
|
-
// Handle inlineData (images, audio, PDFs)
|
|
3339
|
-
else if (part && typeof part === 'object' && 'inlineData' in part) {
|
|
3340
|
-
const inlineData = part.inlineData;
|
|
3341
|
-
const mimeType = inlineData.mimeType || inlineData.mime_type || 'application/octet-stream';
|
|
3342
|
-
blocks.push(buildInlineDataBlock(mimeType, inlineData.data));
|
|
3343
|
-
}
|
|
3344
|
-
}
|
|
3345
|
-
return blocks;
|
|
3346
|
-
}
|
|
3347
|
-
formatInput(contents) {
|
|
3348
|
-
if (typeof contents === 'string') {
|
|
3349
|
-
return [{
|
|
3350
|
-
role: 'user',
|
|
3351
|
-
content: contents
|
|
3352
|
-
}];
|
|
3353
|
-
}
|
|
3354
|
-
if (Array.isArray(contents)) {
|
|
3355
|
-
return contents.map(item => {
|
|
3356
|
-
if (typeof item === 'string') {
|
|
3357
|
-
return {
|
|
3358
|
-
role: 'user',
|
|
3359
|
-
content: item
|
|
3360
|
-
};
|
|
3361
|
-
}
|
|
3362
|
-
if (item && typeof item === 'object') {
|
|
3363
|
-
const obj = item;
|
|
3364
|
-
if ('text' in obj && obj.text) {
|
|
3365
|
-
return {
|
|
3366
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
3367
|
-
content: obj.text
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
|
-
if ('content' in obj && obj.content) {
|
|
3371
|
-
// If content is a list, format it as content blocks
|
|
3372
|
-
if (Array.isArray(obj.content)) {
|
|
3373
|
-
const contentBlocks = this.formatPartsAsContentBlocks(obj.content);
|
|
3374
|
-
return {
|
|
3375
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
3376
|
-
content: contentBlocks
|
|
3377
|
-
};
|
|
3378
|
-
}
|
|
3379
|
-
return {
|
|
3380
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
3381
|
-
content: obj.content
|
|
3382
|
-
};
|
|
3383
|
-
}
|
|
3384
|
-
if ('parts' in obj && Array.isArray(obj.parts)) {
|
|
3385
|
-
const contentBlocks = this.formatPartsAsContentBlocks(obj.parts);
|
|
3386
|
-
return {
|
|
3387
|
-
role: isString(obj.role) ? obj.role : 'user',
|
|
3388
|
-
content: contentBlocks
|
|
3389
|
-
};
|
|
3390
|
-
}
|
|
3391
|
-
}
|
|
3392
|
-
return {
|
|
3393
|
-
role: 'user',
|
|
3394
|
-
content: toContentString(item)
|
|
3395
|
-
};
|
|
3396
|
-
});
|
|
3397
|
-
}
|
|
3398
|
-
if (contents && typeof contents === 'object') {
|
|
3399
|
-
const obj = contents;
|
|
3400
|
-
if ('text' in obj && obj.text) {
|
|
3401
|
-
return [{
|
|
3402
|
-
role: 'user',
|
|
3403
|
-
content: obj.text
|
|
3404
|
-
}];
|
|
3405
|
-
}
|
|
3406
|
-
if ('content' in obj && obj.content) {
|
|
3407
|
-
return [{
|
|
3408
|
-
role: 'user',
|
|
3409
|
-
content: obj.content
|
|
3410
|
-
}];
|
|
3411
|
-
}
|
|
3412
|
-
}
|
|
3413
|
-
return [{
|
|
3414
|
-
role: 'user',
|
|
3415
|
-
content: toContentString(contents)
|
|
3416
|
-
}];
|
|
3417
|
-
}
|
|
3418
|
-
extractSystemInstruction(params) {
|
|
3419
|
-
if (!params || typeof params !== 'object' || !params.config) {
|
|
3420
|
-
return null;
|
|
3421
|
-
}
|
|
3422
|
-
const config = params.config;
|
|
3423
|
-
if (!('systemInstruction' in config)) {
|
|
3424
|
-
return null;
|
|
3425
|
-
}
|
|
3426
|
-
const systemInstruction = config.systemInstruction;
|
|
3427
|
-
if (typeof systemInstruction === 'string') {
|
|
3428
|
-
return systemInstruction;
|
|
3429
|
-
}
|
|
3430
|
-
if (systemInstruction && typeof systemInstruction === 'object' && 'text' in systemInstruction) {
|
|
3431
|
-
return systemInstruction.text;
|
|
3432
|
-
}
|
|
3433
|
-
if (systemInstruction && typeof systemInstruction === 'object' && 'parts' in systemInstruction && Array.isArray(systemInstruction.parts)) {
|
|
3434
|
-
for (const part of systemInstruction.parts) {
|
|
3435
|
-
if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
|
|
3436
|
-
return part.text;
|
|
3437
|
-
}
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
if (Array.isArray(systemInstruction)) {
|
|
3441
|
-
for (const part of systemInstruction) {
|
|
3442
|
-
if (typeof part === 'string') {
|
|
3443
|
-
return part;
|
|
3444
|
-
}
|
|
3445
|
-
if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string') {
|
|
3446
|
-
return part.text;
|
|
3447
|
-
}
|
|
3448
|
-
}
|
|
3449
|
-
}
|
|
3450
|
-
return null;
|
|
3451
|
-
}
|
|
3452
|
-
formatInputForPostHog(params) {
|
|
3453
|
-
const sanitized = sanitizeGemini(params.contents);
|
|
3454
|
-
const messages = this.formatInput(sanitized);
|
|
3455
|
-
const systemInstruction = this.extractSystemInstruction(params);
|
|
3456
|
-
if (systemInstruction) {
|
|
3457
|
-
const hasSystemMessage = messages.some(msg => msg.role === 'system');
|
|
3458
|
-
if (!hasSystemMessage) {
|
|
3459
|
-
return [{
|
|
3460
|
-
role: 'system',
|
|
3461
|
-
content: systemInstruction
|
|
3462
|
-
}, ...messages];
|
|
3463
|
-
}
|
|
3464
|
-
}
|
|
3465
|
-
return messages;
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
/**
|
|
3469
|
-
* Extract total token count from a Gemini embed_content response.
|
|
3470
|
-
* Token counts are only available per-embedding via Vertex AI's statistics.tokenCount.
|
|
3471
|
-
* Returns 0 if no token counts are available.
|
|
3472
|
-
*/
|
|
3473
|
-
function extractEmbeddingTokenCount(response) {
|
|
3474
|
-
let total = 0;
|
|
3475
|
-
if (response.embeddings) {
|
|
3476
|
-
for (const embedding of response.embeddings) {
|
|
3477
|
-
if (embedding.statistics?.tokenCount != null) {
|
|
3478
|
-
total += embedding.statistics.tokenCount;
|
|
3479
|
-
}
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
return total;
|
|
3483
|
-
}
|
|
3484
|
-
/**
|
|
3485
|
-
* Detect if Google Search grounding was used in the response.
|
|
3486
|
-
* Gemini bills per request that uses grounding, not per individual query.
|
|
3487
|
-
* Returns 1 if grounding was used, 0 otherwise.
|
|
3488
|
-
*/
|
|
3489
|
-
function calculateGoogleWebSearchCount(response) {
|
|
3490
|
-
if (!response || typeof response !== 'object' || !('candidates' in response)) {
|
|
3491
|
-
return 0;
|
|
3492
|
-
}
|
|
3493
|
-
const candidates = response.candidates;
|
|
3494
|
-
if (!Array.isArray(candidates)) {
|
|
3495
|
-
return 0;
|
|
3496
|
-
}
|
|
3497
|
-
const hasGrounding = candidates.some(candidate => {
|
|
3498
|
-
if (!candidate || typeof candidate !== 'object') {
|
|
3499
|
-
return false;
|
|
3500
|
-
}
|
|
3501
|
-
// Check for grounding metadata
|
|
3502
|
-
if ('groundingMetadata' in candidate && candidate.groundingMetadata) {
|
|
3503
|
-
const metadata = candidate.groundingMetadata;
|
|
3504
|
-
if (typeof metadata === 'object') {
|
|
3505
|
-
// Check if web_search_queries exists and is non-empty
|
|
3506
|
-
if ('webSearchQueries' in metadata && Array.isArray(metadata.webSearchQueries) && metadata.webSearchQueries.length > 0) {
|
|
3507
|
-
return true;
|
|
3508
|
-
}
|
|
3509
|
-
// Check if grounding_chunks exists and is non-empty
|
|
3510
|
-
if ('groundingChunks' in metadata && Array.isArray(metadata.groundingChunks) && metadata.groundingChunks.length > 0) {
|
|
3511
|
-
return true;
|
|
3512
|
-
}
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
// Check for google search in function calls
|
|
3516
|
-
if ('content' in candidate && candidate.content && typeof candidate.content === 'object') {
|
|
3517
|
-
const content = candidate.content;
|
|
3518
|
-
if ('parts' in content && Array.isArray(content.parts)) {
|
|
3519
|
-
return content.parts.some(part => {
|
|
3520
|
-
if (!part || typeof part !== 'object' || !('functionCall' in part)) {
|
|
3521
|
-
return false;
|
|
3522
|
-
}
|
|
3523
|
-
const functionCall = part.functionCall;
|
|
3524
|
-
if (functionCall && typeof functionCall === 'object' && 'name' in functionCall && typeof functionCall.name === 'string') {
|
|
3525
|
-
return functionCall.name.includes('google_search') || functionCall.name.includes('grounding');
|
|
3526
|
-
}
|
|
3527
|
-
return false;
|
|
3528
|
-
});
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
return false;
|
|
3532
|
-
});
|
|
3533
|
-
return hasGrounding ? 1 : 0;
|
|
3534
|
-
}
|
|
3535
|
-
|
|
3536
|
-
function getDefaultExportFromCjs (x) {
|
|
3537
|
-
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
var decamelize;
|
|
3541
|
-
var hasRequiredDecamelize;
|
|
3542
|
-
|
|
3543
|
-
function requireDecamelize () {
|
|
3544
|
-
if (hasRequiredDecamelize) return decamelize;
|
|
3545
|
-
hasRequiredDecamelize = 1;
|
|
3546
|
-
decamelize = function (str, sep) {
|
|
3547
|
-
if (typeof str !== 'string') {
|
|
3548
|
-
throw new TypeError('Expected a string');
|
|
3549
|
-
}
|
|
3550
|
-
|
|
3551
|
-
sep = typeof sep === 'undefined' ? '_' : sep;
|
|
3552
|
-
|
|
3553
|
-
return str
|
|
3554
|
-
.replace(/([a-z\d])([A-Z])/g, '$1' + sep + '$2')
|
|
3555
|
-
.replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + sep + '$2')
|
|
3556
|
-
.toLowerCase();
|
|
3557
|
-
};
|
|
3558
|
-
return decamelize;
|
|
3559
|
-
}
|
|
3560
|
-
|
|
3561
|
-
var decamelizeExports = requireDecamelize();
|
|
3562
|
-
var snakeCase = /*@__PURE__*/getDefaultExportFromCjs(decamelizeExports);
|
|
3563
|
-
|
|
3564
|
-
var camelcase = {exports: {}};
|
|
3565
|
-
|
|
3566
|
-
var hasRequiredCamelcase;
|
|
3567
|
-
|
|
3568
|
-
function requireCamelcase () {
|
|
3569
|
-
if (hasRequiredCamelcase) return camelcase.exports;
|
|
3570
|
-
hasRequiredCamelcase = 1;
|
|
3571
|
-
|
|
3572
|
-
const UPPERCASE = /[\p{Lu}]/u;
|
|
3573
|
-
const LOWERCASE = /[\p{Ll}]/u;
|
|
3574
|
-
const LEADING_CAPITAL = /^[\p{Lu}](?![\p{Lu}])/gu;
|
|
3575
|
-
const IDENTIFIER = /([\p{Alpha}\p{N}_]|$)/u;
|
|
3576
|
-
const SEPARATORS = /[_.\- ]+/;
|
|
3577
|
-
|
|
3578
|
-
const LEADING_SEPARATORS = new RegExp('^' + SEPARATORS.source);
|
|
3579
|
-
const SEPARATORS_AND_IDENTIFIER = new RegExp(SEPARATORS.source + IDENTIFIER.source, 'gu');
|
|
3580
|
-
const NUMBERS_AND_IDENTIFIER = new RegExp('\\d+' + IDENTIFIER.source, 'gu');
|
|
3581
|
-
|
|
3582
|
-
const preserveCamelCase = (string, toLowerCase, toUpperCase) => {
|
|
3583
|
-
let isLastCharLower = false;
|
|
3584
|
-
let isLastCharUpper = false;
|
|
3585
|
-
let isLastLastCharUpper = false;
|
|
3586
|
-
|
|
3587
|
-
for (let i = 0; i < string.length; i++) {
|
|
3588
|
-
const character = string[i];
|
|
3589
|
-
|
|
3590
|
-
if (isLastCharLower && UPPERCASE.test(character)) {
|
|
3591
|
-
string = string.slice(0, i) + '-' + string.slice(i);
|
|
3592
|
-
isLastCharLower = false;
|
|
3593
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
3594
|
-
isLastCharUpper = true;
|
|
3595
|
-
i++;
|
|
3596
|
-
} else if (isLastCharUpper && isLastLastCharUpper && LOWERCASE.test(character)) {
|
|
3597
|
-
string = string.slice(0, i - 1) + '-' + string.slice(i - 1);
|
|
3598
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
3599
|
-
isLastCharUpper = false;
|
|
3600
|
-
isLastCharLower = true;
|
|
3601
|
-
} else {
|
|
3602
|
-
isLastCharLower = toLowerCase(character) === character && toUpperCase(character) !== character;
|
|
3603
|
-
isLastLastCharUpper = isLastCharUpper;
|
|
3604
|
-
isLastCharUpper = toUpperCase(character) === character && toLowerCase(character) !== character;
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
return string;
|
|
3609
|
-
};
|
|
3610
|
-
|
|
3611
|
-
const preserveConsecutiveUppercase = (input, toLowerCase) => {
|
|
3612
|
-
LEADING_CAPITAL.lastIndex = 0;
|
|
3613
|
-
|
|
3614
|
-
return input.replace(LEADING_CAPITAL, m1 => toLowerCase(m1));
|
|
3615
|
-
};
|
|
3616
|
-
|
|
3617
|
-
const postProcess = (input, toUpperCase) => {
|
|
3618
|
-
SEPARATORS_AND_IDENTIFIER.lastIndex = 0;
|
|
3619
|
-
NUMBERS_AND_IDENTIFIER.lastIndex = 0;
|
|
3620
|
-
|
|
3621
|
-
return input.replace(SEPARATORS_AND_IDENTIFIER, (_, identifier) => toUpperCase(identifier))
|
|
3622
|
-
.replace(NUMBERS_AND_IDENTIFIER, m => toUpperCase(m));
|
|
3623
|
-
};
|
|
3624
|
-
|
|
3625
|
-
const camelCase = (input, options) => {
|
|
3626
|
-
if (!(typeof input === 'string' || Array.isArray(input))) {
|
|
3627
|
-
throw new TypeError('Expected the input to be `string | string[]`');
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
options = {
|
|
3631
|
-
pascalCase: false,
|
|
3632
|
-
preserveConsecutiveUppercase: false,
|
|
3633
|
-
...options
|
|
3634
|
-
};
|
|
3635
|
-
|
|
3636
|
-
if (Array.isArray(input)) {
|
|
3637
|
-
input = input.map(x => x.trim())
|
|
3638
|
-
.filter(x => x.length)
|
|
3639
|
-
.join('-');
|
|
3640
|
-
} else {
|
|
3641
|
-
input = input.trim();
|
|
3642
|
-
}
|
|
3643
|
-
|
|
3644
|
-
if (input.length === 0) {
|
|
3645
|
-
return '';
|
|
3646
|
-
}
|
|
3647
|
-
|
|
3648
|
-
const toLowerCase = options.locale === false ?
|
|
3649
|
-
string => string.toLowerCase() :
|
|
3650
|
-
string => string.toLocaleLowerCase(options.locale);
|
|
3651
|
-
const toUpperCase = options.locale === false ?
|
|
3652
|
-
string => string.toUpperCase() :
|
|
3653
|
-
string => string.toLocaleUpperCase(options.locale);
|
|
3654
|
-
|
|
3655
|
-
if (input.length === 1) {
|
|
3656
|
-
return options.pascalCase ? toUpperCase(input) : toLowerCase(input);
|
|
3657
|
-
}
|
|
3658
|
-
|
|
3659
|
-
const hasUpperCase = input !== toLowerCase(input);
|
|
3660
|
-
|
|
3661
|
-
if (hasUpperCase) {
|
|
3662
|
-
input = preserveCamelCase(input, toLowerCase, toUpperCase);
|
|
3663
|
-
}
|
|
3664
|
-
|
|
3665
|
-
input = input.replace(LEADING_SEPARATORS, '');
|
|
3666
|
-
|
|
3667
|
-
if (options.preserveConsecutiveUppercase) {
|
|
3668
|
-
input = preserveConsecutiveUppercase(input, toLowerCase);
|
|
3669
|
-
} else {
|
|
3670
|
-
input = toLowerCase(input);
|
|
3671
|
-
}
|
|
3672
|
-
|
|
3673
|
-
if (options.pascalCase) {
|
|
3674
|
-
input = toUpperCase(input.charAt(0)) + input.slice(1);
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
return postProcess(input, toUpperCase);
|
|
3678
|
-
};
|
|
3679
|
-
|
|
3680
|
-
camelcase.exports = camelCase;
|
|
3681
|
-
// TODO: Remove this for the next major release
|
|
3682
|
-
camelcase.exports.default = camelCase;
|
|
3683
|
-
return camelcase.exports;
|
|
3684
|
-
}
|
|
3685
|
-
|
|
3686
|
-
requireCamelcase();
|
|
3687
|
-
|
|
3688
|
-
//#region src/load/map_keys.ts
|
|
3689
|
-
function keyToJson(key, map) {
|
|
3690
|
-
return map?.[key] || snakeCase(key);
|
|
3691
|
-
}
|
|
3692
|
-
function mapKeys(fields, mapper, map) {
|
|
3693
|
-
const mapped = {};
|
|
3694
|
-
for (const key in fields) if (Object.hasOwn(fields, key)) mapped[mapper(key, map)] = fields[key];
|
|
3695
|
-
return mapped;
|
|
3696
|
-
}
|
|
3697
|
-
|
|
3698
|
-
//#region src/load/validation.ts
|
|
3699
|
-
/**
|
|
3700
|
-
* Sentinel key used to mark escaped user objects during serialization.
|
|
3701
|
-
*
|
|
3702
|
-
* When a plain object contains 'lc' key (which could be confused with LC objects),
|
|
3703
|
-
* we wrap it as `{"__lc_escaped__": {...original...}}`.
|
|
3704
|
-
*/
|
|
3705
|
-
const LC_ESCAPED_KEY = "__lc_escaped__";
|
|
3706
|
-
/**
|
|
3707
|
-
* Check if an object needs escaping to prevent confusion with LC objects.
|
|
3708
|
-
*
|
|
3709
|
-
* An object needs escaping if:
|
|
3710
|
-
* 1. It has an `'lc'` key (could be confused with LC serialization format)
|
|
3711
|
-
* 2. It has only the escape key (would be mistaken for an escaped object)
|
|
3712
|
-
*/
|
|
3713
|
-
function needsEscaping(obj) {
|
|
3714
|
-
return "lc" in obj || Object.keys(obj).length === 1 && LC_ESCAPED_KEY in obj;
|
|
3715
|
-
}
|
|
3716
|
-
/**
|
|
3717
|
-
* Wrap an object in the escape marker.
|
|
3718
|
-
*
|
|
3719
|
-
* @example
|
|
3720
|
-
* ```typescript
|
|
3721
|
-
* {"key": "value"} // becomes {"__lc_escaped__": {"key": "value"}}
|
|
3722
|
-
* ```
|
|
3723
|
-
*/
|
|
3724
|
-
function escapeObject(obj) {
|
|
3725
|
-
return { [LC_ESCAPED_KEY]: obj };
|
|
3726
|
-
}
|
|
3727
|
-
/**
|
|
3728
|
-
* Check if an object looks like a Serializable instance (duck typing).
|
|
3729
|
-
*/
|
|
3730
|
-
function isSerializableLike(obj) {
|
|
3731
|
-
return obj !== null && typeof obj === "object" && "lc_serializable" in obj && typeof obj.toJSON === "function";
|
|
3732
|
-
}
|
|
3733
|
-
/**
|
|
3734
|
-
* Create a "not_implemented" serialization result for objects that cannot be serialized.
|
|
3735
|
-
*/
|
|
3736
|
-
function createNotImplemented(obj) {
|
|
3737
|
-
let id;
|
|
3738
|
-
if (obj !== null && typeof obj === "object") if ("lc_id" in obj && Array.isArray(obj.lc_id)) id = obj.lc_id;
|
|
3739
|
-
else id = [obj.constructor?.name ?? "Object"];
|
|
3740
|
-
else id = [typeof obj];
|
|
3741
|
-
return {
|
|
3742
|
-
lc: 1,
|
|
3743
|
-
type: "not_implemented",
|
|
3744
|
-
id
|
|
3745
|
-
};
|
|
3746
|
-
}
|
|
3747
|
-
/**
|
|
3748
|
-
* Escape a value if it needs escaping (contains `lc` key).
|
|
3749
|
-
*
|
|
3750
|
-
* This is a simpler version of `serializeValue` that doesn't handle Serializable
|
|
3751
|
-
* objects - it's meant to be called on kwargs values that have already been
|
|
3752
|
-
* processed by `toJSON()`.
|
|
3753
|
-
*
|
|
3754
|
-
* @param value - The value to potentially escape.
|
|
3755
|
-
* @param pathSet - WeakSet to track ancestor objects in the current path to detect circular references.
|
|
3756
|
-
* Objects are removed after processing to allow shared references (same object in
|
|
3757
|
-
* multiple places) while still detecting true circular references (ancestor in descendant).
|
|
3758
|
-
* @returns The value with any `lc`-containing objects wrapped in escape markers.
|
|
3759
|
-
*/
|
|
3760
|
-
function escapeIfNeeded(value, pathSet = /* @__PURE__ */ new WeakSet()) {
|
|
3761
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
3762
|
-
if (pathSet.has(value)) return createNotImplemented(value);
|
|
3763
|
-
if (isSerializableLike(value)) return value;
|
|
3764
|
-
pathSet.add(value);
|
|
3765
|
-
const record = value;
|
|
3766
|
-
if (needsEscaping(record)) {
|
|
3767
|
-
pathSet.delete(value);
|
|
3768
|
-
return escapeObject(record);
|
|
3769
|
-
}
|
|
3770
|
-
const result = {};
|
|
3771
|
-
for (const [key, val] of Object.entries(record)) result[key] = escapeIfNeeded(val, pathSet);
|
|
3772
|
-
pathSet.delete(value);
|
|
3773
|
-
return result;
|
|
3774
|
-
}
|
|
3775
|
-
if (Array.isArray(value)) return value.map((item) => escapeIfNeeded(item, pathSet));
|
|
3776
|
-
return value;
|
|
3777
|
-
}
|
|
3778
|
-
|
|
3779
|
-
function shallowCopy(obj) {
|
|
3780
|
-
return Array.isArray(obj) ? [...obj] : { ...obj };
|
|
3781
|
-
}
|
|
3782
|
-
function replaceSecrets(root, secretsMap) {
|
|
3783
|
-
const result = shallowCopy(root);
|
|
3784
|
-
for (const [path, secretId] of Object.entries(secretsMap)) {
|
|
3785
|
-
const [last, ...partsReverse] = path.split(".").reverse();
|
|
3786
|
-
let current = result;
|
|
3787
|
-
for (const part of partsReverse.reverse()) {
|
|
3788
|
-
if (current[part] === void 0) break;
|
|
3789
|
-
current[part] = shallowCopy(current[part]);
|
|
3790
|
-
current = current[part];
|
|
3791
|
-
}
|
|
3792
|
-
if (current[last] !== void 0) current[last] = {
|
|
3793
|
-
lc: 1,
|
|
3794
|
-
type: "secret",
|
|
3795
|
-
id: [secretId]
|
|
3796
|
-
};
|
|
3797
|
-
}
|
|
3798
|
-
return result;
|
|
3799
|
-
}
|
|
3800
|
-
/**
|
|
3801
|
-
* Get a unique name for the module, rather than parent class implementations.
|
|
3802
|
-
* Should not be subclassed, subclass lc_name above instead.
|
|
3803
|
-
*/
|
|
3804
|
-
function get_lc_unique_name(serializableClass) {
|
|
3805
|
-
const parentClass = Object.getPrototypeOf(serializableClass);
|
|
3806
|
-
if (typeof serializableClass.lc_name === "function" && (typeof parentClass.lc_name !== "function" || serializableClass.lc_name() !== parentClass.lc_name())) return serializableClass.lc_name();
|
|
3807
|
-
else return serializableClass.name;
|
|
3808
|
-
}
|
|
3809
|
-
var Serializable = class Serializable {
|
|
3810
|
-
lc_serializable = false;
|
|
3811
|
-
lc_kwargs;
|
|
3812
|
-
/**
|
|
3813
|
-
* The name of the serializable. Override to provide an alias or
|
|
3814
|
-
* to preserve the serialized module name in minified environments.
|
|
3815
|
-
*
|
|
3816
|
-
* Implemented as a static method to support loading logic.
|
|
3817
|
-
*/
|
|
3818
|
-
static lc_name() {
|
|
3819
|
-
return this.name;
|
|
3820
|
-
}
|
|
3821
|
-
/**
|
|
3822
|
-
* The final serialized identifier for the module.
|
|
3823
|
-
*/
|
|
3824
|
-
get lc_id() {
|
|
3825
|
-
return [...this.lc_namespace, get_lc_unique_name(this.constructor)];
|
|
3826
|
-
}
|
|
3827
|
-
/**
|
|
3828
|
-
* A map of secrets, which will be omitted from serialization.
|
|
3829
|
-
* Keys are paths to the secret in constructor args, e.g. "foo.bar.baz".
|
|
3830
|
-
* Values are the secret ids, which will be used when deserializing.
|
|
3831
|
-
*/
|
|
3832
|
-
get lc_secrets() {}
|
|
3833
|
-
/**
|
|
3834
|
-
* A map of additional attributes to merge with constructor args.
|
|
3835
|
-
* Keys are the attribute names, e.g. "foo".
|
|
3836
|
-
* Values are the attribute values, which will be serialized.
|
|
3837
|
-
* These attributes need to be accepted by the constructor as arguments.
|
|
3838
|
-
*/
|
|
3839
|
-
get lc_attributes() {}
|
|
3840
|
-
/**
|
|
3841
|
-
* A map of aliases for constructor args.
|
|
3842
|
-
* Keys are the attribute names, e.g. "foo".
|
|
3843
|
-
* Values are the alias that will replace the key in serialization.
|
|
3844
|
-
* This is used to eg. make argument names match Python.
|
|
3845
|
-
*/
|
|
3846
|
-
get lc_aliases() {}
|
|
3847
|
-
/**
|
|
3848
|
-
* A manual list of keys that should be serialized.
|
|
3849
|
-
* If not overridden, all fields passed into the constructor will be serialized.
|
|
3850
|
-
*/
|
|
3851
|
-
get lc_serializable_keys() {}
|
|
3852
|
-
constructor(kwargs, ..._args) {
|
|
3853
|
-
if (this.lc_serializable_keys !== void 0) this.lc_kwargs = Object.fromEntries(Object.entries(kwargs || {}).filter(([key]) => this.lc_serializable_keys?.includes(key)));
|
|
3854
|
-
else this.lc_kwargs = kwargs ?? {};
|
|
3855
|
-
}
|
|
3856
|
-
toJSON() {
|
|
3857
|
-
if (!this.lc_serializable) return this.toJSONNotImplemented();
|
|
3858
|
-
if (this.lc_kwargs instanceof Serializable || typeof this.lc_kwargs !== "object" || Array.isArray(this.lc_kwargs)) return this.toJSONNotImplemented();
|
|
3859
|
-
const aliases = {};
|
|
3860
|
-
const secrets = {};
|
|
3861
|
-
const kwargs = Object.keys(this.lc_kwargs).reduce((acc, key) => {
|
|
3862
|
-
acc[key] = key in this ? this[key] : this.lc_kwargs[key];
|
|
3863
|
-
return acc;
|
|
3864
|
-
}, {});
|
|
3865
|
-
for (let current = Object.getPrototypeOf(this); current; current = Object.getPrototypeOf(current)) {
|
|
3866
|
-
Object.assign(aliases, Reflect.get(current, "lc_aliases", this));
|
|
3867
|
-
Object.assign(secrets, Reflect.get(current, "lc_secrets", this));
|
|
3868
|
-
Object.assign(kwargs, Reflect.get(current, "lc_attributes", this));
|
|
3869
|
-
}
|
|
3870
|
-
Object.keys(secrets).forEach((keyPath) => {
|
|
3871
|
-
let read = this;
|
|
3872
|
-
let write = kwargs;
|
|
3873
|
-
const [last, ...partsReverse] = keyPath.split(".").reverse();
|
|
3874
|
-
for (const key of partsReverse.reverse()) {
|
|
3875
|
-
if (!(key in read) || read[key] === void 0) return;
|
|
3876
|
-
if (!(key in write) || write[key] === void 0) {
|
|
3877
|
-
if (typeof read[key] === "object" && read[key] != null) write[key] = {};
|
|
3878
|
-
else if (Array.isArray(read[key])) write[key] = [];
|
|
3879
|
-
}
|
|
3880
|
-
read = read[key];
|
|
3881
|
-
write = write[key];
|
|
3882
|
-
}
|
|
3883
|
-
if (last in read && read[last] !== void 0) write[last] = write[last] || read[last];
|
|
3884
|
-
});
|
|
3885
|
-
const escapedKwargs = {};
|
|
3886
|
-
const pathSet = /* @__PURE__ */ new WeakSet();
|
|
3887
|
-
pathSet.add(this);
|
|
3888
|
-
for (const [key, value] of Object.entries(kwargs)) escapedKwargs[key] = escapeIfNeeded(value, pathSet);
|
|
3889
|
-
const processedKwargs = mapKeys(Object.keys(secrets).length ? replaceSecrets(escapedKwargs, secrets) : escapedKwargs, keyToJson, aliases);
|
|
3890
|
-
return {
|
|
3891
|
-
lc: 1,
|
|
3892
|
-
type: "constructor",
|
|
3893
|
-
id: this.lc_id,
|
|
3894
|
-
kwargs: processedKwargs
|
|
3895
|
-
};
|
|
3896
|
-
}
|
|
3897
|
-
toJSONNotImplemented() {
|
|
3898
|
-
return {
|
|
3899
|
-
lc: 1,
|
|
3900
|
-
type: "not_implemented",
|
|
3901
|
-
id: this.lc_id
|
|
3902
|
-
};
|
|
3903
|
-
}
|
|
3904
|
-
};
|
|
3905
|
-
|
|
3906
|
-
const isDeno = () => typeof Deno !== "undefined";
|
|
3907
|
-
function getEnvironmentVariable(name) {
|
|
3908
|
-
try {
|
|
3909
|
-
if (typeof process !== "undefined") return process.env?.[name];
|
|
3910
|
-
else if (isDeno()) return Deno?.env.get(name);
|
|
3911
|
-
else return;
|
|
3912
|
-
} catch {
|
|
3913
|
-
return;
|
|
3914
|
-
}
|
|
3915
|
-
}
|
|
3916
|
-
|
|
3917
|
-
/**
|
|
3918
|
-
* Abstract class that provides a set of optional methods that can be
|
|
3919
|
-
* overridden in derived classes to handle various events during the
|
|
3920
|
-
* execution of a LangChain application.
|
|
3921
|
-
*/
|
|
3922
|
-
var BaseCallbackHandlerMethodsClass = class {};
|
|
3923
|
-
/**
|
|
3924
|
-
* Abstract base class for creating callback handlers in the LangChain
|
|
3925
|
-
* framework. It provides a set of optional methods that can be overridden
|
|
3926
|
-
* in derived classes to handle various events during the execution of a
|
|
3927
|
-
* LangChain application.
|
|
3928
|
-
*/
|
|
3929
|
-
var BaseCallbackHandler = class extends BaseCallbackHandlerMethodsClass {
|
|
3930
|
-
lc_serializable = false;
|
|
3931
|
-
get lc_namespace() {
|
|
3932
|
-
return [
|
|
3933
|
-
"langchain_core",
|
|
3934
|
-
"callbacks",
|
|
3935
|
-
this.name
|
|
3936
|
-
];
|
|
3937
|
-
}
|
|
3938
|
-
get lc_secrets() {}
|
|
3939
|
-
get lc_attributes() {}
|
|
3940
|
-
get lc_aliases() {}
|
|
3941
|
-
get lc_serializable_keys() {}
|
|
3942
|
-
/**
|
|
3943
|
-
* The name of the serializable. Override to provide an alias or
|
|
3944
|
-
* to preserve the serialized module name in minified environments.
|
|
3945
|
-
*
|
|
3946
|
-
* Implemented as a static method to support loading logic.
|
|
3947
|
-
*/
|
|
3948
|
-
static lc_name() {
|
|
3949
|
-
return this.name;
|
|
3950
|
-
}
|
|
3951
|
-
/**
|
|
3952
|
-
* The final serialized identifier for the module.
|
|
3953
|
-
*/
|
|
3954
|
-
get lc_id() {
|
|
3955
|
-
return [...this.lc_namespace, get_lc_unique_name(this.constructor)];
|
|
3956
|
-
}
|
|
3957
|
-
lc_kwargs;
|
|
3958
|
-
ignoreLLM = false;
|
|
3959
|
-
ignoreChain = false;
|
|
3960
|
-
ignoreAgent = false;
|
|
3961
|
-
ignoreRetriever = false;
|
|
3962
|
-
ignoreCustomEvent = false;
|
|
3963
|
-
raiseError = false;
|
|
3964
|
-
awaitHandlers = getEnvironmentVariable("LANGCHAIN_CALLBACKS_BACKGROUND") === "false";
|
|
3965
|
-
constructor(input) {
|
|
3966
|
-
super();
|
|
3967
|
-
this.lc_kwargs = input || {};
|
|
3968
|
-
if (input) {
|
|
3969
|
-
this.ignoreLLM = input.ignoreLLM ?? this.ignoreLLM;
|
|
3970
|
-
this.ignoreChain = input.ignoreChain ?? this.ignoreChain;
|
|
3971
|
-
this.ignoreAgent = input.ignoreAgent ?? this.ignoreAgent;
|
|
3972
|
-
this.ignoreRetriever = input.ignoreRetriever ?? this.ignoreRetriever;
|
|
3973
|
-
this.ignoreCustomEvent = input.ignoreCustomEvent ?? this.ignoreCustomEvent;
|
|
3974
|
-
this.raiseError = input.raiseError ?? this.raiseError;
|
|
3975
|
-
this.awaitHandlers = this.raiseError || (input._awaitHandler ?? this.awaitHandlers);
|
|
3976
|
-
}
|
|
3977
|
-
}
|
|
3978
|
-
copy() {
|
|
3979
|
-
return new this.constructor(this);
|
|
3980
|
-
}
|
|
3981
|
-
toJSON() {
|
|
3982
|
-
return Serializable.prototype.toJSON.call(this);
|
|
3983
|
-
}
|
|
3984
|
-
toJSONNotImplemented() {
|
|
3985
|
-
return Serializable.prototype.toJSONNotImplemented.call(this);
|
|
3986
|
-
}
|
|
3987
|
-
static fromMethods(methods) {
|
|
3988
|
-
class Handler extends BaseCallbackHandler {
|
|
3989
|
-
name = uuid__namespace.v7();
|
|
3990
|
-
constructor() {
|
|
3991
|
-
super();
|
|
3992
|
-
Object.assign(this, methods);
|
|
3993
|
-
}
|
|
3994
|
-
}
|
|
3995
|
-
return new Handler();
|
|
3996
|
-
}
|
|
3997
|
-
};
|
|
3998
|
-
|
|
3999
|
-
class LangChainCallbackHandler extends BaseCallbackHandler {
|
|
4000
|
-
constructor(options) {
|
|
4001
|
-
if (!options.client) {
|
|
4002
|
-
throw new Error('PostHog client is required');
|
|
4003
|
-
}
|
|
4004
|
-
super();
|
|
4005
|
-
this.name = 'PosthogCallbackHandler';
|
|
4006
|
-
this.runs = {};
|
|
4007
|
-
this.parentTree = {};
|
|
4008
|
-
this.client = options.client;
|
|
4009
|
-
this.distinctId = options.distinctId;
|
|
4010
|
-
this.traceId = options.traceId;
|
|
4011
|
-
this.properties = options.properties || {};
|
|
4012
|
-
this.privacyMode = options.privacyMode || false;
|
|
4013
|
-
this.groups = options.groups || {};
|
|
4014
|
-
this.debug = options.debug || false;
|
|
4015
|
-
}
|
|
4016
|
-
// ===== CALLBACK METHODS =====
|
|
4017
|
-
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName) {
|
|
4018
|
-
this._logDebugEvent('on_chain_start', runId, parentRunId, {
|
|
4019
|
-
inputs,
|
|
4020
|
-
tags
|
|
4021
|
-
});
|
|
4022
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4023
|
-
this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
|
|
4024
|
-
}
|
|
4025
|
-
handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
|
|
4026
|
-
this._logDebugEvent('on_chain_end', runId, parentRunId, {
|
|
4027
|
-
outputs,
|
|
4028
|
-
tags
|
|
4029
|
-
});
|
|
4030
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs);
|
|
4031
|
-
}
|
|
4032
|
-
handleChainError(error, runId, parentRunId, tags, _kwargs) {
|
|
4033
|
-
this._logDebugEvent('on_chain_error', runId, parentRunId, {
|
|
4034
|
-
error,
|
|
4035
|
-
tags
|
|
4036
|
-
});
|
|
4037
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, error);
|
|
4038
|
-
}
|
|
4039
|
-
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
4040
|
-
this._logDebugEvent('on_chat_model_start', runId, parentRunId, {
|
|
4041
|
-
messages,
|
|
4042
|
-
tags
|
|
4043
|
-
});
|
|
4044
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4045
|
-
// Flatten the two-dimensional messages and convert each message to a plain object
|
|
4046
|
-
const input = messages.flat().map(m => this._convertMessageToDict(m));
|
|
4047
|
-
this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
|
|
4048
|
-
}
|
|
4049
|
-
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
4050
|
-
this._logDebugEvent('on_llm_start', runId, parentRunId, {
|
|
4051
|
-
prompts,
|
|
4052
|
-
tags
|
|
4053
|
-
});
|
|
4054
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4055
|
-
this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
|
|
4056
|
-
}
|
|
4057
|
-
handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
|
|
4058
|
-
this._logDebugEvent('on_llm_end', runId, parentRunId, {
|
|
4059
|
-
output,
|
|
4060
|
-
tags
|
|
4061
|
-
});
|
|
4062
|
-
this._popRunAndCaptureGeneration(runId, parentRunId, output);
|
|
4063
|
-
}
|
|
4064
|
-
handleLLMError(err, runId, parentRunId, tags, _extraParams) {
|
|
4065
|
-
this._logDebugEvent('on_llm_error', runId, parentRunId, {
|
|
4066
|
-
err,
|
|
4067
|
-
tags
|
|
4068
|
-
});
|
|
4069
|
-
this._popRunAndCaptureGeneration(runId, parentRunId, err);
|
|
4070
|
-
}
|
|
4071
|
-
handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
|
|
4072
|
-
this._logDebugEvent('on_tool_start', runId, parentRunId, {
|
|
4073
|
-
input,
|
|
4074
|
-
tags
|
|
4075
|
-
});
|
|
4076
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4077
|
-
this._setTraceOrSpanMetadata(tool, input, runId, parentRunId, metadata, tags, runName);
|
|
4078
|
-
}
|
|
4079
|
-
handleToolEnd(output, runId, parentRunId, tags) {
|
|
4080
|
-
this._logDebugEvent('on_tool_end', runId, parentRunId, {
|
|
4081
|
-
output,
|
|
4082
|
-
tags
|
|
4083
|
-
});
|
|
4084
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, output);
|
|
4085
|
-
}
|
|
4086
|
-
handleToolError(err, runId, parentRunId, tags) {
|
|
4087
|
-
this._logDebugEvent('on_tool_error', runId, parentRunId, {
|
|
4088
|
-
err,
|
|
4089
|
-
tags
|
|
4090
|
-
});
|
|
4091
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, err);
|
|
4092
|
-
}
|
|
4093
|
-
handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
|
|
4094
|
-
this._logDebugEvent('on_retriever_start', runId, parentRunId, {
|
|
4095
|
-
query,
|
|
4096
|
-
tags
|
|
4097
|
-
});
|
|
4098
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4099
|
-
this._setTraceOrSpanMetadata(retriever, query, runId, parentRunId, metadata, tags, name);
|
|
4100
|
-
}
|
|
4101
|
-
handleRetrieverEnd(documents, runId, parentRunId, tags) {
|
|
4102
|
-
this._logDebugEvent('on_retriever_end', runId, parentRunId, {
|
|
4103
|
-
documents,
|
|
4104
|
-
tags
|
|
4105
|
-
});
|
|
4106
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, documents);
|
|
4107
|
-
}
|
|
4108
|
-
handleRetrieverError(err, runId, parentRunId, tags) {
|
|
4109
|
-
this._logDebugEvent('on_retriever_error', runId, parentRunId, {
|
|
4110
|
-
err,
|
|
4111
|
-
tags
|
|
4112
|
-
});
|
|
4113
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, err);
|
|
4114
|
-
}
|
|
4115
|
-
handleAgentAction(action, runId, parentRunId, tags) {
|
|
4116
|
-
this._logDebugEvent('on_agent_action', runId, parentRunId, {
|
|
4117
|
-
action,
|
|
4118
|
-
tags
|
|
4119
|
-
});
|
|
4120
|
-
this._setParentOfRun(runId, parentRunId);
|
|
4121
|
-
this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
|
|
4122
|
-
}
|
|
4123
|
-
handleAgentEnd(action, runId, parentRunId, tags) {
|
|
4124
|
-
this._logDebugEvent('on_agent_finish', runId, parentRunId, {
|
|
4125
|
-
action,
|
|
4126
|
-
tags
|
|
4127
|
-
});
|
|
4128
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
|
|
4129
|
-
}
|
|
4130
|
-
// ===== PRIVATE HELPERS =====
|
|
4131
|
-
_setParentOfRun(runId, parentRunId) {
|
|
4132
|
-
if (parentRunId) {
|
|
4133
|
-
this.parentTree[runId] = parentRunId;
|
|
4134
|
-
}
|
|
4135
|
-
}
|
|
4136
|
-
_popParentOfRun(runId) {
|
|
4137
|
-
delete this.parentTree[runId];
|
|
4138
|
-
}
|
|
4139
|
-
_findRootRun(runId) {
|
|
4140
|
-
let id = runId;
|
|
4141
|
-
while (this.parentTree[id]) {
|
|
4142
|
-
id = this.parentTree[id];
|
|
4143
|
-
}
|
|
4144
|
-
return id;
|
|
4145
|
-
}
|
|
4146
|
-
_setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
|
|
4147
|
-
// Use default names if not provided: if this is a top-level run, we mark it as a trace, otherwise as a span.
|
|
4148
|
-
const defaultName = parentRunId ? 'span' : 'trace';
|
|
4149
|
-
const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
|
|
4150
|
-
this.runs[runId] = {
|
|
4151
|
-
name: runName,
|
|
4152
|
-
input,
|
|
4153
|
-
startTime: Date.now()
|
|
4154
|
-
};
|
|
4155
|
-
}
|
|
4156
|
-
_setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
|
|
4157
|
-
const runNameFound = this._getLangchainRunName(serialized, {
|
|
4158
|
-
extraParams,
|
|
4159
|
-
runName
|
|
4160
|
-
}) || 'generation';
|
|
4161
|
-
const generation = {
|
|
4162
|
-
name: runNameFound,
|
|
4163
|
-
input: sanitizeLangChain(messages),
|
|
4164
|
-
startTime: Date.now()
|
|
4165
|
-
};
|
|
4166
|
-
if (extraParams) {
|
|
4167
|
-
generation.modelParams = getModelParams(extraParams.invocation_params);
|
|
4168
|
-
if (extraParams.invocation_params && extraParams.invocation_params.tools) {
|
|
4169
|
-
generation.tools = extraParams.invocation_params.tools;
|
|
4170
|
-
}
|
|
4171
|
-
}
|
|
4172
|
-
if (metadata) {
|
|
4173
|
-
if (metadata.ls_model_name) {
|
|
4174
|
-
generation.model = metadata.ls_model_name;
|
|
4175
|
-
}
|
|
4176
|
-
if (metadata.ls_provider) {
|
|
4177
|
-
generation.provider = metadata.ls_provider;
|
|
4178
|
-
}
|
|
4179
|
-
}
|
|
4180
|
-
if (serialized && 'kwargs' in serialized && serialized.kwargs.openai_api_base) {
|
|
4181
|
-
generation.baseUrl = serialized.kwargs.openai_api_base;
|
|
4182
|
-
}
|
|
4183
|
-
this.runs[runId] = generation;
|
|
4184
|
-
}
|
|
4185
|
-
_popRunMetadata(runId) {
|
|
4186
|
-
const endTime = Date.now();
|
|
4187
|
-
const run = this.runs[runId];
|
|
4188
|
-
if (!run) {
|
|
4189
|
-
console.warn(`No run metadata found for run ${runId}`);
|
|
4190
|
-
return undefined;
|
|
4191
|
-
}
|
|
4192
|
-
run.endTime = endTime;
|
|
4193
|
-
delete this.runs[runId];
|
|
4194
|
-
return run;
|
|
4195
|
-
}
|
|
4196
|
-
_getTraceId(runId) {
|
|
4197
|
-
return this.traceId ? String(this.traceId) : this._findRootRun(runId);
|
|
4198
|
-
}
|
|
4199
|
-
_getParentRunId(traceId, _runId, parentRunId) {
|
|
4200
|
-
// Replace the parent-run if not found in our stored parent tree.
|
|
4201
|
-
if (parentRunId && !this.parentTree[parentRunId]) {
|
|
4202
|
-
return traceId;
|
|
4203
|
-
}
|
|
4204
|
-
return parentRunId;
|
|
4205
|
-
}
|
|
4206
|
-
_popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
|
|
4207
|
-
const traceId = this._getTraceId(runId);
|
|
4208
|
-
this._popParentOfRun(runId);
|
|
4209
|
-
const run = this._popRunMetadata(runId);
|
|
4210
|
-
if (!run) {
|
|
4211
|
-
return;
|
|
4212
|
-
}
|
|
4213
|
-
if ('modelParams' in run) {
|
|
4214
|
-
console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
|
|
4215
|
-
return;
|
|
4216
|
-
}
|
|
4217
|
-
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
4218
|
-
this._captureTraceOrSpan(traceId, runId, run, outputs, actualParentRunId);
|
|
4219
|
-
}
|
|
4220
|
-
_captureTraceOrSpan(traceId, runId, run, outputs, parentRunId) {
|
|
4221
|
-
const eventName = parentRunId ? '$ai_span' : '$ai_trace';
|
|
4222
|
-
const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
|
|
4223
|
-
const eventProperties = {
|
|
4224
|
-
$ai_lib: 'posthog-ai',
|
|
4225
|
-
$ai_lib_version: version,
|
|
4226
|
-
$ai_trace_id: traceId,
|
|
4227
|
-
$ai_input_state: withPrivacyMode(this.client, this.privacyMode, run.input),
|
|
4228
|
-
$ai_latency: latency,
|
|
4229
|
-
$ai_span_name: run.name,
|
|
4230
|
-
$ai_span_id: runId,
|
|
4231
|
-
$ai_framework: 'langchain'
|
|
4232
|
-
};
|
|
4233
|
-
if (parentRunId) {
|
|
4234
|
-
eventProperties['$ai_parent_id'] = parentRunId;
|
|
4235
|
-
}
|
|
4236
|
-
Object.assign(eventProperties, this.properties);
|
|
4237
|
-
if (!this.distinctId) {
|
|
4238
|
-
eventProperties['$process_person_profile'] = false;
|
|
4239
|
-
}
|
|
4240
|
-
if (outputs instanceof Error) {
|
|
4241
|
-
eventProperties['$ai_error'] = stringifyError(outputs);
|
|
4242
|
-
eventProperties['$ai_is_error'] = true;
|
|
4243
|
-
} else if (outputs !== undefined) {
|
|
4244
|
-
eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, outputs);
|
|
4245
|
-
}
|
|
4246
|
-
this.client.capture({
|
|
4247
|
-
distinctId: this.distinctId ? this.distinctId.toString() : runId,
|
|
4248
|
-
event: eventName,
|
|
4249
|
-
properties: eventProperties,
|
|
4250
|
-
groups: this.groups
|
|
4251
|
-
});
|
|
4252
|
-
}
|
|
4253
|
-
_popRunAndCaptureGeneration(runId, parentRunId, response) {
|
|
4254
|
-
const traceId = this._getTraceId(runId);
|
|
4255
|
-
this._popParentOfRun(runId);
|
|
4256
|
-
const run = this._popRunMetadata(runId);
|
|
4257
|
-
if (!run || typeof run !== 'object' || !('modelParams' in run)) {
|
|
4258
|
-
console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
|
|
4259
|
-
return;
|
|
4260
|
-
}
|
|
4261
|
-
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
4262
|
-
this._captureGeneration(traceId, runId, run, response, actualParentRunId);
|
|
4263
|
-
}
|
|
4264
|
-
_captureGeneration(traceId, runId, run, output, parentRunId) {
|
|
4265
|
-
const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
|
|
4266
|
-
const eventProperties = {
|
|
4267
|
-
$ai_lib: 'posthog-ai',
|
|
4268
|
-
$ai_lib_version: version,
|
|
4269
|
-
$ai_trace_id: traceId,
|
|
4270
|
-
$ai_span_id: runId,
|
|
4271
|
-
$ai_span_name: run.name,
|
|
4272
|
-
$ai_parent_id: parentRunId,
|
|
4273
|
-
$ai_provider: run.provider,
|
|
4274
|
-
$ai_model: run.model,
|
|
4275
|
-
$ai_model_parameters: run.modelParams,
|
|
4276
|
-
$ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
|
|
4277
|
-
$ai_http_status: 200,
|
|
4278
|
-
$ai_latency: latency,
|
|
4279
|
-
$ai_base_url: run.baseUrl,
|
|
4280
|
-
$ai_framework: 'langchain'
|
|
4281
|
-
};
|
|
4282
|
-
if (run.tools) {
|
|
4283
|
-
eventProperties['$ai_tools'] = run.tools;
|
|
4284
|
-
}
|
|
4285
|
-
if (output instanceof Error) {
|
|
4286
|
-
eventProperties['$ai_http_status'] = output.status || 500;
|
|
4287
|
-
eventProperties['$ai_error'] = stringifyError(output);
|
|
4288
|
-
eventProperties['$ai_is_error'] = true;
|
|
4289
|
-
} else {
|
|
4290
|
-
// Handle token usage
|
|
4291
|
-
const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
|
|
4292
|
-
eventProperties['$ai_input_tokens'] = inputTokens;
|
|
4293
|
-
eventProperties['$ai_output_tokens'] = outputTokens;
|
|
4294
|
-
// Add additional token data to properties
|
|
4295
|
-
if (additionalTokenData.cacheReadInputTokens) {
|
|
4296
|
-
eventProperties['$ai_cache_read_input_tokens'] = additionalTokenData.cacheReadInputTokens;
|
|
4297
|
-
}
|
|
4298
|
-
if (additionalTokenData.cacheWriteInputTokens) {
|
|
4299
|
-
eventProperties['$ai_cache_creation_input_tokens'] = additionalTokenData.cacheWriteInputTokens;
|
|
4300
|
-
}
|
|
4301
|
-
if (additionalTokenData.reasoningTokens) {
|
|
4302
|
-
eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
|
|
4303
|
-
}
|
|
4304
|
-
if (additionalTokenData.webSearchCount !== undefined) {
|
|
4305
|
-
eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
|
|
4306
|
-
}
|
|
4307
|
-
// Extract stop reason from generation info
|
|
4308
|
-
const stopReason = this._extractStopReason(output);
|
|
4309
|
-
if (stopReason) {
|
|
4310
|
-
eventProperties['$ai_stop_reason'] = stopReason;
|
|
4311
|
-
}
|
|
4312
|
-
// Handle generations/completions
|
|
4313
|
-
let completions;
|
|
4314
|
-
if (output.generations && Array.isArray(output.generations)) {
|
|
4315
|
-
const lastGeneration = output.generations[output.generations.length - 1];
|
|
4316
|
-
if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
|
|
4317
|
-
// Check if this is a ChatGeneration by looking at the first item
|
|
4318
|
-
const isChatGeneration = 'message' in lastGeneration[0] && lastGeneration[0].message;
|
|
4319
|
-
if (isChatGeneration) {
|
|
4320
|
-
// For ChatGeneration, convert messages to dict format
|
|
4321
|
-
completions = lastGeneration.map(gen => {
|
|
4322
|
-
return this._convertMessageToDict(gen.message);
|
|
4323
|
-
});
|
|
4324
|
-
} else {
|
|
4325
|
-
// For non-ChatGeneration, extract raw response
|
|
4326
|
-
completions = lastGeneration.map(gen => {
|
|
4327
|
-
return this._extractRawResponse(gen);
|
|
4328
|
-
});
|
|
4329
|
-
}
|
|
4330
|
-
}
|
|
4331
|
-
}
|
|
4332
|
-
if (completions) {
|
|
4333
|
-
eventProperties['$ai_output_choices'] = withPrivacyMode(this.client, this.privacyMode, completions);
|
|
4334
|
-
}
|
|
4335
|
-
}
|
|
4336
|
-
Object.assign(eventProperties, this.properties);
|
|
4337
|
-
if (!this.distinctId) {
|
|
4338
|
-
eventProperties['$process_person_profile'] = false;
|
|
4339
|
-
}
|
|
4340
|
-
this.client.capture({
|
|
4341
|
-
distinctId: this.distinctId ? this.distinctId.toString() : traceId,
|
|
4342
|
-
event: '$ai_generation',
|
|
4343
|
-
properties: eventProperties,
|
|
4344
|
-
groups: this.groups
|
|
4345
|
-
});
|
|
4346
|
-
}
|
|
4347
|
-
_logDebugEvent(eventName, runId, parentRunId, extra) {
|
|
4348
|
-
if (this.debug) {
|
|
4349
|
-
console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
_getLangchainRunName(serialized, ...args) {
|
|
4353
|
-
if (args && args.length > 0) {
|
|
4354
|
-
for (const arg of args) {
|
|
4355
|
-
if (arg && typeof arg === 'object' && 'name' in arg) {
|
|
4356
|
-
return arg.name;
|
|
4357
|
-
} else if (arg && typeof arg === 'object' && 'runName' in arg) {
|
|
4358
|
-
return arg.runName;
|
|
4359
|
-
}
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
if (serialized && serialized.name) {
|
|
4363
|
-
return serialized.name;
|
|
4364
|
-
}
|
|
4365
|
-
if (serialized && serialized.id) {
|
|
4366
|
-
return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
|
|
4367
|
-
}
|
|
4368
|
-
return undefined;
|
|
4369
|
-
}
|
|
4370
|
-
_convertLcToolCallsToOai(toolCalls) {
|
|
4371
|
-
return toolCalls.map(toolCall => ({
|
|
4372
|
-
type: 'function',
|
|
4373
|
-
id: toolCall.id,
|
|
4374
|
-
function: {
|
|
4375
|
-
name: toolCall.name,
|
|
4376
|
-
arguments: JSON.stringify(toolCall.args)
|
|
4377
|
-
}
|
|
4378
|
-
}));
|
|
4379
|
-
}
|
|
4380
|
-
_extractRawResponse(generation) {
|
|
4381
|
-
// Extract the response from the last response of the LLM call
|
|
4382
|
-
// We return the text of the response if not empty
|
|
4383
|
-
if (generation.text != null && generation.text.trim() !== '') {
|
|
4384
|
-
return generation.text.trim();
|
|
4385
|
-
} else if (generation.message) {
|
|
4386
|
-
// Additional kwargs contains the response in case of tool usage
|
|
4387
|
-
return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
|
|
4388
|
-
} else {
|
|
4389
|
-
// Not tool usage, some LLM responses can be simply empty
|
|
4390
|
-
return '';
|
|
4391
|
-
}
|
|
4392
|
-
}
|
|
4393
|
-
_convertMessageToDict(message) {
|
|
4394
|
-
let messageDict = {};
|
|
4395
|
-
const messageType = message.getType();
|
|
4396
|
-
switch (messageType) {
|
|
4397
|
-
case 'human':
|
|
4398
|
-
messageDict = {
|
|
4399
|
-
role: 'user',
|
|
4400
|
-
content: message.content
|
|
4401
|
-
};
|
|
4402
|
-
break;
|
|
4403
|
-
case 'ai':
|
|
4404
|
-
messageDict = {
|
|
4405
|
-
role: 'assistant',
|
|
4406
|
-
content: message.content
|
|
4407
|
-
};
|
|
4408
|
-
if (message.tool_calls) {
|
|
4409
|
-
messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
|
|
4410
|
-
}
|
|
4411
|
-
break;
|
|
4412
|
-
case 'system':
|
|
4413
|
-
messageDict = {
|
|
4414
|
-
role: 'system',
|
|
4415
|
-
content: message.content
|
|
4416
|
-
};
|
|
4417
|
-
break;
|
|
4418
|
-
case 'tool':
|
|
4419
|
-
messageDict = {
|
|
4420
|
-
role: 'tool',
|
|
4421
|
-
content: message.content
|
|
4422
|
-
};
|
|
4423
|
-
break;
|
|
4424
|
-
case 'function':
|
|
4425
|
-
messageDict = {
|
|
4426
|
-
role: 'function',
|
|
4427
|
-
content: message.content
|
|
4428
|
-
};
|
|
4429
|
-
break;
|
|
4430
|
-
default:
|
|
4431
|
-
messageDict = {
|
|
4432
|
-
role: messageType,
|
|
4433
|
-
content: toContentString(message.content)
|
|
4434
|
-
};
|
|
4435
|
-
break;
|
|
4436
|
-
}
|
|
4437
|
-
if (message.additional_kwargs) {
|
|
4438
|
-
messageDict = {
|
|
4439
|
-
...messageDict,
|
|
4440
|
-
...message.additional_kwargs
|
|
4441
|
-
};
|
|
4442
|
-
}
|
|
4443
|
-
// Sanitize the message content to redact base64 images
|
|
4444
|
-
return sanitizeLangChain(messageDict);
|
|
4445
|
-
}
|
|
4446
|
-
_extractStopReason(output) {
|
|
4447
|
-
if (!output.generations || !Array.isArray(output.generations)) {
|
|
4448
|
-
return undefined;
|
|
4449
|
-
}
|
|
4450
|
-
const lastGeneration = output.generations[output.generations.length - 1];
|
|
4451
|
-
if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
|
|
4452
|
-
return undefined;
|
|
4453
|
-
}
|
|
4454
|
-
const gen = lastGeneration[0];
|
|
4455
|
-
// Check generationInfo for finish_reason (OpenAI format)
|
|
4456
|
-
if (gen.generationInfo?.finish_reason) {
|
|
4457
|
-
return String(gen.generationInfo.finish_reason);
|
|
4458
|
-
}
|
|
4459
|
-
// Check generationInfo for response_metadata.stop_reason (Anthropic format)
|
|
4460
|
-
if (gen.generationInfo?.response_metadata?.stop_reason) {
|
|
4461
|
-
return String(gen.generationInfo.response_metadata.stop_reason);
|
|
4462
|
-
}
|
|
4463
|
-
// Check message response_metadata for finish_reason (common LangChain format)
|
|
4464
|
-
if (gen.generationInfo?.response_metadata?.finish_reason) {
|
|
4465
|
-
return String(gen.generationInfo.response_metadata.finish_reason);
|
|
4466
|
-
}
|
|
4467
|
-
// Check for stop_reason directly in generationInfo
|
|
4468
|
-
if (gen.generationInfo?.stop_reason) {
|
|
4469
|
-
return String(gen.generationInfo.stop_reason);
|
|
4470
|
-
}
|
|
4471
|
-
return undefined;
|
|
4472
|
-
}
|
|
4473
|
-
_parseUsageModel(usage, provider, model) {
|
|
4474
|
-
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']];
|
|
4475
|
-
const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {
|
|
4476
|
-
const value = usage[modelKey];
|
|
4477
|
-
if (value != null) {
|
|
4478
|
-
const finalCount = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
|
|
4479
|
-
acc[typeKey] = finalCount;
|
|
4480
|
-
}
|
|
4481
|
-
return acc;
|
|
4482
|
-
}, {
|
|
4483
|
-
input: 0,
|
|
4484
|
-
output: 0
|
|
4485
|
-
});
|
|
4486
|
-
// Extract additional token details like cached tokens and reasoning tokens
|
|
4487
|
-
const additionalTokenData = {};
|
|
4488
|
-
// Check for cached tokens in various formats
|
|
4489
|
-
if (usage.prompt_tokens_details?.cached_tokens != null) {
|
|
4490
|
-
additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
|
|
4491
|
-
} else if (usage.input_token_details?.cache_read != null) {
|
|
4492
|
-
additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
|
|
4493
|
-
} else if (usage.cachedPromptTokens != null) {
|
|
4494
|
-
additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
|
|
4495
|
-
} else if (usage.cache_read_input_tokens != null) {
|
|
4496
|
-
additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
|
|
4497
|
-
}
|
|
4498
|
-
// Check for cache write/creation tokens in various formats
|
|
4499
|
-
if (usage.cache_creation_input_tokens != null) {
|
|
4500
|
-
additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
|
|
4501
|
-
} else if (usage.input_token_details?.cache_creation != null) {
|
|
4502
|
-
additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
|
|
4503
|
-
}
|
|
4504
|
-
// Check for reasoning tokens in various formats
|
|
4505
|
-
if (usage.completion_tokens_details?.reasoning_tokens != null) {
|
|
4506
|
-
additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
|
|
4507
|
-
} else if (usage.output_token_details?.reasoning != null) {
|
|
4508
|
-
additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
|
|
4509
|
-
} else if (usage.reasoningTokens != null) {
|
|
4510
|
-
additionalTokenData.reasoningTokens = usage.reasoningTokens;
|
|
4511
|
-
}
|
|
4512
|
-
// Extract web search counts from various provider formats
|
|
4513
|
-
let webSearchCount;
|
|
4514
|
-
// Priority 1: Exact Count
|
|
4515
|
-
// Check Anthropic format (server_tool_use.web_search_requests)
|
|
4516
|
-
if (usage.server_tool_use?.web_search_requests !== undefined) {
|
|
4517
|
-
webSearchCount = usage.server_tool_use.web_search_requests;
|
|
4518
|
-
}
|
|
4519
|
-
// Priority 2: Binary Detection (1 or 0)
|
|
4520
|
-
// Check for citations array (Perplexity)
|
|
4521
|
-
else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) {
|
|
4522
|
-
webSearchCount = 1;
|
|
4523
|
-
}
|
|
4524
|
-
// Check for search_results array (Perplexity via OpenRouter)
|
|
4525
|
-
else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) {
|
|
4526
|
-
webSearchCount = 1;
|
|
4527
|
-
}
|
|
4528
|
-
// Check for search_context_size (Perplexity via OpenRouter)
|
|
4529
|
-
else if (usage.search_context_size) {
|
|
4530
|
-
webSearchCount = 1;
|
|
4531
|
-
}
|
|
4532
|
-
// Check for annotations with url_citation type
|
|
4533
|
-
else if (usage.annotations && Array.isArray(usage.annotations)) {
|
|
4534
|
-
const hasUrlCitation = usage.annotations.some(ann => {
|
|
4535
|
-
return ann && typeof ann === 'object' && 'type' in ann && ann.type === 'url_citation';
|
|
4536
|
-
});
|
|
4537
|
-
if (hasUrlCitation) {
|
|
4538
|
-
webSearchCount = 1;
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
// Check Gemini format (grounding metadata - binary 0 or 1)
|
|
4542
|
-
else if (usage.grounding_metadata?.grounding_support !== undefined || usage.grounding_metadata?.web_search_queries !== undefined) {
|
|
4543
|
-
webSearchCount = 1;
|
|
4544
|
-
}
|
|
4545
|
-
if (webSearchCount !== undefined) {
|
|
4546
|
-
additionalTokenData.webSearchCount = webSearchCount;
|
|
4547
|
-
}
|
|
4548
|
-
// For Anthropic providers, LangChain reports input_tokens as the sum of all input tokens.
|
|
4549
|
-
// Our cost calculation expects them to be separate for Anthropic, so we subtract cache tokens.
|
|
4550
|
-
// Both cache_read and cache_write tokens should be subtracted since Anthropic's raw API
|
|
4551
|
-
// reports input_tokens as tokens NOT read from or used to create a cache.
|
|
4552
|
-
// For other providers (OpenAI, etc.), input_tokens already excludes cache tokens as expected.
|
|
4553
|
-
// Match logic consistent with plugin-server: exact match on provider OR substring match on model
|
|
4554
|
-
let isAnthropic = false;
|
|
4555
|
-
if (provider && provider.toLowerCase() === 'anthropic') {
|
|
4556
|
-
isAnthropic = true;
|
|
4557
|
-
} else if (model && model.toLowerCase().includes('anthropic')) {
|
|
4558
|
-
isAnthropic = true;
|
|
4559
|
-
}
|
|
4560
|
-
if (isAnthropic && parsedUsage.input) {
|
|
4561
|
-
const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
|
|
4562
|
-
if (cacheTokens > 0) {
|
|
4563
|
-
parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
|
|
4564
|
-
}
|
|
4565
|
-
}
|
|
4566
|
-
return [parsedUsage.input, parsedUsage.output, additionalTokenData];
|
|
4567
|
-
}
|
|
4568
|
-
parseUsage(response, provider, model) {
|
|
4569
|
-
let llmUsage = [0, 0, {}];
|
|
4570
|
-
const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
|
|
4571
|
-
if (response.llmOutput != null) {
|
|
4572
|
-
const key = llmUsageKeys.find(k => response.llmOutput?.[k] != null);
|
|
4573
|
-
if (key) {
|
|
4574
|
-
llmUsage = this._parseUsageModel(response.llmOutput[key], provider, model);
|
|
4575
|
-
}
|
|
4576
|
-
}
|
|
4577
|
-
// If top-level usage info was not found, try checking the generations.
|
|
4578
|
-
if (llmUsage[0] === 0 && llmUsage[1] === 0 && response.generations) {
|
|
4579
|
-
for (const generation of response.generations) {
|
|
4580
|
-
for (const genChunk of generation) {
|
|
4581
|
-
// Check other paths for usage information
|
|
4582
|
-
if (genChunk.generationInfo?.usage_metadata) {
|
|
4583
|
-
llmUsage = this._parseUsageModel(genChunk.generationInfo.usage_metadata, provider, model);
|
|
4584
|
-
return llmUsage;
|
|
4585
|
-
}
|
|
4586
|
-
const messageChunk = genChunk.generationInfo ?? {};
|
|
4587
|
-
const responseMetadata = messageChunk.response_metadata ?? {};
|
|
4588
|
-
const chunkUsage = responseMetadata['usage'] ?? responseMetadata['amazon-bedrock-invocationMetrics'] ?? messageChunk.usage_metadata;
|
|
4589
|
-
if (chunkUsage) {
|
|
4590
|
-
llmUsage = this._parseUsageModel(chunkUsage, provider, model);
|
|
4591
|
-
return llmUsage;
|
|
4592
|
-
}
|
|
4593
|
-
}
|
|
4594
|
-
}
|
|
4595
|
-
}
|
|
4596
|
-
return llmUsage;
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
|
|
4600
|
-
/// <reference lib="dom" />
|
|
4601
|
-
const DEFAULT_CACHE_TTL_SECONDS = 300; // 5 minutes
|
|
4602
|
-
const DEFAULT_PROMPTS_HOST = 'https://us.posthog.com';
|
|
4603
|
-
function normalizeApiKey(value) {
|
|
4604
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
4605
|
-
}
|
|
4606
|
-
function normalizeHost(value) {
|
|
4607
|
-
const normalizedHost = typeof value === 'string' ? value.trim() : '';
|
|
4608
|
-
return (normalizedHost || DEFAULT_PROMPTS_HOST).replace(/\/+$/, '');
|
|
4609
|
-
}
|
|
4610
|
-
function isPromptApiResponse(data) {
|
|
4611
|
-
if (typeof data !== 'object' || data === null) {
|
|
4612
|
-
return false;
|
|
1194
|
+
function isPromptApiResponse(data) {
|
|
1195
|
+
if (typeof data !== 'object' || data === null) {
|
|
1196
|
+
return false;
|
|
4613
1197
|
}
|
|
4614
1198
|
const record = data;
|
|
4615
1199
|
return typeof record.prompt === 'string' && typeof record.name === 'string' && typeof record.version === 'number';
|
|
@@ -4633,18 +1217,18 @@ function isPromptsWithPostHog(options) {
|
|
|
4633
1217
|
* })
|
|
4634
1218
|
*
|
|
4635
1219
|
* // Fetch with caching and fallback
|
|
4636
|
-
* const
|
|
1220
|
+
* const result = await prompts.get('support-system-prompt', {
|
|
4637
1221
|
* cacheTtlSeconds: 300,
|
|
4638
1222
|
* fallback: 'You are a helpful assistant.',
|
|
4639
1223
|
* })
|
|
4640
1224
|
*
|
|
4641
1225
|
* // Or fetch an exact published version
|
|
4642
|
-
* const
|
|
1226
|
+
* const v3 = await prompts.get('support-system-prompt', {
|
|
4643
1227
|
* version: 3,
|
|
4644
1228
|
* })
|
|
4645
1229
|
*
|
|
4646
1230
|
* // Compile with variables
|
|
4647
|
-
* const systemPrompt = prompts.compile(
|
|
1231
|
+
* const systemPrompt = prompts.compile(result.prompt, {
|
|
4648
1232
|
* company: 'Acme Corp',
|
|
4649
1233
|
* tier: 'premium',
|
|
4650
1234
|
* })
|
|
@@ -4653,7 +1237,6 @@ function isPromptsWithPostHog(options) {
|
|
|
4653
1237
|
class Prompts {
|
|
4654
1238
|
constructor(options) {
|
|
4655
1239
|
this.cache = new Map();
|
|
4656
|
-
this.hasWarnedDeprecation = false;
|
|
4657
1240
|
this.defaultCacheTtlSeconds = options.defaultCacheTtlSeconds ?? DEFAULT_CACHE_TTL_SECONDS;
|
|
4658
1241
|
if (isPromptsWithPostHog(options)) {
|
|
4659
1242
|
this.personalApiKey = options.posthog.options.personalApiKey ?? '';
|
|
@@ -4681,32 +1264,26 @@ class Prompts {
|
|
|
4681
1264
|
getPromptLabel(name, version) {
|
|
4682
1265
|
return version === undefined ? `"${name}"` : `"${name}" version ${version}`;
|
|
4683
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Fetch a prompt by name from the PostHog API.
|
|
1269
|
+
*
|
|
1270
|
+
* Returns a `PromptResult` object carrying the prompt text alongside `source`,
|
|
1271
|
+
* `name`, and `version` metadata. Read `result.prompt` for the template string.
|
|
1272
|
+
*/
|
|
4684
1273
|
async get(name, options) {
|
|
4685
|
-
const withMetadata = options?.withMetadata;
|
|
4686
|
-
if (withMetadata === undefined && !this.hasWarnedDeprecation) {
|
|
4687
|
-
this.hasWarnedDeprecation = true;
|
|
4688
|
-
console.warn('[PostHog Prompts] Calling get() without { withMetadata: true } is deprecated and will be ' + 'removed in a future major version. Pass { withMetadata: true } to receive a PromptResult ' + 'object with source, name, and version metadata. ' + 'You can pass { withMetadata: false } to silence this warning, but the plain-string return ' + 'will still be removed in the next major version.');
|
|
4689
|
-
}
|
|
4690
1274
|
try {
|
|
4691
|
-
|
|
4692
|
-
if (withMetadata) {
|
|
4693
|
-
return result;
|
|
4694
|
-
}
|
|
4695
|
-
return result.prompt;
|
|
1275
|
+
return await this.getInternal(name, options);
|
|
4696
1276
|
} catch (error) {
|
|
4697
1277
|
const fallback = options?.fallback;
|
|
4698
1278
|
if (fallback !== undefined) {
|
|
4699
1279
|
const promptLabel = this.getPromptLabel(name, options?.version);
|
|
4700
1280
|
console.warn(`[PostHog Prompts] Failed to fetch prompt ${promptLabel}, using fallback:`, error);
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
};
|
|
4708
|
-
}
|
|
4709
|
-
return fallback;
|
|
1281
|
+
return {
|
|
1282
|
+
source: 'code_fallback',
|
|
1283
|
+
prompt: fallback,
|
|
1284
|
+
name: undefined,
|
|
1285
|
+
version: undefined
|
|
1286
|
+
};
|
|
4710
1287
|
}
|
|
4711
1288
|
throw error;
|
|
4712
1289
|
}
|
|
@@ -4844,11 +1421,6 @@ class Prompts {
|
|
|
4844
1421
|
}
|
|
4845
1422
|
}
|
|
4846
1423
|
|
|
4847
|
-
exports.Anthropic = PostHogAnthropic;
|
|
4848
|
-
exports.AzureOpenAI = PostHogAzureOpenAI;
|
|
4849
|
-
exports.GoogleGenAI = PostHogGoogleGenAI;
|
|
4850
|
-
exports.LangChainCallbackHandler = LangChainCallbackHandler;
|
|
4851
|
-
exports.OpenAI = PostHogOpenAI;
|
|
4852
1424
|
exports.Prompts = Prompts;
|
|
4853
1425
|
exports.captureAiGeneration = captureAiGeneration;
|
|
4854
1426
|
exports.withTracing = wrapVercelLanguageModel;
|