@lmnr-ai/lmnr 0.8.36 → 0.8.38
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/cli.cjs +2 -2
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{dist-y1eeOsL1.cjs → dist-DGDsKbQ8.cjs} +77 -3
- package/dist/dist-DGDsKbQ8.cjs.map +1 -0
- package/dist/{dist-CKEmP0bk.mjs → dist-DVpPVAC5.mjs} +66 -4
- package/dist/dist-DVpPVAC5.mjs.map +1 -0
- package/dist/{evaluations-BQ_n7gj5.d.mts → evaluations-CzqDQADS.d.mts} +98 -30
- package/dist/{evaluations-BToNCOH0.d.cts → evaluations-F6Zzae_Y.d.cts} +98 -30
- package/dist/{file-utils-CceRl1Dm.mjs → file-utils-D3Jj4yAt.mjs} +2 -2
- package/dist/{file-utils-CceRl1Dm.mjs.map → file-utils-D3Jj4yAt.mjs.map} +1 -1
- package/dist/{file-utils-C9iU5M-4.cjs → file-utils-wA-Z77kv.cjs} +2 -2
- package/dist/{file-utils-C9iU5M-4.cjs.map → file-utils-wA-Z77kv.cjs.map} +1 -1
- package/dist/index.cjs +323 -521
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13943 -7780
- package/dist/index.d.mts +13943 -7780
- package/dist/index.mjs +325 -523
- package/dist/index.mjs.map +1 -1
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs +71 -55
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs.map +1 -1
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.cts +4410 -61
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.mts +4410 -61
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs +71 -55
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs.map +1 -1
- package/package.json +26 -26
- package/dist/dist-CKEmP0bk.mjs.map +0 -1
- package/dist/dist-y1eeOsL1.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_rolldown_runtime = require("./rolldown-runtime-CVvi-lCc.cjs");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-DGDsKbQ8.cjs");
|
|
4
4
|
const require_consts = require("./consts-DV6_Ea-8.cjs");
|
|
5
5
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
6
6
|
let _opentelemetry_context_async_hooks = require("@opentelemetry/context-async-hooks");
|
|
@@ -19,7 +19,6 @@ let _traceloop_instrumentation_openai = require("@traceloop/instrumentation-open
|
|
|
19
19
|
let _traceloop_instrumentation_pinecone = require("@traceloop/instrumentation-pinecone");
|
|
20
20
|
let _traceloop_instrumentation_qdrant = require("@traceloop/instrumentation-qdrant");
|
|
21
21
|
let _traceloop_instrumentation_together = require("@traceloop/instrumentation-together");
|
|
22
|
-
let _traceloop_instrumentation_vertexai = require("@traceloop/instrumentation-vertexai");
|
|
23
22
|
let node_child_process = require("node:child_process");
|
|
24
23
|
let node_crypto = require("node:crypto");
|
|
25
24
|
let node_fs = require("node:fs");
|
|
@@ -95,7 +94,7 @@ var LaminarDataset = class extends EvaluationDataset {
|
|
|
95
94
|
*/
|
|
96
95
|
async push(paths, recursive = false) {
|
|
97
96
|
if (!this.client) throw new Error("Client not set");
|
|
98
|
-
const { loadFromPaths } = await Promise.resolve().then(() => require("./file-utils-
|
|
97
|
+
const { loadFromPaths } = await Promise.resolve().then(() => require("./file-utils-wA-Z77kv.cjs")).then((n) => n.file_utils_exports);
|
|
99
98
|
const data = await loadFromPaths(Array.isArray(paths) ? paths : [paths], recursive);
|
|
100
99
|
if (data.length === 0) {
|
|
101
100
|
console.warn("No data to push. Skipping");
|
|
@@ -167,7 +166,10 @@ const stringifyPromptForTelemetry = (prompt) => JSON.stringify(prompt.map((messa
|
|
|
167
166
|
...message,
|
|
168
167
|
content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "file" ? {
|
|
169
168
|
...part,
|
|
170
|
-
data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data
|
|
169
|
+
data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data && typeof part.data === "object" && part.data.type === "data" && part.data.data instanceof Uint8Array ? {
|
|
170
|
+
...part.data,
|
|
171
|
+
data: convertDataContentToBase64String(part.data.data)
|
|
172
|
+
} : part.data
|
|
171
173
|
} : part)
|
|
172
174
|
})));
|
|
173
175
|
/**
|
|
@@ -604,28 +606,6 @@ const parseCacheUntil = (value) => {
|
|
|
604
606
|
* the SDK only needs both ids present to arm replay.
|
|
605
607
|
*/
|
|
606
608
|
const replayEnabledForConfig = (config) => config.replayTraceId !== null && config.cacheUntilSpanId !== null;
|
|
607
|
-
const ROLLOUT_NOTE_KEY = "rollout.note";
|
|
608
|
-
/**
|
|
609
|
-
* Resolve the debug run note from the environment.
|
|
610
|
-
*
|
|
611
|
-
* Only read when debug mode is enabled (LMNR_DEBUG truthy). `LMNR_DEBUG_RUN_NOTES_FILE`
|
|
612
|
-
* (a path to a file holding raw markdown) takes precedence over `LMNR_DEBUG_RUN_NOTES`
|
|
613
|
-
* (inline raw markdown). A missing / unreadable file is logged and skipped (returns
|
|
614
|
-
* null) rather than throwing — a bad note path must never block SDK init.
|
|
615
|
-
*
|
|
616
|
-
* Returns the raw note string, or null when debug is off or no note var is set.
|
|
617
|
-
*/
|
|
618
|
-
const resolveDebugRunNote = () => {
|
|
619
|
-
if (!isTruthy(process.env.LMNR_DEBUG)) return null;
|
|
620
|
-
const file = process.env.LMNR_DEBUG_RUN_NOTES_FILE;
|
|
621
|
-
if (file) try {
|
|
622
|
-
return (0, node_fs.readFileSync)(file, "utf-8");
|
|
623
|
-
} catch (err) {
|
|
624
|
-
logger$22.warn(`LMNR_DEBUG_RUN_NOTES_FILE=${JSON.stringify(file)} could not be read; ignoring run note: ${require_dist.errorMessage(err)}`);
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
return process.env.LMNR_DEBUG_RUN_NOTES ?? null;
|
|
628
|
-
};
|
|
629
609
|
/**
|
|
630
610
|
* Build the debug config from the environment.
|
|
631
611
|
*
|
|
@@ -1841,8 +1821,6 @@ var Laminar = class Laminar {
|
|
|
1841
1821
|
this.baseHttpUrl = `${httpUrlWithoutSlash}:${port}`;
|
|
1842
1822
|
} else this.baseHttpUrl = "";
|
|
1843
1823
|
const envMetadata = process.env.LMNR_TRACE_METADATA ? JSON.parse(process.env.LMNR_TRACE_METADATA) : {};
|
|
1844
|
-
const debugRunNote = resolveDebugRunNote();
|
|
1845
|
-
if (debugRunNote !== null) envMetadata[ROLLOUT_NOTE_KEY] = debugRunNote;
|
|
1846
1824
|
this.globalMetadata = {
|
|
1847
1825
|
...envMetadata,
|
|
1848
1826
|
...metadata ?? {}
|
|
@@ -6262,8 +6240,6 @@ const initInstrumentations = (client, suppressContentTracing, sessionRecordingOp
|
|
|
6262
6240
|
}));
|
|
6263
6241
|
instrumentations.push(new _traceloop_instrumentation_anthropic.AnthropicInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6264
6242
|
instrumentations.push(new _traceloop_instrumentation_cohere.CohereInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6265
|
-
instrumentations.push(new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6266
|
-
instrumentations.push(new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6267
6243
|
instrumentations.push(new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6268
6244
|
instrumentations.push(new _traceloop_instrumentation_pinecone.PineconeInstrumentation());
|
|
6269
6245
|
instrumentations.push(new _traceloop_instrumentation_langchain.LangChainInstrumentation({ traceContent: !suppressContentTracing }));
|
|
@@ -6315,16 +6291,6 @@ const manuallyInitInstrumentations = (client, instrumentModules, suppressContent
|
|
|
6315
6291
|
instrumentations.push(cohereInstrumentation);
|
|
6316
6292
|
cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
|
|
6317
6293
|
}
|
|
6318
|
-
if (instrumentModules?.google_vertexai) {
|
|
6319
|
-
const vertexaiInstrumentation = new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing });
|
|
6320
|
-
instrumentations.push(vertexaiInstrumentation);
|
|
6321
|
-
vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
|
|
6322
|
-
}
|
|
6323
|
-
if (instrumentModules?.google_aiplatform) {
|
|
6324
|
-
const aiplatformInstrumentation = new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing });
|
|
6325
|
-
instrumentations.push(aiplatformInstrumentation);
|
|
6326
|
-
aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
|
|
6327
|
-
}
|
|
6328
6294
|
if (instrumentModules?.bedrock) {
|
|
6329
6295
|
const bedrockInstrumentation = new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing });
|
|
6330
6296
|
instrumentations.push(bedrockInstrumentation);
|
|
@@ -7013,6 +6979,29 @@ const logger$2 = require_dist.initializeLogger();
|
|
|
7013
6979
|
const getEvaluationUrl = (projectId, evaluationId, baseUrl, frontendPort) => {
|
|
7014
6980
|
return `${require_dist.getFrontendUrl(baseUrl, frontendPort)}/project/${projectId}/evaluations/${evaluationId}`;
|
|
7015
6981
|
};
|
|
6982
|
+
const SESSION_METADATA_KEY = "rollout.session_id";
|
|
6983
|
+
/**
|
|
6984
|
+
* When this eval runs under a debug session, auto-stamp the session id into the
|
|
6985
|
+
* evaluation metadata so the created evaluation links back to it with no extra
|
|
6986
|
+
* step. The session id is resolved by the debug runtime EXACTLY like traces —
|
|
6987
|
+
* `LMNR_DEBUG_SESSION_ID` env → `.lmnr/debug-session.json` → freshly minted — so
|
|
6988
|
+
* a plain `LMNR_DEBUG=1 <run-your-eval>` groups the eval under the current debug
|
|
6989
|
+
* session (no CLI wrapper needed). `getRuntime()` is null when debug mode is off,
|
|
6990
|
+
* so the metadata is returned unchanged.
|
|
6991
|
+
*
|
|
6992
|
+
* The backend writes the `evaluation` block from this key at eval creation; notes
|
|
6993
|
+
* are attached separately as `text` blocks keyed by the same session id
|
|
6994
|
+
* (`lmnr-cli debug session add-note`). Called after `Laminar.initialize()`, so
|
|
6995
|
+
* the runtime (and its resolved session id) already exist.
|
|
6996
|
+
*/
|
|
6997
|
+
const withSessionMetadata = (metadata) => {
|
|
6998
|
+
const sessionId = getRuntime()?.sessionId ?? null;
|
|
6999
|
+
if (sessionId === null) return metadata;
|
|
7000
|
+
return {
|
|
7001
|
+
...metadata ?? {},
|
|
7002
|
+
[SESSION_METADATA_KEY]: sessionId
|
|
7003
|
+
};
|
|
7004
|
+
};
|
|
7016
7005
|
const getAverageScores = (results) => {
|
|
7017
7006
|
const perScoreValues = {};
|
|
7018
7007
|
for (const result of results) for (const key in result.scores) {
|
|
@@ -7133,7 +7122,7 @@ var Evaluation = class {
|
|
|
7133
7122
|
}
|
|
7134
7123
|
let resultDatapoints;
|
|
7135
7124
|
try {
|
|
7136
|
-
const evaluation = await this.client.evals.init(this.name, this.groupName, this.metadata);
|
|
7125
|
+
const evaluation = await this.client.evals.init(this.name, this.groupName, withSessionMetadata(this.metadata));
|
|
7137
7126
|
const url = getEvaluationUrl(evaluation.projectId, evaluation.id, this.progressReporter.baseUrl, this.frontendPort);
|
|
7138
7127
|
process.stdout.write(`\nCheck results at ${url}\n`);
|
|
7139
7128
|
this.progressReporter.start({ length: await this.getLength() });
|
|
@@ -7326,260 +7315,6 @@ async function evaluate({ data, executor, evaluators, groupName, name, metadata,
|
|
|
7326
7315
|
else return await evaluation.run();
|
|
7327
7316
|
}
|
|
7328
7317
|
//#endregion
|
|
7329
|
-
//#region src/debug/aisdk-normalize.ts
|
|
7330
|
-
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
7331
|
-
/** Mirror Rust `is_url`. */
|
|
7332
|
-
const isUrl = (data) => data.startsWith("http://") || data.startsWith("https://") || data.startsWith("data:");
|
|
7333
|
-
/** Mirror Rust `infer_image_type`. */
|
|
7334
|
-
const inferImageType = (base64) => {
|
|
7335
|
-
if (base64.startsWith("/9j/")) return "image/jpeg";
|
|
7336
|
-
if (base64.startsWith("iVBORw0KGgo")) return "image/png";
|
|
7337
|
-
if (base64.startsWith("R0lGODlh")) return "image/gif";
|
|
7338
|
-
if (base64.startsWith("UklGR")) return "image/webp";
|
|
7339
|
-
if (base64.startsWith("PHN2Zz")) return "image/svg+xml";
|
|
7340
|
-
return "image/png";
|
|
7341
|
-
};
|
|
7342
|
-
const base64FromBytes = (bytes) => {
|
|
7343
|
-
let latin1 = "";
|
|
7344
|
-
for (const b of bytes) latin1 += String.fromCharCode(b & 255);
|
|
7345
|
-
return btoa(latin1);
|
|
7346
|
-
};
|
|
7347
|
-
/**
|
|
7348
|
-
* Mirror Rust `json_value_to_string`: a JSON string stays itself; anything else
|
|
7349
|
-
* is compact-serialized. With serde `preserve_order` on, `JSON.stringify` matches
|
|
7350
|
-
* `serde_json::Value::to_string()` byte-for-byte for object key order.
|
|
7351
|
-
*/
|
|
7352
|
-
const jsonValueToString = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
7353
|
-
/** First string among the candidates (ports serde field aliases). */
|
|
7354
|
-
const firstString = (...candidates) => {
|
|
7355
|
-
for (const c of candidates) if (typeof c === "string") return c;
|
|
7356
|
-
};
|
|
7357
|
-
/**
|
|
7358
|
-
* Port of `InstrumentationChatMessageImage` (untagged) →
|
|
7359
|
-
* `from_instrumentation_content_part`'s `Image` arm. Returns the output
|
|
7360
|
-
* `ChatMessageContentPart` object, or `null` if no untagged variant matches.
|
|
7361
|
-
*/
|
|
7362
|
-
const parseImage = (e) => {
|
|
7363
|
-
if (isRecord(e.source)) {
|
|
7364
|
-
const src = e.source;
|
|
7365
|
-
if (typeof src.media_type === "string" && typeof src.data === "string") return {
|
|
7366
|
-
type: "image",
|
|
7367
|
-
mediaType: src.media_type,
|
|
7368
|
-
data: src.data
|
|
7369
|
-
};
|
|
7370
|
-
if (typeof src.url === "string") return {
|
|
7371
|
-
type: "image_url",
|
|
7372
|
-
url: src.url,
|
|
7373
|
-
detail: typeof src.detail === "string" ? src.detail : null
|
|
7374
|
-
};
|
|
7375
|
-
}
|
|
7376
|
-
if (Array.isArray(e.image) && e.image.every((n) => typeof n === "number")) {
|
|
7377
|
-
const b64 = base64FromBytes(e.image);
|
|
7378
|
-
return {
|
|
7379
|
-
type: "image_url",
|
|
7380
|
-
url: `data:${typeof e.mime_type === "string" ? e.mime_type : inferImageType(b64)};base64,${b64}`,
|
|
7381
|
-
detail: null
|
|
7382
|
-
};
|
|
7383
|
-
}
|
|
7384
|
-
if (typeof e.image === "string") {
|
|
7385
|
-
if (isUrl(e.image)) return {
|
|
7386
|
-
type: "image_url",
|
|
7387
|
-
url: e.image,
|
|
7388
|
-
detail: null
|
|
7389
|
-
};
|
|
7390
|
-
return {
|
|
7391
|
-
type: "image",
|
|
7392
|
-
data: e.image,
|
|
7393
|
-
mediaType: typeof e.mime_type === "string" ? e.mime_type : "image/png"
|
|
7394
|
-
};
|
|
7395
|
-
}
|
|
7396
|
-
return null;
|
|
7397
|
-
};
|
|
7398
|
-
/** Port of the `Document` arm (only the Base64 source shape exists). */
|
|
7399
|
-
const parseDocument = (e) => {
|
|
7400
|
-
if (!isRecord(e.source) || e.source.type !== "base64") return null;
|
|
7401
|
-
const data = e.source.data;
|
|
7402
|
-
if (typeof data !== "string") return null;
|
|
7403
|
-
return {
|
|
7404
|
-
type: "document",
|
|
7405
|
-
source: {
|
|
7406
|
-
type: "base64",
|
|
7407
|
-
media_type: firstString(e.source.media_type, e.source.mediaType) ?? "application/octet-stream",
|
|
7408
|
-
data
|
|
7409
|
-
}
|
|
7410
|
-
};
|
|
7411
|
-
};
|
|
7412
|
-
const DATA_URL_RE = /^data:((?:application|image)\/[a-zA-Z-]+);base64,/;
|
|
7413
|
-
/**
|
|
7414
|
-
* Port of the `File` arm (OpenAI file-id / base64, AI SDK v1/v2).
|
|
7415
|
-
*
|
|
7416
|
-
* Known parity gap (v7 + image-by-URL only): on AI SDK v7 the recording path
|
|
7417
|
-
* stores `ai.prompt.messages` from the user-facing `ModelMessage[]` (an image
|
|
7418
|
-
* passed by URL is a `{type:"image", image:"https://…"}` part), which the
|
|
7419
|
-
* server reshapes via `parseImage` into an `image_url`. Replay instead hashes
|
|
7420
|
-
* the LanguageModel-level prompt, where that same image is already a
|
|
7421
|
-
* `{type:"file", data:"https://…", mediaType:"image/*"}` part — this arm maps
|
|
7422
|
-
* it to `image`, not `image_url`, so the two hashes diverge and the lookup
|
|
7423
|
-
* MISSes (degrading safely to a live call). This can't be reconciled here: the
|
|
7424
|
-
* arm is a faithful port of the server's Rust `parseFile`, so on v6 (where the
|
|
7425
|
-
* server itself reshapes a file part) emitting `image_url` would instead BREAK
|
|
7426
|
-
* parity, and the replay wrapper has no signal for which SDK version recorded
|
|
7427
|
-
* the span. Plain text, multi-part text, tool calls, and base64 images all hash
|
|
7428
|
-
* identically across both paths; only URL-referenced images on v7 are affected.
|
|
7429
|
-
*/
|
|
7430
|
-
const parseFile = (e) => {
|
|
7431
|
-
if (isRecord(e.file)) {
|
|
7432
|
-
const file = e.file;
|
|
7433
|
-
if (typeof file.file_id === "string") return {
|
|
7434
|
-
type: "text",
|
|
7435
|
-
text: JSON.stringify({ file_id: file.file_id })
|
|
7436
|
-
};
|
|
7437
|
-
if (typeof file.file_data === "string") {
|
|
7438
|
-
const m = DATA_URL_RE.exec(file.file_data.slice(0, 50));
|
|
7439
|
-
return {
|
|
7440
|
-
type: "document",
|
|
7441
|
-
source: {
|
|
7442
|
-
type: "base64",
|
|
7443
|
-
media_type: m ? m[1] : "application/octet-stream",
|
|
7444
|
-
data: m ? file.file_data.slice(file.file_data.indexOf(",") + 1) : file.file_data
|
|
7445
|
-
}
|
|
7446
|
-
};
|
|
7447
|
-
}
|
|
7448
|
-
return null;
|
|
7449
|
-
}
|
|
7450
|
-
if (typeof e.data === "string") {
|
|
7451
|
-
const mediaType = firstString(e.media_type, e.mediaType, e.mime_type);
|
|
7452
|
-
if (mediaType === void 0) return null;
|
|
7453
|
-
if (mediaType.startsWith("image/")) return {
|
|
7454
|
-
type: "image",
|
|
7455
|
-
data: e.data,
|
|
7456
|
-
mediaType
|
|
7457
|
-
};
|
|
7458
|
-
return {
|
|
7459
|
-
type: "document",
|
|
7460
|
-
source: {
|
|
7461
|
-
type: "base64",
|
|
7462
|
-
media_type: mediaType,
|
|
7463
|
-
data: e.data
|
|
7464
|
-
}
|
|
7465
|
-
};
|
|
7466
|
-
}
|
|
7467
|
-
return null;
|
|
7468
|
-
};
|
|
7469
|
-
/**
|
|
7470
|
-
* Port of `serde_json::from_value::<InstrumentationChatMessageContentPart>` for a
|
|
7471
|
-
* single part, returning the mapped output `ChatMessageContentPart` object or
|
|
7472
|
-
* `null` when no variant matches (an unrecognized `type`, or a recognized tag
|
|
7473
|
-
* whose inner shape fails). `null` propagates to an all-or-nothing Text fallback.
|
|
7474
|
-
*
|
|
7475
|
-
* Output objects carry serde's serialized field names; `skip_serializing_if` is
|
|
7476
|
-
* reproduced by omitting the field (ChatMessage.tool_call_id only), and `Option`
|
|
7477
|
-
* fields WITHOUT skip (`detail`, tool-call `id`/`arguments`) always emit `null`.
|
|
7478
|
-
*/
|
|
7479
|
-
const parsePart = (el) => {
|
|
7480
|
-
if (!isRecord(el)) return null;
|
|
7481
|
-
switch (el.type) {
|
|
7482
|
-
case "text": {
|
|
7483
|
-
const text = firstString(el.text, el.input_text, el.output_text);
|
|
7484
|
-
return text === void 0 ? null : {
|
|
7485
|
-
type: "text",
|
|
7486
|
-
text
|
|
7487
|
-
};
|
|
7488
|
-
}
|
|
7489
|
-
case "image_url": {
|
|
7490
|
-
if (!isRecord(el.image_url) || typeof el.image_url.url !== "string") return null;
|
|
7491
|
-
const detail = el.image_url.detail;
|
|
7492
|
-
return {
|
|
7493
|
-
type: "image_url",
|
|
7494
|
-
url: el.image_url.url,
|
|
7495
|
-
detail: typeof detail === "string" ? detail : null
|
|
7496
|
-
};
|
|
7497
|
-
}
|
|
7498
|
-
case "image": return parseImage(el);
|
|
7499
|
-
case "document": return parseDocument(el);
|
|
7500
|
-
case "file": return parseFile(el);
|
|
7501
|
-
case "tool-call": {
|
|
7502
|
-
if (typeof el.toolName !== "string") return null;
|
|
7503
|
-
const rawInput = "input" in el ? el.input : el.args;
|
|
7504
|
-
return {
|
|
7505
|
-
type: "tool_call",
|
|
7506
|
-
name: el.toolName,
|
|
7507
|
-
id: typeof el.toolCallId === "string" ? el.toolCallId : null,
|
|
7508
|
-
arguments: rawInput === void 0 || rawInput === null ? null : rawInput
|
|
7509
|
-
};
|
|
7510
|
-
}
|
|
7511
|
-
case "tool-result":
|
|
7512
|
-
if (typeof el.toolCallId !== "string" || typeof el.toolName !== "string") return null;
|
|
7513
|
-
if (!("output" in el || "result" in el)) return null;
|
|
7514
|
-
return {
|
|
7515
|
-
type: "tool-result",
|
|
7516
|
-
toolCallId: el.toolCallId,
|
|
7517
|
-
output: "output" in el ? el.output : el.result,
|
|
7518
|
-
toolName: el.toolName
|
|
7519
|
-
};
|
|
7520
|
-
default: return null;
|
|
7521
|
-
}
|
|
7522
|
-
};
|
|
7523
|
-
/**
|
|
7524
|
-
* Port of `input_chat_messages_from_json`. `input` is the parsed
|
|
7525
|
-
* `ai.prompt.messages` array. Each message's `content` becomes either a
|
|
7526
|
-
* `ContentPartList` (when EVERY element parses as an instrumentation part) or a
|
|
7527
|
-
* Text blob (`json_value_to_string` of the whole content) otherwise. A message
|
|
7528
|
-
* missing `role`/`content` would be a server error; we skip it defensively so the
|
|
7529
|
-
* hash is still computed over the well-formed remainder.
|
|
7530
|
-
*/
|
|
7531
|
-
const inputChatMessagesFromJson = (input) => {
|
|
7532
|
-
if (!Array.isArray(input)) return [];
|
|
7533
|
-
const messages = [];
|
|
7534
|
-
for (const message of input) {
|
|
7535
|
-
if (!isRecord(message)) continue;
|
|
7536
|
-
const role = message.role;
|
|
7537
|
-
if (typeof role !== "string" || !("content" in message)) continue;
|
|
7538
|
-
const otelContent = message.content;
|
|
7539
|
-
let parts = null;
|
|
7540
|
-
if (Array.isArray(otelContent)) {
|
|
7541
|
-
parts = [];
|
|
7542
|
-
for (const el of otelContent) {
|
|
7543
|
-
const parsed = parsePart(el);
|
|
7544
|
-
if (parsed === null) {
|
|
7545
|
-
parts = null;
|
|
7546
|
-
break;
|
|
7547
|
-
}
|
|
7548
|
-
parts.push(parsed);
|
|
7549
|
-
}
|
|
7550
|
-
}
|
|
7551
|
-
const out = {
|
|
7552
|
-
role,
|
|
7553
|
-
content: parts !== null ? parts : jsonValueToString(otelContent)
|
|
7554
|
-
};
|
|
7555
|
-
if (typeof message.tool_call_id === "string") out.tool_call_id = message.tool_call_id;
|
|
7556
|
-
messages.push(out);
|
|
7557
|
-
}
|
|
7558
|
-
return messages;
|
|
7559
|
-
};
|
|
7560
|
-
/**
|
|
7561
|
-
* Reshape an AI SDK call's `options.prompt` into the reconstructed message array
|
|
7562
|
-
* the server hashes. Version-agnostic: `stringifyPromptForTelemetry` already
|
|
7563
|
-
* normalizes V2/V3/V4 prompts (Uint8Array file `data` → base64) into the stored
|
|
7564
|
-
* `ai.prompt.messages` JSON, and the reshape operates on that JSON identically
|
|
7565
|
-
* across versions.
|
|
7566
|
-
*
|
|
7567
|
-
* Returns `null` when the prompt cannot be reshaped at all (a `stringify` /
|
|
7568
|
-
* `JSON.parse` failure). The caller MUST treat `null` as "can't compute a real
|
|
7569
|
-
* key" and degrade to a live call WITHOUT latching: hashing a default/empty
|
|
7570
|
-
* payload here would key the lookup off the wrong bytes, force a spurious MISS,
|
|
7571
|
-
* and wrongly latch live mode for the rest of the replay.
|
|
7572
|
-
*/
|
|
7573
|
-
const extractInputMessages = (options) => {
|
|
7574
|
-
let aiPromptMessages;
|
|
7575
|
-
try {
|
|
7576
|
-
aiPromptMessages = JSON.parse(stringifyPromptForTelemetry(options.prompt));
|
|
7577
|
-
} catch {
|
|
7578
|
-
return null;
|
|
7579
|
-
}
|
|
7580
|
-
return inputChatMessagesFromJson(aiPromptMessages);
|
|
7581
|
-
};
|
|
7582
|
-
//#endregion
|
|
7583
7318
|
//#region src/debug/hash.ts
|
|
7584
7319
|
/**
|
|
7585
7320
|
* Debug-replay input hashing.
|
|
@@ -7587,13 +7322,13 @@ const extractInputMessages = (options) => {
|
|
|
7587
7322
|
* Computes the per-LLM-call `input_hash` the server-side debugger cache (v2) is
|
|
7588
7323
|
* keyed by. The SDK and the Rust app-server MUST produce byte-identical hashes,
|
|
7589
7324
|
* so this is a faithful port of the app-server primitives:
|
|
7590
|
-
* - `canonical_json`
|
|
7591
|
-
* - `
|
|
7592
|
-
* - `debug_input_hash` — `app-server/src/traces/debug_cache.rs` (plan 01 §3)
|
|
7325
|
+
* - `canonical_json` — `app-server/src/traces/input_dedup.rs`
|
|
7326
|
+
* - `debug_input_hash` — `app-server/src/traces/input_dedup.rs`
|
|
7593
7327
|
*
|
|
7594
|
-
* The whole non-system message array is hashed as ONE blob: strip
|
|
7595
|
-
* `role === "system"` message (
|
|
7596
|
-
* remaining array, then blake3 over its UTF-8 bytes and hex-encode the
|
|
7328
|
+
* The whole non-system message array is hashed as ONE blob: strip EVERY
|
|
7329
|
+
* `role === "system"` message (regardless of content or position), canonicalize
|
|
7330
|
+
* the remaining array, then blake3 over its UTF-8 bytes and hex-encode the
|
|
7331
|
+
* digest.
|
|
7597
7332
|
*
|
|
7598
7333
|
* Keep this file line-comparable with the Python `debug/hash.py`.
|
|
7599
7334
|
*/
|
|
@@ -7614,53 +7349,25 @@ const canonicalJson = (value) => {
|
|
|
7614
7349
|
return JSON.stringify(value);
|
|
7615
7350
|
};
|
|
7616
7351
|
/**
|
|
7617
|
-
*
|
|
7618
|
-
*
|
|
7619
|
-
* Faithful port of Rust `extract_system_message`'s text resolution: a plain
|
|
7620
|
-
* string content, else an array of `{text}` blocks joined by a space (only when
|
|
7621
|
-
* the join is non-empty), else `parts[0].text` / `parts[0].content`.
|
|
7622
|
-
*/
|
|
7623
|
-
const systemMessageText = (sysMsg) => {
|
|
7624
|
-
const content = sysMsg.content;
|
|
7625
|
-
if (typeof content === "string") return content;
|
|
7626
|
-
if (Array.isArray(content)) {
|
|
7627
|
-
const joined = content.map((block) => block !== null && typeof block === "object" ? block.text : void 0).filter((t) => typeof t === "string").join(" ");
|
|
7628
|
-
if (joined.length > 0) return joined;
|
|
7629
|
-
}
|
|
7630
|
-
const parts = sysMsg.parts;
|
|
7631
|
-
if (Array.isArray(parts) && parts.length > 0) {
|
|
7632
|
-
const first = parts[0];
|
|
7633
|
-
if (first !== null && typeof first === "object") {
|
|
7634
|
-
const f = first;
|
|
7635
|
-
const t = typeof f.text === "string" ? f.text : f.content;
|
|
7636
|
-
if (typeof t === "string") return t;
|
|
7637
|
-
}
|
|
7638
|
-
}
|
|
7639
|
-
return "";
|
|
7640
|
-
};
|
|
7641
|
-
/**
|
|
7642
|
-
* Drop the first `role === "system"` message, matching the server.
|
|
7352
|
+
* Drop EVERY `role === "system"` message, matching the server.
|
|
7643
7353
|
*
|
|
7644
|
-
*
|
|
7645
|
-
*
|
|
7646
|
-
*
|
|
7354
|
+
* Content and position are irrelevant: an empty system string, a block-array
|
|
7355
|
+
* system, or repeated/trailing system messages are all stripped. The coding
|
|
7356
|
+
* agent may add/remove/edit its own system prompt(s) between iterations and
|
|
7357
|
+
* those edits must not change the cache key.
|
|
7647
7358
|
*/
|
|
7648
|
-
const
|
|
7649
|
-
const sysIdx = messages.findIndex((m) => m !== null && typeof m === "object" && m.role === "system");
|
|
7650
|
-
if (sysIdx === -1) return messages;
|
|
7651
|
-
if (systemMessageText(messages[sysIdx]).length === 0) return messages;
|
|
7652
|
-
return messages.filter((_, i) => i !== sysIdx);
|
|
7653
|
-
};
|
|
7359
|
+
const stripSystemMessages = (messages) => messages.filter((m) => !(m !== null && typeof m === "object" && m.role === "system"));
|
|
7654
7360
|
/**
|
|
7655
|
-
* Compute the hex blake3 `input_hash` for a
|
|
7361
|
+
* Compute the hex blake3 `input_hash` for a message array.
|
|
7656
7362
|
*
|
|
7657
|
-
* `messages`
|
|
7658
|
-
*
|
|
7659
|
-
*
|
|
7660
|
-
* canonicalized, and the digest
|
|
7363
|
+
* `messages` is the verbatim LanguageModel-level prompt
|
|
7364
|
+
* (`JSON.parse(stringifyPromptForTelemetry(options.prompt))`) — the same bytes
|
|
7365
|
+
* the SDK records as `gen_ai.input.messages` and the server hashes on ingest.
|
|
7366
|
+
* System messages are stripped, the remainder canonicalized, and the digest
|
|
7367
|
+
* hex-encoded (64 chars).
|
|
7661
7368
|
*/
|
|
7662
7369
|
const debugInputHash = (messages) => {
|
|
7663
|
-
const canonical = canonicalJson(
|
|
7370
|
+
const canonical = canonicalJson(stripSystemMessages(messages));
|
|
7664
7371
|
return (0, _noble_hashes_utils_js.bytesToHex)((0, _noble_hashes_blake3_js.blake3)(new TextEncoder().encode(canonical)));
|
|
7665
7372
|
};
|
|
7666
7373
|
//#endregion
|
|
@@ -7720,6 +7427,107 @@ const removeActiveLlmSpan = (span) => {
|
|
|
7720
7427
|
/** Most recently pushed still-open LLM span, or undefined if none. */
|
|
7721
7428
|
const peekActiveLlmSpan = () => openLlmSpans.length > 0 ? openLlmSpans[openLlmSpans.length - 1] : void 0;
|
|
7722
7429
|
//#endregion
|
|
7430
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
|
|
7431
|
+
const serializeJSON$1 = (value) => {
|
|
7432
|
+
if (typeof value === "string") return value;
|
|
7433
|
+
try {
|
|
7434
|
+
return JSON.stringify(value);
|
|
7435
|
+
} catch {
|
|
7436
|
+
return "[unserializable]";
|
|
7437
|
+
}
|
|
7438
|
+
};
|
|
7439
|
+
const normalizeProvider$1 = (provider) => {
|
|
7440
|
+
if (!provider) return void 0;
|
|
7441
|
+
return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
|
|
7442
|
+
};
|
|
7443
|
+
const applyUsage = (attributes, usage) => {
|
|
7444
|
+
if (!usage || typeof usage !== "object") return;
|
|
7445
|
+
if (typeof usage.inputTokens === "number") attributes[require_dist.LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
|
|
7446
|
+
if (typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
|
|
7447
|
+
if (typeof usage.totalTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
|
|
7448
|
+
else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
7449
|
+
if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
|
|
7450
|
+
else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
|
|
7451
|
+
if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
|
|
7452
|
+
else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
|
|
7453
|
+
if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
|
|
7454
|
+
};
|
|
7455
|
+
const verbatimStandardizedMessages = (event) => {
|
|
7456
|
+
const messages = [];
|
|
7457
|
+
const sys = event.instructions || event.system;
|
|
7458
|
+
if (typeof sys === "string" && sys.length > 0) messages.push({
|
|
7459
|
+
role: "system",
|
|
7460
|
+
content: sys
|
|
7461
|
+
});
|
|
7462
|
+
else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
|
|
7463
|
+
else if (sys && typeof sys === "object") messages.push(sys);
|
|
7464
|
+
if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(m);
|
|
7465
|
+
return messages;
|
|
7466
|
+
};
|
|
7467
|
+
const buildVerbatimOutputMessages = (content) => {
|
|
7468
|
+
if (!Array.isArray(content) || content.length === 0) return void 0;
|
|
7469
|
+
try {
|
|
7470
|
+
return JSON.stringify([{
|
|
7471
|
+
role: "assistant",
|
|
7472
|
+
content
|
|
7473
|
+
}]);
|
|
7474
|
+
} catch {
|
|
7475
|
+
return;
|
|
7476
|
+
}
|
|
7477
|
+
};
|
|
7478
|
+
const buildTextOutputMessages = (text) => text.length > 0 ? buildVerbatimOutputMessages([{
|
|
7479
|
+
type: "text",
|
|
7480
|
+
text
|
|
7481
|
+
}]) : void 0;
|
|
7482
|
+
const applyUsageToSpan = (span, usage) => {
|
|
7483
|
+
const attrs = {};
|
|
7484
|
+
applyUsage(attrs, usage);
|
|
7485
|
+
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
|
|
7486
|
+
};
|
|
7487
|
+
const applyFinishReason = (span, finishReason) => {
|
|
7488
|
+
span.setAttribute("gen_ai.response.finish_reason", finishReason);
|
|
7489
|
+
span.setAttribute("ai.response.finishReason", finishReason);
|
|
7490
|
+
};
|
|
7491
|
+
const applyRequestModelAttributes = (span, event) => {
|
|
7492
|
+
if (typeof event.provider === "string") {
|
|
7493
|
+
const provider = normalizeProvider$1(event.provider);
|
|
7494
|
+
if (provider) span.setAttribute(require_dist.LaminarAttributes.PROVIDER, provider);
|
|
7495
|
+
span.setAttribute("ai.model.provider", event.provider);
|
|
7496
|
+
}
|
|
7497
|
+
if (typeof event.modelId === "string") {
|
|
7498
|
+
span.setAttribute(require_dist.LaminarAttributes.REQUEST_MODEL, event.modelId);
|
|
7499
|
+
span.setAttribute("ai.model.id", event.modelId);
|
|
7500
|
+
}
|
|
7501
|
+
};
|
|
7502
|
+
const compareHrTime = (a, b) => {
|
|
7503
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
7504
|
+
return a[1] - b[1];
|
|
7505
|
+
};
|
|
7506
|
+
const readSpanEndTime = (span) => {
|
|
7507
|
+
const innerEnd = span._span?.endTime;
|
|
7508
|
+
if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
|
|
7509
|
+
const outerEnd = span.endTime;
|
|
7510
|
+
if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
|
|
7511
|
+
};
|
|
7512
|
+
const verbatimPromptString = (prompt) => {
|
|
7513
|
+
try {
|
|
7514
|
+
const serialized = stringifyPromptForTelemetry(prompt);
|
|
7515
|
+
return typeof serialized === "string" ? serialized : null;
|
|
7516
|
+
} catch {
|
|
7517
|
+
return null;
|
|
7518
|
+
}
|
|
7519
|
+
};
|
|
7520
|
+
const verbatimPromptMessages = (prompt) => {
|
|
7521
|
+
const serialized = verbatimPromptString(prompt);
|
|
7522
|
+
if (serialized === null) return null;
|
|
7523
|
+
try {
|
|
7524
|
+
const parsed = JSON.parse(serialized);
|
|
7525
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
7526
|
+
} catch {
|
|
7527
|
+
return null;
|
|
7528
|
+
}
|
|
7529
|
+
};
|
|
7530
|
+
//#endregion
|
|
7723
7531
|
//#region src/opentelemetry-lib/instrumentation/aisdk/base-language-model.ts
|
|
7724
7532
|
/**
|
|
7725
7533
|
* Base class for Laminar language model wrappers.
|
|
@@ -7762,7 +7570,7 @@ var BaseLaminarLanguageModel = class {
|
|
|
7762
7570
|
async doGenerateOrStreamWithCaching(options, originalFn, buildFromCached) {
|
|
7763
7571
|
if (!replayEnabled() || Laminar.debugRunLive) return originalFn(options);
|
|
7764
7572
|
const spanToMark = peekActiveLlmSpan() ?? Laminar.getCurrentSpan();
|
|
7765
|
-
const messages =
|
|
7573
|
+
const messages = verbatimPromptMessages(options.prompt);
|
|
7766
7574
|
if (messages === null || messages.length === 0) return originalFn(options);
|
|
7767
7575
|
const inputHash = debugInputHash(messages);
|
|
7768
7576
|
const outcome = await getRuntime()?.lookupCache(inputHash) ?? { kind: "live" };
|
|
@@ -7810,17 +7618,45 @@ var BaseLaminarLanguageModel = class {
|
|
|
7810
7618
|
}];
|
|
7811
7619
|
if (output === null || typeof output !== "object") return [];
|
|
7812
7620
|
const handleItem = (item) => {
|
|
7813
|
-
if (item.type === "text")
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7621
|
+
if (item.type === "text") {
|
|
7622
|
+
if (typeof item.text === "string") return [{
|
|
7623
|
+
...item,
|
|
7624
|
+
type: "text",
|
|
7625
|
+
text: item.text
|
|
7626
|
+
}];
|
|
7627
|
+
return [{
|
|
7628
|
+
type: "text",
|
|
7629
|
+
text: item.content ?? ""
|
|
7630
|
+
}];
|
|
7631
|
+
}
|
|
7632
|
+
if (item.type === "tool-call") return [{
|
|
7633
|
+
...item,
|
|
7818
7634
|
type: "tool-call",
|
|
7819
7635
|
toolCallId: item.toolCallId ?? item.id,
|
|
7820
7636
|
toolName: item.toolName ?? item.name,
|
|
7821
|
-
input: JSON.stringify(item.input ?? item.arguments)
|
|
7637
|
+
input: typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? item.arguments)
|
|
7822
7638
|
}];
|
|
7823
|
-
if (
|
|
7639
|
+
if (item.type === "tool_call") {
|
|
7640
|
+
const rawInput = item.input ?? item.arguments;
|
|
7641
|
+
return [{
|
|
7642
|
+
type: "tool-call",
|
|
7643
|
+
toolCallId: item.toolCallId ?? item.id,
|
|
7644
|
+
toolName: item.toolName ?? item.name,
|
|
7645
|
+
input: typeof rawInput === "string" ? rawInput : JSON.stringify(rawInput)
|
|
7646
|
+
}];
|
|
7647
|
+
}
|
|
7648
|
+
if (item.type === "reasoning") {
|
|
7649
|
+
if (typeof item.text === "string") return [{
|
|
7650
|
+
...item,
|
|
7651
|
+
type: "reasoning",
|
|
7652
|
+
text: item.text
|
|
7653
|
+
}];
|
|
7654
|
+
return [{
|
|
7655
|
+
type: "reasoning",
|
|
7656
|
+
text: item.content ?? ""
|
|
7657
|
+
}];
|
|
7658
|
+
}
|
|
7659
|
+
if (item.type === "thinking") return [{
|
|
7824
7660
|
type: "reasoning",
|
|
7825
7661
|
text: item.text ?? item.content ?? ""
|
|
7826
7662
|
}];
|
|
@@ -7886,7 +7722,8 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7886
7722
|
const id = `text-${textIndex++}`;
|
|
7887
7723
|
parts.push({
|
|
7888
7724
|
type: "text-start",
|
|
7889
|
-
id
|
|
7725
|
+
id,
|
|
7726
|
+
providerMetadata: block.providerMetadata
|
|
7890
7727
|
});
|
|
7891
7728
|
parts.push({
|
|
7892
7729
|
type: "text-delta",
|
|
@@ -7914,16 +7751,15 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7914
7751
|
id
|
|
7915
7752
|
});
|
|
7916
7753
|
parts.push({
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
toolName: block.toolName,
|
|
7920
|
-
input: block.input
|
|
7754
|
+
...block,
|
|
7755
|
+
type: "tool-call"
|
|
7921
7756
|
});
|
|
7922
7757
|
} else if (block.type === "reasoning") {
|
|
7923
7758
|
const id = `reasoning-${reasoningIndex++}`;
|
|
7924
7759
|
parts.push({
|
|
7925
7760
|
type: "reasoning-start",
|
|
7926
|
-
id
|
|
7761
|
+
id,
|
|
7762
|
+
providerMetadata: block.providerMetadata
|
|
7927
7763
|
});
|
|
7928
7764
|
parts.push({
|
|
7929
7765
|
type: "reasoning-delta",
|
|
@@ -7988,7 +7824,8 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
7988
7824
|
const id = `text-${textIndex++}`;
|
|
7989
7825
|
parts.push({
|
|
7990
7826
|
type: "text-start",
|
|
7991
|
-
id
|
|
7827
|
+
id,
|
|
7828
|
+
providerMetadata: block.providerMetadata
|
|
7992
7829
|
});
|
|
7993
7830
|
parts.push({
|
|
7994
7831
|
type: "text-delta",
|
|
@@ -8016,16 +7853,15 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
8016
7853
|
id
|
|
8017
7854
|
});
|
|
8018
7855
|
parts.push({
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
toolName: block.toolName,
|
|
8022
|
-
input: block.input
|
|
7856
|
+
...block,
|
|
7857
|
+
type: "tool-call"
|
|
8023
7858
|
});
|
|
8024
7859
|
} else if (block.type === "reasoning") {
|
|
8025
7860
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8026
7861
|
parts.push({
|
|
8027
7862
|
type: "reasoning-start",
|
|
8028
|
-
id
|
|
7863
|
+
id,
|
|
7864
|
+
providerMetadata: block.providerMetadata
|
|
8029
7865
|
});
|
|
8030
7866
|
parts.push({
|
|
8031
7867
|
type: "reasoning-delta",
|
|
@@ -8090,7 +7926,8 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8090
7926
|
const id = `text-${textIndex++}`;
|
|
8091
7927
|
parts.push({
|
|
8092
7928
|
type: "text-start",
|
|
8093
|
-
id
|
|
7929
|
+
id,
|
|
7930
|
+
providerMetadata: block.providerMetadata
|
|
8094
7931
|
});
|
|
8095
7932
|
parts.push({
|
|
8096
7933
|
type: "text-delta",
|
|
@@ -8118,16 +7955,15 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8118
7955
|
id
|
|
8119
7956
|
});
|
|
8120
7957
|
parts.push({
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
toolName: block.toolName,
|
|
8124
|
-
input: block.input
|
|
7958
|
+
...block,
|
|
7959
|
+
type: "tool-call"
|
|
8125
7960
|
});
|
|
8126
7961
|
} else if (block.type === "reasoning") {
|
|
8127
7962
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8128
7963
|
parts.push({
|
|
8129
7964
|
type: "reasoning-start",
|
|
8130
|
-
id
|
|
7965
|
+
id,
|
|
7966
|
+
providerMetadata: block.providerMetadata
|
|
8131
7967
|
});
|
|
8132
7968
|
parts.push({
|
|
8133
7969
|
type: "reasoning-delta",
|
|
@@ -8207,136 +8043,94 @@ function wrapLanguageModel(languageModel) {
|
|
|
8207
8043
|
return new LaminarLanguageModelV2(languageModel);
|
|
8208
8044
|
}
|
|
8209
8045
|
//#endregion
|
|
8210
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
|
|
8214
|
-
const serializeJSON$1 = (value) => {
|
|
8215
|
-
if (typeof value === "string") return value;
|
|
8046
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/package-versions.ts
|
|
8047
|
+
let cached;
|
|
8048
|
+
const readPackageVersion = (specifier) => {
|
|
8216
8049
|
try {
|
|
8217
|
-
|
|
8050
|
+
const pkg = require(`${specifier}/package.json`);
|
|
8051
|
+
return typeof pkg?.version === "string" ? pkg.version : void 0;
|
|
8218
8052
|
} catch {
|
|
8219
|
-
return
|
|
8053
|
+
return;
|
|
8220
8054
|
}
|
|
8221
8055
|
};
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
else if (sys && typeof sys === "object") messages.push(sys);
|
|
8247
|
-
if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(convertRawStringTextToTextBlock(m));
|
|
8248
|
-
return messages;
|
|
8249
|
-
};
|
|
8250
|
-
const convertRawStringTextToTextBlock = (m) => ({
|
|
8251
|
-
...m,
|
|
8252
|
-
content: typeof m?.content === "string" ? [{
|
|
8253
|
-
type: "text",
|
|
8254
|
-
text: m.content
|
|
8255
|
-
}] : m?.content
|
|
8256
|
-
});
|
|
8257
|
-
const normalizeToolCalls = (toolCalls) => {
|
|
8258
|
-
if (!Array.isArray(toolCalls)) return [];
|
|
8259
|
-
return toolCalls.map((tc) => ({
|
|
8260
|
-
toolCallType: "function",
|
|
8261
|
-
toolCallId: tc?.toolCallId,
|
|
8262
|
-
toolName: tc?.toolName,
|
|
8263
|
-
args: typeof tc?.input === "string" ? tc.input : serializeJSON$1(tc?.input ?? {})
|
|
8264
|
-
}));
|
|
8265
|
-
};
|
|
8266
|
-
const buildGenAiOutputMessages = (args) => {
|
|
8267
|
-
const parts = [];
|
|
8268
|
-
if (args.reasoningText && args.reasoningText.length > 0) parts.push({
|
|
8269
|
-
type: "thinking",
|
|
8270
|
-
content: args.reasoningText
|
|
8271
|
-
});
|
|
8272
|
-
if (args.text && args.text.length > 0) parts.push({
|
|
8273
|
-
type: "text",
|
|
8274
|
-
content: args.text
|
|
8275
|
-
});
|
|
8276
|
-
for (const tc of args.toolCalls ?? []) {
|
|
8277
|
-
let argsObj = tc.args;
|
|
8278
|
-
if (typeof argsObj === "string") try {
|
|
8279
|
-
argsObj = JSON.parse(argsObj);
|
|
8056
|
+
/**
|
|
8057
|
+
* A `node_modules` search-path entry is a package-manager-internal virtual
|
|
8058
|
+
* store (pnpm's `.pnpm`, yarn's `.yarn`, etc.) if any path segment starts
|
|
8059
|
+
* with `.` — excluding the `.`/`..` relative-path segments themselves, which
|
|
8060
|
+
* are unrelated. Packages resolved from inside such a store are some
|
|
8061
|
+
* dependency's OWN transitive deps (e.g. `ai`'s `@ai-sdk/provider`), not
|
|
8062
|
+
* what the host project declared, so those entries must be skipped.
|
|
8063
|
+
*/
|
|
8064
|
+
const isPackageManagerInternalPath = (path) => path.split(node_path.sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
|
|
8065
|
+
/**
|
|
8066
|
+
* Given the ordered `node_modules` search-path list Node would use to
|
|
8067
|
+
* resolve a module from this file's location, find the closest directory
|
|
8068
|
+
* that actually has an `@ai-sdk` scope — skipping package-manager-internal
|
|
8069
|
+
* stores — and return the packages listed under it. The first path with a
|
|
8070
|
+
* usable `@ai-sdk` directory wins; levels are not merged, so an unrelated
|
|
8071
|
+
* ancestor/monorepo-root's `@ai-sdk` scope is never mixed in once a closer
|
|
8072
|
+
* one has already matched.
|
|
8073
|
+
*/
|
|
8074
|
+
const findAiSdkScopeEntries = (searchPaths) => {
|
|
8075
|
+
for (const path of searchPaths) {
|
|
8076
|
+
if (isPackageManagerInternalPath(path)) continue;
|
|
8077
|
+
try {
|
|
8078
|
+
const entries = (0, node_fs.readdirSync)((0, node_path.join)(path, "@ai-sdk"));
|
|
8079
|
+
if (entries.length > 0) return entries;
|
|
8280
8080
|
} catch {}
|
|
8281
|
-
parts.push({
|
|
8282
|
-
type: "tool_call",
|
|
8283
|
-
id: tc.toolCallId,
|
|
8284
|
-
name: tc.toolName,
|
|
8285
|
-
arguments: argsObj
|
|
8286
|
-
});
|
|
8287
8081
|
}
|
|
8288
|
-
|
|
8289
|
-
return JSON.stringify([{
|
|
8290
|
-
role: "assistant",
|
|
8291
|
-
parts
|
|
8292
|
-
}]);
|
|
8293
|
-
};
|
|
8294
|
-
const extractTextFromContent = (content) => {
|
|
8295
|
-
if (!Array.isArray(content)) return "";
|
|
8296
|
-
return content.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
8297
|
-
};
|
|
8298
|
-
const extractReasoningFromContent = (content) => {
|
|
8299
|
-
if (!Array.isArray(content)) return "";
|
|
8300
|
-
return content.filter((p) => p && p.type === "reasoning" && typeof p.text === "string").map((p) => p.text).join("");
|
|
8301
|
-
};
|
|
8302
|
-
const applyUsageToSpan = (span, usage) => {
|
|
8303
|
-
const attrs = {};
|
|
8304
|
-
applyUsage(attrs, usage);
|
|
8305
|
-
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
|
|
8082
|
+
return [];
|
|
8306
8083
|
};
|
|
8307
|
-
const
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
if (provider) span.setAttribute(require_dist.LaminarAttributes.PROVIDER, provider);
|
|
8315
|
-
span.setAttribute("ai.model.provider", event.provider);
|
|
8084
|
+
const discoverAiSdkPackages = () => {
|
|
8085
|
+
const packages = {};
|
|
8086
|
+
let searchPaths;
|
|
8087
|
+
try {
|
|
8088
|
+
searchPaths = require.resolve.paths("@ai-sdk/__probe__") ?? [];
|
|
8089
|
+
} catch {
|
|
8090
|
+
searchPaths = [];
|
|
8316
8091
|
}
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8092
|
+
for (const entry of findAiSdkScopeEntries(searchPaths)) {
|
|
8093
|
+
const specifier = `@ai-sdk/${entry}`;
|
|
8094
|
+
const version = readPackageVersion(specifier);
|
|
8095
|
+
if (version) packages[specifier] = version;
|
|
8320
8096
|
}
|
|
8097
|
+
return packages;
|
|
8321
8098
|
};
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8099
|
+
/**
|
|
8100
|
+
* Reads installed `ai` / `@ai-sdk/*` package versions once and caches the
|
|
8101
|
+
* result for the lifetime of the process — this is expected to be called on
|
|
8102
|
+
* every span creation, so the actual `require`/`readdirSync` work must not
|
|
8103
|
+
* repeat.
|
|
8104
|
+
*/
|
|
8105
|
+
const getAiSdkPackageVersions = () => {
|
|
8106
|
+
if (cached) return cached;
|
|
8107
|
+
try {
|
|
8108
|
+
cached = {
|
|
8109
|
+
aiVersion: readPackageVersion("ai"),
|
|
8110
|
+
aiSdkPackages: discoverAiSdkPackages()
|
|
8111
|
+
};
|
|
8112
|
+
} catch {
|
|
8113
|
+
cached = { aiSdkPackages: {} };
|
|
8114
|
+
}
|
|
8115
|
+
return cached;
|
|
8332
8116
|
};
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8117
|
+
/**
|
|
8118
|
+
* Flat span-attribute object for the `ai` package's instrumentation scope
|
|
8119
|
+
* plus one `lmnr.span.instrumentation.@ai-sdk/<pkg>.version` entry per
|
|
8120
|
+
* resolvable `@ai-sdk/*` package. Merge this into every span this
|
|
8121
|
+
* integration creates.
|
|
8122
|
+
*/
|
|
8123
|
+
const buildAiSdkInstrumentationAttributes = () => {
|
|
8124
|
+
const { aiVersion, aiSdkPackages } = getAiSdkPackageVersions();
|
|
8125
|
+
const attributes = { [require_dist.SPAN_INSTRUMENTATION_SCOPE_NAME]: "ai" };
|
|
8126
|
+
if (aiVersion) attributes[require_dist.SPAN_INSTRUMENTATION_SCOPE_VERSION] = aiVersion;
|
|
8127
|
+
for (const [pkg, version] of Object.entries(aiSdkPackages)) attributes[`lmnr.span.instrumentation.${pkg}.version`] = version;
|
|
8128
|
+
return attributes;
|
|
8338
8129
|
};
|
|
8339
8130
|
//#endregion
|
|
8131
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
|
|
8132
|
+
const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
|
|
8133
|
+
//#endregion
|
|
8340
8134
|
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/index.ts
|
|
8341
8135
|
/**
|
|
8342
8136
|
* The AI SDK v7 diagnostics channel name. Every lifecycle event fires
|
|
@@ -8358,6 +8152,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8358
8152
|
this.llmByKey = /* @__PURE__ */ new Map();
|
|
8359
8153
|
this.toolByCallId = /* @__PURE__ */ new Map();
|
|
8360
8154
|
this.activeStreamStepByCallId = /* @__PURE__ */ new Map();
|
|
8155
|
+
this.promptByCallId = /* @__PURE__ */ new Map();
|
|
8156
|
+
this.logger = require_dist.initializeLogger();
|
|
8361
8157
|
this.onStart = (event) => {
|
|
8362
8158
|
const callId = event?.callId;
|
|
8363
8159
|
if (!callId) return;
|
|
@@ -8369,6 +8165,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8369
8165
|
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
8370
8166
|
attributes: {
|
|
8371
8167
|
[require_dist.SPAN_TYPE]: "DEFAULT",
|
|
8168
|
+
...buildAiSdkInstrumentationAttributes(),
|
|
8372
8169
|
...typeof event.functionId === "string" ? {
|
|
8373
8170
|
"operation.name": event.functionId,
|
|
8374
8171
|
"ai.prompt": "_"
|
|
@@ -8389,8 +8186,10 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8389
8186
|
if (typeof event.functionId === "string") span.setAttribute("ai.functionId", event.functionId);
|
|
8390
8187
|
if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany" || event.operationId === "ai.rerank") span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8391
8188
|
if (this.recordInputs) {
|
|
8392
|
-
if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0)
|
|
8393
|
-
|
|
8189
|
+
if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) {
|
|
8190
|
+
const msgs = verbatimStandardizedMessages(event);
|
|
8191
|
+
if (msgs.length > 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(msgs));
|
|
8192
|
+
} else if (event.value !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(event.value));
|
|
8394
8193
|
else if (event.query !== void 0 || event.documents !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1({
|
|
8395
8194
|
query: event.query,
|
|
8396
8195
|
documents: event.documents
|
|
@@ -8405,17 +8204,25 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8405
8204
|
});
|
|
8406
8205
|
};
|
|
8407
8206
|
this.onStepStart = (event) => {
|
|
8408
|
-
if (!this.createStepSpan) return;
|
|
8409
8207
|
const callId = event?.callId;
|
|
8410
8208
|
const stepNumber = event?.stepNumber ?? 0;
|
|
8411
8209
|
if (!callId) return;
|
|
8210
|
+
if (Array.isArray(event?.promptMessages)) {
|
|
8211
|
+
const serialized = verbatimPromptString(event.promptMessages);
|
|
8212
|
+
if (serialized !== null) this.promptByCallId.set(callId, serialized);
|
|
8213
|
+
}
|
|
8214
|
+
if (!this.createStepSpan) return;
|
|
8412
8215
|
const op = this.operationByCallId.get(callId);
|
|
8413
8216
|
if (!op) return;
|
|
8414
8217
|
const span = getTracer().startSpan(`ai.step`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
|
|
8415
8218
|
const spanCtx = _opentelemetry_api.trace.setSpan(op.ctx, span);
|
|
8416
8219
|
span.setAttribute(require_dist.SPAN_TYPE, "DEFAULT");
|
|
8220
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8417
8221
|
span.setAttribute("ai.step.number", stepNumber);
|
|
8418
|
-
if (this.recordInputs)
|
|
8222
|
+
if (this.recordInputs) {
|
|
8223
|
+
const msgs = verbatimStandardizedMessages(event);
|
|
8224
|
+
if (msgs.length > 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(msgs));
|
|
8225
|
+
}
|
|
8419
8226
|
this.stepByKey.set(stepKey(callId, stepNumber), {
|
|
8420
8227
|
span,
|
|
8421
8228
|
ctx: spanCtx,
|
|
@@ -8431,8 +8238,17 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8431
8238
|
if (!parentCtx) return;
|
|
8432
8239
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
|
|
8433
8240
|
span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8241
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8434
8242
|
applyRequestModelAttributes(span, event);
|
|
8435
|
-
if (this.recordInputs)
|
|
8243
|
+
if (this.recordInputs) {
|
|
8244
|
+
if (event.tools) try {
|
|
8245
|
+
span.setAttribute("gen_ai.tool.definitions", JSON.stringify(event.tools));
|
|
8246
|
+
} catch (e) {
|
|
8247
|
+
this.logger.debug(`Failed to record span tool definitions: ${require_dist.errorMessage(e)}`);
|
|
8248
|
+
}
|
|
8249
|
+
const serialized = this.promptByCallId.get(callId);
|
|
8250
|
+
if (serialized !== void 0) span.setAttribute("gen_ai.input.messages", serialized);
|
|
8251
|
+
}
|
|
8436
8252
|
this.llmByKey.set(stepKey(callId, stepNumber), {
|
|
8437
8253
|
span,
|
|
8438
8254
|
textDeltas: []
|
|
@@ -8455,18 +8271,14 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8455
8271
|
if (typeof event.responseId === "string") span.setAttribute("gen_ai.response.id", event.responseId);
|
|
8456
8272
|
applyUsageToSpan(span, event.usage);
|
|
8457
8273
|
if (this.recordOutputs) {
|
|
8458
|
-
const
|
|
8459
|
-
const outputMessages = buildGenAiOutputMessages({
|
|
8460
|
-
text: extractTextFromContent(content),
|
|
8461
|
-
reasoningText: extractReasoningFromContent(content),
|
|
8462
|
-
toolCalls: normalizeToolCalls(content ? content.filter((p) => p && p.type === "tool-call") : [])
|
|
8463
|
-
});
|
|
8274
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8464
8275
|
if (outputMessages) span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8465
8276
|
}
|
|
8466
8277
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
8467
8278
|
span.end();
|
|
8468
8279
|
removeActiveLlmSpan(span);
|
|
8469
8280
|
this.llmByKey.delete(stepKey(callId, stepNumber));
|
|
8281
|
+
this.promptByCallId.delete(callId);
|
|
8470
8282
|
};
|
|
8471
8283
|
this.onChunk = (event) => {
|
|
8472
8284
|
const chunk = event?.chunk;
|
|
@@ -8495,7 +8307,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8495
8307
|
const llm = this.llmByKey.get(key);
|
|
8496
8308
|
if (llm) {
|
|
8497
8309
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8498
|
-
const outputMessages =
|
|
8310
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8499
8311
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8500
8312
|
}
|
|
8501
8313
|
applyUsageToSpan(llm.span, event.usage);
|
|
@@ -8503,21 +8315,13 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8503
8315
|
removeActiveLlmSpan(llm.span);
|
|
8504
8316
|
this.llmByKey.delete(key);
|
|
8505
8317
|
}
|
|
8318
|
+
this.promptByCallId.delete(callId);
|
|
8506
8319
|
const step = this.stepByKey.get(key);
|
|
8507
8320
|
if (!step) return;
|
|
8508
8321
|
if (typeof event.finishReason === "string") applyFinishReason(step.span, event.finishReason);
|
|
8509
8322
|
if (this.recordOutputs) {
|
|
8510
|
-
const
|
|
8511
|
-
if (
|
|
8512
|
-
outputPayload.text = event.text;
|
|
8513
|
-
step.span.setAttribute("ai.response.text", event.text);
|
|
8514
|
-
}
|
|
8515
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8516
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8517
|
-
outputPayload.toolCalls = normalizedToolCallsList;
|
|
8518
|
-
step.span.setAttribute("ai.response.toolCalls", serializeJSON$1(normalizedToolCallsList));
|
|
8519
|
-
}
|
|
8520
|
-
if (Object.keys(outputPayload).length > 0) step.span.setAttribute(require_dist.SPAN_OUTPUT, serializeJSON$1(outputPayload));
|
|
8323
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8324
|
+
if (outputMessages) step.span.setAttribute(require_dist.SPAN_OUTPUT, outputMessages);
|
|
8521
8325
|
}
|
|
8522
8326
|
step.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
8523
8327
|
step.span.end();
|
|
@@ -8530,7 +8334,12 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8530
8334
|
if (!op) return;
|
|
8531
8335
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
|
|
8532
8336
|
span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8337
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8533
8338
|
applyRequestModelAttributes(span, event);
|
|
8339
|
+
if (this.recordInputs && Array.isArray(event?.promptMessages)) {
|
|
8340
|
+
const serialized = verbatimPromptString(event.promptMessages);
|
|
8341
|
+
if (serialized !== null) span.setAttribute("gen_ai.input.messages", serialized);
|
|
8342
|
+
}
|
|
8534
8343
|
this.llmByKey.set(stepKey(callId, 0), {
|
|
8535
8344
|
span,
|
|
8536
8345
|
textDeltas: []
|
|
@@ -8550,7 +8359,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8550
8359
|
}
|
|
8551
8360
|
applyUsageToSpan(llm.span, event.usage);
|
|
8552
8361
|
if (this.recordOutputs && typeof event.objectText === "string") {
|
|
8553
|
-
const outputMessages =
|
|
8362
|
+
const outputMessages = buildTextOutputMessages(event.objectText);
|
|
8554
8363
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8555
8364
|
}
|
|
8556
8365
|
llm.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
@@ -8569,6 +8378,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8569
8378
|
const span = getTracer().startSpan(`ai.tool ${toolName ?? "unknown"}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
|
|
8570
8379
|
const spanCtx = _opentelemetry_api.trace.setSpan(parentCtx, span);
|
|
8571
8380
|
span.setAttribute(require_dist.SPAN_TYPE, "TOOL");
|
|
8381
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8572
8382
|
if (toolName) span.setAttribute("ai.toolCall.name", toolName);
|
|
8573
8383
|
span.setAttribute("ai.toolCall.id", toolCallId);
|
|
8574
8384
|
if (this.recordInputs && toolCall?.input !== void 0) {
|
|
@@ -8646,17 +8456,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8646
8456
|
if (typeof event.finishReason === "string") applyFinishReason(op.span, event.finishReason);
|
|
8647
8457
|
if (op.operationId === "ai.embed" || op.operationId === "ai.embedMany" || op.operationId === "ai.rerank") applyUsageToSpan(op.span, event.totalUsage ?? event.usage);
|
|
8648
8458
|
if (this.recordOutputs) {
|
|
8649
|
-
const
|
|
8650
|
-
if (
|
|
8651
|
-
op.span.setAttribute("ai.response.text", event.text);
|
|
8652
|
-
op.span.setAttribute(require_dist.SPAN_OUTPUT, event.text);
|
|
8653
|
-
}
|
|
8654
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8655
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8656
|
-
const serialized = serializeJSON$1(normalizedToolCallsList);
|
|
8657
|
-
op.span.setAttribute("ai.response.toolCalls", serialized);
|
|
8658
|
-
if (!hasText) op.span.setAttribute(require_dist.SPAN_OUTPUT, serialized);
|
|
8659
|
-
}
|
|
8459
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8460
|
+
if (outputMessages) op.span.setAttribute(require_dist.SPAN_OUTPUT, outputMessages);
|
|
8660
8461
|
if (event.embedding !== void 0) {
|
|
8661
8462
|
const emb = event.embedding;
|
|
8662
8463
|
if (Array.isArray(emb) && Array.isArray(emb[0])) op.span.setAttribute("ai.embed.count", emb.length);
|
|
@@ -8760,7 +8561,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8760
8561
|
};
|
|
8761
8562
|
for (const [key, llm] of this.llmByKey) if (key.startsWith(prefix)) {
|
|
8762
8563
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8763
|
-
const outputMessages =
|
|
8564
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8764
8565
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8765
8566
|
}
|
|
8766
8567
|
llm.span.end();
|
|
@@ -8778,6 +8579,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8778
8579
|
for (const key of this.llmByKey.keys()) if (key.startsWith(prefix)) this.llmByKey.delete(key);
|
|
8779
8580
|
for (const [toolCallId, tool] of this.toolByCallId) if (tool.callId === callId) this.toolByCallId.delete(toolCallId);
|
|
8780
8581
|
for (const key of this.stepByKey.keys()) if (key.startsWith(prefix)) this.stepByKey.delete(key);
|
|
8582
|
+
this.promptByCallId.delete(callId);
|
|
8781
8583
|
return latest;
|
|
8782
8584
|
}
|
|
8783
8585
|
};
|