braintrust 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dev/dist/index.js +1465 -881
- package/dev/dist/index.mjs +846 -262
- package/dist/apply-auto-instrumentation.js +217 -217
- package/dist/apply-auto-instrumentation.mjs +2 -2
- package/dist/auto-instrumentations/bundler/esbuild.cjs +13 -1
- package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
- package/dist/auto-instrumentations/bundler/next.cjs +13 -1
- package/dist/auto-instrumentations/bundler/next.mjs +3 -3
- package/dist/auto-instrumentations/bundler/rollup.cjs +13 -1
- package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
- package/dist/auto-instrumentations/bundler/vite.cjs +13 -1
- package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
- package/dist/auto-instrumentations/bundler/webpack-loader.cjs +13 -1
- package/dist/auto-instrumentations/bundler/webpack.cjs +13 -1
- package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
- package/dist/auto-instrumentations/{chunk-DKTGDNA7.mjs → chunk-LW4HDHXT.mjs} +1 -1
- package/dist/auto-instrumentations/{chunk-OMCZ3MV2.mjs → chunk-OXINGIEZ.mjs} +1 -1
- package/dist/auto-instrumentations/{chunk-AOIYCVEL.mjs → chunk-U64OYU4Q.mjs} +13 -1
- package/dist/auto-instrumentations/hook.mjs +16 -4
- package/dist/auto-instrumentations/index.cjs +13 -1
- package/dist/auto-instrumentations/index.mjs +1 -1
- package/dist/browser.d.mts +133 -2
- package/dist/browser.d.ts +133 -2
- package/dist/browser.js +1143 -297
- package/dist/browser.mjs +1143 -297
- package/dist/{chunk-6Z5S7VOU.js → chunk-2XDW3UCG.js} +13 -1
- package/dist/{chunk-M6XPNJC4.mjs → chunk-BU6SM54N.mjs} +687 -115
- package/dist/{chunk-7FA6VP2S.mjs → chunk-N3JKQ3D3.mjs} +13 -1
- package/dist/{chunk-XLLGRXGR.js → chunk-TWCDSYJN.js} +1803 -1231
- package/dist/cli.js +819 -235
- package/dist/edge-light.d.mts +1 -1
- package/dist/edge-light.d.ts +1 -1
- package/dist/edge-light.js +1143 -297
- package/dist/edge-light.mjs +1143 -297
- package/dist/index.d.mts +133 -2
- package/dist/index.d.ts +133 -2
- package/dist/index.js +902 -640
- package/dist/index.mjs +336 -74
- package/dist/instrumentation/index.d.mts +15 -0
- package/dist/instrumentation/index.d.ts +15 -0
- package/dist/instrumentation/index.js +857 -262
- package/dist/instrumentation/index.mjs +857 -262
- package/dist/vitest-evals-reporter.js +16 -16
- package/dist/vitest-evals-reporter.mjs +2 -2
- package/dist/workerd.d.mts +1 -1
- package/dist/workerd.d.ts +1 -1
- package/dist/workerd.js +1143 -297
- package/dist/workerd.mjs +1143 -297
- package/package.json +1 -1
|
@@ -5815,7 +5815,7 @@ var INSTRUMENTATION_NAMES = {
|
|
|
5815
5815
|
var INTERNAL_SPAN_INSTRUMENTATION_NAME = /* @__PURE__ */ Symbol.for(
|
|
5816
5816
|
"braintrust.spanInstrumentationName"
|
|
5817
5817
|
);
|
|
5818
|
-
var SDK_VERSION = true ? "3.
|
|
5818
|
+
var SDK_VERSION = true ? "3.30.0" : "0.0.0";
|
|
5819
5819
|
function withSpanInstrumentationName(args, instrumentationName) {
|
|
5820
5820
|
return {
|
|
5821
5821
|
...args,
|
|
@@ -8531,6 +8531,9 @@ function getSpanParentObjectAndPropagatedState(options) {
|
|
|
8531
8531
|
}
|
|
8532
8532
|
return { parentObject: NOOP_SPAN, propagatedState: void 0 };
|
|
8533
8533
|
}
|
|
8534
|
+
function getSpanParentObject(options) {
|
|
8535
|
+
return getSpanParentObjectAndPropagatedState(options).parentObject;
|
|
8536
|
+
}
|
|
8534
8537
|
function currentBraintrustParent(state) {
|
|
8535
8538
|
const resolvedState = state ?? _globalState;
|
|
8536
8539
|
const experiment = currentExperiment({ state: resolvedState });
|
|
@@ -8777,6 +8780,15 @@ function _internalStartSpanWithInitialMerge(args) {
|
|
|
8777
8780
|
[INITIAL_SPAN_WRITE_AS_MERGE]: true
|
|
8778
8781
|
}).span;
|
|
8779
8782
|
}
|
|
8783
|
+
function _internalStartSpanWithInitialMergeAndParentSpanIds(args) {
|
|
8784
|
+
return startSpanAndIsLogger(
|
|
8785
|
+
{
|
|
8786
|
+
...args,
|
|
8787
|
+
[INITIAL_SPAN_WRITE_AS_MERGE]: true
|
|
8788
|
+
},
|
|
8789
|
+
{ useParentSpanIdsForObjectParent: true }
|
|
8790
|
+
).span;
|
|
8791
|
+
}
|
|
8780
8792
|
function _internalStartSpanWithContext(args, context) {
|
|
8781
8793
|
return startSpanAndIsLogger({
|
|
8782
8794
|
...args,
|
|
@@ -8787,7 +8799,7 @@ async function flush(options) {
|
|
|
8787
8799
|
const state = options?.state ?? _globalState;
|
|
8788
8800
|
return await state.bgLogger().flush();
|
|
8789
8801
|
}
|
|
8790
|
-
function startSpanAndIsLogger(args) {
|
|
8802
|
+
function startSpanAndIsLogger(args, internalOptions) {
|
|
8791
8803
|
const state = args?.state ?? _globalState;
|
|
8792
8804
|
const { parentObject, propagatedState } = getSpanParentObjectAndPropagatedState({
|
|
8793
8805
|
asyncFlush: args?.asyncFlush,
|
|
@@ -8801,7 +8813,7 @@ function startSpanAndIsLogger(args) {
|
|
|
8801
8813
|
) ? {
|
|
8802
8814
|
spanId: parentObject.data.span_id,
|
|
8803
8815
|
rootSpanId: parentObject.data.root_span_id
|
|
8804
|
-
} : void 0;
|
|
8816
|
+
} : internalOptions?.useParentSpanIdsForObjectParent ? args?.parentSpanIds : void 0;
|
|
8805
8817
|
const { parent: _ignoredParent, ...spanArgs } = args ?? {};
|
|
8806
8818
|
const span = new SpanImpl({
|
|
8807
8819
|
state,
|
|
@@ -11831,6 +11843,131 @@ function unsubscribeAll(unsubscribers) {
|
|
|
11831
11843
|
return [];
|
|
11832
11844
|
}
|
|
11833
11845
|
|
|
11846
|
+
// src/instrumentation/core/channel-definitions.ts
|
|
11847
|
+
function channel(spec) {
|
|
11848
|
+
return spec;
|
|
11849
|
+
}
|
|
11850
|
+
function defineChannels(pkg, channels, options) {
|
|
11851
|
+
const { instrumentationName } = options;
|
|
11852
|
+
return Object.fromEntries(
|
|
11853
|
+
Object.entries(channels).map(([key, spec]) => {
|
|
11854
|
+
const fullChannelName = `orchestrion:${pkg}:${spec.channelName}`;
|
|
11855
|
+
if (spec.kind === "async") {
|
|
11856
|
+
const asyncSpec = spec;
|
|
11857
|
+
const tracingChannel2 = () => isomorph_default.newTracingChannel(
|
|
11858
|
+
fullChannelName
|
|
11859
|
+
);
|
|
11860
|
+
const intercept2 = (interceptor) => {
|
|
11861
|
+
const hook = tracingChannel2();
|
|
11862
|
+
return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
|
|
11863
|
+
};
|
|
11864
|
+
};
|
|
11865
|
+
return [
|
|
11866
|
+
key,
|
|
11867
|
+
{
|
|
11868
|
+
...asyncSpec,
|
|
11869
|
+
instrumentationName,
|
|
11870
|
+
intercept: intercept2,
|
|
11871
|
+
invoke: (target, thisArg, args, additional) => {
|
|
11872
|
+
const hook = tracingChannel2();
|
|
11873
|
+
return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
|
|
11874
|
+
},
|
|
11875
|
+
tracingChannel: tracingChannel2,
|
|
11876
|
+
tracePromise: (fn, context) => tracingChannel2().tracePromise(
|
|
11877
|
+
fn,
|
|
11878
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
11879
|
+
context
|
|
11880
|
+
)
|
|
11881
|
+
}
|
|
11882
|
+
];
|
|
11883
|
+
}
|
|
11884
|
+
const syncSpec = spec;
|
|
11885
|
+
const tracingChannel = () => isomorph_default.newTracingChannel(
|
|
11886
|
+
fullChannelName
|
|
11887
|
+
);
|
|
11888
|
+
const intercept = (interceptor) => {
|
|
11889
|
+
const hook = tracingChannel();
|
|
11890
|
+
return typeof hook.intercept === "function" ? hook.intercept(interceptor) : () => {
|
|
11891
|
+
};
|
|
11892
|
+
};
|
|
11893
|
+
return [
|
|
11894
|
+
key,
|
|
11895
|
+
{
|
|
11896
|
+
...syncSpec,
|
|
11897
|
+
instrumentationName,
|
|
11898
|
+
intercept,
|
|
11899
|
+
invoke: (target, thisArg, args, additional) => {
|
|
11900
|
+
const hook = tracingChannel();
|
|
11901
|
+
return typeof hook.invoke === "function" ? hook.invoke(target, thisArg, args, additional) : Reflect.apply(target, thisArg, args);
|
|
11902
|
+
},
|
|
11903
|
+
tracingChannel,
|
|
11904
|
+
traceSync: (fn, context) => tracingChannel().traceSync(
|
|
11905
|
+
fn,
|
|
11906
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
11907
|
+
context
|
|
11908
|
+
)
|
|
11909
|
+
}
|
|
11910
|
+
];
|
|
11911
|
+
})
|
|
11912
|
+
);
|
|
11913
|
+
}
|
|
11914
|
+
|
|
11915
|
+
// src/instrumentation/plugins/openai-channels.ts
|
|
11916
|
+
var openAIChannels = defineChannels(
|
|
11917
|
+
"openai",
|
|
11918
|
+
{
|
|
11919
|
+
filesCreateTraced: channel({
|
|
11920
|
+
channelName: "files.create-traced",
|
|
11921
|
+
kind: "async"
|
|
11922
|
+
}),
|
|
11923
|
+
batchesRetrieveTraced: channel({
|
|
11924
|
+
channelName: "batches.retrieve-traced",
|
|
11925
|
+
kind: "async"
|
|
11926
|
+
}),
|
|
11927
|
+
batchesCompleteTrace: channel({
|
|
11928
|
+
channelName: "batches.complete-trace",
|
|
11929
|
+
kind: "async"
|
|
11930
|
+
}),
|
|
11931
|
+
chatCompletionsCreate: channel({
|
|
11932
|
+
channelName: "chat.completions.create",
|
|
11933
|
+
kind: "async"
|
|
11934
|
+
}),
|
|
11935
|
+
embeddingsCreate: channel({
|
|
11936
|
+
channelName: "embeddings.create",
|
|
11937
|
+
kind: "async"
|
|
11938
|
+
}),
|
|
11939
|
+
betaChatCompletionsParse: channel({
|
|
11940
|
+
channelName: "beta.chat.completions.parse",
|
|
11941
|
+
kind: "async"
|
|
11942
|
+
}),
|
|
11943
|
+
betaChatCompletionsStream: channel({
|
|
11944
|
+
channelName: "beta.chat.completions.stream",
|
|
11945
|
+
kind: "sync-stream"
|
|
11946
|
+
}),
|
|
11947
|
+
moderationsCreate: channel({
|
|
11948
|
+
channelName: "moderations.create",
|
|
11949
|
+
kind: "async"
|
|
11950
|
+
}),
|
|
11951
|
+
responsesCreate: channel({
|
|
11952
|
+
channelName: "responses.create",
|
|
11953
|
+
kind: "async"
|
|
11954
|
+
}),
|
|
11955
|
+
responsesStream: channel({
|
|
11956
|
+
channelName: "responses.stream",
|
|
11957
|
+
kind: "sync-stream"
|
|
11958
|
+
}),
|
|
11959
|
+
responsesParse: channel({
|
|
11960
|
+
channelName: "responses.parse",
|
|
11961
|
+
kind: "async"
|
|
11962
|
+
}),
|
|
11963
|
+
responsesCompact: channel({
|
|
11964
|
+
channelName: "responses.compact",
|
|
11965
|
+
kind: "async"
|
|
11966
|
+
})
|
|
11967
|
+
},
|
|
11968
|
+
{ instrumentationName: INSTRUMENTATION_NAMES.OPENAI }
|
|
11969
|
+
);
|
|
11970
|
+
|
|
11834
11971
|
// src/wrappers/attachment-utils.ts
|
|
11835
11972
|
function getExtensionFromMediaType(mediaType) {
|
|
11836
11973
|
const extensionMap = {
|
|
@@ -12006,173 +12143,691 @@ function processInputAttachments(input) {
|
|
|
12006
12143
|
return processNode(input);
|
|
12007
12144
|
}
|
|
12008
12145
|
|
|
12009
|
-
// src/instrumentation/
|
|
12010
|
-
|
|
12011
|
-
|
|
12146
|
+
// src/instrumentation/plugins/openai-span-data.ts
|
|
12147
|
+
var OPENAI_METADATA_KEYS = [
|
|
12148
|
+
"model",
|
|
12149
|
+
"temperature",
|
|
12150
|
+
"top_p",
|
|
12151
|
+
"max_tokens",
|
|
12152
|
+
"frequency_penalty",
|
|
12153
|
+
"presence_penalty",
|
|
12154
|
+
"stop",
|
|
12155
|
+
"response_format",
|
|
12156
|
+
"tools",
|
|
12157
|
+
"tool_choice",
|
|
12158
|
+
"parallel_tool_calls",
|
|
12159
|
+
"max_tool_calls"
|
|
12160
|
+
];
|
|
12161
|
+
function batchMetadata(params) {
|
|
12162
|
+
const metadata = { provider: "openai" };
|
|
12163
|
+
for (const key of OPENAI_METADATA_KEYS) {
|
|
12164
|
+
try {
|
|
12165
|
+
const value = Reflect.get(params, key);
|
|
12166
|
+
if (value !== void 0) {
|
|
12167
|
+
metadata[key] = value;
|
|
12168
|
+
}
|
|
12169
|
+
} catch {
|
|
12170
|
+
}
|
|
12171
|
+
}
|
|
12172
|
+
return metadata;
|
|
12012
12173
|
}
|
|
12013
|
-
function
|
|
12014
|
-
const
|
|
12015
|
-
return
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12024
|
-
|
|
12025
|
-
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12174
|
+
function extractOpenAIBatchInput(endpoint, params) {
|
|
12175
|
+
const input = endpoint === "/v1/chat/completions" ? params.messages : params.input;
|
|
12176
|
+
return {
|
|
12177
|
+
input: processInputAttachments(input),
|
|
12178
|
+
metadata: batchMetadata(params)
|
|
12179
|
+
};
|
|
12180
|
+
}
|
|
12181
|
+
function extractOpenAIChatInput(params) {
|
|
12182
|
+
const { messages, ...metadata } = params;
|
|
12183
|
+
return {
|
|
12184
|
+
input: processInputAttachments(messages),
|
|
12185
|
+
metadata: { ...metadata, provider: "openai" }
|
|
12186
|
+
};
|
|
12187
|
+
}
|
|
12188
|
+
function extractOpenAIResponsesInput(params) {
|
|
12189
|
+
const { input, ...metadata } = params;
|
|
12190
|
+
return {
|
|
12191
|
+
input: processInputAttachments(input),
|
|
12192
|
+
metadata: { ...metadata, provider: "openai" }
|
|
12193
|
+
};
|
|
12194
|
+
}
|
|
12195
|
+
function extractOpenAIResponsesMetadata(result) {
|
|
12196
|
+
if (!result) {
|
|
12197
|
+
return void 0;
|
|
12198
|
+
}
|
|
12199
|
+
const { output: _output, usage: _usage, ...metadata } = result;
|
|
12200
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
12201
|
+
}
|
|
12202
|
+
function processImagesInOutput(output) {
|
|
12203
|
+
if (Array.isArray(output)) {
|
|
12204
|
+
return output.map(processImagesInOutput);
|
|
12205
|
+
}
|
|
12206
|
+
if (isObject(output) && output.type === "image_generation_call" && typeof output.result === "string" && output.result) {
|
|
12207
|
+
const fileExtension = output.output_format || "png";
|
|
12208
|
+
const contentType = `image/${fileExtension}`;
|
|
12209
|
+
const baseFilename = typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
|
|
12210
|
+
let binaryString;
|
|
12211
|
+
try {
|
|
12212
|
+
binaryString = atob(output.result);
|
|
12213
|
+
} catch {
|
|
12214
|
+
return output;
|
|
12215
|
+
}
|
|
12216
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
12217
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
12218
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
12219
|
+
}
|
|
12220
|
+
return {
|
|
12221
|
+
...output,
|
|
12222
|
+
result: new Attachment({
|
|
12223
|
+
data: new Blob([bytes], { type: contentType }),
|
|
12224
|
+
filename: `${baseFilename}.${fileExtension}`,
|
|
12225
|
+
contentType
|
|
12226
|
+
})
|
|
12227
|
+
};
|
|
12228
|
+
}
|
|
12229
|
+
return output;
|
|
12230
|
+
}
|
|
12231
|
+
|
|
12232
|
+
// src/openai-utils.ts
|
|
12233
|
+
var BRAINTRUST_CACHED_STREAM_METRIC = "__braintrust_cached_metric";
|
|
12234
|
+
var LEGACY_CACHED_HEADER = "x-cached";
|
|
12235
|
+
var X_CACHED_HEADER = "x-bt-cached";
|
|
12236
|
+
var TOKEN_NAME_MAP = {
|
|
12237
|
+
input_tokens: "prompt_tokens",
|
|
12238
|
+
output_tokens: "completion_tokens",
|
|
12239
|
+
total_tokens: "tokens"
|
|
12240
|
+
};
|
|
12241
|
+
var TOKEN_PREFIX_MAP = {
|
|
12242
|
+
input: "prompt",
|
|
12243
|
+
output: "completion"
|
|
12244
|
+
};
|
|
12245
|
+
function parseMetricsFromUsage(usage) {
|
|
12246
|
+
if (!usage) {
|
|
12247
|
+
return {};
|
|
12248
|
+
}
|
|
12249
|
+
const metrics = {};
|
|
12250
|
+
for (const [oaiName, value] of Object.entries(usage)) {
|
|
12251
|
+
if (typeof value === "number") {
|
|
12252
|
+
const metricName = TOKEN_NAME_MAP[oaiName] || oaiName;
|
|
12253
|
+
metrics[metricName] = value;
|
|
12254
|
+
continue;
|
|
12255
|
+
}
|
|
12256
|
+
if (!oaiName.endsWith("_tokens_details") || !isObject(value)) {
|
|
12257
|
+
continue;
|
|
12258
|
+
}
|
|
12259
|
+
const rawPrefix = oaiName.slice(0, -"_tokens_details".length);
|
|
12260
|
+
const prefix = TOKEN_PREFIX_MAP[rawPrefix] || rawPrefix;
|
|
12261
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
12262
|
+
if (typeof nestedValue !== "number") {
|
|
12263
|
+
continue;
|
|
12046
12264
|
}
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12265
|
+
metrics[`${prefix}_${key}`] = nestedValue;
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
return metrics;
|
|
12269
|
+
}
|
|
12270
|
+
function parseCachedHeader(value) {
|
|
12271
|
+
if (!value) {
|
|
12272
|
+
return void 0;
|
|
12273
|
+
}
|
|
12274
|
+
return ["true", "hit"].includes(value.toLowerCase()) ? 1 : 0;
|
|
12275
|
+
}
|
|
12276
|
+
function getCachedMetricFromHeaders(headers) {
|
|
12277
|
+
if (!headers || typeof headers.get !== "function") {
|
|
12278
|
+
return void 0;
|
|
12279
|
+
}
|
|
12280
|
+
const cachedHeader = headers.get(X_CACHED_HEADER);
|
|
12281
|
+
if (cachedHeader) {
|
|
12282
|
+
return parseCachedHeader(cachedHeader);
|
|
12283
|
+
}
|
|
12284
|
+
return parseCachedHeader(headers.get(LEGACY_CACHED_HEADER));
|
|
12285
|
+
}
|
|
12286
|
+
|
|
12287
|
+
// src/instrumentation/plugins/openai-batch-instrumentation.ts
|
|
12288
|
+
var SUPPORTED_ENDPOINTS = /* @__PURE__ */ new Set(["/v1/chat/completions", "/v1/responses"]);
|
|
12289
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
12290
|
+
"completed",
|
|
12291
|
+
"failed",
|
|
12292
|
+
"expired",
|
|
12293
|
+
"cancelled"
|
|
12294
|
+
]);
|
|
12295
|
+
var pendingBatchTraces = /* @__PURE__ */ new Map();
|
|
12296
|
+
function read(value, key) {
|
|
12297
|
+
if (!isObject(value)) {
|
|
12298
|
+
return void 0;
|
|
12299
|
+
}
|
|
12300
|
+
try {
|
|
12301
|
+
return Reflect.get(value, key);
|
|
12302
|
+
} catch {
|
|
12303
|
+
return void 0;
|
|
12304
|
+
}
|
|
12305
|
+
}
|
|
12306
|
+
function isBatchRecordIterable(value) {
|
|
12307
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") {
|
|
12308
|
+
return false;
|
|
12309
|
+
}
|
|
12310
|
+
return typeof read(value, Symbol.iterator) === "function" || typeof read(value, Symbol.asyncIterator) === "function";
|
|
12311
|
+
}
|
|
12312
|
+
function validCustomId(value) {
|
|
12313
|
+
return typeof value === "string" && value.length > 0 && value.length <= 64;
|
|
12314
|
+
}
|
|
12315
|
+
function logBatchInstrumentationError(context, error) {
|
|
12316
|
+
debugLogger.debug(`OpenAI Batch instrumentation ${context}:`, error);
|
|
12317
|
+
}
|
|
12318
|
+
async function exportParent(parent) {
|
|
12319
|
+
if ("toStr" in parent && typeof parent.toStr === "function") {
|
|
12320
|
+
return parent.toStr();
|
|
12321
|
+
}
|
|
12322
|
+
if ("export" in parent && typeof parent.export === "function") {
|
|
12323
|
+
return await parent.export();
|
|
12324
|
+
}
|
|
12325
|
+
return void 0;
|
|
12326
|
+
}
|
|
12327
|
+
async function deterministicDigest(namespace, ...parts) {
|
|
12328
|
+
const encoded = new TextEncoder().encode(
|
|
12329
|
+
[namespace, ...parts].map((part) => `${part.length}:${part}`).join("\0")
|
|
12330
|
+
);
|
|
12331
|
+
return new Uint8Array(
|
|
12332
|
+
await globalThis.crypto.subtle.digest("SHA-256", encoded)
|
|
12333
|
+
);
|
|
12334
|
+
}
|
|
12335
|
+
function digestHex(bytes, length) {
|
|
12336
|
+
return Array.from(bytes.slice(0, length)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12337
|
+
}
|
|
12338
|
+
function digestUuid(bytes) {
|
|
12339
|
+
const hex = digestHex(bytes, 16);
|
|
12340
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
12341
|
+
}
|
|
12342
|
+
async function batchSpanIds(inputFileId) {
|
|
12343
|
+
const [row, span, root] = await Promise.all([
|
|
12344
|
+
deterministicDigest("openai:batch:row", inputFileId),
|
|
12345
|
+
deterministicDigest("openai:batch:span", inputFileId),
|
|
12346
|
+
deterministicDigest("openai:batch:root", inputFileId)
|
|
12347
|
+
]);
|
|
12348
|
+
return {
|
|
12349
|
+
rowId: digestUuid(row),
|
|
12350
|
+
spanId: digestHex(span, 8),
|
|
12351
|
+
rootSpanId: digestHex(root, 16)
|
|
12352
|
+
};
|
|
12353
|
+
}
|
|
12354
|
+
async function childSpanIds(inputFileId, customId) {
|
|
12355
|
+
const [row, span] = await Promise.all([
|
|
12356
|
+
deterministicDigest("openai:batch:child:row", inputFileId, customId),
|
|
12357
|
+
deterministicDigest("openai:batch:child:span", inputFileId, customId)
|
|
12358
|
+
]);
|
|
12359
|
+
return { rowId: digestUuid(row), spanId: digestHex(span, 8) };
|
|
12360
|
+
}
|
|
12361
|
+
async function startBatchSpan(context) {
|
|
12362
|
+
const ids = await batchSpanIds(context.inputFileId);
|
|
12363
|
+
const parent = SpanComponentsV4.fromStr(context.parent);
|
|
12364
|
+
const hasParentSpan = Boolean(
|
|
12365
|
+
parent.data.row_id && parent.data.span_id && parent.data.root_span_id
|
|
12366
|
+
);
|
|
12367
|
+
return withCurrent(
|
|
12368
|
+
NOOP_SPAN,
|
|
12369
|
+
() => _internalStartSpanWithInitialMergeAndParentSpanIds(
|
|
12370
|
+
withSpanInstrumentationName(
|
|
12058
12371
|
{
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12372
|
+
name: "openai.batch",
|
|
12373
|
+
type: "task" /* TASK */,
|
|
12374
|
+
parent: context.parent,
|
|
12375
|
+
...!hasParentSpan ? {
|
|
12376
|
+
parentSpanIds: {
|
|
12377
|
+
parentSpanIds: [],
|
|
12378
|
+
rootSpanId: ids.rootSpanId
|
|
12379
|
+
}
|
|
12380
|
+
} : {},
|
|
12381
|
+
spanId: ids.spanId,
|
|
12382
|
+
startTime: context.taskStartTime,
|
|
12383
|
+
event: {
|
|
12384
|
+
id: ids.rowId,
|
|
12385
|
+
metadata: {
|
|
12386
|
+
endpoint: context.endpoint,
|
|
12387
|
+
input_file_id: context.inputFileId,
|
|
12388
|
+
provider: "openai"
|
|
12389
|
+
}
|
|
12390
|
+
}
|
|
12391
|
+
},
|
|
12392
|
+
INSTRUMENTATION_NAMES.OPENAI
|
|
12393
|
+
)
|
|
12394
|
+
)
|
|
12075
12395
|
);
|
|
12076
12396
|
}
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
12130
|
-
|
|
12131
|
-
|
|
12132
|
-
|
|
12133
|
-
|
|
12397
|
+
async function startBatchChild(context, taskParent, input) {
|
|
12398
|
+
const ids = await childSpanIds(context.inputFileId, input.customId);
|
|
12399
|
+
return withCurrent(
|
|
12400
|
+
NOOP_SPAN,
|
|
12401
|
+
() => _internalStartSpanWithInitialMerge(
|
|
12402
|
+
withSpanInstrumentationName(
|
|
12403
|
+
{
|
|
12404
|
+
name: context.endpoint === "/v1/chat/completions" ? "Chat Completion" : "openai.responses.create",
|
|
12405
|
+
type: "llm" /* LLM */,
|
|
12406
|
+
parent: taskParent,
|
|
12407
|
+
spanId: ids.spanId,
|
|
12408
|
+
startTime: context.childStartTime,
|
|
12409
|
+
event: {
|
|
12410
|
+
id: ids.rowId,
|
|
12411
|
+
...input.spanData?.input !== void 0 ? { input: input.spanData.input } : {},
|
|
12412
|
+
metadata: {
|
|
12413
|
+
...input.spanData?.metadata,
|
|
12414
|
+
custom_id: input.customId,
|
|
12415
|
+
provider: "openai"
|
|
12416
|
+
}
|
|
12417
|
+
}
|
|
12418
|
+
},
|
|
12419
|
+
INSTRUMENTATION_NAMES.OPENAI
|
|
12420
|
+
)
|
|
12421
|
+
)
|
|
12422
|
+
);
|
|
12423
|
+
}
|
|
12424
|
+
async function* jsonlRecords(file, onIssue = () => {
|
|
12425
|
+
}) {
|
|
12426
|
+
const resolvedFile = await file;
|
|
12427
|
+
if (typeof resolvedFile === "string") {
|
|
12428
|
+
for (const line of resolvedFile.split("\n")) {
|
|
12429
|
+
if (!line.trim()) {
|
|
12430
|
+
continue;
|
|
12431
|
+
}
|
|
12432
|
+
try {
|
|
12433
|
+
yield JSON.parse(line.replace(/\r$/, ""));
|
|
12434
|
+
} catch (error) {
|
|
12435
|
+
logBatchInstrumentationError("skipped malformed JSONL", error);
|
|
12436
|
+
onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
return;
|
|
12440
|
+
}
|
|
12441
|
+
const body = read(resolvedFile, "body");
|
|
12442
|
+
const getReader = read(body, "getReader");
|
|
12443
|
+
if (isObject(body) && typeof getReader === "function") {
|
|
12444
|
+
let reader;
|
|
12445
|
+
try {
|
|
12446
|
+
reader = Reflect.apply(getReader, body, []);
|
|
12447
|
+
const decoder = new TextDecoder();
|
|
12448
|
+
let pending = "";
|
|
12449
|
+
while (true) {
|
|
12450
|
+
const readChunk = read(reader, "read");
|
|
12451
|
+
if (typeof readChunk !== "function") {
|
|
12452
|
+
throw new Error("Response body stream has no read method");
|
|
12453
|
+
}
|
|
12454
|
+
const chunk = await Reflect.apply(readChunk, reader, []);
|
|
12455
|
+
if (!isObject(chunk)) {
|
|
12456
|
+
throw new Error("Response body stream returned an invalid chunk");
|
|
12457
|
+
}
|
|
12458
|
+
if (chunk.done === true) {
|
|
12459
|
+
pending += decoder.decode();
|
|
12460
|
+
break;
|
|
12461
|
+
}
|
|
12462
|
+
if (!(chunk.value instanceof Uint8Array)) {
|
|
12463
|
+
throw new Error("Response body stream returned a non-byte chunk");
|
|
12464
|
+
}
|
|
12465
|
+
pending += decoder.decode(chunk.value, { stream: true });
|
|
12466
|
+
let newline = pending.indexOf("\n");
|
|
12467
|
+
while (newline !== -1) {
|
|
12468
|
+
const line = pending.slice(0, newline).replace(/\r$/, "");
|
|
12469
|
+
pending = pending.slice(newline + 1);
|
|
12470
|
+
if (line.trim()) {
|
|
12471
|
+
try {
|
|
12472
|
+
yield JSON.parse(line);
|
|
12473
|
+
} catch (error) {
|
|
12474
|
+
logBatchInstrumentationError("skipped malformed JSONL", error);
|
|
12475
|
+
onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12478
|
+
newline = pending.indexOf("\n");
|
|
12479
|
+
}
|
|
12480
|
+
}
|
|
12481
|
+
if (pending.trim()) {
|
|
12482
|
+
try {
|
|
12483
|
+
yield JSON.parse(pending.replace(/\r$/, ""));
|
|
12484
|
+
} catch (error) {
|
|
12485
|
+
logBatchInstrumentationError("skipped malformed JSONL", error);
|
|
12486
|
+
onIssue(new Error("OpenAI Batch file contains malformed JSONL"));
|
|
12487
|
+
}
|
|
12488
|
+
}
|
|
12489
|
+
} catch (error) {
|
|
12490
|
+
logBatchInstrumentationError("could not read JSONL response body", error);
|
|
12491
|
+
onIssue(new Error("OpenAI Batch response body could not be read"));
|
|
12492
|
+
} finally {
|
|
12493
|
+
const releaseLock = read(reader, "releaseLock");
|
|
12494
|
+
if (typeof releaseLock === "function") {
|
|
12495
|
+
try {
|
|
12496
|
+
Reflect.apply(releaseLock, reader, []);
|
|
12497
|
+
} catch (error) {
|
|
12498
|
+
logBatchInstrumentationError(
|
|
12499
|
+
"could not release stream reader",
|
|
12500
|
+
error
|
|
12501
|
+
);
|
|
12502
|
+
}
|
|
12503
|
+
}
|
|
12504
|
+
}
|
|
12505
|
+
return;
|
|
12506
|
+
}
|
|
12507
|
+
if (isBatchRecordIterable(resolvedFile)) {
|
|
12508
|
+
for await (const record of resolvedFile) {
|
|
12509
|
+
yield record;
|
|
12510
|
+
}
|
|
12511
|
+
return;
|
|
12512
|
+
}
|
|
12513
|
+
logBatchInstrumentationError("skipped invalid JSONL source", resolvedFile);
|
|
12514
|
+
onIssue(new Error("OpenAI Batch file source is invalid"));
|
|
12515
|
+
}
|
|
12516
|
+
async function readBatchInputs(file) {
|
|
12517
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
12518
|
+
const issues = [];
|
|
12519
|
+
let endpoint;
|
|
12520
|
+
try {
|
|
12521
|
+
for await (const value of jsonlRecords(
|
|
12522
|
+
file,
|
|
12523
|
+
(issue) => issues.push(issue)
|
|
12524
|
+
)) {
|
|
12525
|
+
const customId = read(value, "custom_id");
|
|
12526
|
+
const url = read(value, "url");
|
|
12527
|
+
const body = read(value, "body");
|
|
12528
|
+
if (!validCustomId(customId) || inputs.has(customId) || read(value, "method") !== "POST" || typeof url !== "string" || !SUPPORTED_ENDPOINTS.has(url) || endpoint !== void 0 && endpoint !== url || !isObject(body)) {
|
|
12529
|
+
issues.push(new Error("OpenAI Batch input contains an invalid record"));
|
|
12530
|
+
continue;
|
|
12531
|
+
}
|
|
12532
|
+
endpoint = url;
|
|
12533
|
+
let spanData;
|
|
12534
|
+
try {
|
|
12535
|
+
spanData = extractOpenAIBatchInput(url, body);
|
|
12536
|
+
} catch (error) {
|
|
12537
|
+
logBatchInstrumentationError("could not extract batch input", error);
|
|
12538
|
+
}
|
|
12539
|
+
inputs.set(customId, { customId, spanData });
|
|
12540
|
+
}
|
|
12541
|
+
} catch (error) {
|
|
12542
|
+
logBatchInstrumentationError("could not process input file", error);
|
|
12543
|
+
issues.push(new Error("OpenAI Batch input file could not be processed"));
|
|
12544
|
+
}
|
|
12545
|
+
if (!endpoint || inputs.size === 0) {
|
|
12546
|
+
issues.push(new Error("OpenAI Batch input contains no supported records"));
|
|
12547
|
+
}
|
|
12548
|
+
return { endpoint, inputs, issues };
|
|
12549
|
+
}
|
|
12550
|
+
async function writePendingSpans(trace, endTime) {
|
|
12551
|
+
const task = await startBatchSpan(trace.context);
|
|
12552
|
+
const taskParent = await task.export();
|
|
12553
|
+
for (const input of trace.inputs.values()) {
|
|
12554
|
+
try {
|
|
12555
|
+
const child = await startBatchChild(trace.context, taskParent, input);
|
|
12556
|
+
if (endTime !== void 0) {
|
|
12557
|
+
child.end({ endTime });
|
|
12558
|
+
}
|
|
12559
|
+
} catch (error) {
|
|
12560
|
+
logBatchInstrumentationError("could not write batch request span", error);
|
|
12561
|
+
}
|
|
12562
|
+
}
|
|
12563
|
+
if (endTime !== void 0) {
|
|
12564
|
+
if (trace.status && trace.status !== "completed") {
|
|
12565
|
+
task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
|
|
12566
|
+
}
|
|
12567
|
+
task.end({ endTime });
|
|
12568
|
+
}
|
|
12569
|
+
}
|
|
12570
|
+
var interceptOpenAIFilesCreateTraced = async (target, thisArg, args) => {
|
|
12571
|
+
const startTime = getCurrentUnixTimestamp();
|
|
12572
|
+
const inputPromise = readBatchInputs(args[0].inputFileContent);
|
|
12573
|
+
const parentPromise = exportParent(args[0].parent);
|
|
12574
|
+
const file = await Reflect.apply(target, thisArg, args);
|
|
12575
|
+
try {
|
|
12576
|
+
const inputFileId = read(file, "id");
|
|
12577
|
+
const [inputData, exportedParent2] = await Promise.all([
|
|
12578
|
+
inputPromise,
|
|
12579
|
+
parentPromise
|
|
12580
|
+
]);
|
|
12581
|
+
if (typeof inputFileId !== "string" || !exportedParent2 || !inputData.endpoint || inputData.issues.length > 0) {
|
|
12582
|
+
if (inputData.issues[0]) {
|
|
12583
|
+
logBatchInstrumentationError(
|
|
12584
|
+
"skipped invalid input file",
|
|
12585
|
+
inputData.issues[0]
|
|
12586
|
+
);
|
|
12587
|
+
}
|
|
12588
|
+
return file;
|
|
12589
|
+
}
|
|
12590
|
+
const trace = {
|
|
12591
|
+
context: {
|
|
12592
|
+
inputFileId,
|
|
12593
|
+
endpoint: inputData.endpoint,
|
|
12594
|
+
parent: exportedParent2,
|
|
12595
|
+
taskStartTime: startTime,
|
|
12596
|
+
childStartTime: startTime
|
|
12597
|
+
},
|
|
12598
|
+
inputs: inputData.inputs
|
|
12599
|
+
};
|
|
12600
|
+
pendingBatchTraces.set(inputFileId, trace);
|
|
12601
|
+
await writePendingSpans(trace);
|
|
12602
|
+
} catch (error) {
|
|
12603
|
+
logBatchInstrumentationError("could not start batch spans", error);
|
|
12604
|
+
}
|
|
12605
|
+
return file;
|
|
12134
12606
|
};
|
|
12135
|
-
function
|
|
12136
|
-
|
|
12137
|
-
|
|
12607
|
+
function validTimestamp(value) {
|
|
12608
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
12609
|
+
}
|
|
12610
|
+
function terminalEndTime(batch, startTime) {
|
|
12611
|
+
let timestamp;
|
|
12612
|
+
switch (batch.status) {
|
|
12613
|
+
case "completed":
|
|
12614
|
+
timestamp = batch.completed_at;
|
|
12615
|
+
break;
|
|
12616
|
+
case "failed":
|
|
12617
|
+
timestamp = batch.failed_at;
|
|
12618
|
+
break;
|
|
12619
|
+
case "expired":
|
|
12620
|
+
timestamp = batch.expired_at;
|
|
12621
|
+
break;
|
|
12622
|
+
default:
|
|
12623
|
+
timestamp = batch.cancelled_at;
|
|
12138
12624
|
}
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12625
|
+
return validTimestamp(timestamp) ? Math.max(timestamp, startTime) : Math.max(getCurrentUnixTimestamp(), startTime);
|
|
12626
|
+
}
|
|
12627
|
+
async function updateBatchTimestamps(batch) {
|
|
12628
|
+
const trace = pendingBatchTraces.get(batch.input_file_id);
|
|
12629
|
+
if (!trace || trace.context.endpoint !== batch.endpoint) {
|
|
12630
|
+
return;
|
|
12631
|
+
}
|
|
12632
|
+
if (validTimestamp(batch.created_at)) {
|
|
12633
|
+
trace.context.taskStartTime = batch.created_at;
|
|
12634
|
+
}
|
|
12635
|
+
trace.context.childStartTime = validTimestamp(batch.in_progress_at) ? Math.max(batch.in_progress_at, trace.context.taskStartTime) : trace.context.taskStartTime;
|
|
12636
|
+
trace.status = batch.status;
|
|
12637
|
+
if (TERMINAL_STATUSES.has(batch.status)) {
|
|
12638
|
+
trace.endTime = terminalEndTime(batch, trace.context.childStartTime);
|
|
12639
|
+
}
|
|
12640
|
+
await writePendingSpans(trace, trace.endTime);
|
|
12641
|
+
}
|
|
12642
|
+
var interceptOpenAIBatchesRetrieveTraced = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (batch) => {
|
|
12643
|
+
try {
|
|
12644
|
+
await updateBatchTimestamps(batch);
|
|
12645
|
+
} catch (error) {
|
|
12646
|
+
logBatchInstrumentationError("could not update batch timestamps", error);
|
|
12647
|
+
}
|
|
12648
|
+
return batch;
|
|
12649
|
+
});
|
|
12650
|
+
function errorFromResult(result) {
|
|
12651
|
+
const error = read(result.value, "error");
|
|
12652
|
+
if (isObject(error)) {
|
|
12653
|
+
const message = read(error, "message");
|
|
12654
|
+
return new Error(
|
|
12655
|
+
typeof message === "string" ? message : "OpenAI Batch request failed"
|
|
12656
|
+
);
|
|
12657
|
+
}
|
|
12658
|
+
const response = read(result.value, "response");
|
|
12659
|
+
const statusCode = read(response, "status_code");
|
|
12660
|
+
if (result.source === "error" || typeof statusCode === "number" && (statusCode < 200 || statusCode >= 300)) {
|
|
12661
|
+
const message = read(read(read(response, "body"), "error"), "message");
|
|
12662
|
+
return new Error(
|
|
12663
|
+
typeof message === "string" ? message : "OpenAI Batch request failed"
|
|
12664
|
+
);
|
|
12665
|
+
}
|
|
12666
|
+
return void 0;
|
|
12667
|
+
}
|
|
12668
|
+
async function completeBatchResult(context, taskParent, endTime, input, result) {
|
|
12669
|
+
const child = await startBatchChild(context, taskParent, input);
|
|
12670
|
+
try {
|
|
12671
|
+
const resultError = errorFromResult(result);
|
|
12672
|
+
const responseBody = read(read(result.value, "response"), "body");
|
|
12673
|
+
if (resultError) {
|
|
12674
|
+
child.log({ error: resultError });
|
|
12675
|
+
} else if (isObject(responseBody)) {
|
|
12676
|
+
const model = read(responseBody, "model");
|
|
12677
|
+
child.log({
|
|
12678
|
+
output: context.endpoint === "/v1/chat/completions" ? read(responseBody, "choices") : processImagesInOutput(read(responseBody, "output")),
|
|
12679
|
+
...typeof model === "string" ? { metadata: { model } } : {},
|
|
12680
|
+
metrics: parseMetricsFromUsage(read(responseBody, "usage"))
|
|
12681
|
+
});
|
|
12682
|
+
} else {
|
|
12683
|
+
child.log({ error: new Error("OpenAI Batch response body is missing") });
|
|
12684
|
+
}
|
|
12685
|
+
} catch (error) {
|
|
12686
|
+
child.log({ error });
|
|
12687
|
+
} finally {
|
|
12688
|
+
child.end({ endTime });
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
async function completeResultFile({
|
|
12692
|
+
context,
|
|
12693
|
+
endTime,
|
|
12694
|
+
file,
|
|
12695
|
+
inputs,
|
|
12696
|
+
issues,
|
|
12697
|
+
seen,
|
|
12698
|
+
source,
|
|
12699
|
+
taskParent
|
|
12700
|
+
}) {
|
|
12701
|
+
if (file === void 0) {
|
|
12702
|
+
return;
|
|
12703
|
+
}
|
|
12704
|
+
for await (const value of jsonlRecords(file, (issue) => issues.push(issue))) {
|
|
12705
|
+
const customId = read(value, "custom_id");
|
|
12706
|
+
if (!validCustomId(customId) || !isObject(value)) {
|
|
12707
|
+
issues.push(
|
|
12708
|
+
new Error("OpenAI Batch result is missing a valid custom_id")
|
|
12709
|
+
);
|
|
12144
12710
|
continue;
|
|
12145
12711
|
}
|
|
12146
|
-
if (
|
|
12712
|
+
if (seen.has(customId)) {
|
|
12713
|
+
issues.push(new Error("OpenAI Batch result contains a duplicate"));
|
|
12147
12714
|
continue;
|
|
12148
12715
|
}
|
|
12149
|
-
const
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
metrics[`${prefix}_${key}`] = nestedValue;
|
|
12716
|
+
const input = inputs.get(customId);
|
|
12717
|
+
if (!input) {
|
|
12718
|
+
issues.push(
|
|
12719
|
+
new Error("OpenAI Batch result does not match an input record")
|
|
12720
|
+
);
|
|
12721
|
+
continue;
|
|
12156
12722
|
}
|
|
12723
|
+
seen.add(customId);
|
|
12724
|
+
await completeBatchResult(context, taskParent, endTime, input, {
|
|
12725
|
+
value,
|
|
12726
|
+
source
|
|
12727
|
+
});
|
|
12157
12728
|
}
|
|
12158
|
-
return metrics;
|
|
12159
12729
|
}
|
|
12160
|
-
function
|
|
12161
|
-
|
|
12730
|
+
function sameInputs(first, second) {
|
|
12731
|
+
return first.size === second.size && [...first.keys()].every((customId) => second.has(customId));
|
|
12732
|
+
}
|
|
12733
|
+
async function contextForCompletion(inputFileId, inputData) {
|
|
12734
|
+
if (!inputData.endpoint || inputData.issues.length > 0) {
|
|
12162
12735
|
return void 0;
|
|
12163
12736
|
}
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12167
|
-
|
|
12737
|
+
const existing = pendingBatchTraces.get(inputFileId);
|
|
12738
|
+
if (existing) {
|
|
12739
|
+
if (existing.context.endpoint !== inputData.endpoint || !sameInputs(existing.inputs, inputData.inputs)) {
|
|
12740
|
+
return void 0;
|
|
12741
|
+
}
|
|
12742
|
+
return { ...existing, inputs: inputData.inputs };
|
|
12743
|
+
}
|
|
12744
|
+
const parent = await exportParent(getSpanParentObject());
|
|
12745
|
+
if (!parent) {
|
|
12168
12746
|
return void 0;
|
|
12169
12747
|
}
|
|
12170
|
-
const
|
|
12171
|
-
|
|
12172
|
-
|
|
12748
|
+
const startTime = getCurrentUnixTimestamp();
|
|
12749
|
+
return {
|
|
12750
|
+
context: {
|
|
12751
|
+
inputFileId,
|
|
12752
|
+
endpoint: inputData.endpoint,
|
|
12753
|
+
parent,
|
|
12754
|
+
taskStartTime: startTime,
|
|
12755
|
+
childStartTime: startTime
|
|
12756
|
+
},
|
|
12757
|
+
inputs: inputData.inputs
|
|
12758
|
+
};
|
|
12759
|
+
}
|
|
12760
|
+
async function completeBatch(args) {
|
|
12761
|
+
const inputData = await readBatchInputs(args.inputFileContent);
|
|
12762
|
+
const trace = await contextForCompletion(args.inputFileId, inputData);
|
|
12763
|
+
if (!trace) {
|
|
12764
|
+
logBatchInstrumentationError(
|
|
12765
|
+
"left batch spans pending",
|
|
12766
|
+
inputData.issues[0] ?? new Error("OpenAI Batch input does not match")
|
|
12767
|
+
);
|
|
12768
|
+
return;
|
|
12173
12769
|
}
|
|
12174
|
-
|
|
12770
|
+
const endTime = trace.endTime ?? getCurrentUnixTimestamp();
|
|
12771
|
+
const task = await startBatchSpan(trace.context);
|
|
12772
|
+
const taskParent = await task.export();
|
|
12773
|
+
const issues = [];
|
|
12774
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12775
|
+
await Promise.all([
|
|
12776
|
+
completeResultFile({
|
|
12777
|
+
context: trace.context,
|
|
12778
|
+
endTime,
|
|
12779
|
+
file: args.outputFileContent,
|
|
12780
|
+
inputs: trace.inputs,
|
|
12781
|
+
issues,
|
|
12782
|
+
seen,
|
|
12783
|
+
source: "output",
|
|
12784
|
+
taskParent
|
|
12785
|
+
}),
|
|
12786
|
+
completeResultFile({
|
|
12787
|
+
context: trace.context,
|
|
12788
|
+
endTime,
|
|
12789
|
+
file: args.errorFileContent,
|
|
12790
|
+
inputs: trace.inputs,
|
|
12791
|
+
issues,
|
|
12792
|
+
seen,
|
|
12793
|
+
source: "error",
|
|
12794
|
+
taskParent
|
|
12795
|
+
})
|
|
12796
|
+
]);
|
|
12797
|
+
if (issues.length > 0) {
|
|
12798
|
+
logBatchInstrumentationError("left batch spans pending", issues[0]);
|
|
12799
|
+
return;
|
|
12800
|
+
}
|
|
12801
|
+
const missing = [...trace.inputs.values()].filter(
|
|
12802
|
+
({ customId }) => !seen.has(customId)
|
|
12803
|
+
);
|
|
12804
|
+
if (!trace.status || trace.status === "completed") {
|
|
12805
|
+
if (missing.length > 0) {
|
|
12806
|
+
logBatchInstrumentationError(
|
|
12807
|
+
"left batch spans pending",
|
|
12808
|
+
new Error("OpenAI Batch result files are incomplete")
|
|
12809
|
+
);
|
|
12810
|
+
return;
|
|
12811
|
+
}
|
|
12812
|
+
} else {
|
|
12813
|
+
for (const input of missing) {
|
|
12814
|
+
const child = await startBatchChild(trace.context, taskParent, input);
|
|
12815
|
+
child.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
|
|
12816
|
+
child.end({ endTime });
|
|
12817
|
+
}
|
|
12818
|
+
task.log({ error: new Error(`OpenAI Batch ${trace.status}`) });
|
|
12819
|
+
}
|
|
12820
|
+
task.end({ endTime });
|
|
12821
|
+
pendingBatchTraces.delete(args.inputFileId);
|
|
12175
12822
|
}
|
|
12823
|
+
var interceptOpenAIBatchTraceComplete = (target, thisArg, args) => Promise.resolve(Reflect.apply(target, thisArg, args)).then(async (result) => {
|
|
12824
|
+
try {
|
|
12825
|
+
await completeBatch(args[0]);
|
|
12826
|
+
} catch (error) {
|
|
12827
|
+
logBatchInstrumentationError("could not complete batch", error);
|
|
12828
|
+
}
|
|
12829
|
+
return result;
|
|
12830
|
+
});
|
|
12176
12831
|
|
|
12177
12832
|
// src/instrumentation/plugins/openai-plugin.ts
|
|
12178
12833
|
var OpenAIPlugin = class extends BasePlugin {
|
|
@@ -12180,17 +12835,22 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12180
12835
|
super();
|
|
12181
12836
|
}
|
|
12182
12837
|
onEnable() {
|
|
12838
|
+
this.unsubscribers.push(
|
|
12839
|
+
openAIChannels.filesCreateTraced.intercept(
|
|
12840
|
+
interceptOpenAIFilesCreateTraced
|
|
12841
|
+
),
|
|
12842
|
+
openAIChannels.batchesRetrieveTraced.intercept(
|
|
12843
|
+
interceptOpenAIBatchesRetrieveTraced
|
|
12844
|
+
),
|
|
12845
|
+
openAIChannels.batchesCompleteTrace.intercept(
|
|
12846
|
+
interceptOpenAIBatchTraceComplete
|
|
12847
|
+
)
|
|
12848
|
+
);
|
|
12183
12849
|
this.unsubscribers.push(
|
|
12184
12850
|
traceStreamingChannel(openAIChannels.chatCompletionsCreate, {
|
|
12185
12851
|
name: "Chat Completion",
|
|
12186
12852
|
type: "llm" /* LLM */,
|
|
12187
|
-
extractInput: ([params]) =>
|
|
12188
|
-
const { messages, ...metadata } = params;
|
|
12189
|
-
return {
|
|
12190
|
-
input: processInputAttachments(messages),
|
|
12191
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12192
|
-
};
|
|
12193
|
-
},
|
|
12853
|
+
extractInput: ([params]) => extractOpenAIChatInput(params),
|
|
12194
12854
|
extractOutput: (result) => {
|
|
12195
12855
|
return result?.choices;
|
|
12196
12856
|
},
|
|
@@ -12236,13 +12896,7 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12236
12896
|
traceStreamingChannel(openAIChannels.betaChatCompletionsParse, {
|
|
12237
12897
|
name: "Chat Completion",
|
|
12238
12898
|
type: "llm" /* LLM */,
|
|
12239
|
-
extractInput: ([params]) =>
|
|
12240
|
-
const { messages, ...metadata } = params;
|
|
12241
|
-
return {
|
|
12242
|
-
input: processInputAttachments(messages),
|
|
12243
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12244
|
-
};
|
|
12245
|
-
},
|
|
12899
|
+
extractInput: ([params]) => extractOpenAIChatInput(params),
|
|
12246
12900
|
extractOutput: (result) => {
|
|
12247
12901
|
return result?.choices;
|
|
12248
12902
|
},
|
|
@@ -12264,13 +12918,7 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12264
12918
|
traceSyncStreamChannel(openAIChannels.betaChatCompletionsStream, {
|
|
12265
12919
|
name: "Chat Completion",
|
|
12266
12920
|
type: "llm" /* LLM */,
|
|
12267
|
-
extractInput: ([params]) =>
|
|
12268
|
-
const { messages, ...metadata } = params;
|
|
12269
|
-
return {
|
|
12270
|
-
input: processInputAttachments(messages),
|
|
12271
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12272
|
-
};
|
|
12273
|
-
}
|
|
12921
|
+
extractInput: ([params]) => extractOpenAIChatInput(params)
|
|
12274
12922
|
})
|
|
12275
12923
|
);
|
|
12276
12924
|
this.unsubscribers.push(
|
|
@@ -12300,23 +12948,11 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12300
12948
|
traceStreamingChannel(openAIChannels.responsesCreate, {
|
|
12301
12949
|
name: "openai.responses.create",
|
|
12302
12950
|
type: "llm" /* LLM */,
|
|
12303
|
-
extractInput: ([params]) =>
|
|
12304
|
-
const { input, ...metadata } = params;
|
|
12305
|
-
return {
|
|
12306
|
-
input: processInputAttachments(input),
|
|
12307
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12308
|
-
};
|
|
12309
|
-
},
|
|
12951
|
+
extractInput: ([params]) => extractOpenAIResponsesInput(params),
|
|
12310
12952
|
extractOutput: (result) => {
|
|
12311
12953
|
return processImagesInOutput(result?.output);
|
|
12312
12954
|
},
|
|
12313
|
-
extractMetadata: (result) =>
|
|
12314
|
-
if (!result) {
|
|
12315
|
-
return void 0;
|
|
12316
|
-
}
|
|
12317
|
-
const { output: _output, usage: _usage, ...metadata } = result;
|
|
12318
|
-
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
12319
|
-
},
|
|
12955
|
+
extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
|
|
12320
12956
|
extractMetrics: (result, startTime, endEvent) => {
|
|
12321
12957
|
const metrics = withCachedMetric(
|
|
12322
12958
|
parseMetricsFromUsage(result?.usage),
|
|
@@ -12335,13 +12971,7 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12335
12971
|
traceSyncStreamChannel(openAIChannels.responsesStream, {
|
|
12336
12972
|
name: "openai.responses.create",
|
|
12337
12973
|
type: "llm" /* LLM */,
|
|
12338
|
-
extractInput: ([params]) =>
|
|
12339
|
-
const { input, ...metadata } = params;
|
|
12340
|
-
return {
|
|
12341
|
-
input: processInputAttachments(input),
|
|
12342
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12343
|
-
};
|
|
12344
|
-
},
|
|
12974
|
+
extractInput: ([params]) => extractOpenAIResponsesInput(params),
|
|
12345
12975
|
extractFromEvent: (event) => {
|
|
12346
12976
|
if (event.type !== "response.completed" || !event.response) {
|
|
12347
12977
|
return {};
|
|
@@ -12364,23 +12994,11 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12364
12994
|
traceStreamingChannel(openAIChannels.responsesParse, {
|
|
12365
12995
|
name: "openai.responses.parse",
|
|
12366
12996
|
type: "llm" /* LLM */,
|
|
12367
|
-
extractInput: ([params]) =>
|
|
12368
|
-
const { input, ...metadata } = params;
|
|
12369
|
-
return {
|
|
12370
|
-
input: processInputAttachments(input),
|
|
12371
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12372
|
-
};
|
|
12373
|
-
},
|
|
12997
|
+
extractInput: ([params]) => extractOpenAIResponsesInput(params),
|
|
12374
12998
|
extractOutput: (result) => {
|
|
12375
12999
|
return processImagesInOutput(result?.output);
|
|
12376
13000
|
},
|
|
12377
|
-
extractMetadata: (result) =>
|
|
12378
|
-
if (!result) {
|
|
12379
|
-
return void 0;
|
|
12380
|
-
}
|
|
12381
|
-
const { output: _output, usage: _usage, ...metadata } = result;
|
|
12382
|
-
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
12383
|
-
},
|
|
13001
|
+
extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
|
|
12384
13002
|
extractMetrics: (result, startTime, endEvent) => {
|
|
12385
13003
|
const metrics = withCachedMetric(
|
|
12386
13004
|
parseMetricsFromUsage(result?.usage),
|
|
@@ -12399,23 +13017,11 @@ var OpenAIPlugin = class extends BasePlugin {
|
|
|
12399
13017
|
traceAsyncChannel(openAIChannels.responsesCompact, {
|
|
12400
13018
|
name: "openai.responses.compact",
|
|
12401
13019
|
type: "llm" /* LLM */,
|
|
12402
|
-
extractInput: ([params]) =>
|
|
12403
|
-
const { input, ...metadata } = params;
|
|
12404
|
-
return {
|
|
12405
|
-
input: processInputAttachments(input),
|
|
12406
|
-
metadata: { ...metadata, provider: "openai" }
|
|
12407
|
-
};
|
|
12408
|
-
},
|
|
13020
|
+
extractInput: ([params]) => extractOpenAIResponsesInput(params),
|
|
12409
13021
|
extractOutput: (result) => {
|
|
12410
13022
|
return processImagesInOutput(result?.output);
|
|
12411
13023
|
},
|
|
12412
|
-
extractMetadata: (result) =>
|
|
12413
|
-
if (!result) {
|
|
12414
|
-
return void 0;
|
|
12415
|
-
}
|
|
12416
|
-
const { output: _output, usage: _usage, ...metadata } = result;
|
|
12417
|
-
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
12418
|
-
},
|
|
13024
|
+
extractMetadata: (result) => extractOpenAIResponsesMetadata(result),
|
|
12419
13025
|
extractMetrics: (result, startTime, endEvent) => {
|
|
12420
13026
|
const metrics = withCachedMetric(
|
|
12421
13027
|
parseMetricsFromUsage(result?.usage),
|
|
@@ -12471,35 +13077,6 @@ function withCachedMetric(metrics, result, endEvent) {
|
|
|
12471
13077
|
cached
|
|
12472
13078
|
};
|
|
12473
13079
|
}
|
|
12474
|
-
function processImagesInOutput(output) {
|
|
12475
|
-
if (Array.isArray(output)) {
|
|
12476
|
-
return output.map(processImagesInOutput);
|
|
12477
|
-
}
|
|
12478
|
-
if (isObject(output)) {
|
|
12479
|
-
if (output.type === "image_generation_call" && output.result && typeof output.result === "string") {
|
|
12480
|
-
const fileExtension = output.output_format || "png";
|
|
12481
|
-
const contentType = `image/${fileExtension}`;
|
|
12482
|
-
const baseFilename = output.revised_prompt && typeof output.revised_prompt === "string" ? output.revised_prompt.slice(0, 50).replace(/[^a-zA-Z0-9]/g, "_") : "generated_image";
|
|
12483
|
-
const filename = `${baseFilename}.${fileExtension}`;
|
|
12484
|
-
const binaryString = atob(output.result);
|
|
12485
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
12486
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
12487
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
12488
|
-
}
|
|
12489
|
-
const blob = new Blob([bytes], { type: contentType });
|
|
12490
|
-
const attachment = new Attachment({
|
|
12491
|
-
data: blob,
|
|
12492
|
-
filename,
|
|
12493
|
-
contentType
|
|
12494
|
-
});
|
|
12495
|
-
return {
|
|
12496
|
-
...output,
|
|
12497
|
-
result: attachment
|
|
12498
|
-
};
|
|
12499
|
-
}
|
|
12500
|
-
}
|
|
12501
|
-
return output;
|
|
12502
|
-
}
|
|
12503
13080
|
function mergeLogprobTokens(existing, incoming) {
|
|
12504
13081
|
if (incoming === void 0) {
|
|
12505
13082
|
return existing;
|
|
@@ -32519,7 +33096,7 @@ function extractSession(event) {
|
|
|
32519
33096
|
return isObject(candidate) && typeof candidate.prompt === "function" ? candidate : void 0;
|
|
32520
33097
|
}
|
|
32521
33098
|
function isPiAgent(value) {
|
|
32522
|
-
return isObject(value) && typeof value.streamFn === "function" && typeof value.subscribe === "function";
|
|
33099
|
+
return isObject(value) && (typeof value.streamFunction === "function" || typeof value.streamFn === "function") && typeof value.subscribe === "function";
|
|
32523
33100
|
}
|
|
32524
33101
|
function promptContextStore() {
|
|
32525
33102
|
piPromptContextStore ??= isomorph_default.newAsyncLocalStorage();
|
|
@@ -32529,17 +33106,21 @@ function currentPiPromptState() {
|
|
|
32529
33106
|
return promptContextStore().getStore();
|
|
32530
33107
|
}
|
|
32531
33108
|
function installPiAgentInstrumentation(agent) {
|
|
33109
|
+
const property = typeof agent.streamFunction === "function" ? "streamFunction" : "streamFn";
|
|
33110
|
+
const streamFunction = agent[property];
|
|
32532
33111
|
const existing = piAgentPatchStates.get(agent);
|
|
32533
|
-
if (!existing ||
|
|
33112
|
+
if (streamFunction && (!existing || existing.property !== property || streamFunction !== existing.wrappedStreamFunction)) {
|
|
32534
33113
|
const patchState = {
|
|
32535
|
-
|
|
32536
|
-
|
|
33114
|
+
originalStreamFunction: streamFunction,
|
|
33115
|
+
property,
|
|
33116
|
+
wrappedStreamFunction: streamFunction
|
|
32537
33117
|
};
|
|
32538
|
-
patchState.
|
|
33118
|
+
patchState.wrappedStreamFunction = makeInstrumentedStreamFunction(
|
|
32539
33119
|
agent,
|
|
32540
|
-
patchState.
|
|
33120
|
+
patchState.originalStreamFunction,
|
|
33121
|
+
property
|
|
32541
33122
|
);
|
|
32542
|
-
agent
|
|
33123
|
+
agent[property] = patchState.wrappedStreamFunction;
|
|
32543
33124
|
piAgentPatchStates.set(agent, patchState);
|
|
32544
33125
|
}
|
|
32545
33126
|
if (piAgentEventSubscriptions.has(agent)) {
|
|
@@ -32564,15 +33145,21 @@ function installPiAgentInstrumentation(agent) {
|
|
|
32564
33145
|
logInstrumentationError4("Pi Coding Agent event subscription", error);
|
|
32565
33146
|
}
|
|
32566
33147
|
}
|
|
32567
|
-
function
|
|
32568
|
-
return async function
|
|
32569
|
-
const invokeOriginal = () => Reflect.apply(
|
|
33148
|
+
function makeInstrumentedStreamFunction(agent, originalStreamFunction, property) {
|
|
33149
|
+
return async function instrumentedPiStreamFunction(model, context, options) {
|
|
33150
|
+
const invokeOriginal = () => Reflect.apply(originalStreamFunction, this, [model, context, options]);
|
|
32570
33151
|
const state = currentPiPromptState();
|
|
32571
33152
|
if (!state || state.agent !== agent || state.finalized) {
|
|
32572
33153
|
return invokeOriginal();
|
|
32573
33154
|
}
|
|
32574
33155
|
wrapPiToolExecutors(context.tools);
|
|
32575
|
-
const llmState = await startPiLlmSpan(
|
|
33156
|
+
const llmState = await startPiLlmSpan(
|
|
33157
|
+
state,
|
|
33158
|
+
model,
|
|
33159
|
+
context,
|
|
33160
|
+
property,
|
|
33161
|
+
options
|
|
33162
|
+
);
|
|
32576
33163
|
try {
|
|
32577
33164
|
const stream = await runWithAutoInstrumentationSuppressed(invokeOriginal);
|
|
32578
33165
|
return patchAssistantMessageStream(stream, state, llmState);
|
|
@@ -32609,12 +33196,12 @@ function wrapPiToolExecutors(tools) {
|
|
|
32609
33196
|
}
|
|
32610
33197
|
}
|
|
32611
33198
|
}
|
|
32612
|
-
async function startPiLlmSpan(state, model, context, options) {
|
|
33199
|
+
async function startPiLlmSpan(state, model, context, property, options) {
|
|
32613
33200
|
const metadata = {
|
|
32614
33201
|
...extractModelMetadata2(model),
|
|
32615
33202
|
...extractStreamOptionsMetadata(options),
|
|
32616
33203
|
...extractToolMetadata(context.tools),
|
|
32617
|
-
"pi_coding_agent.operation":
|
|
33204
|
+
"pi_coding_agent.operation": `agent.${property}`
|
|
32618
33205
|
};
|
|
32619
33206
|
const span = startSpan(
|
|
32620
33207
|
withSpanInstrumentationName(
|
|
@@ -36925,7 +37512,15 @@ var MAX_EVE_PROVIDER_CACHE_ENTRIES = 1e4;
|
|
|
36925
37512
|
function createEveInstrumentationProvider(options = {}) {
|
|
36926
37513
|
const bridge = new EveProviderBridge(options.metadata);
|
|
36927
37514
|
return {
|
|
37515
|
+
// Eve versions before tracePolicy use capture to decide whether provider
|
|
37516
|
+
// events include content. Newer versions prefer tracePolicy when both are
|
|
37517
|
+
// present, so keep the deprecated field for backwards compatibility.
|
|
36928
37518
|
capture: "content",
|
|
37519
|
+
tracePolicy: () => ({
|
|
37520
|
+
emit: true,
|
|
37521
|
+
recordInputs: true,
|
|
37522
|
+
recordOutputs: true
|
|
37523
|
+
}),
|
|
36929
37524
|
events: {
|
|
36930
37525
|
"action.completed": (event, context) => bridge.handleActionTerminal(event, context),
|
|
36931
37526
|
"action.failed": (event, context) => bridge.handleActionTerminal(event, context),
|