@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.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __require } from "./rolldown-runtime-2rV9d50f.mjs";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as SPAN_INSTRUMENTATION_SOURCE, B as DEBUG_SESSION_DIR, C as PARENT_SPAN_IDS_PATH, D as SPAN_INPUT, E as SPAN_IDS_PATH, F as SPAN_TYPE, H as TracingLevel, I as TRACE_HAS_BROWSER_SESSION, L as TRACE_TYPE, M as SPAN_OUTPUT, N as SPAN_PATH, O as SPAN_INSTRUMENTATION_SCOPE_NAME, P as SPAN_SDK_VERSION, R as USER_ID, S as LaminarAttributes, T as SESSION_ID, U as errorMessage, V as DEBUG_SESSION_FILE, _ as tryToOtelSpanContext, b as ASSOCIATION_PROPERTIES_OVERRIDES, c as initializeLogger, d as newUUID, f as normalizeOtelSpanId, g as parseOtelHeaders, h as otelTraceIdToUUID, i as deserializeLaminarSpanContext, j as SPAN_LANGUAGE_VERSION, k as SPAN_INSTRUMENTATION_SCOPE_VERSION, l as loadEnv, m as otelSpanIdToUUID, n as NIL_UUID, o as getFrontendUrl, p as normalizeOtelTraceId, r as Semaphore, s as getOtelEnvVar, t as LaminarClient, u as metadataToAttributes, v as validateTracingConfig, w as PARENT_SPAN_PATH, x as HUMAN_EVALUATOR_OPTIONS, y as ASSOCIATION_PROPERTIES, z as version } from "./dist-DVpPVAC5.mjs";
|
|
3
3
|
import { n as TRACEPARENT_HEADER, t as LAMINAR_SPAN_CONTEXT_HEADER } from "./consts-3AfXaMBI.mjs";
|
|
4
4
|
import { DiagConsoleLogger, DiagLogLevel, ROOT_CONTEXT, SpanKind, SpanStatusCode, TraceFlags, context, createContextKey, diag, isSpanContextValid, trace } from "@opentelemetry/api";
|
|
5
5
|
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
@@ -17,11 +17,10 @@ import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
|
|
|
17
17
|
import { PineconeInstrumentation } from "@traceloop/instrumentation-pinecone";
|
|
18
18
|
import { QdrantInstrumentation } from "@traceloop/instrumentation-qdrant";
|
|
19
19
|
import { TogetherInstrumentation } from "@traceloop/instrumentation-together";
|
|
20
|
-
import { AIPlatformInstrumentation, VertexAIInstrumentation } from "@traceloop/instrumentation-vertexai";
|
|
21
20
|
import { spawn } from "node:child_process";
|
|
22
21
|
import { randomUUID } from "node:crypto";
|
|
23
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
-
import { dirname, join, parse, resolve } from "node:path";
|
|
22
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
23
|
+
import { dirname, join, parse, resolve, sep } from "node:path";
|
|
25
24
|
import * as fs from "fs";
|
|
26
25
|
import * as net from "net";
|
|
27
26
|
import * as os from "os";
|
|
@@ -88,7 +87,7 @@ var LaminarDataset = class extends EvaluationDataset {
|
|
|
88
87
|
*/
|
|
89
88
|
async push(paths, recursive = false) {
|
|
90
89
|
if (!this.client) throw new Error("Client not set");
|
|
91
|
-
const { loadFromPaths } = await import("./file-utils-
|
|
90
|
+
const { loadFromPaths } = await import("./file-utils-D3Jj4yAt.mjs").then((n) => n.t);
|
|
92
91
|
const data = await loadFromPaths(Array.isArray(paths) ? paths : [paths], recursive);
|
|
93
92
|
if (data.length === 0) {
|
|
94
93
|
console.warn("No data to push. Skipping");
|
|
@@ -160,7 +159,10 @@ const stringifyPromptForTelemetry = (prompt) => JSON.stringify(prompt.map((messa
|
|
|
160
159
|
...message,
|
|
161
160
|
content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "file" ? {
|
|
162
161
|
...part,
|
|
163
|
-
data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data
|
|
162
|
+
data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data && typeof part.data === "object" && part.data.type === "data" && part.data.data instanceof Uint8Array ? {
|
|
163
|
+
...part.data,
|
|
164
|
+
data: convertDataContentToBase64String(part.data.data)
|
|
165
|
+
} : part.data
|
|
164
166
|
} : part)
|
|
165
167
|
})));
|
|
166
168
|
/**
|
|
@@ -597,28 +599,6 @@ const parseCacheUntil = (value) => {
|
|
|
597
599
|
* the SDK only needs both ids present to arm replay.
|
|
598
600
|
*/
|
|
599
601
|
const replayEnabledForConfig = (config) => config.replayTraceId !== null && config.cacheUntilSpanId !== null;
|
|
600
|
-
const ROLLOUT_NOTE_KEY = "rollout.note";
|
|
601
|
-
/**
|
|
602
|
-
* Resolve the debug run note from the environment.
|
|
603
|
-
*
|
|
604
|
-
* Only read when debug mode is enabled (LMNR_DEBUG truthy). `LMNR_DEBUG_RUN_NOTES_FILE`
|
|
605
|
-
* (a path to a file holding raw markdown) takes precedence over `LMNR_DEBUG_RUN_NOTES`
|
|
606
|
-
* (inline raw markdown). A missing / unreadable file is logged and skipped (returns
|
|
607
|
-
* null) rather than throwing — a bad note path must never block SDK init.
|
|
608
|
-
*
|
|
609
|
-
* Returns the raw note string, or null when debug is off or no note var is set.
|
|
610
|
-
*/
|
|
611
|
-
const resolveDebugRunNote = () => {
|
|
612
|
-
if (!isTruthy(process.env.LMNR_DEBUG)) return null;
|
|
613
|
-
const file = process.env.LMNR_DEBUG_RUN_NOTES_FILE;
|
|
614
|
-
if (file) try {
|
|
615
|
-
return readFileSync(file, "utf-8");
|
|
616
|
-
} catch (err) {
|
|
617
|
-
logger$22.warn(`LMNR_DEBUG_RUN_NOTES_FILE=${JSON.stringify(file)} could not be read; ignoring run note: ${errorMessage(err)}`);
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
return process.env.LMNR_DEBUG_RUN_NOTES ?? null;
|
|
621
|
-
};
|
|
622
602
|
/**
|
|
623
603
|
* Build the debug config from the environment.
|
|
624
604
|
*
|
|
@@ -1834,8 +1814,6 @@ var Laminar = class Laminar {
|
|
|
1834
1814
|
this.baseHttpUrl = `${httpUrlWithoutSlash}:${port}`;
|
|
1835
1815
|
} else this.baseHttpUrl = "";
|
|
1836
1816
|
const envMetadata = process.env.LMNR_TRACE_METADATA ? JSON.parse(process.env.LMNR_TRACE_METADATA) : {};
|
|
1837
|
-
const debugRunNote = resolveDebugRunNote();
|
|
1838
|
-
if (debugRunNote !== null) envMetadata[ROLLOUT_NOTE_KEY] = debugRunNote;
|
|
1839
1817
|
this.globalMetadata = {
|
|
1840
1818
|
...envMetadata,
|
|
1841
1819
|
...metadata ?? {}
|
|
@@ -6255,8 +6233,6 @@ const initInstrumentations = (client, suppressContentTracing, sessionRecordingOp
|
|
|
6255
6233
|
}));
|
|
6256
6234
|
instrumentations.push(new AnthropicInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6257
6235
|
instrumentations.push(new CohereInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6258
|
-
instrumentations.push(new VertexAIInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6259
|
-
instrumentations.push(new AIPlatformInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6260
6236
|
instrumentations.push(new BedrockInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6261
6237
|
instrumentations.push(new PineconeInstrumentation());
|
|
6262
6238
|
instrumentations.push(new LangChainInstrumentation({ traceContent: !suppressContentTracing }));
|
|
@@ -6308,16 +6284,6 @@ const manuallyInitInstrumentations = (client, instrumentModules, suppressContent
|
|
|
6308
6284
|
instrumentations.push(cohereInstrumentation);
|
|
6309
6285
|
cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
|
|
6310
6286
|
}
|
|
6311
|
-
if (instrumentModules?.google_vertexai) {
|
|
6312
|
-
const vertexaiInstrumentation = new VertexAIInstrumentation({ traceContent: !suppressContentTracing });
|
|
6313
|
-
instrumentations.push(vertexaiInstrumentation);
|
|
6314
|
-
vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
|
|
6315
|
-
}
|
|
6316
|
-
if (instrumentModules?.google_aiplatform) {
|
|
6317
|
-
const aiplatformInstrumentation = new AIPlatformInstrumentation({ traceContent: !suppressContentTracing });
|
|
6318
|
-
instrumentations.push(aiplatformInstrumentation);
|
|
6319
|
-
aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
|
|
6320
|
-
}
|
|
6321
6287
|
if (instrumentModules?.bedrock) {
|
|
6322
6288
|
const bedrockInstrumentation = new BedrockInstrumentation({ traceContent: !suppressContentTracing });
|
|
6323
6289
|
instrumentations.push(bedrockInstrumentation);
|
|
@@ -7006,6 +6972,29 @@ const logger$2 = initializeLogger();
|
|
|
7006
6972
|
const getEvaluationUrl = (projectId, evaluationId, baseUrl, frontendPort) => {
|
|
7007
6973
|
return `${getFrontendUrl(baseUrl, frontendPort)}/project/${projectId}/evaluations/${evaluationId}`;
|
|
7008
6974
|
};
|
|
6975
|
+
const SESSION_METADATA_KEY = "rollout.session_id";
|
|
6976
|
+
/**
|
|
6977
|
+
* When this eval runs under a debug session, auto-stamp the session id into the
|
|
6978
|
+
* evaluation metadata so the created evaluation links back to it with no extra
|
|
6979
|
+
* step. The session id is resolved by the debug runtime EXACTLY like traces —
|
|
6980
|
+
* `LMNR_DEBUG_SESSION_ID` env → `.lmnr/debug-session.json` → freshly minted — so
|
|
6981
|
+
* a plain `LMNR_DEBUG=1 <run-your-eval>` groups the eval under the current debug
|
|
6982
|
+
* session (no CLI wrapper needed). `getRuntime()` is null when debug mode is off,
|
|
6983
|
+
* so the metadata is returned unchanged.
|
|
6984
|
+
*
|
|
6985
|
+
* The backend writes the `evaluation` block from this key at eval creation; notes
|
|
6986
|
+
* are attached separately as `text` blocks keyed by the same session id
|
|
6987
|
+
* (`lmnr-cli debug session add-note`). Called after `Laminar.initialize()`, so
|
|
6988
|
+
* the runtime (and its resolved session id) already exist.
|
|
6989
|
+
*/
|
|
6990
|
+
const withSessionMetadata = (metadata) => {
|
|
6991
|
+
const sessionId = getRuntime()?.sessionId ?? null;
|
|
6992
|
+
if (sessionId === null) return metadata;
|
|
6993
|
+
return {
|
|
6994
|
+
...metadata ?? {},
|
|
6995
|
+
[SESSION_METADATA_KEY]: sessionId
|
|
6996
|
+
};
|
|
6997
|
+
};
|
|
7009
6998
|
const getAverageScores = (results) => {
|
|
7010
6999
|
const perScoreValues = {};
|
|
7011
7000
|
for (const result of results) for (const key in result.scores) {
|
|
@@ -7126,7 +7115,7 @@ var Evaluation = class {
|
|
|
7126
7115
|
}
|
|
7127
7116
|
let resultDatapoints;
|
|
7128
7117
|
try {
|
|
7129
|
-
const evaluation = await this.client.evals.init(this.name, this.groupName, this.metadata);
|
|
7118
|
+
const evaluation = await this.client.evals.init(this.name, this.groupName, withSessionMetadata(this.metadata));
|
|
7130
7119
|
const url = getEvaluationUrl(evaluation.projectId, evaluation.id, this.progressReporter.baseUrl, this.frontendPort);
|
|
7131
7120
|
process.stdout.write(`\nCheck results at ${url}\n`);
|
|
7132
7121
|
this.progressReporter.start({ length: await this.getLength() });
|
|
@@ -7319,260 +7308,6 @@ async function evaluate({ data, executor, evaluators, groupName, name, metadata,
|
|
|
7319
7308
|
else return await evaluation.run();
|
|
7320
7309
|
}
|
|
7321
7310
|
//#endregion
|
|
7322
|
-
//#region src/debug/aisdk-normalize.ts
|
|
7323
|
-
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
7324
|
-
/** Mirror Rust `is_url`. */
|
|
7325
|
-
const isUrl = (data) => data.startsWith("http://") || data.startsWith("https://") || data.startsWith("data:");
|
|
7326
|
-
/** Mirror Rust `infer_image_type`. */
|
|
7327
|
-
const inferImageType = (base64) => {
|
|
7328
|
-
if (base64.startsWith("/9j/")) return "image/jpeg";
|
|
7329
|
-
if (base64.startsWith("iVBORw0KGgo")) return "image/png";
|
|
7330
|
-
if (base64.startsWith("R0lGODlh")) return "image/gif";
|
|
7331
|
-
if (base64.startsWith("UklGR")) return "image/webp";
|
|
7332
|
-
if (base64.startsWith("PHN2Zz")) return "image/svg+xml";
|
|
7333
|
-
return "image/png";
|
|
7334
|
-
};
|
|
7335
|
-
const base64FromBytes = (bytes) => {
|
|
7336
|
-
let latin1 = "";
|
|
7337
|
-
for (const b of bytes) latin1 += String.fromCharCode(b & 255);
|
|
7338
|
-
return btoa(latin1);
|
|
7339
|
-
};
|
|
7340
|
-
/**
|
|
7341
|
-
* Mirror Rust `json_value_to_string`: a JSON string stays itself; anything else
|
|
7342
|
-
* is compact-serialized. With serde `preserve_order` on, `JSON.stringify` matches
|
|
7343
|
-
* `serde_json::Value::to_string()` byte-for-byte for object key order.
|
|
7344
|
-
*/
|
|
7345
|
-
const jsonValueToString = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
7346
|
-
/** First string among the candidates (ports serde field aliases). */
|
|
7347
|
-
const firstString = (...candidates) => {
|
|
7348
|
-
for (const c of candidates) if (typeof c === "string") return c;
|
|
7349
|
-
};
|
|
7350
|
-
/**
|
|
7351
|
-
* Port of `InstrumentationChatMessageImage` (untagged) →
|
|
7352
|
-
* `from_instrumentation_content_part`'s `Image` arm. Returns the output
|
|
7353
|
-
* `ChatMessageContentPart` object, or `null` if no untagged variant matches.
|
|
7354
|
-
*/
|
|
7355
|
-
const parseImage = (e) => {
|
|
7356
|
-
if (isRecord(e.source)) {
|
|
7357
|
-
const src = e.source;
|
|
7358
|
-
if (typeof src.media_type === "string" && typeof src.data === "string") return {
|
|
7359
|
-
type: "image",
|
|
7360
|
-
mediaType: src.media_type,
|
|
7361
|
-
data: src.data
|
|
7362
|
-
};
|
|
7363
|
-
if (typeof src.url === "string") return {
|
|
7364
|
-
type: "image_url",
|
|
7365
|
-
url: src.url,
|
|
7366
|
-
detail: typeof src.detail === "string" ? src.detail : null
|
|
7367
|
-
};
|
|
7368
|
-
}
|
|
7369
|
-
if (Array.isArray(e.image) && e.image.every((n) => typeof n === "number")) {
|
|
7370
|
-
const b64 = base64FromBytes(e.image);
|
|
7371
|
-
return {
|
|
7372
|
-
type: "image_url",
|
|
7373
|
-
url: `data:${typeof e.mime_type === "string" ? e.mime_type : inferImageType(b64)};base64,${b64}`,
|
|
7374
|
-
detail: null
|
|
7375
|
-
};
|
|
7376
|
-
}
|
|
7377
|
-
if (typeof e.image === "string") {
|
|
7378
|
-
if (isUrl(e.image)) return {
|
|
7379
|
-
type: "image_url",
|
|
7380
|
-
url: e.image,
|
|
7381
|
-
detail: null
|
|
7382
|
-
};
|
|
7383
|
-
return {
|
|
7384
|
-
type: "image",
|
|
7385
|
-
data: e.image,
|
|
7386
|
-
mediaType: typeof e.mime_type === "string" ? e.mime_type : "image/png"
|
|
7387
|
-
};
|
|
7388
|
-
}
|
|
7389
|
-
return null;
|
|
7390
|
-
};
|
|
7391
|
-
/** Port of the `Document` arm (only the Base64 source shape exists). */
|
|
7392
|
-
const parseDocument = (e) => {
|
|
7393
|
-
if (!isRecord(e.source) || e.source.type !== "base64") return null;
|
|
7394
|
-
const data = e.source.data;
|
|
7395
|
-
if (typeof data !== "string") return null;
|
|
7396
|
-
return {
|
|
7397
|
-
type: "document",
|
|
7398
|
-
source: {
|
|
7399
|
-
type: "base64",
|
|
7400
|
-
media_type: firstString(e.source.media_type, e.source.mediaType) ?? "application/octet-stream",
|
|
7401
|
-
data
|
|
7402
|
-
}
|
|
7403
|
-
};
|
|
7404
|
-
};
|
|
7405
|
-
const DATA_URL_RE = /^data:((?:application|image)\/[a-zA-Z-]+);base64,/;
|
|
7406
|
-
/**
|
|
7407
|
-
* Port of the `File` arm (OpenAI file-id / base64, AI SDK v1/v2).
|
|
7408
|
-
*
|
|
7409
|
-
* Known parity gap (v7 + image-by-URL only): on AI SDK v7 the recording path
|
|
7410
|
-
* stores `ai.prompt.messages` from the user-facing `ModelMessage[]` (an image
|
|
7411
|
-
* passed by URL is a `{type:"image", image:"https://…"}` part), which the
|
|
7412
|
-
* server reshapes via `parseImage` into an `image_url`. Replay instead hashes
|
|
7413
|
-
* the LanguageModel-level prompt, where that same image is already a
|
|
7414
|
-
* `{type:"file", data:"https://…", mediaType:"image/*"}` part — this arm maps
|
|
7415
|
-
* it to `image`, not `image_url`, so the two hashes diverge and the lookup
|
|
7416
|
-
* MISSes (degrading safely to a live call). This can't be reconciled here: the
|
|
7417
|
-
* arm is a faithful port of the server's Rust `parseFile`, so on v6 (where the
|
|
7418
|
-
* server itself reshapes a file part) emitting `image_url` would instead BREAK
|
|
7419
|
-
* parity, and the replay wrapper has no signal for which SDK version recorded
|
|
7420
|
-
* the span. Plain text, multi-part text, tool calls, and base64 images all hash
|
|
7421
|
-
* identically across both paths; only URL-referenced images on v7 are affected.
|
|
7422
|
-
*/
|
|
7423
|
-
const parseFile = (e) => {
|
|
7424
|
-
if (isRecord(e.file)) {
|
|
7425
|
-
const file = e.file;
|
|
7426
|
-
if (typeof file.file_id === "string") return {
|
|
7427
|
-
type: "text",
|
|
7428
|
-
text: JSON.stringify({ file_id: file.file_id })
|
|
7429
|
-
};
|
|
7430
|
-
if (typeof file.file_data === "string") {
|
|
7431
|
-
const m = DATA_URL_RE.exec(file.file_data.slice(0, 50));
|
|
7432
|
-
return {
|
|
7433
|
-
type: "document",
|
|
7434
|
-
source: {
|
|
7435
|
-
type: "base64",
|
|
7436
|
-
media_type: m ? m[1] : "application/octet-stream",
|
|
7437
|
-
data: m ? file.file_data.slice(file.file_data.indexOf(",") + 1) : file.file_data
|
|
7438
|
-
}
|
|
7439
|
-
};
|
|
7440
|
-
}
|
|
7441
|
-
return null;
|
|
7442
|
-
}
|
|
7443
|
-
if (typeof e.data === "string") {
|
|
7444
|
-
const mediaType = firstString(e.media_type, e.mediaType, e.mime_type);
|
|
7445
|
-
if (mediaType === void 0) return null;
|
|
7446
|
-
if (mediaType.startsWith("image/")) return {
|
|
7447
|
-
type: "image",
|
|
7448
|
-
data: e.data,
|
|
7449
|
-
mediaType
|
|
7450
|
-
};
|
|
7451
|
-
return {
|
|
7452
|
-
type: "document",
|
|
7453
|
-
source: {
|
|
7454
|
-
type: "base64",
|
|
7455
|
-
media_type: mediaType,
|
|
7456
|
-
data: e.data
|
|
7457
|
-
}
|
|
7458
|
-
};
|
|
7459
|
-
}
|
|
7460
|
-
return null;
|
|
7461
|
-
};
|
|
7462
|
-
/**
|
|
7463
|
-
* Port of `serde_json::from_value::<InstrumentationChatMessageContentPart>` for a
|
|
7464
|
-
* single part, returning the mapped output `ChatMessageContentPart` object or
|
|
7465
|
-
* `null` when no variant matches (an unrecognized `type`, or a recognized tag
|
|
7466
|
-
* whose inner shape fails). `null` propagates to an all-or-nothing Text fallback.
|
|
7467
|
-
*
|
|
7468
|
-
* Output objects carry serde's serialized field names; `skip_serializing_if` is
|
|
7469
|
-
* reproduced by omitting the field (ChatMessage.tool_call_id only), and `Option`
|
|
7470
|
-
* fields WITHOUT skip (`detail`, tool-call `id`/`arguments`) always emit `null`.
|
|
7471
|
-
*/
|
|
7472
|
-
const parsePart = (el) => {
|
|
7473
|
-
if (!isRecord(el)) return null;
|
|
7474
|
-
switch (el.type) {
|
|
7475
|
-
case "text": {
|
|
7476
|
-
const text = firstString(el.text, el.input_text, el.output_text);
|
|
7477
|
-
return text === void 0 ? null : {
|
|
7478
|
-
type: "text",
|
|
7479
|
-
text
|
|
7480
|
-
};
|
|
7481
|
-
}
|
|
7482
|
-
case "image_url": {
|
|
7483
|
-
if (!isRecord(el.image_url) || typeof el.image_url.url !== "string") return null;
|
|
7484
|
-
const detail = el.image_url.detail;
|
|
7485
|
-
return {
|
|
7486
|
-
type: "image_url",
|
|
7487
|
-
url: el.image_url.url,
|
|
7488
|
-
detail: typeof detail === "string" ? detail : null
|
|
7489
|
-
};
|
|
7490
|
-
}
|
|
7491
|
-
case "image": return parseImage(el);
|
|
7492
|
-
case "document": return parseDocument(el);
|
|
7493
|
-
case "file": return parseFile(el);
|
|
7494
|
-
case "tool-call": {
|
|
7495
|
-
if (typeof el.toolName !== "string") return null;
|
|
7496
|
-
const rawInput = "input" in el ? el.input : el.args;
|
|
7497
|
-
return {
|
|
7498
|
-
type: "tool_call",
|
|
7499
|
-
name: el.toolName,
|
|
7500
|
-
id: typeof el.toolCallId === "string" ? el.toolCallId : null,
|
|
7501
|
-
arguments: rawInput === void 0 || rawInput === null ? null : rawInput
|
|
7502
|
-
};
|
|
7503
|
-
}
|
|
7504
|
-
case "tool-result":
|
|
7505
|
-
if (typeof el.toolCallId !== "string" || typeof el.toolName !== "string") return null;
|
|
7506
|
-
if (!("output" in el || "result" in el)) return null;
|
|
7507
|
-
return {
|
|
7508
|
-
type: "tool-result",
|
|
7509
|
-
toolCallId: el.toolCallId,
|
|
7510
|
-
output: "output" in el ? el.output : el.result,
|
|
7511
|
-
toolName: el.toolName
|
|
7512
|
-
};
|
|
7513
|
-
default: return null;
|
|
7514
|
-
}
|
|
7515
|
-
};
|
|
7516
|
-
/**
|
|
7517
|
-
* Port of `input_chat_messages_from_json`. `input` is the parsed
|
|
7518
|
-
* `ai.prompt.messages` array. Each message's `content` becomes either a
|
|
7519
|
-
* `ContentPartList` (when EVERY element parses as an instrumentation part) or a
|
|
7520
|
-
* Text blob (`json_value_to_string` of the whole content) otherwise. A message
|
|
7521
|
-
* missing `role`/`content` would be a server error; we skip it defensively so the
|
|
7522
|
-
* hash is still computed over the well-formed remainder.
|
|
7523
|
-
*/
|
|
7524
|
-
const inputChatMessagesFromJson = (input) => {
|
|
7525
|
-
if (!Array.isArray(input)) return [];
|
|
7526
|
-
const messages = [];
|
|
7527
|
-
for (const message of input) {
|
|
7528
|
-
if (!isRecord(message)) continue;
|
|
7529
|
-
const role = message.role;
|
|
7530
|
-
if (typeof role !== "string" || !("content" in message)) continue;
|
|
7531
|
-
const otelContent = message.content;
|
|
7532
|
-
let parts = null;
|
|
7533
|
-
if (Array.isArray(otelContent)) {
|
|
7534
|
-
parts = [];
|
|
7535
|
-
for (const el of otelContent) {
|
|
7536
|
-
const parsed = parsePart(el);
|
|
7537
|
-
if (parsed === null) {
|
|
7538
|
-
parts = null;
|
|
7539
|
-
break;
|
|
7540
|
-
}
|
|
7541
|
-
parts.push(parsed);
|
|
7542
|
-
}
|
|
7543
|
-
}
|
|
7544
|
-
const out = {
|
|
7545
|
-
role,
|
|
7546
|
-
content: parts !== null ? parts : jsonValueToString(otelContent)
|
|
7547
|
-
};
|
|
7548
|
-
if (typeof message.tool_call_id === "string") out.tool_call_id = message.tool_call_id;
|
|
7549
|
-
messages.push(out);
|
|
7550
|
-
}
|
|
7551
|
-
return messages;
|
|
7552
|
-
};
|
|
7553
|
-
/**
|
|
7554
|
-
* Reshape an AI SDK call's `options.prompt` into the reconstructed message array
|
|
7555
|
-
* the server hashes. Version-agnostic: `stringifyPromptForTelemetry` already
|
|
7556
|
-
* normalizes V2/V3/V4 prompts (Uint8Array file `data` → base64) into the stored
|
|
7557
|
-
* `ai.prompt.messages` JSON, and the reshape operates on that JSON identically
|
|
7558
|
-
* across versions.
|
|
7559
|
-
*
|
|
7560
|
-
* Returns `null` when the prompt cannot be reshaped at all (a `stringify` /
|
|
7561
|
-
* `JSON.parse` failure). The caller MUST treat `null` as "can't compute a real
|
|
7562
|
-
* key" and degrade to a live call WITHOUT latching: hashing a default/empty
|
|
7563
|
-
* payload here would key the lookup off the wrong bytes, force a spurious MISS,
|
|
7564
|
-
* and wrongly latch live mode for the rest of the replay.
|
|
7565
|
-
*/
|
|
7566
|
-
const extractInputMessages = (options) => {
|
|
7567
|
-
let aiPromptMessages;
|
|
7568
|
-
try {
|
|
7569
|
-
aiPromptMessages = JSON.parse(stringifyPromptForTelemetry(options.prompt));
|
|
7570
|
-
} catch {
|
|
7571
|
-
return null;
|
|
7572
|
-
}
|
|
7573
|
-
return inputChatMessagesFromJson(aiPromptMessages);
|
|
7574
|
-
};
|
|
7575
|
-
//#endregion
|
|
7576
7311
|
//#region src/debug/hash.ts
|
|
7577
7312
|
/**
|
|
7578
7313
|
* Debug-replay input hashing.
|
|
@@ -7580,13 +7315,13 @@ const extractInputMessages = (options) => {
|
|
|
7580
7315
|
* Computes the per-LLM-call `input_hash` the server-side debugger cache (v2) is
|
|
7581
7316
|
* keyed by. The SDK and the Rust app-server MUST produce byte-identical hashes,
|
|
7582
7317
|
* so this is a faithful port of the app-server primitives:
|
|
7583
|
-
* - `canonical_json`
|
|
7584
|
-
* - `
|
|
7585
|
-
* - `debug_input_hash` — `app-server/src/traces/debug_cache.rs` (plan 01 §3)
|
|
7318
|
+
* - `canonical_json` — `app-server/src/traces/input_dedup.rs`
|
|
7319
|
+
* - `debug_input_hash` — `app-server/src/traces/input_dedup.rs`
|
|
7586
7320
|
*
|
|
7587
|
-
* The whole non-system message array is hashed as ONE blob: strip
|
|
7588
|
-
* `role === "system"` message (
|
|
7589
|
-
* remaining array, then blake3 over its UTF-8 bytes and hex-encode the
|
|
7321
|
+
* The whole non-system message array is hashed as ONE blob: strip EVERY
|
|
7322
|
+
* `role === "system"` message (regardless of content or position), canonicalize
|
|
7323
|
+
* the remaining array, then blake3 over its UTF-8 bytes and hex-encode the
|
|
7324
|
+
* digest.
|
|
7590
7325
|
*
|
|
7591
7326
|
* Keep this file line-comparable with the Python `debug/hash.py`.
|
|
7592
7327
|
*/
|
|
@@ -7607,53 +7342,25 @@ const canonicalJson = (value) => {
|
|
|
7607
7342
|
return JSON.stringify(value);
|
|
7608
7343
|
};
|
|
7609
7344
|
/**
|
|
7610
|
-
*
|
|
7611
|
-
*
|
|
7612
|
-
* Faithful port of Rust `extract_system_message`'s text resolution: a plain
|
|
7613
|
-
* string content, else an array of `{text}` blocks joined by a space (only when
|
|
7614
|
-
* the join is non-empty), else `parts[0].text` / `parts[0].content`.
|
|
7615
|
-
*/
|
|
7616
|
-
const systemMessageText = (sysMsg) => {
|
|
7617
|
-
const content = sysMsg.content;
|
|
7618
|
-
if (typeof content === "string") return content;
|
|
7619
|
-
if (Array.isArray(content)) {
|
|
7620
|
-
const joined = content.map((block) => block !== null && typeof block === "object" ? block.text : void 0).filter((t) => typeof t === "string").join(" ");
|
|
7621
|
-
if (joined.length > 0) return joined;
|
|
7622
|
-
}
|
|
7623
|
-
const parts = sysMsg.parts;
|
|
7624
|
-
if (Array.isArray(parts) && parts.length > 0) {
|
|
7625
|
-
const first = parts[0];
|
|
7626
|
-
if (first !== null && typeof first === "object") {
|
|
7627
|
-
const f = first;
|
|
7628
|
-
const t = typeof f.text === "string" ? f.text : f.content;
|
|
7629
|
-
if (typeof t === "string") return t;
|
|
7630
|
-
}
|
|
7631
|
-
}
|
|
7632
|
-
return "";
|
|
7633
|
-
};
|
|
7634
|
-
/**
|
|
7635
|
-
* Drop the first `role === "system"` message, matching the server.
|
|
7345
|
+
* Drop EVERY `role === "system"` message, matching the server.
|
|
7636
7346
|
*
|
|
7637
|
-
*
|
|
7638
|
-
*
|
|
7639
|
-
*
|
|
7347
|
+
* Content and position are irrelevant: an empty system string, a block-array
|
|
7348
|
+
* system, or repeated/trailing system messages are all stripped. The coding
|
|
7349
|
+
* agent may add/remove/edit its own system prompt(s) between iterations and
|
|
7350
|
+
* those edits must not change the cache key.
|
|
7640
7351
|
*/
|
|
7641
|
-
const
|
|
7642
|
-
const sysIdx = messages.findIndex((m) => m !== null && typeof m === "object" && m.role === "system");
|
|
7643
|
-
if (sysIdx === -1) return messages;
|
|
7644
|
-
if (systemMessageText(messages[sysIdx]).length === 0) return messages;
|
|
7645
|
-
return messages.filter((_, i) => i !== sysIdx);
|
|
7646
|
-
};
|
|
7352
|
+
const stripSystemMessages = (messages) => messages.filter((m) => !(m !== null && typeof m === "object" && m.role === "system"));
|
|
7647
7353
|
/**
|
|
7648
|
-
* Compute the hex blake3 `input_hash` for a
|
|
7354
|
+
* Compute the hex blake3 `input_hash` for a message array.
|
|
7649
7355
|
*
|
|
7650
|
-
* `messages`
|
|
7651
|
-
*
|
|
7652
|
-
*
|
|
7653
|
-
* canonicalized, and the digest
|
|
7356
|
+
* `messages` is the verbatim LanguageModel-level prompt
|
|
7357
|
+
* (`JSON.parse(stringifyPromptForTelemetry(options.prompt))`) — the same bytes
|
|
7358
|
+
* the SDK records as `gen_ai.input.messages` and the server hashes on ingest.
|
|
7359
|
+
* System messages are stripped, the remainder canonicalized, and the digest
|
|
7360
|
+
* hex-encoded (64 chars).
|
|
7654
7361
|
*/
|
|
7655
7362
|
const debugInputHash = (messages) => {
|
|
7656
|
-
const canonical = canonicalJson(
|
|
7363
|
+
const canonical = canonicalJson(stripSystemMessages(messages));
|
|
7657
7364
|
return bytesToHex(blake3(new TextEncoder().encode(canonical)));
|
|
7658
7365
|
};
|
|
7659
7366
|
//#endregion
|
|
@@ -7713,6 +7420,107 @@ const removeActiveLlmSpan = (span) => {
|
|
|
7713
7420
|
/** Most recently pushed still-open LLM span, or undefined if none. */
|
|
7714
7421
|
const peekActiveLlmSpan = () => openLlmSpans.length > 0 ? openLlmSpans[openLlmSpans.length - 1] : void 0;
|
|
7715
7422
|
//#endregion
|
|
7423
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
|
|
7424
|
+
const serializeJSON$1 = (value) => {
|
|
7425
|
+
if (typeof value === "string") return value;
|
|
7426
|
+
try {
|
|
7427
|
+
return JSON.stringify(value);
|
|
7428
|
+
} catch {
|
|
7429
|
+
return "[unserializable]";
|
|
7430
|
+
}
|
|
7431
|
+
};
|
|
7432
|
+
const normalizeProvider$1 = (provider) => {
|
|
7433
|
+
if (!provider) return void 0;
|
|
7434
|
+
return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
|
|
7435
|
+
};
|
|
7436
|
+
const applyUsage = (attributes, usage) => {
|
|
7437
|
+
if (!usage || typeof usage !== "object") return;
|
|
7438
|
+
if (typeof usage.inputTokens === "number") attributes[LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
|
|
7439
|
+
if (typeof usage.outputTokens === "number") attributes[LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
|
|
7440
|
+
if (typeof usage.totalTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
|
|
7441
|
+
else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
7442
|
+
if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
|
|
7443
|
+
else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
|
|
7444
|
+
if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
|
|
7445
|
+
else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
|
|
7446
|
+
if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
|
|
7447
|
+
};
|
|
7448
|
+
const verbatimStandardizedMessages = (event) => {
|
|
7449
|
+
const messages = [];
|
|
7450
|
+
const sys = event.instructions || event.system;
|
|
7451
|
+
if (typeof sys === "string" && sys.length > 0) messages.push({
|
|
7452
|
+
role: "system",
|
|
7453
|
+
content: sys
|
|
7454
|
+
});
|
|
7455
|
+
else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
|
|
7456
|
+
else if (sys && typeof sys === "object") messages.push(sys);
|
|
7457
|
+
if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(m);
|
|
7458
|
+
return messages;
|
|
7459
|
+
};
|
|
7460
|
+
const buildVerbatimOutputMessages = (content) => {
|
|
7461
|
+
if (!Array.isArray(content) || content.length === 0) return void 0;
|
|
7462
|
+
try {
|
|
7463
|
+
return JSON.stringify([{
|
|
7464
|
+
role: "assistant",
|
|
7465
|
+
content
|
|
7466
|
+
}]);
|
|
7467
|
+
} catch {
|
|
7468
|
+
return;
|
|
7469
|
+
}
|
|
7470
|
+
};
|
|
7471
|
+
const buildTextOutputMessages = (text) => text.length > 0 ? buildVerbatimOutputMessages([{
|
|
7472
|
+
type: "text",
|
|
7473
|
+
text
|
|
7474
|
+
}]) : void 0;
|
|
7475
|
+
const applyUsageToSpan = (span, usage) => {
|
|
7476
|
+
const attrs = {};
|
|
7477
|
+
applyUsage(attrs, usage);
|
|
7478
|
+
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
|
|
7479
|
+
};
|
|
7480
|
+
const applyFinishReason = (span, finishReason) => {
|
|
7481
|
+
span.setAttribute("gen_ai.response.finish_reason", finishReason);
|
|
7482
|
+
span.setAttribute("ai.response.finishReason", finishReason);
|
|
7483
|
+
};
|
|
7484
|
+
const applyRequestModelAttributes = (span, event) => {
|
|
7485
|
+
if (typeof event.provider === "string") {
|
|
7486
|
+
const provider = normalizeProvider$1(event.provider);
|
|
7487
|
+
if (provider) span.setAttribute(LaminarAttributes.PROVIDER, provider);
|
|
7488
|
+
span.setAttribute("ai.model.provider", event.provider);
|
|
7489
|
+
}
|
|
7490
|
+
if (typeof event.modelId === "string") {
|
|
7491
|
+
span.setAttribute(LaminarAttributes.REQUEST_MODEL, event.modelId);
|
|
7492
|
+
span.setAttribute("ai.model.id", event.modelId);
|
|
7493
|
+
}
|
|
7494
|
+
};
|
|
7495
|
+
const compareHrTime = (a, b) => {
|
|
7496
|
+
if (a[0] !== b[0]) return a[0] - b[0];
|
|
7497
|
+
return a[1] - b[1];
|
|
7498
|
+
};
|
|
7499
|
+
const readSpanEndTime = (span) => {
|
|
7500
|
+
const innerEnd = span._span?.endTime;
|
|
7501
|
+
if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
|
|
7502
|
+
const outerEnd = span.endTime;
|
|
7503
|
+
if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
|
|
7504
|
+
};
|
|
7505
|
+
const verbatimPromptString = (prompt) => {
|
|
7506
|
+
try {
|
|
7507
|
+
const serialized = stringifyPromptForTelemetry(prompt);
|
|
7508
|
+
return typeof serialized === "string" ? serialized : null;
|
|
7509
|
+
} catch {
|
|
7510
|
+
return null;
|
|
7511
|
+
}
|
|
7512
|
+
};
|
|
7513
|
+
const verbatimPromptMessages = (prompt) => {
|
|
7514
|
+
const serialized = verbatimPromptString(prompt);
|
|
7515
|
+
if (serialized === null) return null;
|
|
7516
|
+
try {
|
|
7517
|
+
const parsed = JSON.parse(serialized);
|
|
7518
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
7519
|
+
} catch {
|
|
7520
|
+
return null;
|
|
7521
|
+
}
|
|
7522
|
+
};
|
|
7523
|
+
//#endregion
|
|
7716
7524
|
//#region src/opentelemetry-lib/instrumentation/aisdk/base-language-model.ts
|
|
7717
7525
|
/**
|
|
7718
7526
|
* Base class for Laminar language model wrappers.
|
|
@@ -7755,7 +7563,7 @@ var BaseLaminarLanguageModel = class {
|
|
|
7755
7563
|
async doGenerateOrStreamWithCaching(options, originalFn, buildFromCached) {
|
|
7756
7564
|
if (!replayEnabled() || Laminar.debugRunLive) return originalFn(options);
|
|
7757
7565
|
const spanToMark = peekActiveLlmSpan() ?? Laminar.getCurrentSpan();
|
|
7758
|
-
const messages =
|
|
7566
|
+
const messages = verbatimPromptMessages(options.prompt);
|
|
7759
7567
|
if (messages === null || messages.length === 0) return originalFn(options);
|
|
7760
7568
|
const inputHash = debugInputHash(messages);
|
|
7761
7569
|
const outcome = await getRuntime()?.lookupCache(inputHash) ?? { kind: "live" };
|
|
@@ -7803,17 +7611,45 @@ var BaseLaminarLanguageModel = class {
|
|
|
7803
7611
|
}];
|
|
7804
7612
|
if (output === null || typeof output !== "object") return [];
|
|
7805
7613
|
const handleItem = (item) => {
|
|
7806
|
-
if (item.type === "text")
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7614
|
+
if (item.type === "text") {
|
|
7615
|
+
if (typeof item.text === "string") return [{
|
|
7616
|
+
...item,
|
|
7617
|
+
type: "text",
|
|
7618
|
+
text: item.text
|
|
7619
|
+
}];
|
|
7620
|
+
return [{
|
|
7621
|
+
type: "text",
|
|
7622
|
+
text: item.content ?? ""
|
|
7623
|
+
}];
|
|
7624
|
+
}
|
|
7625
|
+
if (item.type === "tool-call") return [{
|
|
7626
|
+
...item,
|
|
7811
7627
|
type: "tool-call",
|
|
7812
7628
|
toolCallId: item.toolCallId ?? item.id,
|
|
7813
7629
|
toolName: item.toolName ?? item.name,
|
|
7814
|
-
input: JSON.stringify(item.input ?? item.arguments)
|
|
7630
|
+
input: typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? item.arguments)
|
|
7815
7631
|
}];
|
|
7816
|
-
if (
|
|
7632
|
+
if (item.type === "tool_call") {
|
|
7633
|
+
const rawInput = item.input ?? item.arguments;
|
|
7634
|
+
return [{
|
|
7635
|
+
type: "tool-call",
|
|
7636
|
+
toolCallId: item.toolCallId ?? item.id,
|
|
7637
|
+
toolName: item.toolName ?? item.name,
|
|
7638
|
+
input: typeof rawInput === "string" ? rawInput : JSON.stringify(rawInput)
|
|
7639
|
+
}];
|
|
7640
|
+
}
|
|
7641
|
+
if (item.type === "reasoning") {
|
|
7642
|
+
if (typeof item.text === "string") return [{
|
|
7643
|
+
...item,
|
|
7644
|
+
type: "reasoning",
|
|
7645
|
+
text: item.text
|
|
7646
|
+
}];
|
|
7647
|
+
return [{
|
|
7648
|
+
type: "reasoning",
|
|
7649
|
+
text: item.content ?? ""
|
|
7650
|
+
}];
|
|
7651
|
+
}
|
|
7652
|
+
if (item.type === "thinking") return [{
|
|
7817
7653
|
type: "reasoning",
|
|
7818
7654
|
text: item.text ?? item.content ?? ""
|
|
7819
7655
|
}];
|
|
@@ -7879,7 +7715,8 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7879
7715
|
const id = `text-${textIndex++}`;
|
|
7880
7716
|
parts.push({
|
|
7881
7717
|
type: "text-start",
|
|
7882
|
-
id
|
|
7718
|
+
id,
|
|
7719
|
+
providerMetadata: block.providerMetadata
|
|
7883
7720
|
});
|
|
7884
7721
|
parts.push({
|
|
7885
7722
|
type: "text-delta",
|
|
@@ -7907,16 +7744,15 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7907
7744
|
id
|
|
7908
7745
|
});
|
|
7909
7746
|
parts.push({
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
toolName: block.toolName,
|
|
7913
|
-
input: block.input
|
|
7747
|
+
...block,
|
|
7748
|
+
type: "tool-call"
|
|
7914
7749
|
});
|
|
7915
7750
|
} else if (block.type === "reasoning") {
|
|
7916
7751
|
const id = `reasoning-${reasoningIndex++}`;
|
|
7917
7752
|
parts.push({
|
|
7918
7753
|
type: "reasoning-start",
|
|
7919
|
-
id
|
|
7754
|
+
id,
|
|
7755
|
+
providerMetadata: block.providerMetadata
|
|
7920
7756
|
});
|
|
7921
7757
|
parts.push({
|
|
7922
7758
|
type: "reasoning-delta",
|
|
@@ -7981,7 +7817,8 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
7981
7817
|
const id = `text-${textIndex++}`;
|
|
7982
7818
|
parts.push({
|
|
7983
7819
|
type: "text-start",
|
|
7984
|
-
id
|
|
7820
|
+
id,
|
|
7821
|
+
providerMetadata: block.providerMetadata
|
|
7985
7822
|
});
|
|
7986
7823
|
parts.push({
|
|
7987
7824
|
type: "text-delta",
|
|
@@ -8009,16 +7846,15 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
8009
7846
|
id
|
|
8010
7847
|
});
|
|
8011
7848
|
parts.push({
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
toolName: block.toolName,
|
|
8015
|
-
input: block.input
|
|
7849
|
+
...block,
|
|
7850
|
+
type: "tool-call"
|
|
8016
7851
|
});
|
|
8017
7852
|
} else if (block.type === "reasoning") {
|
|
8018
7853
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8019
7854
|
parts.push({
|
|
8020
7855
|
type: "reasoning-start",
|
|
8021
|
-
id
|
|
7856
|
+
id,
|
|
7857
|
+
providerMetadata: block.providerMetadata
|
|
8022
7858
|
});
|
|
8023
7859
|
parts.push({
|
|
8024
7860
|
type: "reasoning-delta",
|
|
@@ -8083,7 +7919,8 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8083
7919
|
const id = `text-${textIndex++}`;
|
|
8084
7920
|
parts.push({
|
|
8085
7921
|
type: "text-start",
|
|
8086
|
-
id
|
|
7922
|
+
id,
|
|
7923
|
+
providerMetadata: block.providerMetadata
|
|
8087
7924
|
});
|
|
8088
7925
|
parts.push({
|
|
8089
7926
|
type: "text-delta",
|
|
@@ -8111,16 +7948,15 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8111
7948
|
id
|
|
8112
7949
|
});
|
|
8113
7950
|
parts.push({
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
toolName: block.toolName,
|
|
8117
|
-
input: block.input
|
|
7951
|
+
...block,
|
|
7952
|
+
type: "tool-call"
|
|
8118
7953
|
});
|
|
8119
7954
|
} else if (block.type === "reasoning") {
|
|
8120
7955
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8121
7956
|
parts.push({
|
|
8122
7957
|
type: "reasoning-start",
|
|
8123
|
-
id
|
|
7958
|
+
id,
|
|
7959
|
+
providerMetadata: block.providerMetadata
|
|
8124
7960
|
});
|
|
8125
7961
|
parts.push({
|
|
8126
7962
|
type: "reasoning-delta",
|
|
@@ -8200,136 +8036,94 @@ function wrapLanguageModel(languageModel) {
|
|
|
8200
8036
|
return new LaminarLanguageModelV2(languageModel);
|
|
8201
8037
|
}
|
|
8202
8038
|
//#endregion
|
|
8203
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
|
|
8207
|
-
const serializeJSON$1 = (value) => {
|
|
8208
|
-
if (typeof value === "string") return value;
|
|
8039
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/package-versions.ts
|
|
8040
|
+
let cached;
|
|
8041
|
+
const readPackageVersion = (specifier) => {
|
|
8209
8042
|
try {
|
|
8210
|
-
|
|
8043
|
+
const pkg = __require(`${specifier}/package.json`);
|
|
8044
|
+
return typeof pkg?.version === "string" ? pkg.version : void 0;
|
|
8211
8045
|
} catch {
|
|
8212
|
-
return
|
|
8046
|
+
return;
|
|
8213
8047
|
}
|
|
8214
8048
|
};
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
else if (sys && typeof sys === "object") messages.push(sys);
|
|
8240
|
-
if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(convertRawStringTextToTextBlock(m));
|
|
8241
|
-
return messages;
|
|
8242
|
-
};
|
|
8243
|
-
const convertRawStringTextToTextBlock = (m) => ({
|
|
8244
|
-
...m,
|
|
8245
|
-
content: typeof m?.content === "string" ? [{
|
|
8246
|
-
type: "text",
|
|
8247
|
-
text: m.content
|
|
8248
|
-
}] : m?.content
|
|
8249
|
-
});
|
|
8250
|
-
const normalizeToolCalls = (toolCalls) => {
|
|
8251
|
-
if (!Array.isArray(toolCalls)) return [];
|
|
8252
|
-
return toolCalls.map((tc) => ({
|
|
8253
|
-
toolCallType: "function",
|
|
8254
|
-
toolCallId: tc?.toolCallId,
|
|
8255
|
-
toolName: tc?.toolName,
|
|
8256
|
-
args: typeof tc?.input === "string" ? tc.input : serializeJSON$1(tc?.input ?? {})
|
|
8257
|
-
}));
|
|
8258
|
-
};
|
|
8259
|
-
const buildGenAiOutputMessages = (args) => {
|
|
8260
|
-
const parts = [];
|
|
8261
|
-
if (args.reasoningText && args.reasoningText.length > 0) parts.push({
|
|
8262
|
-
type: "thinking",
|
|
8263
|
-
content: args.reasoningText
|
|
8264
|
-
});
|
|
8265
|
-
if (args.text && args.text.length > 0) parts.push({
|
|
8266
|
-
type: "text",
|
|
8267
|
-
content: args.text
|
|
8268
|
-
});
|
|
8269
|
-
for (const tc of args.toolCalls ?? []) {
|
|
8270
|
-
let argsObj = tc.args;
|
|
8271
|
-
if (typeof argsObj === "string") try {
|
|
8272
|
-
argsObj = JSON.parse(argsObj);
|
|
8049
|
+
/**
|
|
8050
|
+
* A `node_modules` search-path entry is a package-manager-internal virtual
|
|
8051
|
+
* store (pnpm's `.pnpm`, yarn's `.yarn`, etc.) if any path segment starts
|
|
8052
|
+
* with `.` — excluding the `.`/`..` relative-path segments themselves, which
|
|
8053
|
+
* are unrelated. Packages resolved from inside such a store are some
|
|
8054
|
+
* dependency's OWN transitive deps (e.g. `ai`'s `@ai-sdk/provider`), not
|
|
8055
|
+
* what the host project declared, so those entries must be skipped.
|
|
8056
|
+
*/
|
|
8057
|
+
const isPackageManagerInternalPath = (path) => path.split(sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
|
|
8058
|
+
/**
|
|
8059
|
+
* Given the ordered `node_modules` search-path list Node would use to
|
|
8060
|
+
* resolve a module from this file's location, find the closest directory
|
|
8061
|
+
* that actually has an `@ai-sdk` scope — skipping package-manager-internal
|
|
8062
|
+
* stores — and return the packages listed under it. The first path with a
|
|
8063
|
+
* usable `@ai-sdk` directory wins; levels are not merged, so an unrelated
|
|
8064
|
+
* ancestor/monorepo-root's `@ai-sdk` scope is never mixed in once a closer
|
|
8065
|
+
* one has already matched.
|
|
8066
|
+
*/
|
|
8067
|
+
const findAiSdkScopeEntries = (searchPaths) => {
|
|
8068
|
+
for (const path of searchPaths) {
|
|
8069
|
+
if (isPackageManagerInternalPath(path)) continue;
|
|
8070
|
+
try {
|
|
8071
|
+
const entries = readdirSync(join(path, "@ai-sdk"));
|
|
8072
|
+
if (entries.length > 0) return entries;
|
|
8273
8073
|
} catch {}
|
|
8274
|
-
parts.push({
|
|
8275
|
-
type: "tool_call",
|
|
8276
|
-
id: tc.toolCallId,
|
|
8277
|
-
name: tc.toolName,
|
|
8278
|
-
arguments: argsObj
|
|
8279
|
-
});
|
|
8280
8074
|
}
|
|
8281
|
-
|
|
8282
|
-
return JSON.stringify([{
|
|
8283
|
-
role: "assistant",
|
|
8284
|
-
parts
|
|
8285
|
-
}]);
|
|
8286
|
-
};
|
|
8287
|
-
const extractTextFromContent = (content) => {
|
|
8288
|
-
if (!Array.isArray(content)) return "";
|
|
8289
|
-
return content.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
|
|
8290
|
-
};
|
|
8291
|
-
const extractReasoningFromContent = (content) => {
|
|
8292
|
-
if (!Array.isArray(content)) return "";
|
|
8293
|
-
return content.filter((p) => p && p.type === "reasoning" && typeof p.text === "string").map((p) => p.text).join("");
|
|
8294
|
-
};
|
|
8295
|
-
const applyUsageToSpan = (span, usage) => {
|
|
8296
|
-
const attrs = {};
|
|
8297
|
-
applyUsage(attrs, usage);
|
|
8298
|
-
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
|
|
8075
|
+
return [];
|
|
8299
8076
|
};
|
|
8300
|
-
const
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
if (provider) span.setAttribute(LaminarAttributes.PROVIDER, provider);
|
|
8308
|
-
span.setAttribute("ai.model.provider", event.provider);
|
|
8077
|
+
const discoverAiSdkPackages = () => {
|
|
8078
|
+
const packages = {};
|
|
8079
|
+
let searchPaths;
|
|
8080
|
+
try {
|
|
8081
|
+
searchPaths = __require.resolve.paths("@ai-sdk/__probe__") ?? [];
|
|
8082
|
+
} catch {
|
|
8083
|
+
searchPaths = [];
|
|
8309
8084
|
}
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8085
|
+
for (const entry of findAiSdkScopeEntries(searchPaths)) {
|
|
8086
|
+
const specifier = `@ai-sdk/${entry}`;
|
|
8087
|
+
const version = readPackageVersion(specifier);
|
|
8088
|
+
if (version) packages[specifier] = version;
|
|
8313
8089
|
}
|
|
8090
|
+
return packages;
|
|
8314
8091
|
};
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8092
|
+
/**
|
|
8093
|
+
* Reads installed `ai` / `@ai-sdk/*` package versions once and caches the
|
|
8094
|
+
* result for the lifetime of the process — this is expected to be called on
|
|
8095
|
+
* every span creation, so the actual `require`/`readdirSync` work must not
|
|
8096
|
+
* repeat.
|
|
8097
|
+
*/
|
|
8098
|
+
const getAiSdkPackageVersions = () => {
|
|
8099
|
+
if (cached) return cached;
|
|
8100
|
+
try {
|
|
8101
|
+
cached = {
|
|
8102
|
+
aiVersion: readPackageVersion("ai"),
|
|
8103
|
+
aiSdkPackages: discoverAiSdkPackages()
|
|
8104
|
+
};
|
|
8105
|
+
} catch {
|
|
8106
|
+
cached = { aiSdkPackages: {} };
|
|
8107
|
+
}
|
|
8108
|
+
return cached;
|
|
8325
8109
|
};
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8110
|
+
/**
|
|
8111
|
+
* Flat span-attribute object for the `ai` package's instrumentation scope
|
|
8112
|
+
* plus one `lmnr.span.instrumentation.@ai-sdk/<pkg>.version` entry per
|
|
8113
|
+
* resolvable `@ai-sdk/*` package. Merge this into every span this
|
|
8114
|
+
* integration creates.
|
|
8115
|
+
*/
|
|
8116
|
+
const buildAiSdkInstrumentationAttributes = () => {
|
|
8117
|
+
const { aiVersion, aiSdkPackages } = getAiSdkPackageVersions();
|
|
8118
|
+
const attributes = { [SPAN_INSTRUMENTATION_SCOPE_NAME]: "ai" };
|
|
8119
|
+
if (aiVersion) attributes[SPAN_INSTRUMENTATION_SCOPE_VERSION] = aiVersion;
|
|
8120
|
+
for (const [pkg, version] of Object.entries(aiSdkPackages)) attributes[`lmnr.span.instrumentation.${pkg}.version`] = version;
|
|
8121
|
+
return attributes;
|
|
8331
8122
|
};
|
|
8332
8123
|
//#endregion
|
|
8124
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
|
|
8125
|
+
const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
|
|
8126
|
+
//#endregion
|
|
8333
8127
|
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/index.ts
|
|
8334
8128
|
/**
|
|
8335
8129
|
* The AI SDK v7 diagnostics channel name. Every lifecycle event fires
|
|
@@ -8351,6 +8145,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8351
8145
|
this.llmByKey = /* @__PURE__ */ new Map();
|
|
8352
8146
|
this.toolByCallId = /* @__PURE__ */ new Map();
|
|
8353
8147
|
this.activeStreamStepByCallId = /* @__PURE__ */ new Map();
|
|
8148
|
+
this.promptByCallId = /* @__PURE__ */ new Map();
|
|
8149
|
+
this.logger = initializeLogger();
|
|
8354
8150
|
this.onStart = (event) => {
|
|
8355
8151
|
const callId = event?.callId;
|
|
8356
8152
|
if (!callId) return;
|
|
@@ -8362,6 +8158,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8362
8158
|
kind: SpanKind.CLIENT,
|
|
8363
8159
|
attributes: {
|
|
8364
8160
|
[SPAN_TYPE]: "DEFAULT",
|
|
8161
|
+
...buildAiSdkInstrumentationAttributes(),
|
|
8365
8162
|
...typeof event.functionId === "string" ? {
|
|
8366
8163
|
"operation.name": event.functionId,
|
|
8367
8164
|
"ai.prompt": "_"
|
|
@@ -8382,8 +8179,10 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8382
8179
|
if (typeof event.functionId === "string") span.setAttribute("ai.functionId", event.functionId);
|
|
8383
8180
|
if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany" || event.operationId === "ai.rerank") span.setAttribute(SPAN_TYPE, "LLM");
|
|
8384
8181
|
if (this.recordInputs) {
|
|
8385
|
-
if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0)
|
|
8386
|
-
|
|
8182
|
+
if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) {
|
|
8183
|
+
const msgs = verbatimStandardizedMessages(event);
|
|
8184
|
+
if (msgs.length > 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(msgs));
|
|
8185
|
+
} else if (event.value !== void 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(event.value));
|
|
8387
8186
|
else if (event.query !== void 0 || event.documents !== void 0) span.setAttribute(SPAN_INPUT, serializeJSON$1({
|
|
8388
8187
|
query: event.query,
|
|
8389
8188
|
documents: event.documents
|
|
@@ -8398,17 +8197,25 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8398
8197
|
});
|
|
8399
8198
|
};
|
|
8400
8199
|
this.onStepStart = (event) => {
|
|
8401
|
-
if (!this.createStepSpan) return;
|
|
8402
8200
|
const callId = event?.callId;
|
|
8403
8201
|
const stepNumber = event?.stepNumber ?? 0;
|
|
8404
8202
|
if (!callId) return;
|
|
8203
|
+
if (Array.isArray(event?.promptMessages)) {
|
|
8204
|
+
const serialized = verbatimPromptString(event.promptMessages);
|
|
8205
|
+
if (serialized !== null) this.promptByCallId.set(callId, serialized);
|
|
8206
|
+
}
|
|
8207
|
+
if (!this.createStepSpan) return;
|
|
8405
8208
|
const op = this.operationByCallId.get(callId);
|
|
8406
8209
|
if (!op) return;
|
|
8407
8210
|
const span = getTracer().startSpan(`ai.step`, { kind: SpanKind.CLIENT }, op.ctx);
|
|
8408
8211
|
const spanCtx = trace.setSpan(op.ctx, span);
|
|
8409
8212
|
span.setAttribute(SPAN_TYPE, "DEFAULT");
|
|
8213
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8410
8214
|
span.setAttribute("ai.step.number", stepNumber);
|
|
8411
|
-
if (this.recordInputs)
|
|
8215
|
+
if (this.recordInputs) {
|
|
8216
|
+
const msgs = verbatimStandardizedMessages(event);
|
|
8217
|
+
if (msgs.length > 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(msgs));
|
|
8218
|
+
}
|
|
8412
8219
|
this.stepByKey.set(stepKey(callId, stepNumber), {
|
|
8413
8220
|
span,
|
|
8414
8221
|
ctx: spanCtx,
|
|
@@ -8424,8 +8231,17 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8424
8231
|
if (!parentCtx) return;
|
|
8425
8232
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: SpanKind.CLIENT }, parentCtx);
|
|
8426
8233
|
span.setAttribute(SPAN_TYPE, "LLM");
|
|
8234
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8427
8235
|
applyRequestModelAttributes(span, event);
|
|
8428
|
-
if (this.recordInputs)
|
|
8236
|
+
if (this.recordInputs) {
|
|
8237
|
+
if (event.tools) try {
|
|
8238
|
+
span.setAttribute("gen_ai.tool.definitions", JSON.stringify(event.tools));
|
|
8239
|
+
} catch (e) {
|
|
8240
|
+
this.logger.debug(`Failed to record span tool definitions: ${errorMessage(e)}`);
|
|
8241
|
+
}
|
|
8242
|
+
const serialized = this.promptByCallId.get(callId);
|
|
8243
|
+
if (serialized !== void 0) span.setAttribute("gen_ai.input.messages", serialized);
|
|
8244
|
+
}
|
|
8429
8245
|
this.llmByKey.set(stepKey(callId, stepNumber), {
|
|
8430
8246
|
span,
|
|
8431
8247
|
textDeltas: []
|
|
@@ -8448,18 +8264,14 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8448
8264
|
if (typeof event.responseId === "string") span.setAttribute("gen_ai.response.id", event.responseId);
|
|
8449
8265
|
applyUsageToSpan(span, event.usage);
|
|
8450
8266
|
if (this.recordOutputs) {
|
|
8451
|
-
const
|
|
8452
|
-
const outputMessages = buildGenAiOutputMessages({
|
|
8453
|
-
text: extractTextFromContent(content),
|
|
8454
|
-
reasoningText: extractReasoningFromContent(content),
|
|
8455
|
-
toolCalls: normalizeToolCalls(content ? content.filter((p) => p && p.type === "tool-call") : [])
|
|
8456
|
-
});
|
|
8267
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8457
8268
|
if (outputMessages) span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8458
8269
|
}
|
|
8459
8270
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
8460
8271
|
span.end();
|
|
8461
8272
|
removeActiveLlmSpan(span);
|
|
8462
8273
|
this.llmByKey.delete(stepKey(callId, stepNumber));
|
|
8274
|
+
this.promptByCallId.delete(callId);
|
|
8463
8275
|
};
|
|
8464
8276
|
this.onChunk = (event) => {
|
|
8465
8277
|
const chunk = event?.chunk;
|
|
@@ -8488,7 +8300,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8488
8300
|
const llm = this.llmByKey.get(key);
|
|
8489
8301
|
if (llm) {
|
|
8490
8302
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8491
|
-
const outputMessages =
|
|
8303
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8492
8304
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8493
8305
|
}
|
|
8494
8306
|
applyUsageToSpan(llm.span, event.usage);
|
|
@@ -8496,21 +8308,13 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8496
8308
|
removeActiveLlmSpan(llm.span);
|
|
8497
8309
|
this.llmByKey.delete(key);
|
|
8498
8310
|
}
|
|
8311
|
+
this.promptByCallId.delete(callId);
|
|
8499
8312
|
const step = this.stepByKey.get(key);
|
|
8500
8313
|
if (!step) return;
|
|
8501
8314
|
if (typeof event.finishReason === "string") applyFinishReason(step.span, event.finishReason);
|
|
8502
8315
|
if (this.recordOutputs) {
|
|
8503
|
-
const
|
|
8504
|
-
if (
|
|
8505
|
-
outputPayload.text = event.text;
|
|
8506
|
-
step.span.setAttribute("ai.response.text", event.text);
|
|
8507
|
-
}
|
|
8508
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8509
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8510
|
-
outputPayload.toolCalls = normalizedToolCallsList;
|
|
8511
|
-
step.span.setAttribute("ai.response.toolCalls", serializeJSON$1(normalizedToolCallsList));
|
|
8512
|
-
}
|
|
8513
|
-
if (Object.keys(outputPayload).length > 0) step.span.setAttribute(SPAN_OUTPUT, serializeJSON$1(outputPayload));
|
|
8316
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8317
|
+
if (outputMessages) step.span.setAttribute(SPAN_OUTPUT, outputMessages);
|
|
8514
8318
|
}
|
|
8515
8319
|
step.span.setStatus({ code: SpanStatusCode.OK });
|
|
8516
8320
|
step.span.end();
|
|
@@ -8523,7 +8327,12 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8523
8327
|
if (!op) return;
|
|
8524
8328
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: SpanKind.CLIENT }, op.ctx);
|
|
8525
8329
|
span.setAttribute(SPAN_TYPE, "LLM");
|
|
8330
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8526
8331
|
applyRequestModelAttributes(span, event);
|
|
8332
|
+
if (this.recordInputs && Array.isArray(event?.promptMessages)) {
|
|
8333
|
+
const serialized = verbatimPromptString(event.promptMessages);
|
|
8334
|
+
if (serialized !== null) span.setAttribute("gen_ai.input.messages", serialized);
|
|
8335
|
+
}
|
|
8527
8336
|
this.llmByKey.set(stepKey(callId, 0), {
|
|
8528
8337
|
span,
|
|
8529
8338
|
textDeltas: []
|
|
@@ -8543,7 +8352,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8543
8352
|
}
|
|
8544
8353
|
applyUsageToSpan(llm.span, event.usage);
|
|
8545
8354
|
if (this.recordOutputs && typeof event.objectText === "string") {
|
|
8546
|
-
const outputMessages =
|
|
8355
|
+
const outputMessages = buildTextOutputMessages(event.objectText);
|
|
8547
8356
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8548
8357
|
}
|
|
8549
8358
|
llm.span.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -8562,6 +8371,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8562
8371
|
const span = getTracer().startSpan(`ai.tool ${toolName ?? "unknown"}`, { kind: SpanKind.CLIENT }, parentCtx);
|
|
8563
8372
|
const spanCtx = trace.setSpan(parentCtx, span);
|
|
8564
8373
|
span.setAttribute(SPAN_TYPE, "TOOL");
|
|
8374
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8565
8375
|
if (toolName) span.setAttribute("ai.toolCall.name", toolName);
|
|
8566
8376
|
span.setAttribute("ai.toolCall.id", toolCallId);
|
|
8567
8377
|
if (this.recordInputs && toolCall?.input !== void 0) {
|
|
@@ -8639,17 +8449,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8639
8449
|
if (typeof event.finishReason === "string") applyFinishReason(op.span, event.finishReason);
|
|
8640
8450
|
if (op.operationId === "ai.embed" || op.operationId === "ai.embedMany" || op.operationId === "ai.rerank") applyUsageToSpan(op.span, event.totalUsage ?? event.usage);
|
|
8641
8451
|
if (this.recordOutputs) {
|
|
8642
|
-
const
|
|
8643
|
-
if (
|
|
8644
|
-
op.span.setAttribute("ai.response.text", event.text);
|
|
8645
|
-
op.span.setAttribute(SPAN_OUTPUT, event.text);
|
|
8646
|
-
}
|
|
8647
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8648
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8649
|
-
const serialized = serializeJSON$1(normalizedToolCallsList);
|
|
8650
|
-
op.span.setAttribute("ai.response.toolCalls", serialized);
|
|
8651
|
-
if (!hasText) op.span.setAttribute(SPAN_OUTPUT, serialized);
|
|
8652
|
-
}
|
|
8452
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8453
|
+
if (outputMessages) op.span.setAttribute(SPAN_OUTPUT, outputMessages);
|
|
8653
8454
|
if (event.embedding !== void 0) {
|
|
8654
8455
|
const emb = event.embedding;
|
|
8655
8456
|
if (Array.isArray(emb) && Array.isArray(emb[0])) op.span.setAttribute("ai.embed.count", emb.length);
|
|
@@ -8753,7 +8554,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8753
8554
|
};
|
|
8754
8555
|
for (const [key, llm] of this.llmByKey) if (key.startsWith(prefix)) {
|
|
8755
8556
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8756
|
-
const outputMessages =
|
|
8557
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8757
8558
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8758
8559
|
}
|
|
8759
8560
|
llm.span.end();
|
|
@@ -8771,6 +8572,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8771
8572
|
for (const key of this.llmByKey.keys()) if (key.startsWith(prefix)) this.llmByKey.delete(key);
|
|
8772
8573
|
for (const [toolCallId, tool] of this.toolByCallId) if (tool.callId === callId) this.toolByCallId.delete(toolCallId);
|
|
8773
8574
|
for (const key of this.stepByKey.keys()) if (key.startsWith(prefix)) this.stepByKey.delete(key);
|
|
8575
|
+
this.promptByCallId.delete(callId);
|
|
8774
8576
|
return latest;
|
|
8775
8577
|
}
|
|
8776
8578
|
};
|