@posthog/ai 8.10.0 → 8.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adk/index.cjs +908 -1198
- package/dist/adk/index.cjs.map +1 -1
- package/dist/adk/index.d.ts +109 -109
- package/dist/adk/index.mjs +907 -1196
- package/dist/adk/index.mjs.map +1 -1
- package/dist/anthropic/index.cjs +927 -1111
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.ts +34 -33
- package/dist/anthropic/index.mjs +899 -1102
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/gemini/index.cjs +863 -1108
- package/dist/gemini/index.cjs.map +1 -1
- package/dist/gemini/index.d.ts +38 -35
- package/dist/gemini/index.mjs +858 -1103
- package/dist/gemini/index.mjs.map +1 -1
- package/dist/index.cjs +1218 -1546
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +170 -163
- package/dist/index.mjs +1216 -1544
- package/dist/index.mjs.map +1 -1
- package/dist/langchain/index.cjs +851 -1029
- package/dist/langchain/index.cjs.map +1 -1
- package/dist/langchain/index.d.ts +75 -75
- package/dist/langchain/index.mjs +850 -1027
- package/dist/langchain/index.mjs.map +1 -1
- package/dist/langchain/middleware/index.cjs +1016 -1225
- package/dist/langchain/middleware/index.cjs.map +1 -1
- package/dist/langchain/middleware/index.d.ts +29 -25
- package/dist/langchain/middleware/index.mjs +1015 -1223
- package/dist/langchain/middleware/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1990 -2523
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.ts +106 -104
- package/dist/openai/index.mjs +1985 -2518
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openai-agents/index.cjs +745 -827
- package/dist/openai-agents/index.cjs.map +1 -1
- package/dist/openai-agents/index.d.ts +48 -47
- package/dist/openai-agents/index.mjs +744 -825
- package/dist/openai-agents/index.mjs.map +1 -1
- package/dist/otel/index.cjs +427 -486
- package/dist/otel/index.cjs.map +1 -1
- package/dist/otel/index.d.ts +36 -35
- package/dist/otel/index.mjs +426 -484
- package/dist/otel/index.mjs.map +1 -1
- package/dist/vercel/index.cjs +992 -1343
- package/dist/vercel/index.cjs.map +1 -1
- package/dist/vercel/index.d.ts +21 -16
- package/dist/vercel/index.mjs +991 -1341
- package/dist/vercel/index.mjs.map +1 -1
- package/package.json +15 -14
|
@@ -1,1275 +1,1067 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { convertToOpenAITool } from
|
|
3
|
-
import { extendInteropZodObject } from
|
|
4
|
-
import { createMiddleware } from
|
|
5
|
-
import { v7 } from
|
|
6
|
-
import { z } from
|
|
7
|
-
import { BaseCallbackHandler } from
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const isObject = value => {
|
|
12
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1
|
+
import { AIMessage, ToolMessage } from "@langchain/core/messages";
|
|
2
|
+
import { convertToOpenAITool } from "@langchain/core/utils/function_calling";
|
|
3
|
+
import { extendInteropZodObject } from "@langchain/core/utils/types";
|
|
4
|
+
import { createMiddleware } from "langchain";
|
|
5
|
+
import { v7 } from "uuid";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
8
|
+
//#region src/typeGuards.ts
|
|
9
|
+
const isObject = (value) => {
|
|
10
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
11
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** @internal */
|
|
18
|
-
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/captureAiEvent.ts
|
|
19
14
|
/** @internal */
|
|
20
15
|
function isFullAiCaptureEnabled(client) {
|
|
21
|
-
|
|
16
|
+
return client?.enableFullAiCapture === true;
|
|
22
17
|
}
|
|
23
|
-
|
|
24
18
|
/** @internal */
|
|
25
19
|
function captureAiEvent(client, event) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
if (isFullAiCaptureEnabled(client) && typeof client.captureAi === "function") {
|
|
21
|
+
client.captureAi(event);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
client.capture(event);
|
|
31
25
|
}
|
|
32
|
-
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/sanitization/base64_recognizer.ts
|
|
33
28
|
const DATA_URL_PREFIX_RE = /^data:([^;,\s]+)(?:;[^;,\s]+)*;base64,/i;
|
|
34
29
|
const BASE64_ALPHABET_RE = /^[A-Za-z0-9+/_=-]+$/;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
30
|
+
var Base64Recognizer = class {
|
|
31
|
+
recognize(value, minLength) {
|
|
32
|
+
const dataUrl = DATA_URL_PREFIX_RE.exec(value);
|
|
33
|
+
if (dataUrl) return {
|
|
34
|
+
kind: "data-url",
|
|
35
|
+
mediaType: dataUrl[1]
|
|
36
|
+
};
|
|
37
|
+
if (value.length < minLength) return { kind: "none" };
|
|
38
|
+
const confidencePrefix = value.slice(0, minLength);
|
|
39
|
+
if (BASE64_ALPHABET_RE.test(confidencePrefix)) return { kind: "raw" };
|
|
40
|
+
else return { kind: "none" };
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/sanitization/media_type_context.ts
|
|
45
|
+
const MIME_HINT_KEYS = [
|
|
46
|
+
"mediaType",
|
|
47
|
+
"media_type",
|
|
48
|
+
"mimeType",
|
|
49
|
+
"mime_type"
|
|
50
|
+
];
|
|
51
|
+
const STRONG_CONTEXT_KEYS = /* @__PURE__ */ new Set([
|
|
52
|
+
"data",
|
|
53
|
+
"file_data",
|
|
54
|
+
"fileData",
|
|
55
|
+
"image_url",
|
|
56
|
+
"imageUrl",
|
|
57
|
+
"video_url",
|
|
58
|
+
"videoUrl",
|
|
59
|
+
"audio",
|
|
60
|
+
"audio_data",
|
|
61
|
+
"audioData",
|
|
62
|
+
"inline_data",
|
|
63
|
+
"inlineData",
|
|
64
|
+
"source",
|
|
65
|
+
"result"
|
|
66
|
+
]);
|
|
67
|
+
const STRONG_CONTEXT_TYPES = /* @__PURE__ */ new Set([
|
|
68
|
+
"image",
|
|
69
|
+
"image_url",
|
|
70
|
+
"input_image",
|
|
71
|
+
"audio",
|
|
72
|
+
"input_audio",
|
|
73
|
+
"video",
|
|
74
|
+
"video_url",
|
|
75
|
+
"file",
|
|
76
|
+
"input_file",
|
|
77
|
+
"document",
|
|
78
|
+
"media",
|
|
79
|
+
"file-data"
|
|
80
|
+
]);
|
|
81
|
+
const FILE_FAMILY_TYPES = /* @__PURE__ */ new Set([
|
|
82
|
+
"file",
|
|
83
|
+
"input_file",
|
|
84
|
+
"document",
|
|
85
|
+
"media",
|
|
86
|
+
"file-data"
|
|
87
|
+
]);
|
|
88
|
+
const KNOWN_AUDIO_FORMATS = /* @__PURE__ */ new Set([
|
|
89
|
+
"wav",
|
|
90
|
+
"mp3",
|
|
91
|
+
"ogg",
|
|
92
|
+
"flac",
|
|
93
|
+
"m4a",
|
|
94
|
+
"aac",
|
|
95
|
+
"webm"
|
|
96
|
+
]);
|
|
97
|
+
var MediaTypeContext = class MediaTypeContext {
|
|
98
|
+
static {
|
|
99
|
+
this.EMPTY = new MediaTypeContext(void 0, void 0);
|
|
100
|
+
}
|
|
101
|
+
constructor(parent, key, explicitMediaType) {
|
|
102
|
+
this.parent = parent;
|
|
103
|
+
this.key = key;
|
|
104
|
+
this.explicitMediaType = explicitMediaType;
|
|
105
|
+
}
|
|
106
|
+
inferMediaType() {
|
|
107
|
+
return this.inferFromSiblingMime() ?? this.inferFromSiblingFormat() ?? this.inferFromParentType() ?? this.inferFromKey();
|
|
108
|
+
}
|
|
109
|
+
inferFromSiblingMime() {
|
|
110
|
+
if (this.explicitMediaType) return this.explicitMediaType;
|
|
111
|
+
if (!this.parent) return void 0;
|
|
112
|
+
for (const hint of MIME_HINT_KEYS) {
|
|
113
|
+
const v = this.parent[hint];
|
|
114
|
+
if (typeof v === "string") return v;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
inferFromSiblingFormat() {
|
|
118
|
+
if (!this.parent) return void 0;
|
|
119
|
+
const fmt = this.parent.format;
|
|
120
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return `audio/${fmt.toLowerCase()}`;
|
|
121
|
+
}
|
|
122
|
+
inferFromParentType() {
|
|
123
|
+
if (!this.parent) return void 0;
|
|
124
|
+
const t = this.parent.type;
|
|
125
|
+
if (typeof t !== "string") return void 0;
|
|
126
|
+
if (t === "image" || t === "image_url" || t === "input_image") return "image";
|
|
127
|
+
if (t === "audio" || t === "input_audio") return "audio";
|
|
128
|
+
if (t === "video" || t === "video_url") return "video";
|
|
129
|
+
if (FILE_FAMILY_TYPES.has(t)) return "application/octet-stream";
|
|
130
|
+
}
|
|
131
|
+
inferFromKey() {
|
|
132
|
+
if (!this.key) return void 0;
|
|
133
|
+
const key = this.key.toLowerCase();
|
|
134
|
+
if (key.includes("audio")) return "audio";
|
|
135
|
+
if (key.includes("video")) return "video";
|
|
136
|
+
if (key.includes("image")) return "image";
|
|
137
|
+
if (key.includes("file") || key.includes("document")) return "application/octet-stream";
|
|
138
|
+
}
|
|
139
|
+
hasExplicitBinaryMediaType() {
|
|
140
|
+
if (!this.explicitMediaType && (!this.parent || !this.key || !STRONG_CONTEXT_KEYS.has(this.key))) return false;
|
|
141
|
+
const mediaType = this.inferFromSiblingMime();
|
|
142
|
+
return mediaType !== void 0 && !mediaType.toLowerCase().startsWith("text/");
|
|
143
|
+
}
|
|
144
|
+
signalsBinary() {
|
|
145
|
+
if (this.explicitMediaType) return true;
|
|
146
|
+
if (this.parent) {
|
|
147
|
+
for (const hint of MIME_HINT_KEYS) if (typeof this.parent[hint] === "string") return true;
|
|
148
|
+
const fmt = this.parent.format;
|
|
149
|
+
if (typeof fmt === "string" && KNOWN_AUDIO_FORMATS.has(fmt.toLowerCase())) return true;
|
|
150
|
+
const t = this.parent.type;
|
|
151
|
+
if (typeof t === "string" && STRONG_CONTEXT_TYPES.has(t)) return true;
|
|
152
|
+
}
|
|
153
|
+
if (this.key && STRONG_CONTEXT_KEYS.has(this.key)) return true;
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/sanitization/binary_content_redactor.ts
|
|
130
159
|
const STRONG_CONTEXT_MIN_LENGTH = 64;
|
|
131
160
|
const WEAK_CONTEXT_MIN_LENGTH = 1024;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return value;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
placeholderFor(mediaType) {
|
|
177
|
-
if (!mediaType) return '[base64 redacted]';
|
|
178
|
-
if (mediaType === 'application/octet-stream') return '[base64 file redacted]';
|
|
179
|
-
return `[base64 ${mediaType} redacted]`;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
161
|
+
var BinaryContentRedactor = class {
|
|
162
|
+
constructor(recognizer = new Base64Recognizer()) {
|
|
163
|
+
this.recognizer = recognizer;
|
|
164
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
165
|
+
}
|
|
166
|
+
redact(value, mediaType) {
|
|
167
|
+
this.visited = /* @__PURE__ */ new WeakSet();
|
|
168
|
+
return this.walk(value, mediaType ? new MediaTypeContext(void 0, void 0, mediaType) : MediaTypeContext.EMPTY);
|
|
169
|
+
}
|
|
170
|
+
walk(value, ctx) {
|
|
171
|
+
if (value === null || value === void 0) return value;
|
|
172
|
+
if (typeof value === "string") return this.redactString(value, ctx);
|
|
173
|
+
if (typeof value !== "object") return value;
|
|
174
|
+
if (typeof Uint8Array !== "undefined" && value instanceof Uint8Array) return this.placeholderFor(ctx.inferMediaType());
|
|
175
|
+
if (this.visited.has(value)) return null;
|
|
176
|
+
this.visited.add(value);
|
|
177
|
+
if (Array.isArray(value)) return value.map((item) => this.walk(item, ctx));
|
|
178
|
+
const obj = value;
|
|
179
|
+
const out = {};
|
|
180
|
+
for (const k of Object.keys(obj)) out[k] = this.walk(obj[k], new MediaTypeContext(obj, k));
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
redactString(value, ctx) {
|
|
184
|
+
const hasExplicitBinaryMediaType = ctx.hasExplicitBinaryMediaType();
|
|
185
|
+
const recognitionValue = hasExplicitBinaryMediaType ? value.replace(/[\r\n]/g, "") : value;
|
|
186
|
+
const minLength = hasExplicitBinaryMediaType ? Math.min(recognitionValue.length, STRONG_CONTEXT_MIN_LENGTH) : ctx.signalsBinary() ? STRONG_CONTEXT_MIN_LENGTH : WEAK_CONTEXT_MIN_LENGTH;
|
|
187
|
+
const recognition = this.recognizer.recognize(recognitionValue, minLength);
|
|
188
|
+
switch (recognition.kind) {
|
|
189
|
+
case "data-url": return this.placeholderFor(recognition.mediaType);
|
|
190
|
+
case "raw": return this.placeholderFor(ctx.inferMediaType());
|
|
191
|
+
case "none": return value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
placeholderFor(mediaType) {
|
|
195
|
+
if (!mediaType) return "[base64 redacted]";
|
|
196
|
+
if (mediaType === "application/octet-stream") return "[base64 file redacted]";
|
|
197
|
+
return `[base64 ${mediaType} redacted]`;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/sanitization.ts
|
|
183
202
|
const redactor = new BinaryContentRedactor();
|
|
184
203
|
const sanitize = (data, client) => isFullAiCaptureEnabled(client) ? data : redactor.redact(data);
|
|
185
204
|
const sanitizeLangChain = (data, client) => sanitize(data, client);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Reused across calls to avoid per-invocation allocation; truncate() runs
|
|
190
|
-
// hundreds of times for prompts with many parts.
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/utils.ts
|
|
207
|
+
const STRING_FORMAT = "utf8";
|
|
191
208
|
new TextEncoder();
|
|
192
|
-
new TextDecoder(STRING_FORMAT, {
|
|
193
|
-
fatal: false
|
|
194
|
-
});
|
|
195
|
-
|
|
209
|
+
new TextDecoder(STRING_FORMAT, { fatal: false });
|
|
196
210
|
/**
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
211
|
+
* Safely converts content to a string, preserving structure for objects/arrays.
|
|
212
|
+
* - If content is already a string, returns it as-is
|
|
213
|
+
* - If content is an object or array, stringifies it with JSON.stringify to preserve structure
|
|
214
|
+
* - Otherwise, converts to string with String()
|
|
215
|
+
*
|
|
216
|
+
* This prevents the "[object Object]" bug when objects are naively converted to strings.
|
|
217
|
+
*
|
|
218
|
+
* @param content - The content to convert to a string
|
|
219
|
+
* @returns A string representation that preserves structure for complex types
|
|
220
|
+
*/
|
|
207
221
|
function toContentString(content) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Fallback for circular refs, BigInt, or objects with throwing toJSON
|
|
216
|
-
return String(content);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return String(content);
|
|
222
|
+
if (typeof content === "string") return content;
|
|
223
|
+
if (content !== void 0 && content !== null && typeof content === "object") try {
|
|
224
|
+
return JSON.stringify(content);
|
|
225
|
+
} catch {
|
|
226
|
+
return String(content);
|
|
227
|
+
}
|
|
228
|
+
return String(content);
|
|
220
229
|
}
|
|
221
230
|
const getModelParams = (params, responseServiceTier) => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
if (!params) return {};
|
|
232
|
+
const modelParams = {};
|
|
233
|
+
for (const key of [
|
|
234
|
+
"temperature",
|
|
235
|
+
"max_tokens",
|
|
236
|
+
"max_completion_tokens",
|
|
237
|
+
"top_p",
|
|
238
|
+
"frequency_penalty",
|
|
239
|
+
"presence_penalty",
|
|
240
|
+
"n",
|
|
241
|
+
"stop",
|
|
242
|
+
"stream",
|
|
243
|
+
"streaming",
|
|
244
|
+
"language",
|
|
245
|
+
"response_format",
|
|
246
|
+
"timestamp_granularities",
|
|
247
|
+
"service_tier"
|
|
248
|
+
]) if (key in params && params[key] !== void 0) modelParams[key] = params[key];
|
|
249
|
+
if (responseServiceTier != null) modelParams.service_tier = responseServiceTier;
|
|
250
|
+
return modelParams;
|
|
233
251
|
};
|
|
234
252
|
const withPrivacyMode = (client, privacyMode, input) => {
|
|
235
|
-
|
|
253
|
+
return client.privacy_mode || privacyMode ? null : input;
|
|
236
254
|
};
|
|
237
255
|
function sanitizeValues(obj) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
|
|
245
|
-
} else if (Array.isArray(jsonSafe)) {
|
|
246
|
-
return jsonSafe.map(sanitizeValues);
|
|
247
|
-
} else if (jsonSafe && typeof jsonSafe === 'object') {
|
|
248
|
-
return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
|
|
249
|
-
}
|
|
250
|
-
return jsonSafe;
|
|
256
|
+
if (obj === void 0 || obj === null) return obj;
|
|
257
|
+
const jsonSafe = JSON.parse(JSON.stringify(obj));
|
|
258
|
+
if (typeof jsonSafe === "string") return new TextDecoder().decode(new TextEncoder().encode(jsonSafe));
|
|
259
|
+
else if (Array.isArray(jsonSafe)) return jsonSafe.map(sanitizeValues);
|
|
260
|
+
else if (jsonSafe && typeof jsonSafe === "object") return Object.fromEntries(Object.entries(jsonSafe).map(([k, v]) => [k, sanitizeValues(v)]));
|
|
261
|
+
return jsonSafe;
|
|
251
262
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region package.json
|
|
265
|
+
var version = "8.10.2";
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/serializeError.ts
|
|
255
268
|
const DEFAULT_MAX_DEPTH = 3;
|
|
256
269
|
const MAX_STACK_LINES = 20;
|
|
257
270
|
function serializeError(value, depth = DEFAULT_MAX_DEPTH) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
out.cause = serializeError(value.cause, depth - 1);
|
|
272
|
-
}
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
if (Array.isArray(value)) {
|
|
276
|
-
return value.map(item => serializeError(item, depth - 1));
|
|
277
|
-
}
|
|
278
|
-
return value;
|
|
271
|
+
if (depth < 0 || value === null || typeof value !== "object") return value;
|
|
272
|
+
if (value instanceof Error) {
|
|
273
|
+
const out = {
|
|
274
|
+
name: value.name,
|
|
275
|
+
message: value.message,
|
|
276
|
+
stack: truncateStack(value.stack)
|
|
277
|
+
};
|
|
278
|
+
for (const key of Object.keys(value)) out[key] = serializeError(value[key], depth - 1);
|
|
279
|
+
if (value.cause !== void 0) out.cause = serializeError(value.cause, depth - 1);
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
if (Array.isArray(value)) return value.map((item) => serializeError(item, depth - 1));
|
|
283
|
+
return value;
|
|
279
284
|
}
|
|
280
285
|
function stringifyError(error) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return JSON.stringify({
|
|
291
|
-
message: String(error)
|
|
292
|
-
});
|
|
293
|
-
}
|
|
286
|
+
try {
|
|
287
|
+
return JSON.stringify(sanitizeValues(serializeError(error)));
|
|
288
|
+
} catch {
|
|
289
|
+
if (error instanceof Error) return JSON.stringify({
|
|
290
|
+
name: error.name,
|
|
291
|
+
message: error.message
|
|
292
|
+
});
|
|
293
|
+
return JSON.stringify({ message: String(error) });
|
|
294
|
+
}
|
|
294
295
|
}
|
|
295
296
|
function truncateStack(stack) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (lines.length <= MAX_STACK_LINES) {
|
|
301
|
-
return stack;
|
|
302
|
-
}
|
|
303
|
-
return [...lines.slice(0, MAX_STACK_LINES), '... (truncated)'].join('\n');
|
|
297
|
+
if (!stack) return stack;
|
|
298
|
+
const lines = stack.split("\n");
|
|
299
|
+
if (lines.length <= MAX_STACK_LINES) return stack;
|
|
300
|
+
return [...lines.slice(0, MAX_STACK_LINES), "... (truncated)"].join("\n");
|
|
304
301
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
323
|
-
} catch {
|
|
324
|
-
return undefined;
|
|
325
|
-
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/gatewayWarning.ts
|
|
304
|
+
const POSTHOG_AI_GATEWAY_HOSTS = [
|
|
305
|
+
"gateway.posthog.com",
|
|
306
|
+
"gateway.us.posthog.com",
|
|
307
|
+
"gateway.eu.posthog.com",
|
|
308
|
+
"ai-gateway.us.posthog.com",
|
|
309
|
+
"ai-gateway.eu.posthog.com"
|
|
310
|
+
];
|
|
311
|
+
const GATEWAY_DOCS_URL = "https://posthog.com/docs/ai-observability";
|
|
312
|
+
const extractHost = (baseURL) => {
|
|
313
|
+
try {
|
|
314
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(baseURL);
|
|
315
|
+
return new URL(hasScheme ? baseURL : `https://${baseURL}`).hostname.toLowerCase();
|
|
316
|
+
} catch {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
326
319
|
};
|
|
327
|
-
const isPostHogAiGatewayUrl = baseURL => {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
const host = extractHost(baseURL);
|
|
332
|
-
return host !== undefined && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
320
|
+
const isPostHogAiGatewayUrl = (baseURL) => {
|
|
321
|
+
if (!baseURL) return false;
|
|
322
|
+
const host = extractHost(baseURL);
|
|
323
|
+
return host !== void 0 && POSTHOG_AI_GATEWAY_HOSTS.includes(host);
|
|
333
324
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const warnIfPostHogAiGateway = baseURL => {
|
|
338
|
-
if (!isPostHogAiGatewayUrl(baseURL)) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
console.warn('[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. ' + 'Both capture $ai_generation, so every call is double-counted and double-billed. ' + `Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
325
|
+
const warnIfPostHogAiGateway = (baseURL) => {
|
|
326
|
+
if (!isPostHogAiGatewayUrl(baseURL)) return;
|
|
327
|
+
console.warn(`[PostHog] The PostHog AI wrapper is pointed at the PostHog AI Gateway. Both capture \$ai_generation, so every call is double-counted and double-billed. Use one or the other — see ${GATEWAY_DOCS_URL}.`);
|
|
342
328
|
};
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
runs = {};
|
|
358
|
-
parentTree = {};
|
|
359
|
-
constructor(options) {
|
|
360
|
-
if (!options.client) {
|
|
361
|
-
throw new Error('PostHog client is required');
|
|
362
|
-
}
|
|
363
|
-
super();
|
|
364
|
-
this.client = options.client;
|
|
365
|
-
this.distinctId = options.distinctId;
|
|
366
|
-
this.traceId = options.traceId;
|
|
367
|
-
this.properties = options.properties || {};
|
|
368
|
-
this.privacyMode = options.privacyMode || false;
|
|
369
|
-
this.groups = options.groups || {};
|
|
370
|
-
this.debug = options.debug || false;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// ===== CALLBACK METHODS =====
|
|
374
|
-
|
|
375
|
-
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName, extra) {
|
|
376
|
-
this._logDebugEvent('on_chain_start', runId, parentRunId, {
|
|
377
|
-
inputs,
|
|
378
|
-
tags
|
|
379
|
-
});
|
|
380
|
-
this._setParentOfRun(runId, parentRunId);
|
|
381
|
-
this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
|
|
382
|
-
if (typeof extra?.posthogStartTime === 'number' && Number.isFinite(extra.posthogStartTime)) {
|
|
383
|
-
this.runs[runId].startTime = extra.posthogStartTime;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
|
|
387
|
-
this._logAndPopTraceOrSpan('on_chain_end', runId, parentRunId, {
|
|
388
|
-
outputs,
|
|
389
|
-
tags
|
|
390
|
-
}, outputs);
|
|
391
|
-
}
|
|
392
|
-
handleChainError(error, runId, parentRunId, tags, _kwargs) {
|
|
393
|
-
this._logAndPopTraceOrSpan('on_chain_error', runId, parentRunId, {
|
|
394
|
-
error,
|
|
395
|
-
tags
|
|
396
|
-
}, error);
|
|
397
|
-
}
|
|
398
|
-
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
399
|
-
this._logDebugEvent('on_chat_model_start', runId, parentRunId, {
|
|
400
|
-
messages,
|
|
401
|
-
tags
|
|
402
|
-
});
|
|
403
|
-
this._setParentOfRun(runId, parentRunId);
|
|
404
|
-
// Flatten the two-dimensional messages and convert each message to a plain object
|
|
405
|
-
const input = messages.flat().map(m => this._convertMessageToDict(m));
|
|
406
|
-
this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
|
|
407
|
-
}
|
|
408
|
-
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
409
|
-
this._logDebugEvent('on_llm_start', runId, parentRunId, {
|
|
410
|
-
prompts,
|
|
411
|
-
tags
|
|
412
|
-
});
|
|
413
|
-
this._setParentOfRun(runId, parentRunId);
|
|
414
|
-
this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
|
|
415
|
-
}
|
|
416
|
-
handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
|
|
417
|
-
this._logAndPopGeneration('on_llm_end', runId, parentRunId, {
|
|
418
|
-
output,
|
|
419
|
-
tags
|
|
420
|
-
}, output);
|
|
421
|
-
}
|
|
422
|
-
handleLLMError(err, runId, parentRunId, tags, _extraParams) {
|
|
423
|
-
this._logAndPopGeneration('on_llm_error', runId, parentRunId, {
|
|
424
|
-
err,
|
|
425
|
-
tags
|
|
426
|
-
}, err);
|
|
427
|
-
}
|
|
428
|
-
handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
|
|
429
|
-
this._logAndSetTraceOrSpan('on_tool_start', tool, input, runId, parentRunId, {
|
|
430
|
-
input,
|
|
431
|
-
tags
|
|
432
|
-
}, tags, metadata, runName);
|
|
433
|
-
}
|
|
434
|
-
handleToolEnd(output, runId, parentRunId, tags) {
|
|
435
|
-
this._logAndPopTraceOrSpan('on_tool_end', runId, parentRunId, {
|
|
436
|
-
output,
|
|
437
|
-
tags
|
|
438
|
-
}, output);
|
|
439
|
-
}
|
|
440
|
-
handleToolError(err, runId, parentRunId, tags) {
|
|
441
|
-
this._logAndPopTraceOrSpan('on_tool_error', runId, parentRunId, {
|
|
442
|
-
err,
|
|
443
|
-
tags
|
|
444
|
-
}, err);
|
|
445
|
-
}
|
|
446
|
-
handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
|
|
447
|
-
this._logAndSetTraceOrSpan('on_retriever_start', retriever, query, runId, parentRunId, {
|
|
448
|
-
query,
|
|
449
|
-
tags
|
|
450
|
-
}, tags, metadata, name);
|
|
451
|
-
}
|
|
452
|
-
handleRetrieverEnd(documents, runId, parentRunId, tags) {
|
|
453
|
-
this._logAndPopTraceOrSpan('on_retriever_end', runId, parentRunId, {
|
|
454
|
-
documents,
|
|
455
|
-
tags
|
|
456
|
-
}, documents);
|
|
457
|
-
}
|
|
458
|
-
handleRetrieverError(err, runId, parentRunId, tags) {
|
|
459
|
-
this._logAndPopTraceOrSpan('on_retriever_error', runId, parentRunId, {
|
|
460
|
-
err,
|
|
461
|
-
tags
|
|
462
|
-
}, err);
|
|
463
|
-
}
|
|
464
|
-
handleAgentAction(action, runId, parentRunId, tags) {
|
|
465
|
-
this._logDebugEvent('on_agent_action', runId, parentRunId, {
|
|
466
|
-
action,
|
|
467
|
-
tags
|
|
468
|
-
});
|
|
469
|
-
this._setParentOfRun(runId, parentRunId);
|
|
470
|
-
this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
|
|
471
|
-
}
|
|
472
|
-
handleAgentEnd(action, runId, parentRunId, tags) {
|
|
473
|
-
this._logDebugEvent('on_agent_finish', runId, parentRunId, {
|
|
474
|
-
action,
|
|
475
|
-
tags
|
|
476
|
-
});
|
|
477
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// ===== PRIVATE HELPERS =====
|
|
481
|
-
|
|
482
|
-
_logAndSetTraceOrSpan(eventName, serialized, input, runId, parentRunId, debugPayload, tags, metadata, runName) {
|
|
483
|
-
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
484
|
-
this._setParentOfRun(runId, parentRunId);
|
|
485
|
-
this._setTraceOrSpanMetadata(serialized, input, runId, parentRunId, metadata, tags, runName);
|
|
486
|
-
}
|
|
487
|
-
_logAndPopTraceOrSpan(eventName, runId, parentRunId, debugPayload, result) {
|
|
488
|
-
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
489
|
-
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, result);
|
|
490
|
-
}
|
|
491
|
-
_logAndPopGeneration(eventName, runId, parentRunId, debugPayload, result) {
|
|
492
|
-
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
493
|
-
this._popRunAndCaptureGeneration(runId, parentRunId, result);
|
|
494
|
-
}
|
|
495
|
-
_setParentOfRun(runId, parentRunId) {
|
|
496
|
-
if (parentRunId) {
|
|
497
|
-
this.parentTree[runId] = parentRunId;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
_popParentOfRun(runId) {
|
|
501
|
-
delete this.parentTree[runId];
|
|
502
|
-
}
|
|
503
|
-
_findRootRun(runId) {
|
|
504
|
-
let id = runId;
|
|
505
|
-
while (this.parentTree[id]) {
|
|
506
|
-
id = this.parentTree[id];
|
|
507
|
-
}
|
|
508
|
-
return id;
|
|
509
|
-
}
|
|
510
|
-
_setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
|
|
511
|
-
// Use default names if not provided: if this is a top-level run, we mark it as a trace, otherwise as a span.
|
|
512
|
-
const defaultName = parentRunId ? 'span' : 'trace';
|
|
513
|
-
const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
|
|
514
|
-
this.runs[runId] = {
|
|
515
|
-
name: runName,
|
|
516
|
-
input,
|
|
517
|
-
startTime: Date.now()
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
_setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
|
|
521
|
-
const runNameFound = this._getLangchainRunName(serialized, {
|
|
522
|
-
extraParams,
|
|
523
|
-
runName
|
|
524
|
-
}) || 'generation';
|
|
525
|
-
const generation = {
|
|
526
|
-
name: runNameFound,
|
|
527
|
-
input: sanitizeLangChain(messages, this.client),
|
|
528
|
-
startTime: Date.now()
|
|
529
|
-
};
|
|
530
|
-
if (extraParams) {
|
|
531
|
-
generation.modelParams = getModelParams(extraParams.invocation_params);
|
|
532
|
-
if (extraParams.invocation_params && extraParams.invocation_params.tools) {
|
|
533
|
-
generation.tools = extraParams.invocation_params.tools;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
if (metadata) {
|
|
537
|
-
if (metadata.ls_model_name) {
|
|
538
|
-
generation.model = metadata.ls_model_name;
|
|
539
|
-
}
|
|
540
|
-
if (metadata.ls_provider) {
|
|
541
|
-
generation.provider = metadata.ls_provider;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (serialized && 'kwargs' in serialized && serialized.kwargs.openai_api_base) {
|
|
545
|
-
generation.baseUrl = serialized.kwargs.openai_api_base;
|
|
546
|
-
}
|
|
547
|
-
this.runs[runId] = generation;
|
|
548
|
-
}
|
|
549
|
-
_popRunMetadata(runId) {
|
|
550
|
-
const endTime = Date.now();
|
|
551
|
-
const run = this.runs[runId];
|
|
552
|
-
if (!run) {
|
|
553
|
-
console.warn(`No run metadata found for run ${runId}`);
|
|
554
|
-
return undefined;
|
|
555
|
-
}
|
|
556
|
-
run.endTime = endTime;
|
|
557
|
-
delete this.runs[runId];
|
|
558
|
-
return run;
|
|
559
|
-
}
|
|
560
|
-
_getTraceId(runId) {
|
|
561
|
-
return this.traceId ? String(this.traceId) : this._findRootRun(runId);
|
|
562
|
-
}
|
|
563
|
-
_getParentRunId(traceId, _runId, parentRunId) {
|
|
564
|
-
// Replace the parent-run if not found in our stored parent tree.
|
|
565
|
-
if (parentRunId && !this.parentTree[parentRunId]) {
|
|
566
|
-
return traceId;
|
|
567
|
-
}
|
|
568
|
-
return parentRunId;
|
|
569
|
-
}
|
|
570
|
-
_safeCapture(message) {
|
|
571
|
-
try {
|
|
572
|
-
captureAiEvent(this.client, message);
|
|
573
|
-
} catch {
|
|
574
|
-
// Telemetry delivery must never affect the LangChain callback lifecycle.
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
_popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
|
|
578
|
-
const traceId = this._getTraceId(runId);
|
|
579
|
-
const isSpan = Boolean(parentRunId || this.parentTree[runId]);
|
|
580
|
-
this._popParentOfRun(runId);
|
|
581
|
-
const run = this._popRunMetadata(runId);
|
|
582
|
-
if (!run) {
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
if ('modelParams' in run) {
|
|
586
|
-
console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
590
|
-
this._captureTraceOrSpan(traceId, runId, run, outputs, isSpan, actualParentRunId);
|
|
591
|
-
}
|
|
592
|
-
_captureTraceOrSpan(traceId, runId, run, outputs, isSpan, parentRunId) {
|
|
593
|
-
const eventName = isSpan ? '$ai_span' : '$ai_trace';
|
|
594
|
-
const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
|
|
595
|
-
const eventProperties = {
|
|
596
|
-
$ai_lib: 'posthog-ai',
|
|
597
|
-
$ai_lib_version: version,
|
|
598
|
-
$ai_trace_id: traceId,
|
|
599
|
-
$ai_input_state: withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(run.input, this.client)),
|
|
600
|
-
$ai_latency: latency,
|
|
601
|
-
$ai_span_name: run.name,
|
|
602
|
-
$ai_span_id: runId,
|
|
603
|
-
$ai_framework: 'langchain'
|
|
604
|
-
};
|
|
605
|
-
if (parentRunId) {
|
|
606
|
-
eventProperties['$ai_parent_id'] = parentRunId;
|
|
607
|
-
}
|
|
608
|
-
Object.assign(eventProperties, this.properties);
|
|
609
|
-
if (!this.distinctId) {
|
|
610
|
-
eventProperties['$process_person_profile'] = false;
|
|
611
|
-
}
|
|
612
|
-
if (outputs instanceof Error) {
|
|
613
|
-
if (isLangGraphControlFlow(outputs)) {
|
|
614
|
-
// GraphInterrupt carries the pending interrupts (e.g. the question posed to a human).
|
|
615
|
-
// Surface them under the same `__interrupt__` key LangGraph hands back to the caller,
|
|
616
|
-
// so an interrupted span stays distinguishable from a node that returned nothing.
|
|
617
|
-
const interrupts = outputs.interrupts;
|
|
618
|
-
if (interrupts !== undefined) {
|
|
619
|
-
eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain({
|
|
620
|
-
__interrupt__: interrupts
|
|
621
|
-
}, this.client));
|
|
622
|
-
}
|
|
623
|
-
} else {
|
|
624
|
-
eventProperties['$ai_error'] = stringifyError(outputs);
|
|
625
|
-
eventProperties['$ai_is_error'] = true;
|
|
626
|
-
}
|
|
627
|
-
} else if (outputs !== undefined) {
|
|
628
|
-
eventProperties['$ai_output_state'] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(outputs, this.client));
|
|
629
|
-
}
|
|
630
|
-
this._safeCapture({
|
|
631
|
-
distinctId: this.distinctId ? this.distinctId.toString() : runId,
|
|
632
|
-
event: eventName,
|
|
633
|
-
properties: eventProperties,
|
|
634
|
-
groups: this.groups
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
_popRunAndCaptureGeneration(runId, parentRunId, response) {
|
|
638
|
-
const traceId = this._getTraceId(runId);
|
|
639
|
-
this._popParentOfRun(runId);
|
|
640
|
-
const run = this._popRunMetadata(runId);
|
|
641
|
-
if (!run || typeof run !== 'object' || !('modelParams' in run)) {
|
|
642
|
-
console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
646
|
-
this._captureGeneration(traceId, runId, run, response, actualParentRunId);
|
|
647
|
-
}
|
|
648
|
-
_captureGeneration(traceId, runId, run, output, parentRunId) {
|
|
649
|
-
const latency = run.endTime ? (run.endTime - run.startTime) / 1000 : 0;
|
|
650
|
-
warnIfPostHogAiGateway(run.baseUrl);
|
|
651
|
-
const eventProperties = {
|
|
652
|
-
$ai_lib: 'posthog-ai',
|
|
653
|
-
$ai_lib_version: version,
|
|
654
|
-
$ai_trace_id: traceId,
|
|
655
|
-
$ai_span_id: runId,
|
|
656
|
-
$ai_span_name: run.name,
|
|
657
|
-
$ai_provider: run.provider,
|
|
658
|
-
$ai_model: run.model,
|
|
659
|
-
$ai_model_parameters: run.modelParams,
|
|
660
|
-
$ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
|
|
661
|
-
$ai_http_status: 200,
|
|
662
|
-
$ai_latency: latency,
|
|
663
|
-
$ai_base_url: run.baseUrl,
|
|
664
|
-
$ai_framework: 'langchain'
|
|
665
|
-
};
|
|
666
|
-
if (parentRunId) {
|
|
667
|
-
eventProperties['$ai_parent_id'] = parentRunId;
|
|
668
|
-
}
|
|
669
|
-
if (run.tools) {
|
|
670
|
-
eventProperties['$ai_tools'] = run.tools;
|
|
671
|
-
}
|
|
672
|
-
if (output instanceof Error) {
|
|
673
|
-
eventProperties['$ai_http_status'] = output.status || 500;
|
|
674
|
-
eventProperties['$ai_error'] = stringifyError(output);
|
|
675
|
-
eventProperties['$ai_is_error'] = true;
|
|
676
|
-
} else {
|
|
677
|
-
// Handle token usage
|
|
678
|
-
const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
|
|
679
|
-
eventProperties['$ai_input_tokens'] = inputTokens;
|
|
680
|
-
eventProperties['$ai_output_tokens'] = outputTokens;
|
|
681
|
-
|
|
682
|
-
// Add additional token data to properties
|
|
683
|
-
if (additionalTokenData.cacheReadInputTokens) {
|
|
684
|
-
eventProperties['$ai_cache_read_input_tokens'] = additionalTokenData.cacheReadInputTokens;
|
|
685
|
-
}
|
|
686
|
-
if (additionalTokenData.cacheWriteInputTokens) {
|
|
687
|
-
eventProperties['$ai_cache_creation_input_tokens'] = additionalTokenData.cacheWriteInputTokens;
|
|
688
|
-
}
|
|
689
|
-
if (additionalTokenData.cacheWrite5mInputTokens !== undefined && additionalTokenData.cacheWrite1hInputTokens !== undefined) {
|
|
690
|
-
eventProperties['$ai_cache_creation_5m_input_tokens'] = additionalTokenData.cacheWrite5mInputTokens;
|
|
691
|
-
eventProperties['$ai_cache_creation_1h_input_tokens'] = additionalTokenData.cacheWrite1hInputTokens;
|
|
692
|
-
}
|
|
693
|
-
if (additionalTokenData.reasoningTokens) {
|
|
694
|
-
eventProperties['$ai_reasoning_tokens'] = additionalTokenData.reasoningTokens;
|
|
695
|
-
}
|
|
696
|
-
if (additionalTokenData.webSearchCount !== undefined) {
|
|
697
|
-
eventProperties['$ai_web_search_count'] = additionalTokenData.webSearchCount;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Extract stop reason from generation info
|
|
701
|
-
const stopReason = this._extractStopReason(output);
|
|
702
|
-
if (stopReason) {
|
|
703
|
-
eventProperties['$ai_stop_reason'] = stopReason;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// Handle generations/completions
|
|
707
|
-
let completions;
|
|
708
|
-
if (output.generations && Array.isArray(output.generations)) {
|
|
709
|
-
const lastGeneration = output.generations[output.generations.length - 1];
|
|
710
|
-
if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
|
|
711
|
-
// Check if this is a ChatGeneration by looking at the first item
|
|
712
|
-
const isChatGeneration = 'message' in lastGeneration[0] && lastGeneration[0].message;
|
|
713
|
-
if (isChatGeneration) {
|
|
714
|
-
// For ChatGeneration, convert messages to dict format
|
|
715
|
-
completions = lastGeneration.map(gen => {
|
|
716
|
-
return this._convertMessageToDict(gen.message);
|
|
717
|
-
});
|
|
718
|
-
} else {
|
|
719
|
-
// For non-ChatGeneration, extract raw response
|
|
720
|
-
completions = lastGeneration.map(gen => {
|
|
721
|
-
return this._extractRawResponse(gen);
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (completions) {
|
|
727
|
-
eventProperties['$ai_output_choices'] = withPrivacyMode(this.client, this.privacyMode, completions);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
Object.assign(eventProperties, this.properties);
|
|
731
|
-
if (!this.distinctId) {
|
|
732
|
-
eventProperties['$process_person_profile'] = false;
|
|
733
|
-
}
|
|
734
|
-
this._safeCapture({
|
|
735
|
-
distinctId: this.distinctId ? this.distinctId.toString() : traceId,
|
|
736
|
-
event: '$ai_generation',
|
|
737
|
-
properties: eventProperties,
|
|
738
|
-
groups: this.groups
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
_logDebugEvent(eventName, runId, parentRunId, extra) {
|
|
742
|
-
if (this.debug) {
|
|
743
|
-
console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
_getLangchainRunName(serialized, ...args) {
|
|
747
|
-
if (args && args.length > 0) {
|
|
748
|
-
for (const arg of args) {
|
|
749
|
-
// LangChain hands runName through as a bare string, not wrapped in an object
|
|
750
|
-
if (typeof arg === 'string' && arg) {
|
|
751
|
-
return arg;
|
|
752
|
-
}
|
|
753
|
-
if (arg && typeof arg === 'object') {
|
|
754
|
-
if (arg.name) {
|
|
755
|
-
return arg.name;
|
|
756
|
-
}
|
|
757
|
-
if (arg.runName) {
|
|
758
|
-
return arg.runName;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
if (serialized && serialized.name) {
|
|
764
|
-
return serialized.name;
|
|
765
|
-
}
|
|
766
|
-
if (serialized && serialized.id) {
|
|
767
|
-
return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
|
|
768
|
-
}
|
|
769
|
-
return undefined;
|
|
770
|
-
}
|
|
771
|
-
_convertLcToolCallsToOai(toolCalls) {
|
|
772
|
-
return toolCalls.map(toolCall => ({
|
|
773
|
-
type: 'function',
|
|
774
|
-
id: toolCall.id,
|
|
775
|
-
function: {
|
|
776
|
-
name: toolCall.name,
|
|
777
|
-
arguments: JSON.stringify(toolCall.args)
|
|
778
|
-
}
|
|
779
|
-
}));
|
|
780
|
-
}
|
|
781
|
-
_extractRawResponse(generation) {
|
|
782
|
-
// Extract the response from the last response of the LLM call
|
|
783
|
-
// We return the text of the response if not empty
|
|
784
|
-
if (generation.text != null && generation.text.trim() !== '') {
|
|
785
|
-
return generation.text.trim();
|
|
786
|
-
} else if (generation.message) {
|
|
787
|
-
// Additional kwargs contains the response in case of tool usage
|
|
788
|
-
return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
|
|
789
|
-
} else {
|
|
790
|
-
// Not tool usage, some LLM responses can be simply empty
|
|
791
|
-
return '';
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
_convertMessageToDict(message) {
|
|
795
|
-
let messageDict = {};
|
|
796
|
-
const messageType = message.getType();
|
|
797
|
-
switch (messageType) {
|
|
798
|
-
case 'human':
|
|
799
|
-
messageDict = {
|
|
800
|
-
role: 'user',
|
|
801
|
-
content: message.content
|
|
802
|
-
};
|
|
803
|
-
break;
|
|
804
|
-
case 'ai':
|
|
805
|
-
messageDict = {
|
|
806
|
-
role: 'assistant',
|
|
807
|
-
content: message.content
|
|
808
|
-
};
|
|
809
|
-
if (message.tool_calls) {
|
|
810
|
-
messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
|
|
811
|
-
}
|
|
812
|
-
break;
|
|
813
|
-
case 'system':
|
|
814
|
-
messageDict = {
|
|
815
|
-
role: 'system',
|
|
816
|
-
content: message.content
|
|
817
|
-
};
|
|
818
|
-
break;
|
|
819
|
-
case 'tool':
|
|
820
|
-
messageDict = {
|
|
821
|
-
role: 'tool',
|
|
822
|
-
content: message.content
|
|
823
|
-
};
|
|
824
|
-
break;
|
|
825
|
-
case 'function':
|
|
826
|
-
messageDict = {
|
|
827
|
-
role: 'function',
|
|
828
|
-
content: message.content
|
|
829
|
-
};
|
|
830
|
-
break;
|
|
831
|
-
default:
|
|
832
|
-
messageDict = {
|
|
833
|
-
role: messageType,
|
|
834
|
-
content: toContentString(message.content)
|
|
835
|
-
};
|
|
836
|
-
break;
|
|
837
|
-
}
|
|
838
|
-
if (message.additional_kwargs) {
|
|
839
|
-
messageDict = {
|
|
840
|
-
...messageDict,
|
|
841
|
-
...message.additional_kwargs
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Sanitize the message content to redact base64 images
|
|
846
|
-
return sanitizeLangChain(messageDict, this.client);
|
|
847
|
-
}
|
|
848
|
-
_extractStopReason(output) {
|
|
849
|
-
if (!output.generations || !Array.isArray(output.generations)) {
|
|
850
|
-
return undefined;
|
|
851
|
-
}
|
|
852
|
-
const lastGeneration = output.generations[output.generations.length - 1];
|
|
853
|
-
if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) {
|
|
854
|
-
return undefined;
|
|
855
|
-
}
|
|
856
|
-
const gen = lastGeneration[0];
|
|
857
|
-
const messageResponseMetadata = gen.message?.response_metadata;
|
|
858
|
-
const generationResponseMetadata = gen.generationInfo?.response_metadata;
|
|
859
|
-
const stopReason = messageResponseMetadata?.finish_reason || messageResponseMetadata?.stop_reason || gen.generationInfo?.finish_reason || generationResponseMetadata?.stop_reason || generationResponseMetadata?.finish_reason || gen.generationInfo?.stop_reason ||
|
|
860
|
-
// The Responses API reports no finish_reason. An early stop is named by
|
|
861
|
-
// `incomplete_details.reason`, and `status` covers the rest, matching the
|
|
862
|
-
// native OpenAI Responses wrapper.
|
|
863
|
-
messageResponseMetadata?.incomplete_details?.reason || generationResponseMetadata?.incomplete_details?.reason || messageResponseMetadata?.status || generationResponseMetadata?.status;
|
|
864
|
-
return stopReason != null ? String(stopReason) : undefined;
|
|
865
|
-
}
|
|
866
|
-
_extractCacheCreationTtlBreakdown(cacheCreation, aggregateValues) {
|
|
867
|
-
if (!isObject(cacheCreation)) {
|
|
868
|
-
return undefined;
|
|
869
|
-
}
|
|
870
|
-
const {
|
|
871
|
-
ephemeral_5m_input_tokens: cache5m,
|
|
872
|
-
ephemeral_1h_input_tokens: cache1h
|
|
873
|
-
} = cacheCreation;
|
|
874
|
-
const providedValues = [cache5m, cache1h].filter(value => value != null);
|
|
875
|
-
if (providedValues.length === 0 || !providedValues.every(value => typeof value === 'number' && Number.isFinite(value) && value >= 0)) {
|
|
876
|
-
return undefined;
|
|
877
|
-
}
|
|
878
|
-
const breakdown = [typeof cache5m === 'number' ? cache5m : 0, typeof cache1h === 'number' ? cache1h : 0];
|
|
879
|
-
const total = breakdown[0] + breakdown[1];
|
|
880
|
-
const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
|
|
881
|
-
return total > 0 && !validAggregates.some(aggregate => aggregate !== total) ? breakdown : undefined;
|
|
882
|
-
}
|
|
883
|
-
_extractBedrockCacheCreationTtlBreakdown(cacheDetails, aggregateValues) {
|
|
884
|
-
if (!Array.isArray(cacheDetails)) {
|
|
885
|
-
return undefined;
|
|
886
|
-
}
|
|
887
|
-
let cache5m = 0;
|
|
888
|
-
let cache1h = 0;
|
|
889
|
-
for (const detail of cacheDetails) {
|
|
890
|
-
if (!isObject(detail)) {
|
|
891
|
-
continue;
|
|
892
|
-
}
|
|
893
|
-
const ttl = typeof detail.ttl === 'string' ? detail.ttl.toLowerCase() : undefined;
|
|
894
|
-
const inputTokens = detail.inputTokens;
|
|
895
|
-
if (ttl !== '5m' && ttl !== 't5m' && ttl !== '1h' && ttl !== 't1h' || typeof inputTokens !== 'number' || !Number.isFinite(inputTokens) || inputTokens < 0) {
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
if (ttl === '5m' || ttl === 't5m') {
|
|
899
|
-
cache5m += inputTokens;
|
|
900
|
-
} else {
|
|
901
|
-
cache1h += inputTokens;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
const total = cache5m + cache1h;
|
|
905
|
-
const validAggregates = aggregateValues.filter(value => typeof value === 'number' && Number.isFinite(value) && value >= 0);
|
|
906
|
-
if (total === 0 || validAggregates.some(aggregate => aggregate !== total)) {
|
|
907
|
-
return undefined;
|
|
908
|
-
}
|
|
909
|
-
return [cache5m, cache1h];
|
|
910
|
-
}
|
|
911
|
-
_parseUsageModel(usage, provider, model, inputIncludesCacheTokens = true, rawUsage) {
|
|
912
|
-
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']];
|
|
913
|
-
const parsedUsage = conversionList.reduce((acc, [modelKey, typeKey]) => {
|
|
914
|
-
const value = usage[modelKey];
|
|
915
|
-
if (value != null) {
|
|
916
|
-
const finalCount = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
|
|
917
|
-
acc[typeKey] = finalCount;
|
|
918
|
-
}
|
|
919
|
-
return acc;
|
|
920
|
-
}, {
|
|
921
|
-
input: 0,
|
|
922
|
-
output: 0
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
// Extract additional token details like cached tokens and reasoning tokens
|
|
926
|
-
const additionalTokenData = {};
|
|
927
|
-
|
|
928
|
-
// Check for cached tokens in various formats
|
|
929
|
-
if (usage.prompt_tokens_details?.cached_tokens != null) {
|
|
930
|
-
additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
|
|
931
|
-
} else if (usage.input_token_details?.cache_read != null) {
|
|
932
|
-
additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
|
|
933
|
-
} else if (usage.cachedPromptTokens != null) {
|
|
934
|
-
additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
|
|
935
|
-
} else if (usage.cache_read_input_tokens != null) {
|
|
936
|
-
additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Check for cache write/creation tokens in various formats
|
|
940
|
-
if (usage.cache_creation_input_tokens != null) {
|
|
941
|
-
additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
|
|
942
|
-
} else if (usage.input_token_details?.cache_creation != null) {
|
|
943
|
-
additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
|
|
944
|
-
}
|
|
945
|
-
const directCacheCreationAggregates = [usage.cache_creation_input_tokens, usage.input_token_details?.cache_creation, usage.cacheWriteInputTokens, rawUsage?.cache_creation_input_tokens, rawUsage?.input_token_details?.cache_creation, rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens];
|
|
946
|
-
const cacheCreationTtl = this._extractCacheCreationTtlBreakdown(usage.cache_creation, directCacheCreationAggregates) ?? this._extractCacheCreationTtlBreakdown(rawUsage?.cache_creation, directCacheCreationAggregates) ?? this._extractBedrockCacheCreationTtlBreakdown(usage.cacheDetails, [usage.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]) ?? this._extractBedrockCacheCreationTtlBreakdown(rawUsage?.cacheDetails, [rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]);
|
|
947
|
-
if (cacheCreationTtl) {
|
|
948
|
-
const [cacheWrite5mInputTokens, cacheWrite1hInputTokens] = cacheCreationTtl;
|
|
949
|
-
additionalTokenData.cacheWrite5mInputTokens = cacheWrite5mInputTokens;
|
|
950
|
-
additionalTokenData.cacheWrite1hInputTokens = cacheWrite1hInputTokens;
|
|
951
|
-
additionalTokenData.cacheWriteInputTokens = cacheWrite5mInputTokens + cacheWrite1hInputTokens;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Check for reasoning tokens in various formats
|
|
955
|
-
if (usage.completion_tokens_details?.reasoning_tokens != null) {
|
|
956
|
-
additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
|
|
957
|
-
} else if (usage.output_token_details?.reasoning != null) {
|
|
958
|
-
additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
|
|
959
|
-
} else if (usage.reasoningTokens != null) {
|
|
960
|
-
additionalTokenData.reasoningTokens = usage.reasoningTokens;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Extract web search counts from various provider formats
|
|
964
|
-
let webSearchCount;
|
|
965
|
-
|
|
966
|
-
// Priority 1: Exact Count
|
|
967
|
-
// Check Anthropic format (server_tool_use.web_search_requests)
|
|
968
|
-
if (usage.server_tool_use?.web_search_requests !== undefined) {
|
|
969
|
-
webSearchCount = usage.server_tool_use.web_search_requests;
|
|
970
|
-
}
|
|
971
|
-
// Priority 2: Binary Detection (1 or 0)
|
|
972
|
-
// Check for citations array (Perplexity)
|
|
973
|
-
else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) {
|
|
974
|
-
webSearchCount = 1;
|
|
975
|
-
}
|
|
976
|
-
// Check for search_results array (Perplexity via OpenRouter)
|
|
977
|
-
else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) {
|
|
978
|
-
webSearchCount = 1;
|
|
979
|
-
}
|
|
980
|
-
// Check for search_context_size (Perplexity via OpenRouter)
|
|
981
|
-
else if (usage.search_context_size) {
|
|
982
|
-
webSearchCount = 1;
|
|
983
|
-
}
|
|
984
|
-
// Check for annotations with url_citation type
|
|
985
|
-
else if (usage.annotations && Array.isArray(usage.annotations)) {
|
|
986
|
-
const hasUrlCitation = usage.annotations.some(ann => {
|
|
987
|
-
return ann && typeof ann === 'object' && 'type' in ann && ann.type === 'url_citation';
|
|
988
|
-
});
|
|
989
|
-
if (hasUrlCitation) {
|
|
990
|
-
webSearchCount = 1;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
// Check Gemini format (grounding metadata - binary 0 or 1)
|
|
994
|
-
else if (usage.grounding_metadata?.grounding_support !== undefined || usage.grounding_metadata?.web_search_queries !== undefined) {
|
|
995
|
-
webSearchCount = 1;
|
|
996
|
-
}
|
|
997
|
-
if (webSearchCount !== undefined) {
|
|
998
|
-
additionalTokenData.webSearchCount = webSearchCount;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// For Anthropic providers, LangChain reports input_tokens as the sum of all input tokens.
|
|
1002
|
-
// Our cost calculation expects them to be separate for Anthropic, so we subtract cache tokens.
|
|
1003
|
-
// Both cache_read and cache_write tokens should be subtracted since Anthropic's raw API
|
|
1004
|
-
// reports input_tokens as tokens NOT read from or used to create a cache.
|
|
1005
|
-
// For other providers (OpenAI, etc.), input_tokens already excludes cache tokens as expected.
|
|
1006
|
-
// Match logic consistent with plugin-server: exact match on provider OR substring match on model
|
|
1007
|
-
let isAnthropic = false;
|
|
1008
|
-
if (provider && provider.toLowerCase() === 'anthropic') {
|
|
1009
|
-
isAnthropic = true;
|
|
1010
|
-
} else if (model && model.toLowerCase().includes('anthropic')) {
|
|
1011
|
-
isAnthropic = true;
|
|
1012
|
-
}
|
|
1013
|
-
if (isAnthropic && inputIncludesCacheTokens && parsedUsage.input) {
|
|
1014
|
-
const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
|
|
1015
|
-
if (cacheTokens > 0) {
|
|
1016
|
-
parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
return [parsedUsage.input, parsedUsage.output, additionalTokenData];
|
|
1020
|
-
}
|
|
1021
|
-
parseUsage(response, provider, model) {
|
|
1022
|
-
const isNonEmptyUsage = usage => isObject(usage) && Object.keys(usage).length > 0;
|
|
1023
|
-
const firstNonEmptyUsage = (...candidates) => candidates.find(isNonEmptyUsage);
|
|
1024
|
-
let normalizedGenerationUsage;
|
|
1025
|
-
let rawGenerationUsage;
|
|
1026
|
-
let fallbackGenerationUsage;
|
|
1027
|
-
for (const generation of response.generations ?? []) {
|
|
1028
|
-
for (const genChunk of generation) {
|
|
1029
|
-
const generationInfo = genChunk.generationInfo ?? {};
|
|
1030
|
-
const message = 'message' in genChunk ? genChunk.message : undefined;
|
|
1031
|
-
const messageUsage = message && typeof message === 'object' && 'usage_metadata' in message ? message.usage_metadata : undefined;
|
|
1032
|
-
normalizedGenerationUsage = firstNonEmptyUsage(normalizedGenerationUsage, messageUsage, generationInfo.usage_metadata);
|
|
1033
|
-
const messageResponseMetadata = message && typeof message === 'object' && 'response_metadata' in message && isObject(message.response_metadata) ? message.response_metadata : undefined;
|
|
1034
|
-
const generationResponseMetadata = isObject(generationInfo.response_metadata) ? generationInfo.response_metadata : undefined;
|
|
1035
|
-
const messageStreamMetadata = isObject(messageResponseMetadata?.metadata) ? messageResponseMetadata.metadata : undefined;
|
|
1036
|
-
const generationStreamMetadata = isObject(generationResponseMetadata?.metadata) ? generationResponseMetadata.metadata : undefined;
|
|
1037
|
-
rawGenerationUsage = firstNonEmptyUsage(rawGenerationUsage, messageResponseMetadata?.usage, messageStreamMetadata?.usage, generationResponseMetadata?.usage, generationStreamMetadata?.usage);
|
|
1038
|
-
fallbackGenerationUsage = firstNonEmptyUsage(fallbackGenerationUsage, messageResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationResponseMetadata?.['amazon-bedrock-invocationMetrics'], generationInfo.usage_metadata);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
const isAnthropic = provider?.toLowerCase() === 'anthropic' || model?.toLowerCase().includes('anthropic') === true;
|
|
1042
|
-
if (isAnthropic && isNonEmptyUsage(normalizedGenerationUsage)) {
|
|
1043
|
-
return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
|
|
1044
|
-
}
|
|
1045
|
-
const llmUsageKeys = ['token_usage', 'usage', 'tokenUsage'];
|
|
1046
|
-
if (response.llmOutput != null) {
|
|
1047
|
-
for (const key of llmUsageKeys) {
|
|
1048
|
-
const llmUsage = response.llmOutput[key];
|
|
1049
|
-
if (!isNonEmptyUsage(llmUsage)) {
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
return this._parseUsageModel(llmUsage, provider, model, key !== 'usage', llmUsage);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
if (isNonEmptyUsage(normalizedGenerationUsage)) {
|
|
1056
|
-
return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
|
|
1057
|
-
}
|
|
1058
|
-
if (isNonEmptyUsage(rawGenerationUsage)) {
|
|
1059
|
-
return this._parseUsageModel(rawGenerationUsage, provider, model, false, rawGenerationUsage);
|
|
1060
|
-
}
|
|
1061
|
-
if (isNonEmptyUsage(fallbackGenerationUsage)) {
|
|
1062
|
-
return this._parseUsageModel(fallbackGenerationUsage, provider, model);
|
|
1063
|
-
}
|
|
1064
|
-
return [0, 0, {}];
|
|
1065
|
-
}
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/openai/utils.ts
|
|
331
|
+
const TERMINAL_RESPONSE_STATUSES = /* @__PURE__ */ new Set([
|
|
332
|
+
"completed",
|
|
333
|
+
"failed",
|
|
334
|
+
"cancelled",
|
|
335
|
+
"incomplete"
|
|
336
|
+
]);
|
|
337
|
+
/**
|
|
338
|
+
* Checks whether a Responses API response has reached a status that should
|
|
339
|
+
* produce a final `$ai_generation` event.
|
|
340
|
+
*/
|
|
341
|
+
function isTerminalResponse(response) {
|
|
342
|
+
return !!response?.status && TERMINAL_RESPONSE_STATUSES.has(response.status);
|
|
1066
343
|
}
|
|
1067
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Maps a Responses API outcome to a `$ai_stop_reason`. An incomplete run is
|
|
346
|
+
* named by what cut it short (`incomplete_details.reason`, e.g.
|
|
347
|
+
* `max_output_tokens`); the other terminal statuses stand for themselves.
|
|
348
|
+
* Non-terminal lifecycle statuses (`queued`, `in_progress`) are not stop
|
|
349
|
+
* reasons, so they yield undefined.
|
|
350
|
+
*/
|
|
351
|
+
function responsesStopReason(response) {
|
|
352
|
+
if (!response || !isTerminalResponse(response)) return;
|
|
353
|
+
if (response.status === "incomplete" && response.incomplete_details?.reason) return response.incomplete_details.reason;
|
|
354
|
+
return response.status ?? void 0;
|
|
355
|
+
}
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/langchain/callbacks.ts
|
|
358
|
+
const isLangGraphControlFlow = (error) => error.is_bubble_up === true;
|
|
359
|
+
var LangChainCallbackHandler = class extends BaseCallbackHandler {
|
|
360
|
+
constructor(options) {
|
|
361
|
+
if (!options.client) throw new Error("PostHog client is required");
|
|
362
|
+
super();
|
|
363
|
+
this.name = "PosthogCallbackHandler";
|
|
364
|
+
this.runs = {};
|
|
365
|
+
this.parentTree = {};
|
|
366
|
+
this.client = options.client;
|
|
367
|
+
this.distinctId = options.distinctId;
|
|
368
|
+
this.traceId = options.traceId;
|
|
369
|
+
this.properties = options.properties || {};
|
|
370
|
+
this.privacyMode = options.privacyMode || false;
|
|
371
|
+
this.groups = options.groups || {};
|
|
372
|
+
this.debug = options.debug || false;
|
|
373
|
+
}
|
|
374
|
+
handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, runName, extra) {
|
|
375
|
+
this._logDebugEvent("on_chain_start", runId, parentRunId, {
|
|
376
|
+
inputs,
|
|
377
|
+
tags
|
|
378
|
+
});
|
|
379
|
+
this._setParentOfRun(runId, parentRunId);
|
|
380
|
+
this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName);
|
|
381
|
+
if (typeof extra?.posthogStartTime === "number" && Number.isFinite(extra.posthogStartTime)) this.runs[runId].startTime = extra.posthogStartTime;
|
|
382
|
+
}
|
|
383
|
+
handleChainEnd(outputs, runId, parentRunId, tags, _kwargs) {
|
|
384
|
+
this._logAndPopTraceOrSpan("on_chain_end", runId, parentRunId, {
|
|
385
|
+
outputs,
|
|
386
|
+
tags
|
|
387
|
+
}, outputs);
|
|
388
|
+
}
|
|
389
|
+
handleChainError(error, runId, parentRunId, tags, _kwargs) {
|
|
390
|
+
this._logAndPopTraceOrSpan("on_chain_error", runId, parentRunId, {
|
|
391
|
+
error,
|
|
392
|
+
tags
|
|
393
|
+
}, error);
|
|
394
|
+
}
|
|
395
|
+
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
396
|
+
this._logDebugEvent("on_chat_model_start", runId, parentRunId, {
|
|
397
|
+
messages,
|
|
398
|
+
tags
|
|
399
|
+
});
|
|
400
|
+
this._setParentOfRun(runId, parentRunId);
|
|
401
|
+
const input = messages.flat().map((m) => this._convertMessageToDict(m));
|
|
402
|
+
this._setLLMMetadata(serialized, runId, input, metadata, extraParams, runName);
|
|
403
|
+
}
|
|
404
|
+
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata, runName) {
|
|
405
|
+
this._logDebugEvent("on_llm_start", runId, parentRunId, {
|
|
406
|
+
prompts,
|
|
407
|
+
tags
|
|
408
|
+
});
|
|
409
|
+
this._setParentOfRun(runId, parentRunId);
|
|
410
|
+
this._setLLMMetadata(serialized, runId, prompts, metadata, extraParams, runName);
|
|
411
|
+
}
|
|
412
|
+
handleLLMEnd(output, runId, parentRunId, tags, _extraParams) {
|
|
413
|
+
this._logAndPopGeneration("on_llm_end", runId, parentRunId, {
|
|
414
|
+
output,
|
|
415
|
+
tags
|
|
416
|
+
}, output);
|
|
417
|
+
}
|
|
418
|
+
handleLLMError(err, runId, parentRunId, tags, _extraParams) {
|
|
419
|
+
this._logAndPopGeneration("on_llm_error", runId, parentRunId, {
|
|
420
|
+
err,
|
|
421
|
+
tags
|
|
422
|
+
}, err);
|
|
423
|
+
}
|
|
424
|
+
handleToolStart(tool, input, runId, parentRunId, tags, metadata, runName) {
|
|
425
|
+
this._logAndSetTraceOrSpan("on_tool_start", tool, input, runId, parentRunId, {
|
|
426
|
+
input,
|
|
427
|
+
tags
|
|
428
|
+
}, tags, metadata, runName);
|
|
429
|
+
}
|
|
430
|
+
handleToolEnd(output, runId, parentRunId, tags) {
|
|
431
|
+
this._logAndPopTraceOrSpan("on_tool_end", runId, parentRunId, {
|
|
432
|
+
output,
|
|
433
|
+
tags
|
|
434
|
+
}, output);
|
|
435
|
+
}
|
|
436
|
+
handleToolError(err, runId, parentRunId, tags) {
|
|
437
|
+
this._logAndPopTraceOrSpan("on_tool_error", runId, parentRunId, {
|
|
438
|
+
err,
|
|
439
|
+
tags
|
|
440
|
+
}, err);
|
|
441
|
+
}
|
|
442
|
+
handleRetrieverStart(retriever, query, runId, parentRunId, tags, metadata, name) {
|
|
443
|
+
this._logAndSetTraceOrSpan("on_retriever_start", retriever, query, runId, parentRunId, {
|
|
444
|
+
query,
|
|
445
|
+
tags
|
|
446
|
+
}, tags, metadata, name);
|
|
447
|
+
}
|
|
448
|
+
handleRetrieverEnd(documents, runId, parentRunId, tags) {
|
|
449
|
+
this._logAndPopTraceOrSpan("on_retriever_end", runId, parentRunId, {
|
|
450
|
+
documents,
|
|
451
|
+
tags
|
|
452
|
+
}, documents);
|
|
453
|
+
}
|
|
454
|
+
handleRetrieverError(err, runId, parentRunId, tags) {
|
|
455
|
+
this._logAndPopTraceOrSpan("on_retriever_error", runId, parentRunId, {
|
|
456
|
+
err,
|
|
457
|
+
tags
|
|
458
|
+
}, err);
|
|
459
|
+
}
|
|
460
|
+
handleAgentAction(action, runId, parentRunId, tags) {
|
|
461
|
+
this._logDebugEvent("on_agent_action", runId, parentRunId, {
|
|
462
|
+
action,
|
|
463
|
+
tags
|
|
464
|
+
});
|
|
465
|
+
this._setParentOfRun(runId, parentRunId);
|
|
466
|
+
this._setTraceOrSpanMetadata(null, action, runId, parentRunId);
|
|
467
|
+
}
|
|
468
|
+
handleAgentEnd(action, runId, parentRunId, tags) {
|
|
469
|
+
this._logDebugEvent("on_agent_finish", runId, parentRunId, {
|
|
470
|
+
action,
|
|
471
|
+
tags
|
|
472
|
+
});
|
|
473
|
+
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, action);
|
|
474
|
+
}
|
|
475
|
+
_logAndSetTraceOrSpan(eventName, serialized, input, runId, parentRunId, debugPayload, tags, metadata, runName) {
|
|
476
|
+
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
477
|
+
this._setParentOfRun(runId, parentRunId);
|
|
478
|
+
this._setTraceOrSpanMetadata(serialized, input, runId, parentRunId, metadata, tags, runName);
|
|
479
|
+
}
|
|
480
|
+
_logAndPopTraceOrSpan(eventName, runId, parentRunId, debugPayload, result) {
|
|
481
|
+
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
482
|
+
this._popRunAndCaptureTraceOrSpan(runId, parentRunId, result);
|
|
483
|
+
}
|
|
484
|
+
_logAndPopGeneration(eventName, runId, parentRunId, debugPayload, result) {
|
|
485
|
+
this._logDebugEvent(eventName, runId, parentRunId, debugPayload);
|
|
486
|
+
this._popRunAndCaptureGeneration(runId, parentRunId, result);
|
|
487
|
+
}
|
|
488
|
+
_setParentOfRun(runId, parentRunId) {
|
|
489
|
+
if (parentRunId) this.parentTree[runId] = parentRunId;
|
|
490
|
+
}
|
|
491
|
+
_popParentOfRun(runId) {
|
|
492
|
+
delete this.parentTree[runId];
|
|
493
|
+
}
|
|
494
|
+
_findRootRun(runId) {
|
|
495
|
+
let id = runId;
|
|
496
|
+
while (this.parentTree[id]) id = this.parentTree[id];
|
|
497
|
+
return id;
|
|
498
|
+
}
|
|
499
|
+
_setTraceOrSpanMetadata(serialized, input, runId, parentRunId, ...args) {
|
|
500
|
+
const defaultName = parentRunId ? "span" : "trace";
|
|
501
|
+
const runName = this._getLangchainRunName(serialized, ...args) || defaultName;
|
|
502
|
+
this.runs[runId] = {
|
|
503
|
+
name: runName,
|
|
504
|
+
input,
|
|
505
|
+
startTime: Date.now()
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
_setLLMMetadata(serialized, runId, messages, metadata, extraParams, runName) {
|
|
509
|
+
const generation = {
|
|
510
|
+
name: this._getLangchainRunName(serialized, {
|
|
511
|
+
extraParams,
|
|
512
|
+
runName
|
|
513
|
+
}) || "generation",
|
|
514
|
+
input: sanitizeLangChain(messages, this.client),
|
|
515
|
+
startTime: Date.now()
|
|
516
|
+
};
|
|
517
|
+
if (extraParams) {
|
|
518
|
+
generation.modelParams = getModelParams(extraParams.invocation_params);
|
|
519
|
+
if (extraParams.invocation_params && extraParams.invocation_params.tools) generation.tools = extraParams.invocation_params.tools;
|
|
520
|
+
}
|
|
521
|
+
if (metadata) {
|
|
522
|
+
if (metadata.ls_model_name) generation.model = metadata.ls_model_name;
|
|
523
|
+
if (metadata.ls_provider) generation.provider = metadata.ls_provider;
|
|
524
|
+
}
|
|
525
|
+
if (serialized && "kwargs" in serialized && serialized.kwargs.openai_api_base) generation.baseUrl = serialized.kwargs.openai_api_base;
|
|
526
|
+
this.runs[runId] = generation;
|
|
527
|
+
}
|
|
528
|
+
_popRunMetadata(runId) {
|
|
529
|
+
const endTime = Date.now();
|
|
530
|
+
const run = this.runs[runId];
|
|
531
|
+
if (!run) {
|
|
532
|
+
console.warn(`No run metadata found for run ${runId}`);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
run.endTime = endTime;
|
|
536
|
+
delete this.runs[runId];
|
|
537
|
+
return run;
|
|
538
|
+
}
|
|
539
|
+
_getTraceId(runId) {
|
|
540
|
+
return this.traceId ? String(this.traceId) : this._findRootRun(runId);
|
|
541
|
+
}
|
|
542
|
+
_getParentRunId(traceId, _runId, parentRunId) {
|
|
543
|
+
if (parentRunId && !this.parentTree[parentRunId]) return traceId;
|
|
544
|
+
return parentRunId;
|
|
545
|
+
}
|
|
546
|
+
_safeCapture(message) {
|
|
547
|
+
try {
|
|
548
|
+
captureAiEvent(this.client, message);
|
|
549
|
+
} catch {}
|
|
550
|
+
}
|
|
551
|
+
_popRunAndCaptureTraceOrSpan(runId, parentRunId, outputs) {
|
|
552
|
+
const traceId = this._getTraceId(runId);
|
|
553
|
+
const isSpan = Boolean(parentRunId || this.parentTree[runId]);
|
|
554
|
+
this._popParentOfRun(runId);
|
|
555
|
+
const run = this._popRunMetadata(runId);
|
|
556
|
+
if (!run) return;
|
|
557
|
+
if ("modelParams" in run) {
|
|
558
|
+
console.warn(`Run ${runId} is a generation, but attempted to be captured as a trace/span.`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
562
|
+
this._captureTraceOrSpan(traceId, runId, run, outputs, isSpan, actualParentRunId);
|
|
563
|
+
}
|
|
564
|
+
_captureTraceOrSpan(traceId, runId, run, outputs, isSpan, parentRunId) {
|
|
565
|
+
const eventName = isSpan ? "$ai_span" : "$ai_trace";
|
|
566
|
+
const latency = run.endTime ? (run.endTime - run.startTime) / 1e3 : 0;
|
|
567
|
+
const eventProperties = {
|
|
568
|
+
$ai_lib: "posthog-ai",
|
|
569
|
+
$ai_lib_version: version,
|
|
570
|
+
$ai_trace_id: traceId,
|
|
571
|
+
$ai_input_state: withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(run.input, this.client)),
|
|
572
|
+
$ai_latency: latency,
|
|
573
|
+
$ai_span_name: run.name,
|
|
574
|
+
$ai_span_id: runId,
|
|
575
|
+
$ai_framework: "langchain"
|
|
576
|
+
};
|
|
577
|
+
if (parentRunId) eventProperties["$ai_parent_id"] = parentRunId;
|
|
578
|
+
Object.assign(eventProperties, this.properties);
|
|
579
|
+
if (!this.distinctId) eventProperties["$process_person_profile"] = false;
|
|
580
|
+
if (outputs instanceof Error) {
|
|
581
|
+
if (isLangGraphControlFlow(outputs)) {
|
|
582
|
+
const interrupts = outputs.interrupts;
|
|
583
|
+
if (interrupts !== void 0) eventProperties["$ai_output_state"] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain({ __interrupt__: interrupts }, this.client));
|
|
584
|
+
} else {
|
|
585
|
+
eventProperties["$ai_error"] = stringifyError(outputs);
|
|
586
|
+
eventProperties["$ai_is_error"] = true;
|
|
587
|
+
}
|
|
588
|
+
} else if (outputs !== void 0) eventProperties["$ai_output_state"] = withPrivacyMode(this.client, this.privacyMode, sanitizeLangChain(outputs, this.client));
|
|
589
|
+
this._safeCapture({
|
|
590
|
+
distinctId: this.distinctId ? this.distinctId.toString() : runId,
|
|
591
|
+
event: eventName,
|
|
592
|
+
properties: eventProperties,
|
|
593
|
+
groups: this.groups
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
_popRunAndCaptureGeneration(runId, parentRunId, response) {
|
|
597
|
+
const traceId = this._getTraceId(runId);
|
|
598
|
+
this._popParentOfRun(runId);
|
|
599
|
+
const run = this._popRunMetadata(runId);
|
|
600
|
+
if (!run || typeof run !== "object" || !("modelParams" in run)) {
|
|
601
|
+
console.warn(`Run ${runId} is not a generation, but attempted to be captured as such.`);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
const actualParentRunId = this._getParentRunId(traceId, runId, parentRunId);
|
|
605
|
+
this._captureGeneration(traceId, runId, run, response, actualParentRunId);
|
|
606
|
+
}
|
|
607
|
+
_captureGeneration(traceId, runId, run, output, parentRunId) {
|
|
608
|
+
const latency = run.endTime ? (run.endTime - run.startTime) / 1e3 : 0;
|
|
609
|
+
warnIfPostHogAiGateway(run.baseUrl);
|
|
610
|
+
const eventProperties = {
|
|
611
|
+
$ai_lib: "posthog-ai",
|
|
612
|
+
$ai_lib_version: version,
|
|
613
|
+
$ai_trace_id: traceId,
|
|
614
|
+
$ai_span_id: runId,
|
|
615
|
+
$ai_span_name: run.name,
|
|
616
|
+
$ai_provider: run.provider,
|
|
617
|
+
$ai_model: run.model,
|
|
618
|
+
$ai_model_parameters: run.modelParams,
|
|
619
|
+
$ai_input: withPrivacyMode(this.client, this.privacyMode, run.input),
|
|
620
|
+
$ai_http_status: 200,
|
|
621
|
+
$ai_latency: latency,
|
|
622
|
+
$ai_base_url: run.baseUrl,
|
|
623
|
+
$ai_framework: "langchain"
|
|
624
|
+
};
|
|
625
|
+
if (parentRunId) eventProperties["$ai_parent_id"] = parentRunId;
|
|
626
|
+
if (run.tools) eventProperties["$ai_tools"] = run.tools;
|
|
627
|
+
if (output instanceof Error) {
|
|
628
|
+
eventProperties["$ai_http_status"] = output.status || 500;
|
|
629
|
+
eventProperties["$ai_error"] = stringifyError(output);
|
|
630
|
+
eventProperties["$ai_is_error"] = true;
|
|
631
|
+
} else {
|
|
632
|
+
const [inputTokens, outputTokens, additionalTokenData] = this.parseUsage(output, run.provider, run.model);
|
|
633
|
+
eventProperties["$ai_input_tokens"] = inputTokens;
|
|
634
|
+
eventProperties["$ai_output_tokens"] = outputTokens;
|
|
635
|
+
if (additionalTokenData.cacheReadInputTokens) eventProperties["$ai_cache_read_input_tokens"] = additionalTokenData.cacheReadInputTokens;
|
|
636
|
+
if (additionalTokenData.cacheWriteInputTokens) eventProperties["$ai_cache_creation_input_tokens"] = additionalTokenData.cacheWriteInputTokens;
|
|
637
|
+
if (additionalTokenData.cacheWrite5mInputTokens !== void 0 && additionalTokenData.cacheWrite1hInputTokens !== void 0) {
|
|
638
|
+
eventProperties["$ai_cache_creation_5m_input_tokens"] = additionalTokenData.cacheWrite5mInputTokens;
|
|
639
|
+
eventProperties["$ai_cache_creation_1h_input_tokens"] = additionalTokenData.cacheWrite1hInputTokens;
|
|
640
|
+
}
|
|
641
|
+
if (additionalTokenData.reasoningTokens) eventProperties["$ai_reasoning_tokens"] = additionalTokenData.reasoningTokens;
|
|
642
|
+
if (additionalTokenData.webSearchCount !== void 0) eventProperties["$ai_web_search_count"] = additionalTokenData.webSearchCount;
|
|
643
|
+
const stopReason = this._extractStopReason(output);
|
|
644
|
+
if (stopReason) eventProperties["$ai_stop_reason"] = stopReason;
|
|
645
|
+
let completions;
|
|
646
|
+
if (output.generations && Array.isArray(output.generations)) {
|
|
647
|
+
const lastGeneration = output.generations[output.generations.length - 1];
|
|
648
|
+
if (Array.isArray(lastGeneration) && lastGeneration.length > 0) {
|
|
649
|
+
if ("message" in lastGeneration[0] && lastGeneration[0].message) completions = lastGeneration.map((gen) => {
|
|
650
|
+
return this._convertMessageToDict(gen.message);
|
|
651
|
+
});
|
|
652
|
+
else completions = lastGeneration.map((gen) => {
|
|
653
|
+
return this._extractRawResponse(gen);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (completions) eventProperties["$ai_output_choices"] = withPrivacyMode(this.client, this.privacyMode, completions);
|
|
658
|
+
}
|
|
659
|
+
Object.assign(eventProperties, this.properties);
|
|
660
|
+
if (!this.distinctId) eventProperties["$process_person_profile"] = false;
|
|
661
|
+
this._safeCapture({
|
|
662
|
+
distinctId: this.distinctId ? this.distinctId.toString() : traceId,
|
|
663
|
+
event: "$ai_generation",
|
|
664
|
+
properties: eventProperties,
|
|
665
|
+
groups: this.groups
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
_logDebugEvent(eventName, runId, parentRunId, extra) {
|
|
669
|
+
if (this.debug) console.log(`Event: ${eventName}, runId: ${runId}, parentRunId: ${parentRunId}, extra:`, extra);
|
|
670
|
+
}
|
|
671
|
+
_getLangchainRunName(serialized, ...args) {
|
|
672
|
+
if (args && args.length > 0) for (const arg of args) {
|
|
673
|
+
if (typeof arg === "string" && arg) return arg;
|
|
674
|
+
if (arg && typeof arg === "object") {
|
|
675
|
+
if (arg.name) return arg.name;
|
|
676
|
+
if (arg.runName) return arg.runName;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (serialized && serialized.name) return serialized.name;
|
|
680
|
+
if (serialized && serialized.id) return Array.isArray(serialized.id) ? serialized.id[serialized.id.length - 1] : serialized.id;
|
|
681
|
+
}
|
|
682
|
+
_convertLcToolCallsToOai(toolCalls) {
|
|
683
|
+
return toolCalls.map((toolCall) => ({
|
|
684
|
+
type: "function",
|
|
685
|
+
id: toolCall.id,
|
|
686
|
+
function: {
|
|
687
|
+
name: toolCall.name,
|
|
688
|
+
arguments: JSON.stringify(toolCall.args)
|
|
689
|
+
}
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
_extractRawResponse(generation) {
|
|
693
|
+
if (generation.text != null && generation.text.trim() !== "") return generation.text.trim();
|
|
694
|
+
else if (generation.message) return generation.message.additional_kwargs || generation.message.additionalKwargs || {};
|
|
695
|
+
else return "";
|
|
696
|
+
}
|
|
697
|
+
_convertMessageToDict(message) {
|
|
698
|
+
let messageDict = {};
|
|
699
|
+
const messageType = message.getType();
|
|
700
|
+
switch (messageType) {
|
|
701
|
+
case "human":
|
|
702
|
+
messageDict = {
|
|
703
|
+
role: "user",
|
|
704
|
+
content: message.content
|
|
705
|
+
};
|
|
706
|
+
break;
|
|
707
|
+
case "ai":
|
|
708
|
+
messageDict = {
|
|
709
|
+
role: "assistant",
|
|
710
|
+
content: message.content
|
|
711
|
+
};
|
|
712
|
+
if (message.tool_calls) messageDict.tool_calls = this._convertLcToolCallsToOai(message.tool_calls);
|
|
713
|
+
break;
|
|
714
|
+
case "system":
|
|
715
|
+
messageDict = {
|
|
716
|
+
role: "system",
|
|
717
|
+
content: message.content
|
|
718
|
+
};
|
|
719
|
+
break;
|
|
720
|
+
case "tool":
|
|
721
|
+
messageDict = {
|
|
722
|
+
role: "tool",
|
|
723
|
+
content: message.content
|
|
724
|
+
};
|
|
725
|
+
break;
|
|
726
|
+
case "function":
|
|
727
|
+
messageDict = {
|
|
728
|
+
role: "function",
|
|
729
|
+
content: message.content
|
|
730
|
+
};
|
|
731
|
+
break;
|
|
732
|
+
default: messageDict = {
|
|
733
|
+
role: messageType,
|
|
734
|
+
content: toContentString(message.content)
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
if (message.additional_kwargs) messageDict = {
|
|
738
|
+
...messageDict,
|
|
739
|
+
...message.additional_kwargs
|
|
740
|
+
};
|
|
741
|
+
return sanitizeLangChain(messageDict, this.client);
|
|
742
|
+
}
|
|
743
|
+
_extractStopReason(output) {
|
|
744
|
+
if (!output.generations || !Array.isArray(output.generations)) return;
|
|
745
|
+
const lastGeneration = output.generations[output.generations.length - 1];
|
|
746
|
+
if (!Array.isArray(lastGeneration) || lastGeneration.length === 0) return;
|
|
747
|
+
const gen = lastGeneration[0];
|
|
748
|
+
const messageResponseMetadata = gen.message?.response_metadata;
|
|
749
|
+
const generationResponseMetadata = gen.generationInfo?.response_metadata;
|
|
750
|
+
const stopReason = messageResponseMetadata?.finish_reason || messageResponseMetadata?.stop_reason || gen.generationInfo?.finish_reason || generationResponseMetadata?.stop_reason || generationResponseMetadata?.finish_reason || gen.generationInfo?.stop_reason || responsesStopReason(messageResponseMetadata) || responsesStopReason(generationResponseMetadata);
|
|
751
|
+
return stopReason != null ? String(stopReason) : void 0;
|
|
752
|
+
}
|
|
753
|
+
_extractCacheCreationTtlBreakdown(cacheCreation, aggregateValues) {
|
|
754
|
+
if (!isObject(cacheCreation)) return;
|
|
755
|
+
const { ephemeral_5m_input_tokens: cache5m, ephemeral_1h_input_tokens: cache1h } = cacheCreation;
|
|
756
|
+
const providedValues = [cache5m, cache1h].filter((value) => value != null);
|
|
757
|
+
if (providedValues.length === 0 || !providedValues.every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0)) return;
|
|
758
|
+
const breakdown = [typeof cache5m === "number" ? cache5m : 0, typeof cache1h === "number" ? cache1h : 0];
|
|
759
|
+
const total = breakdown[0] + breakdown[1];
|
|
760
|
+
const validAggregates = aggregateValues.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
761
|
+
return total > 0 && !validAggregates.some((aggregate) => aggregate !== total) ? breakdown : void 0;
|
|
762
|
+
}
|
|
763
|
+
_extractBedrockCacheCreationTtlBreakdown(cacheDetails, aggregateValues) {
|
|
764
|
+
if (!Array.isArray(cacheDetails)) return;
|
|
765
|
+
let cache5m = 0;
|
|
766
|
+
let cache1h = 0;
|
|
767
|
+
for (const detail of cacheDetails) {
|
|
768
|
+
if (!isObject(detail)) continue;
|
|
769
|
+
const ttl = typeof detail.ttl === "string" ? detail.ttl.toLowerCase() : void 0;
|
|
770
|
+
const inputTokens = detail.inputTokens;
|
|
771
|
+
if (ttl !== "5m" && ttl !== "t5m" && ttl !== "1h" && ttl !== "t1h" || typeof inputTokens !== "number" || !Number.isFinite(inputTokens) || inputTokens < 0) continue;
|
|
772
|
+
if (ttl === "5m" || ttl === "t5m") cache5m += inputTokens;
|
|
773
|
+
else cache1h += inputTokens;
|
|
774
|
+
}
|
|
775
|
+
const total = cache5m + cache1h;
|
|
776
|
+
const validAggregates = aggregateValues.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
777
|
+
if (total === 0 || validAggregates.some((aggregate) => aggregate !== total)) return;
|
|
778
|
+
return [cache5m, cache1h];
|
|
779
|
+
}
|
|
780
|
+
_parseUsageModel(usage, provider, model, inputIncludesCacheTokens = true, rawUsage) {
|
|
781
|
+
const parsedUsage = [
|
|
782
|
+
["promptTokens", "input"],
|
|
783
|
+
["completionTokens", "output"],
|
|
784
|
+
["input_tokens", "input"],
|
|
785
|
+
["output_tokens", "output"],
|
|
786
|
+
["prompt_token_count", "input"],
|
|
787
|
+
["candidates_token_count", "output"],
|
|
788
|
+
["inputTokenCount", "input"],
|
|
789
|
+
["outputTokenCount", "output"],
|
|
790
|
+
["input_token_count", "input"],
|
|
791
|
+
["generated_token_count", "output"]
|
|
792
|
+
].reduce((acc, [modelKey, typeKey]) => {
|
|
793
|
+
const value = usage[modelKey];
|
|
794
|
+
if (value != null) acc[typeKey] = Array.isArray(value) ? value.reduce((sum, tokenCount) => sum + tokenCount, 0) : value;
|
|
795
|
+
return acc;
|
|
796
|
+
}, {
|
|
797
|
+
input: 0,
|
|
798
|
+
output: 0
|
|
799
|
+
});
|
|
800
|
+
const additionalTokenData = {};
|
|
801
|
+
if (usage.prompt_tokens_details?.cached_tokens != null) additionalTokenData.cacheReadInputTokens = usage.prompt_tokens_details.cached_tokens;
|
|
802
|
+
else if (usage.input_token_details?.cache_read != null) additionalTokenData.cacheReadInputTokens = usage.input_token_details.cache_read;
|
|
803
|
+
else if (usage.cachedPromptTokens != null) additionalTokenData.cacheReadInputTokens = usage.cachedPromptTokens;
|
|
804
|
+
else if (usage.cache_read_input_tokens != null) additionalTokenData.cacheReadInputTokens = usage.cache_read_input_tokens;
|
|
805
|
+
if (usage.cache_creation_input_tokens != null) additionalTokenData.cacheWriteInputTokens = usage.cache_creation_input_tokens;
|
|
806
|
+
else if (usage.input_token_details?.cache_creation != null) additionalTokenData.cacheWriteInputTokens = usage.input_token_details.cache_creation;
|
|
807
|
+
const directCacheCreationAggregates = [
|
|
808
|
+
usage.cache_creation_input_tokens,
|
|
809
|
+
usage.input_token_details?.cache_creation,
|
|
810
|
+
usage.cacheWriteInputTokens,
|
|
811
|
+
rawUsage?.cache_creation_input_tokens,
|
|
812
|
+
rawUsage?.input_token_details?.cache_creation,
|
|
813
|
+
rawUsage?.cacheWriteInputTokens,
|
|
814
|
+
additionalTokenData.cacheWriteInputTokens
|
|
815
|
+
];
|
|
816
|
+
const cacheCreationTtl = this._extractCacheCreationTtlBreakdown(usage.cache_creation, directCacheCreationAggregates) ?? this._extractCacheCreationTtlBreakdown(rawUsage?.cache_creation, directCacheCreationAggregates) ?? this._extractBedrockCacheCreationTtlBreakdown(usage.cacheDetails, [usage.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]) ?? this._extractBedrockCacheCreationTtlBreakdown(rawUsage?.cacheDetails, [rawUsage?.cacheWriteInputTokens, additionalTokenData.cacheWriteInputTokens]);
|
|
817
|
+
if (cacheCreationTtl) {
|
|
818
|
+
const [cacheWrite5mInputTokens, cacheWrite1hInputTokens] = cacheCreationTtl;
|
|
819
|
+
additionalTokenData.cacheWrite5mInputTokens = cacheWrite5mInputTokens;
|
|
820
|
+
additionalTokenData.cacheWrite1hInputTokens = cacheWrite1hInputTokens;
|
|
821
|
+
additionalTokenData.cacheWriteInputTokens = cacheWrite5mInputTokens + cacheWrite1hInputTokens;
|
|
822
|
+
}
|
|
823
|
+
if (usage.completion_tokens_details?.reasoning_tokens != null) additionalTokenData.reasoningTokens = usage.completion_tokens_details.reasoning_tokens;
|
|
824
|
+
else if (usage.output_token_details?.reasoning != null) additionalTokenData.reasoningTokens = usage.output_token_details.reasoning;
|
|
825
|
+
else if (usage.reasoningTokens != null) additionalTokenData.reasoningTokens = usage.reasoningTokens;
|
|
826
|
+
let webSearchCount;
|
|
827
|
+
if (usage.server_tool_use?.web_search_requests !== void 0) webSearchCount = usage.server_tool_use.web_search_requests;
|
|
828
|
+
else if (usage.citations && Array.isArray(usage.citations) && usage.citations.length > 0) webSearchCount = 1;
|
|
829
|
+
else if (usage.search_results && Array.isArray(usage.search_results) && usage.search_results.length > 0) webSearchCount = 1;
|
|
830
|
+
else if (usage.search_context_size) webSearchCount = 1;
|
|
831
|
+
else if (usage.annotations && Array.isArray(usage.annotations)) {
|
|
832
|
+
if (usage.annotations.some((ann) => {
|
|
833
|
+
return ann && typeof ann === "object" && "type" in ann && ann.type === "url_citation";
|
|
834
|
+
})) webSearchCount = 1;
|
|
835
|
+
} else if (usage.grounding_metadata?.grounding_support !== void 0 || usage.grounding_metadata?.web_search_queries !== void 0) webSearchCount = 1;
|
|
836
|
+
if (webSearchCount !== void 0) additionalTokenData.webSearchCount = webSearchCount;
|
|
837
|
+
let isAnthropic = false;
|
|
838
|
+
if (provider && provider.toLowerCase() === "anthropic") isAnthropic = true;
|
|
839
|
+
else if (model && model.toLowerCase().includes("anthropic")) isAnthropic = true;
|
|
840
|
+
if (isAnthropic && inputIncludesCacheTokens && parsedUsage.input) {
|
|
841
|
+
const cacheTokens = (additionalTokenData.cacheReadInputTokens || 0) + (additionalTokenData.cacheWriteInputTokens || 0);
|
|
842
|
+
if (cacheTokens > 0) parsedUsage.input = Math.max(parsedUsage.input - cacheTokens, 0);
|
|
843
|
+
}
|
|
844
|
+
return [
|
|
845
|
+
parsedUsage.input,
|
|
846
|
+
parsedUsage.output,
|
|
847
|
+
additionalTokenData
|
|
848
|
+
];
|
|
849
|
+
}
|
|
850
|
+
parseUsage(response, provider, model) {
|
|
851
|
+
const isNonEmptyUsage = (usage) => isObject(usage) && Object.keys(usage).length > 0;
|
|
852
|
+
const firstNonEmptyUsage = (...candidates) => candidates.find(isNonEmptyUsage);
|
|
853
|
+
let normalizedGenerationUsage;
|
|
854
|
+
let rawGenerationUsage;
|
|
855
|
+
let fallbackGenerationUsage;
|
|
856
|
+
for (const generation of response.generations ?? []) for (const genChunk of generation) {
|
|
857
|
+
const generationInfo = genChunk.generationInfo ?? {};
|
|
858
|
+
const message = "message" in genChunk ? genChunk.message : void 0;
|
|
859
|
+
const messageUsage = message && typeof message === "object" && "usage_metadata" in message ? message.usage_metadata : void 0;
|
|
860
|
+
normalizedGenerationUsage = firstNonEmptyUsage(normalizedGenerationUsage, messageUsage, generationInfo.usage_metadata);
|
|
861
|
+
const messageResponseMetadata = message && typeof message === "object" && "response_metadata" in message && isObject(message.response_metadata) ? message.response_metadata : void 0;
|
|
862
|
+
const generationResponseMetadata = isObject(generationInfo.response_metadata) ? generationInfo.response_metadata : void 0;
|
|
863
|
+
const messageStreamMetadata = isObject(messageResponseMetadata?.metadata) ? messageResponseMetadata.metadata : void 0;
|
|
864
|
+
const generationStreamMetadata = isObject(generationResponseMetadata?.metadata) ? generationResponseMetadata.metadata : void 0;
|
|
865
|
+
rawGenerationUsage = firstNonEmptyUsage(rawGenerationUsage, messageResponseMetadata?.usage, messageStreamMetadata?.usage, generationResponseMetadata?.usage, generationStreamMetadata?.usage);
|
|
866
|
+
fallbackGenerationUsage = firstNonEmptyUsage(fallbackGenerationUsage, messageResponseMetadata?.["amazon-bedrock-invocationMetrics"], generationResponseMetadata?.["amazon-bedrock-invocationMetrics"], generationInfo.usage_metadata);
|
|
867
|
+
}
|
|
868
|
+
if ((provider?.toLowerCase() === "anthropic" || model?.toLowerCase().includes("anthropic") === true) && isNonEmptyUsage(normalizedGenerationUsage)) return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
|
|
869
|
+
const llmUsageKeys = [
|
|
870
|
+
"token_usage",
|
|
871
|
+
"usage",
|
|
872
|
+
"tokenUsage"
|
|
873
|
+
];
|
|
874
|
+
if (response.llmOutput != null) for (const key of llmUsageKeys) {
|
|
875
|
+
const llmUsage = response.llmOutput[key];
|
|
876
|
+
if (!isNonEmptyUsage(llmUsage)) continue;
|
|
877
|
+
return this._parseUsageModel(llmUsage, provider, model, key !== "usage", llmUsage);
|
|
878
|
+
}
|
|
879
|
+
if (isNonEmptyUsage(normalizedGenerationUsage)) return this._parseUsageModel(normalizedGenerationUsage, provider, model, true, rawGenerationUsage);
|
|
880
|
+
if (isNonEmptyUsage(rawGenerationUsage)) return this._parseUsageModel(rawGenerationUsage, provider, model, false, rawGenerationUsage);
|
|
881
|
+
if (isNonEmptyUsage(fallbackGenerationUsage)) return this._parseUsageModel(fallbackGenerationUsage, provider, model);
|
|
882
|
+
return [
|
|
883
|
+
0,
|
|
884
|
+
0,
|
|
885
|
+
{}
|
|
886
|
+
];
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
//#endregion
|
|
890
|
+
//#region src/langchain/middleware/index.ts
|
|
1068
891
|
const postHogStateSchema = z.object({
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
892
|
+
_posthogRunId: z.string().optional(),
|
|
893
|
+
_posthogStartTime: z.number().optional(),
|
|
894
|
+
_posthogInput: z.record(z.string(), z.unknown()).optional()
|
|
1072
895
|
});
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
const withoutPostHogState = state => {
|
|
1079
|
-
const {
|
|
1080
|
-
_posthogRunId: _,
|
|
1081
|
-
_posthogStartTime: __,
|
|
1082
|
-
_posthogInput: ___,
|
|
1083
|
-
...rest
|
|
1084
|
-
} = state;
|
|
1085
|
-
return rest;
|
|
896
|
+
var LangChainMiddlewareCallbackHandler = class extends LangChainCallbackHandler {
|
|
897
|
+
_getParentRunId(_traceId, _runId, parentRunId) {
|
|
898
|
+
return parentRunId;
|
|
899
|
+
}
|
|
1086
900
|
};
|
|
1087
|
-
const
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
return JSON.stringify(value) ?? String(value);
|
|
1091
|
-
} catch {
|
|
1092
|
-
try {
|
|
1093
|
-
return String(value);
|
|
1094
|
-
} catch {
|
|
1095
|
-
return '';
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
901
|
+
const withoutPostHogState = (state) => {
|
|
902
|
+
const { _posthogRunId: _, _posthogStartTime: __, _posthogInput: ___, ...rest } = state;
|
|
903
|
+
return rest;
|
|
1098
904
|
};
|
|
1099
|
-
const
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
905
|
+
const getRunId = (state) => state._posthogRunId ?? v7();
|
|
906
|
+
const stringify = (value) => {
|
|
907
|
+
try {
|
|
908
|
+
return JSON.stringify(value) ?? String(value);
|
|
909
|
+
} catch {
|
|
910
|
+
try {
|
|
911
|
+
return String(value);
|
|
912
|
+
} catch {
|
|
913
|
+
return "";
|
|
914
|
+
}
|
|
915
|
+
}
|
|
1106
916
|
};
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
// Fall back to a minimal LangChain serialization below.
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
return {
|
|
1116
|
-
lc: 1,
|
|
1117
|
-
type: 'constructor',
|
|
1118
|
-
id: ['langchain', 'chat_models', 'unknown'],
|
|
1119
|
-
kwargs: {}
|
|
1120
|
-
};
|
|
917
|
+
const toError = (error) => error instanceof Error ? error : new Error(stringify(error));
|
|
918
|
+
const safely = (callback) => {
|
|
919
|
+
try {
|
|
920
|
+
callback();
|
|
921
|
+
} catch {}
|
|
1121
922
|
};
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
923
|
+
const serializeModel = (model) => {
|
|
924
|
+
if (model && typeof model === "object" && "toJSON" in model && typeof model.toJSON === "function") try {
|
|
925
|
+
return model.toJSON();
|
|
926
|
+
} catch {}
|
|
927
|
+
return {
|
|
928
|
+
lc: 1,
|
|
929
|
+
type: "constructor",
|
|
930
|
+
id: [
|
|
931
|
+
"langchain",
|
|
932
|
+
"chat_models",
|
|
933
|
+
"unknown"
|
|
934
|
+
],
|
|
935
|
+
kwargs: {}
|
|
936
|
+
};
|
|
937
|
+
};
|
|
938
|
+
const toModelOptions = (modelSettings) => {
|
|
939
|
+
if (!isObject(modelSettings)) return {};
|
|
940
|
+
try {
|
|
941
|
+
return { ...modelSettings };
|
|
942
|
+
} catch {
|
|
943
|
+
return {};
|
|
944
|
+
}
|
|
1133
945
|
};
|
|
1134
946
|
const getModelMetadata = (model, modelSettings) => {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
return undefined;
|
|
947
|
+
if (model && typeof model === "object" && "getLsParams" in model && typeof model.getLsParams === "function") try {
|
|
948
|
+
return model.getLsParams(toModelOptions(modelSettings));
|
|
949
|
+
} catch {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
1143
952
|
};
|
|
1144
953
|
const getModelInvocationParams = (model, modelSettings) => {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
} catch {
|
|
1156
|
-
// Preserve the bind-time settings when the model cannot expose invocation parameters.
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
return options;
|
|
954
|
+
const options = toModelOptions(modelSettings);
|
|
955
|
+
if (model && typeof model === "object" && "invocationParams" in model && typeof model.invocationParams === "function") try {
|
|
956
|
+
const params = model.invocationParams(options);
|
|
957
|
+
if (isObject(params)) return {
|
|
958
|
+
...options,
|
|
959
|
+
...params
|
|
960
|
+
};
|
|
961
|
+
} catch {}
|
|
962
|
+
return options;
|
|
1160
963
|
};
|
|
1161
|
-
const normalizeTools = tools => {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
964
|
+
const normalizeTools = (tools) => {
|
|
965
|
+
return tools.map((tool) => {
|
|
966
|
+
try {
|
|
967
|
+
return convertToOpenAITool(tool);
|
|
968
|
+
} catch {
|
|
969
|
+
return tool;
|
|
970
|
+
}
|
|
971
|
+
});
|
|
1169
972
|
};
|
|
1170
|
-
const toLLMResult = response => {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
const generation = {
|
|
1177
|
-
text: toContentString(response.content),
|
|
1178
|
-
message: response
|
|
1179
|
-
};
|
|
1180
|
-
return {
|
|
1181
|
-
generations: [[generation]]
|
|
1182
|
-
};
|
|
973
|
+
const toLLMResult = (response) => {
|
|
974
|
+
if (!AIMessage.isInstance(response)) return { generations: [] };
|
|
975
|
+
return { generations: [[{
|
|
976
|
+
text: toContentString(response.content),
|
|
977
|
+
message: response
|
|
978
|
+
}]] };
|
|
1183
979
|
};
|
|
1184
|
-
|
|
1185
|
-
/** Options shared with the LangChain callback integration. */
|
|
1186
|
-
|
|
1187
980
|
/**
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
const createPostHogMiddleware = options => {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1271
|
-
});
|
|
981
|
+
* Creates PostHog AI observability middleware for LangChain v1 agents.
|
|
982
|
+
*
|
|
983
|
+
* Use either this middleware or `LangChainCallbackHandler`, not both, to avoid
|
|
984
|
+
* capturing the same model and tool calls twice.
|
|
985
|
+
*
|
|
986
|
+
* LangChain only invokes `afterAgent` for completed runs. A terminal agent
|
|
987
|
+
* failure still captures the failed model or tool call, but not a root trace.
|
|
988
|
+
*/
|
|
989
|
+
const createPostHogMiddleware = (options) => {
|
|
990
|
+
const { stateSchema, ...callbackOptions } = options;
|
|
991
|
+
const callback = new LangChainMiddlewareCallbackHandler(callbackOptions);
|
|
992
|
+
const middlewareStateSchema = stateSchema ? extendInteropZodObject(stateSchema, postHogStateSchema.shape) : postHogStateSchema;
|
|
993
|
+
return createMiddleware({
|
|
994
|
+
name: "PostHogMiddleware",
|
|
995
|
+
stateSchema: middlewareStateSchema,
|
|
996
|
+
beforeAgent: (state) => {
|
|
997
|
+
return {
|
|
998
|
+
_posthogRunId: v7(),
|
|
999
|
+
_posthogStartTime: Date.now(),
|
|
1000
|
+
_posthogInput: withoutPostHogState(state)
|
|
1001
|
+
};
|
|
1002
|
+
},
|
|
1003
|
+
afterAgent: (state) => {
|
|
1004
|
+
safely(() => {
|
|
1005
|
+
const runId = getRunId(state);
|
|
1006
|
+
callback.handleChainStart({
|
|
1007
|
+
lc: 1,
|
|
1008
|
+
type: "constructor",
|
|
1009
|
+
id: [
|
|
1010
|
+
"langchain",
|
|
1011
|
+
"agents",
|
|
1012
|
+
"PostHogMiddleware"
|
|
1013
|
+
],
|
|
1014
|
+
kwargs: {}
|
|
1015
|
+
}, state._posthogInput ?? withoutPostHogState(state), runId, void 0, void 0, void 0, void 0, "LangChain Agent", { posthogStartTime: state._posthogStartTime });
|
|
1016
|
+
callback.handleChainEnd(withoutPostHogState(state), runId);
|
|
1017
|
+
});
|
|
1018
|
+
},
|
|
1019
|
+
wrapModelCall: async (request, handler) => {
|
|
1020
|
+
const runId = v7();
|
|
1021
|
+
const parentRunId = getRunId(request.state);
|
|
1022
|
+
const messages = request.systemMessage.text === "" ? request.messages : [request.systemMessage, ...request.messages];
|
|
1023
|
+
const invocationParams = {
|
|
1024
|
+
...getModelInvocationParams(request.model, request.modelSettings),
|
|
1025
|
+
tools: normalizeTools(request.tools)
|
|
1026
|
+
};
|
|
1027
|
+
safely(() => callback.handleChatModelStart(serializeModel(request.model), [messages], runId, parentRunId, { invocation_params: invocationParams }, void 0, getModelMetadata(request.model, request.modelSettings)));
|
|
1028
|
+
try {
|
|
1029
|
+
const response = await handler(request);
|
|
1030
|
+
safely(() => callback.handleLLMEnd(toLLMResult(response), runId, parentRunId));
|
|
1031
|
+
return response;
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
safely(() => callback.handleLLMError(toError(error), runId));
|
|
1034
|
+
throw error;
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
wrapToolCall: async (request, handler) => {
|
|
1038
|
+
const runId = v7();
|
|
1039
|
+
const parentRunId = getRunId(request.state);
|
|
1040
|
+
const toolName = String(request.tool?.name ?? request.toolCall.name);
|
|
1041
|
+
const serializedTool = {
|
|
1042
|
+
lc: 1,
|
|
1043
|
+
type: "constructor",
|
|
1044
|
+
id: [
|
|
1045
|
+
"langchain",
|
|
1046
|
+
"tools",
|
|
1047
|
+
toolName
|
|
1048
|
+
],
|
|
1049
|
+
kwargs: {}
|
|
1050
|
+
};
|
|
1051
|
+
safely(() => callback.handleToolStart(serializedTool, stringify(request.toolCall.args), runId, parentRunId, void 0, void 0, toolName));
|
|
1052
|
+
try {
|
|
1053
|
+
const result = await handler(request);
|
|
1054
|
+
if (ToolMessage.isInstance(result) && result.status === "error") safely(() => callback.handleToolError(new Error(toContentString(result.content)), runId, parentRunId));
|
|
1055
|
+
else safely(() => callback.handleToolEnd(result, runId, parentRunId));
|
|
1056
|
+
return result;
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
safely(() => callback.handleToolError(toError(error), runId));
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1272
1063
|
};
|
|
1273
|
-
|
|
1064
|
+
//#endregion
|
|
1274
1065
|
export { createPostHogMiddleware };
|
|
1275
|
-
|
|
1066
|
+
|
|
1067
|
+
//# sourceMappingURL=index.mjs.map
|