langsmith 0.2.0 → 0.2.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/singletons/fetch.d.ts +0 -4
- package/dist/vercel.cjs +630 -0
- package/dist/vercel.d.ts +74 -0
- package/dist/vercel.js +626 -0
- package/dist/vercel.types.cjs +2 -0
- package/dist/vercel.types.d.ts +1 -0
- package/dist/vercel.types.js +1 -0
- package/package.json +18 -3
- package/vercel.cjs +1 -0
- package/vercel.d.cts +1 -0
- package/vercel.d.ts +1 -0
- package/vercel.js +1 -0
package/dist/index.cjs
CHANGED
|
@@ -8,4 +8,4 @@ Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () {
|
|
|
8
8
|
var fetch_js_1 = require("./singletons/fetch.cjs");
|
|
9
9
|
Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true, get: function () { return fetch_js_1.overrideFetchImplementation; } });
|
|
10
10
|
// Update using yarn bump-version
|
|
11
|
-
exports.__version__ = "0.2.0";
|
|
11
|
+
exports.__version__ = "0.2.1-rc.0";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { Client, type ClientConfig } from "./client.js";
|
|
|
2
2
|
export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, } from "./schemas.js";
|
|
3
3
|
export { RunTree, type RunTreeConfig } from "./run_trees.js";
|
|
4
4
|
export { overrideFetchImplementation } from "./singletons/fetch.js";
|
|
5
|
-
export declare const __version__ = "0.2.0";
|
|
5
|
+
export declare const __version__ = "0.2.1-rc.0";
|
package/dist/index.js
CHANGED
package/dist/vercel.cjs
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AISDKExporter = void 0;
|
|
4
|
+
const index_js_1 = require("./index.cjs");
|
|
5
|
+
const uuid_1 = require("uuid");
|
|
6
|
+
const traceable_js_1 = require("./singletons/traceable.cjs");
|
|
7
|
+
const env_js_1 = require("./utils/env.cjs");
|
|
8
|
+
// Attempt to convert CoreMessage to a LangChain-compatible format
|
|
9
|
+
// which allows us to render messages more nicely in LangSmith
|
|
10
|
+
function convertCoreToSmith(message) {
|
|
11
|
+
if (message.role === "assistant") {
|
|
12
|
+
const data = { content: message.content };
|
|
13
|
+
if (Array.isArray(message.content)) {
|
|
14
|
+
data.content = message.content.map((part) => {
|
|
15
|
+
if (part.type === "text") {
|
|
16
|
+
return {
|
|
17
|
+
type: "text",
|
|
18
|
+
text: part.text,
|
|
19
|
+
...part.experimental_providerMetadata,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (part.type === "tool-call") {
|
|
23
|
+
return {
|
|
24
|
+
type: "tool_use",
|
|
25
|
+
name: part.toolName,
|
|
26
|
+
id: part.toolCallId,
|
|
27
|
+
input: part.args,
|
|
28
|
+
...part.experimental_providerMetadata,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return part;
|
|
32
|
+
});
|
|
33
|
+
const toolCalls = message.content.filter((part) => part.type === "tool-call");
|
|
34
|
+
if (toolCalls.length > 0) {
|
|
35
|
+
data.additional_kwargs ??= {};
|
|
36
|
+
data.additional_kwargs.tool_calls = toolCalls.map((part) => {
|
|
37
|
+
return {
|
|
38
|
+
id: part.toolCallId,
|
|
39
|
+
type: "function",
|
|
40
|
+
function: {
|
|
41
|
+
name: part.toolName,
|
|
42
|
+
id: part.toolCallId,
|
|
43
|
+
arguments: JSON.stringify(part.args),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { type: "ai", data };
|
|
50
|
+
}
|
|
51
|
+
if (message.role === "user") {
|
|
52
|
+
const data = { content: message.content };
|
|
53
|
+
if (Array.isArray(message.content)) {
|
|
54
|
+
data.content = message.content.map((part) => {
|
|
55
|
+
if (part.type === "text") {
|
|
56
|
+
return {
|
|
57
|
+
type: "text",
|
|
58
|
+
text: part.text,
|
|
59
|
+
...part.experimental_providerMetadata,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (part.type === "image") {
|
|
63
|
+
return {
|
|
64
|
+
type: "image_url",
|
|
65
|
+
image_url: part.image,
|
|
66
|
+
...part.experimental_providerMetadata,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return part;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return { type: "human", data };
|
|
73
|
+
}
|
|
74
|
+
if (message.role === "system") {
|
|
75
|
+
return { type: "system", data: { content: message.content } };
|
|
76
|
+
}
|
|
77
|
+
if (message.role === "tool") {
|
|
78
|
+
const res = message.content.map((toolCall) => {
|
|
79
|
+
return {
|
|
80
|
+
type: "tool",
|
|
81
|
+
data: {
|
|
82
|
+
content: JSON.stringify(toolCall.result),
|
|
83
|
+
name: toolCall.toolName,
|
|
84
|
+
tool_call_id: toolCall.toolCallId,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
if (res.length === 1)
|
|
89
|
+
return res[0];
|
|
90
|
+
return res;
|
|
91
|
+
}
|
|
92
|
+
return message;
|
|
93
|
+
}
|
|
94
|
+
const tryJson = (str) => {
|
|
95
|
+
try {
|
|
96
|
+
if (!str)
|
|
97
|
+
return str;
|
|
98
|
+
if (typeof str !== "string")
|
|
99
|
+
return str;
|
|
100
|
+
return JSON.parse(str);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return str;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
function stripNonAlphanumeric(input) {
|
|
107
|
+
return input.replace(/[-:.]/g, "");
|
|
108
|
+
}
|
|
109
|
+
function convertToDottedOrderFormat([seconds, nanoseconds], runId, executionOrder) {
|
|
110
|
+
// Date only has millisecond precision, so we use the microseconds to break
|
|
111
|
+
// possible ties, avoiding incorrect run order
|
|
112
|
+
const ms = Number(String(nanoseconds).slice(0, 3));
|
|
113
|
+
const ns = String(Number(String(nanoseconds).slice(3, 6)) + executionOrder)
|
|
114
|
+
.padStart(3, "0")
|
|
115
|
+
.slice(0, 3);
|
|
116
|
+
return (stripNonAlphanumeric(`${new Date(seconds * 1000 + ms).toISOString().slice(0, -1)}${ns}Z`) + runId);
|
|
117
|
+
}
|
|
118
|
+
function convertToTimestamp([seconds, nanoseconds]) {
|
|
119
|
+
const ms = String(nanoseconds).slice(0, 3);
|
|
120
|
+
return Number(String(seconds) + ms);
|
|
121
|
+
}
|
|
122
|
+
function sortByHr(a, b) {
|
|
123
|
+
if (a[0] !== b[0])
|
|
124
|
+
return Math.sign(a[0] - b[0]);
|
|
125
|
+
return Math.sign(a[1] - b[1]);
|
|
126
|
+
}
|
|
127
|
+
const ROOT = "$";
|
|
128
|
+
const RUN_ID_NAMESPACE = "5c718b20-9078-11ef-9a3d-325096b39f47";
|
|
129
|
+
const RUN_ID_METADATA_KEY = {
|
|
130
|
+
input: "langsmith:runId",
|
|
131
|
+
output: "ai.telemetry.metadata.langsmith:runId",
|
|
132
|
+
};
|
|
133
|
+
const RUN_NAME_METADATA_KEY = {
|
|
134
|
+
input: "langsmith:runName",
|
|
135
|
+
output: "ai.telemetry.metadata.langsmith:runName",
|
|
136
|
+
};
|
|
137
|
+
const TRACE_METADATA_KEY = {
|
|
138
|
+
input: "langsmith:trace",
|
|
139
|
+
output: "ai.telemetry.metadata.langsmith:trace",
|
|
140
|
+
};
|
|
141
|
+
const BAGGAGE_METADATA_KEY = {
|
|
142
|
+
input: "langsmith:baggage",
|
|
143
|
+
output: "ai.telemetry.metadata.langsmith:baggage",
|
|
144
|
+
};
|
|
145
|
+
const RESERVED_METADATA_KEYS = [
|
|
146
|
+
RUN_ID_METADATA_KEY.output,
|
|
147
|
+
RUN_NAME_METADATA_KEY.output,
|
|
148
|
+
TRACE_METADATA_KEY.output,
|
|
149
|
+
BAGGAGE_METADATA_KEY.output,
|
|
150
|
+
];
|
|
151
|
+
/**
|
|
152
|
+
* OpenTelemetry trace exporter for Vercel AI SDK.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* import { AISDKExporter } from "langsmith/vercel";
|
|
157
|
+
* import { Client } from "langsmith";
|
|
158
|
+
*
|
|
159
|
+
* import { streamText } from "ai";
|
|
160
|
+
* import { openai } from "@ai-sdk/openai";
|
|
161
|
+
*
|
|
162
|
+
* import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
163
|
+
* import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
164
|
+
*
|
|
165
|
+
* const client = new Client();
|
|
166
|
+
*
|
|
167
|
+
* const sdk = new NodeSDK({
|
|
168
|
+
* traceExporter: new AISDKExporter({ client }),
|
|
169
|
+
* instrumentations: [getNodeAutoInstrumentations()],
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* sdk.start();
|
|
173
|
+
*
|
|
174
|
+
* const res = await generateText({
|
|
175
|
+
* model: openai("gpt-4o-mini"),
|
|
176
|
+
* messages: [
|
|
177
|
+
* {
|
|
178
|
+
* role: "user",
|
|
179
|
+
* content: "What color is the sky?",
|
|
180
|
+
* },
|
|
181
|
+
* ],
|
|
182
|
+
* experimental_telemetry: AISDKExporter.getSettings({
|
|
183
|
+
* runName: "langsmith_traced_call",
|
|
184
|
+
* functionId: "functionId",
|
|
185
|
+
* metadata: { userId: "123", language: "english" },
|
|
186
|
+
* }),
|
|
187
|
+
* });
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
class AISDKExporter {
|
|
191
|
+
constructor(args) {
|
|
192
|
+
Object.defineProperty(this, "client", {
|
|
193
|
+
enumerable: true,
|
|
194
|
+
configurable: true,
|
|
195
|
+
writable: true,
|
|
196
|
+
value: void 0
|
|
197
|
+
});
|
|
198
|
+
Object.defineProperty(this, "traceByMap", {
|
|
199
|
+
enumerable: true,
|
|
200
|
+
configurable: true,
|
|
201
|
+
writable: true,
|
|
202
|
+
value: {}
|
|
203
|
+
});
|
|
204
|
+
/** @internal */
|
|
205
|
+
Object.defineProperty(this, "getSpanAttributeKey", {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
configurable: true,
|
|
208
|
+
writable: true,
|
|
209
|
+
value: (span, key) => {
|
|
210
|
+
const attributes = span.attributes;
|
|
211
|
+
return key in attributes && typeof attributes[key] === "string"
|
|
212
|
+
? attributes[key]
|
|
213
|
+
: undefined;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
this.client = args?.client ?? new index_js_1.Client();
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Helper method for initializing OTEL settings.
|
|
220
|
+
*/
|
|
221
|
+
static getSettings(settings) {
|
|
222
|
+
const { runId, runName, ...rest } = settings;
|
|
223
|
+
const metadata = { ...rest?.metadata };
|
|
224
|
+
if (runId != null)
|
|
225
|
+
metadata[RUN_ID_METADATA_KEY.input] = runId;
|
|
226
|
+
if (runName != null)
|
|
227
|
+
metadata[RUN_NAME_METADATA_KEY.input] = runName;
|
|
228
|
+
// attempt to obtain the run tree if used within a traceable function
|
|
229
|
+
let defaultEnabled = true;
|
|
230
|
+
try {
|
|
231
|
+
const runTree = (0, traceable_js_1.getCurrentRunTree)();
|
|
232
|
+
const headers = runTree.toHeaders();
|
|
233
|
+
metadata[TRACE_METADATA_KEY.input] = headers["langsmith-trace"];
|
|
234
|
+
metadata[BAGGAGE_METADATA_KEY.input] = headers["baggage"];
|
|
235
|
+
// honor the tracingEnabled flag if coming from traceable
|
|
236
|
+
if (runTree.tracingEnabled != null) {
|
|
237
|
+
defaultEnabled = runTree.tracingEnabled;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// pass
|
|
242
|
+
}
|
|
243
|
+
if (metadata[RUN_ID_METADATA_KEY.input] &&
|
|
244
|
+
metadata[TRACE_METADATA_KEY.input]) {
|
|
245
|
+
throw new Error("Cannot provide `runId` when used within traceable function.");
|
|
246
|
+
}
|
|
247
|
+
return { ...rest, isEnabled: rest.isEnabled ?? defaultEnabled, metadata };
|
|
248
|
+
}
|
|
249
|
+
/** @internal */
|
|
250
|
+
parseInteropFromMetadata(span) {
|
|
251
|
+
const userTraceId = this.getSpanAttributeKey(span, RUN_ID_METADATA_KEY.output);
|
|
252
|
+
const parentTrace = this.getSpanAttributeKey(span, TRACE_METADATA_KEY.output);
|
|
253
|
+
if (parentTrace && userTraceId) {
|
|
254
|
+
throw new Error(`Cannot provide both "${RUN_ID_METADATA_KEY.input}" and "${TRACE_METADATA_KEY.input}" metadata keys.`);
|
|
255
|
+
}
|
|
256
|
+
if (parentTrace) {
|
|
257
|
+
const parentRunTree = index_js_1.RunTree.fromHeaders({
|
|
258
|
+
"langsmith-trace": parentTrace,
|
|
259
|
+
baggage: this.getSpanAttributeKey(span, BAGGAGE_METADATA_KEY.output) || "",
|
|
260
|
+
});
|
|
261
|
+
if (!parentRunTree)
|
|
262
|
+
throw new Error("Unreachable code: empty parent run tree");
|
|
263
|
+
return { type: "traceable", parentRunTree };
|
|
264
|
+
}
|
|
265
|
+
if (userTraceId)
|
|
266
|
+
return { type: "user", userTraceId };
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
/** @internal */
|
|
270
|
+
getRunCreate(span) {
|
|
271
|
+
const runId = (0, uuid_1.v5)(span.spanContext().spanId, RUN_ID_NAMESPACE);
|
|
272
|
+
const parentRunId = span.parentSpanId
|
|
273
|
+
? (0, uuid_1.v5)(span.parentSpanId, RUN_ID_NAMESPACE)
|
|
274
|
+
: undefined;
|
|
275
|
+
const asRunCreate = (rawConfig) => {
|
|
276
|
+
const aiMetadata = Object.keys(span.attributes)
|
|
277
|
+
.filter((key) => key.startsWith("ai.telemetry.metadata.") &&
|
|
278
|
+
!RESERVED_METADATA_KEYS.includes(key))
|
|
279
|
+
.reduce((acc, key) => {
|
|
280
|
+
acc[key.slice("ai.telemetry.metadata.".length)] =
|
|
281
|
+
span.attributes[key];
|
|
282
|
+
return acc;
|
|
283
|
+
}, {});
|
|
284
|
+
if (("ai.telemetry.functionId" in span.attributes &&
|
|
285
|
+
span.attributes["ai.telemetry.functionId"]) ||
|
|
286
|
+
("resource.name" in span.attributes && span.attributes["resource.name"])) {
|
|
287
|
+
aiMetadata["functionId"] =
|
|
288
|
+
span.attributes["ai.telemetry.functionId"] ||
|
|
289
|
+
span.attributes["resource.name"];
|
|
290
|
+
}
|
|
291
|
+
const parsedStart = convertToTimestamp(span.startTime);
|
|
292
|
+
const parsedEnd = convertToTimestamp(span.endTime);
|
|
293
|
+
let name = rawConfig.name;
|
|
294
|
+
// if user provided a custom name, only use it if it's the root
|
|
295
|
+
if (span.parentSpanId == null) {
|
|
296
|
+
name =
|
|
297
|
+
this.getSpanAttributeKey(span, RUN_NAME_METADATA_KEY.output) || name;
|
|
298
|
+
}
|
|
299
|
+
const config = {
|
|
300
|
+
...rawConfig,
|
|
301
|
+
name,
|
|
302
|
+
id: runId,
|
|
303
|
+
parent_run_id: parentRunId,
|
|
304
|
+
extra: {
|
|
305
|
+
...rawConfig.extra,
|
|
306
|
+
metadata: {
|
|
307
|
+
...rawConfig.extra?.metadata,
|
|
308
|
+
...aiMetadata,
|
|
309
|
+
"ai.operationId": span.attributes["ai.operationId"],
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
session_name: (0, env_js_1.getLangSmithEnvironmentVariable)("PROJECT") ??
|
|
313
|
+
(0, env_js_1.getLangSmithEnvironmentVariable)("SESSION"),
|
|
314
|
+
start_time: Math.min(parsedStart, parsedEnd),
|
|
315
|
+
end_time: Math.max(parsedStart, parsedEnd),
|
|
316
|
+
};
|
|
317
|
+
return config;
|
|
318
|
+
};
|
|
319
|
+
switch (span.name) {
|
|
320
|
+
case "ai.generateText.doGenerate":
|
|
321
|
+
case "ai.generateText":
|
|
322
|
+
case "ai.streamText.doStream":
|
|
323
|
+
case "ai.streamText": {
|
|
324
|
+
const inputs = (() => {
|
|
325
|
+
if ("ai.prompt.messages" in span.attributes) {
|
|
326
|
+
return {
|
|
327
|
+
messages: tryJson(span.attributes["ai.prompt.messages"]).flatMap((i) => convertCoreToSmith(i)),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if ("ai.prompt" in span.attributes) {
|
|
331
|
+
const input = tryJson(span.attributes["ai.prompt"]);
|
|
332
|
+
if (typeof input === "object" &&
|
|
333
|
+
input != null &&
|
|
334
|
+
"messages" in input &&
|
|
335
|
+
Array.isArray(input.messages)) {
|
|
336
|
+
return {
|
|
337
|
+
messages: input.messages.flatMap((i) => convertCoreToSmith(i)),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return { input };
|
|
341
|
+
}
|
|
342
|
+
return {};
|
|
343
|
+
})();
|
|
344
|
+
const outputs = (() => {
|
|
345
|
+
let result = undefined;
|
|
346
|
+
if (span.attributes["ai.response.toolCalls"]) {
|
|
347
|
+
let content = tryJson(span.attributes["ai.response.toolCalls"]);
|
|
348
|
+
if (Array.isArray(content)) {
|
|
349
|
+
content = content.map((i) => ({
|
|
350
|
+
type: "tool-call",
|
|
351
|
+
...i,
|
|
352
|
+
args: tryJson(i.args),
|
|
353
|
+
}));
|
|
354
|
+
}
|
|
355
|
+
result = {
|
|
356
|
+
llm_output: convertCoreToSmith({
|
|
357
|
+
role: "assistant",
|
|
358
|
+
content,
|
|
359
|
+
}),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
else if (span.attributes["ai.response.text"]) {
|
|
363
|
+
result = {
|
|
364
|
+
llm_output: convertCoreToSmith({
|
|
365
|
+
role: "assistant",
|
|
366
|
+
content: span.attributes["ai.response.text"],
|
|
367
|
+
}),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (span.attributes["ai.usage.completionTokens"]) {
|
|
371
|
+
result ??= {};
|
|
372
|
+
result.llm_output ??= {};
|
|
373
|
+
result.llm_output.token_usage ??= {};
|
|
374
|
+
result.llm_output.token_usage["completion_tokens"] =
|
|
375
|
+
span.attributes["ai.usage.completionTokens"];
|
|
376
|
+
}
|
|
377
|
+
if (span.attributes["ai.usage.promptTokens"]) {
|
|
378
|
+
result ??= {};
|
|
379
|
+
result.llm_output ??= {};
|
|
380
|
+
result.llm_output.token_usage ??= {};
|
|
381
|
+
result.llm_output.token_usage["prompt_tokens"] =
|
|
382
|
+
span.attributes["ai.usage.promptTokens"];
|
|
383
|
+
}
|
|
384
|
+
return result;
|
|
385
|
+
})();
|
|
386
|
+
const events = [];
|
|
387
|
+
const firstChunkEvent = span.events.find((i) => i.name === "ai.stream.firstChunk");
|
|
388
|
+
if (firstChunkEvent) {
|
|
389
|
+
events.push({
|
|
390
|
+
name: "new_token",
|
|
391
|
+
time: convertToTimestamp(firstChunkEvent.time),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// TODO: add first_token_time
|
|
395
|
+
return asRunCreate({
|
|
396
|
+
run_type: "llm",
|
|
397
|
+
name: span.attributes["ai.model.provider"],
|
|
398
|
+
inputs,
|
|
399
|
+
outputs,
|
|
400
|
+
events,
|
|
401
|
+
extra: {
|
|
402
|
+
batch_size: 1,
|
|
403
|
+
metadata: {
|
|
404
|
+
ls_provider: span.attributes["ai.model.provider"]
|
|
405
|
+
.split(".")
|
|
406
|
+
.at(0),
|
|
407
|
+
ls_model_type: span.attributes["ai.model.provider"]
|
|
408
|
+
.split(".")
|
|
409
|
+
.at(1),
|
|
410
|
+
ls_model_name: span.attributes["ai.model.id"],
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
case "ai.toolCall": {
|
|
416
|
+
const args = tryJson(span.attributes["ai.toolCall.args"]);
|
|
417
|
+
let inputs = { args };
|
|
418
|
+
if (typeof args === "object" && args != null) {
|
|
419
|
+
inputs = args;
|
|
420
|
+
}
|
|
421
|
+
const output = tryJson(span.attributes["ai.toolCall.result"]);
|
|
422
|
+
let outputs = { output };
|
|
423
|
+
if (typeof output === "object" && output != null) {
|
|
424
|
+
outputs = output;
|
|
425
|
+
}
|
|
426
|
+
return asRunCreate({
|
|
427
|
+
run_type: "tool",
|
|
428
|
+
name: span.attributes["ai.toolCall.name"],
|
|
429
|
+
inputs,
|
|
430
|
+
outputs,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
case "ai.streamObject":
|
|
434
|
+
case "ai.streamObject.doStream":
|
|
435
|
+
case "ai.generateObject":
|
|
436
|
+
case "ai.generateObject.doGenerate": {
|
|
437
|
+
const inputs = (() => {
|
|
438
|
+
if ("ai.prompt.messages" in span.attributes) {
|
|
439
|
+
return {
|
|
440
|
+
messages: tryJson(span.attributes["ai.prompt.messages"]).flatMap((i) => convertCoreToSmith(i)),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
if ("ai.prompt" in span.attributes) {
|
|
444
|
+
return { input: tryJson(span.attributes["ai.prompt"]) };
|
|
445
|
+
}
|
|
446
|
+
return {};
|
|
447
|
+
})();
|
|
448
|
+
const outputs = (() => {
|
|
449
|
+
let result = undefined;
|
|
450
|
+
if (span.attributes["ai.response.object"]) {
|
|
451
|
+
result = {
|
|
452
|
+
output: tryJson(span.attributes["ai.response.object"]),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (span.attributes["ai.usage.completionTokens"]) {
|
|
456
|
+
result ??= {};
|
|
457
|
+
result.llm_output ??= {};
|
|
458
|
+
result.llm_output.token_usage ??= {};
|
|
459
|
+
result.llm_output.token_usage["completion_tokens"] =
|
|
460
|
+
span.attributes["ai.usage.completionTokens"];
|
|
461
|
+
}
|
|
462
|
+
if (span.attributes["ai.usage.promptTokens"]) {
|
|
463
|
+
result ??= {};
|
|
464
|
+
result.llm_output ??= {};
|
|
465
|
+
result.llm_output.token_usage ??= {};
|
|
466
|
+
result.llm_output.token_usage["prompt_tokens"] =
|
|
467
|
+
+span.attributes["ai.usage.promptTokens"];
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
})();
|
|
471
|
+
const events = [];
|
|
472
|
+
const firstChunkEvent = span.events.find((i) => i.name === "ai.stream.firstChunk");
|
|
473
|
+
if (firstChunkEvent) {
|
|
474
|
+
events.push({
|
|
475
|
+
name: "new_token",
|
|
476
|
+
time: convertToTimestamp(firstChunkEvent.time),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
return asRunCreate({
|
|
480
|
+
run_type: "llm",
|
|
481
|
+
name: span.attributes["ai.model.provider"],
|
|
482
|
+
inputs,
|
|
483
|
+
outputs,
|
|
484
|
+
events,
|
|
485
|
+
extra: {
|
|
486
|
+
batch_size: 1,
|
|
487
|
+
metadata: {
|
|
488
|
+
ls_provider: span.attributes["ai.model.provider"]
|
|
489
|
+
.split(".")
|
|
490
|
+
.at(0),
|
|
491
|
+
ls_model_type: span.attributes["ai.model.provider"]
|
|
492
|
+
.split(".")
|
|
493
|
+
.at(1),
|
|
494
|
+
ls_model_name: span.attributes["ai.model.id"],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
case "ai.embed":
|
|
500
|
+
case "ai.embed.doEmbed":
|
|
501
|
+
case "ai.embedMany":
|
|
502
|
+
case "ai.embedMany.doEmbed":
|
|
503
|
+
default:
|
|
504
|
+
console.warn(`Span "${span.name}" is currently unsupported.`);
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
export(spans, resultCallback) {
|
|
509
|
+
const typedSpans = spans
|
|
510
|
+
.slice()
|
|
511
|
+
.sort((a, b) => sortByHr(a.startTime, b.startTime));
|
|
512
|
+
for (const span of typedSpans) {
|
|
513
|
+
const { traceId, spanId } = span.spanContext();
|
|
514
|
+
const parentId = span.parentSpanId ?? undefined;
|
|
515
|
+
this.traceByMap[traceId] ??= {
|
|
516
|
+
childMap: {},
|
|
517
|
+
nodeMap: {},
|
|
518
|
+
relativeExecutionOrder: {},
|
|
519
|
+
};
|
|
520
|
+
const runId = (0, uuid_1.v5)(spanId, RUN_ID_NAMESPACE);
|
|
521
|
+
const parentRunId = parentId
|
|
522
|
+
? (0, uuid_1.v5)(parentId, RUN_ID_NAMESPACE)
|
|
523
|
+
: undefined;
|
|
524
|
+
const traceMap = this.traceByMap[traceId];
|
|
525
|
+
const run = this.getRunCreate(span);
|
|
526
|
+
if (!run)
|
|
527
|
+
continue;
|
|
528
|
+
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] ??= -1;
|
|
529
|
+
traceMap.relativeExecutionOrder[parentRunId ?? ROOT] += 1;
|
|
530
|
+
traceMap.nodeMap[runId] ??= {
|
|
531
|
+
id: runId,
|
|
532
|
+
parentId: parentRunId,
|
|
533
|
+
startTime: span.startTime,
|
|
534
|
+
run,
|
|
535
|
+
sent: false,
|
|
536
|
+
executionOrder: traceMap.relativeExecutionOrder[parentRunId ?? ROOT],
|
|
537
|
+
};
|
|
538
|
+
traceMap.childMap[parentRunId ?? ROOT] ??= [];
|
|
539
|
+
traceMap.childMap[parentRunId ?? ROOT].push(traceMap.nodeMap[runId]);
|
|
540
|
+
traceMap.interop = this.parseInteropFromMetadata(span);
|
|
541
|
+
}
|
|
542
|
+
// We separate `id`,
|
|
543
|
+
const sampled = [];
|
|
544
|
+
for (const traceId of Object.keys(this.traceByMap)) {
|
|
545
|
+
const traceMap = this.traceByMap[traceId];
|
|
546
|
+
const queue = traceMap.childMap[ROOT]?.map((item) => ({
|
|
547
|
+
item,
|
|
548
|
+
dottedOrder: convertToDottedOrderFormat(item.startTime, item.id, item.executionOrder),
|
|
549
|
+
traceId: item.id,
|
|
550
|
+
})) ?? [];
|
|
551
|
+
const seen = new Set();
|
|
552
|
+
while (queue.length) {
|
|
553
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
554
|
+
const task = queue.shift();
|
|
555
|
+
if (seen.has(task.item.id))
|
|
556
|
+
continue;
|
|
557
|
+
if (!task.item.sent) {
|
|
558
|
+
let override = {
|
|
559
|
+
id: task.item.id,
|
|
560
|
+
parent_run_id: task.item.parentId,
|
|
561
|
+
dotted_order: task.dottedOrder,
|
|
562
|
+
trace_id: task.traceId,
|
|
563
|
+
};
|
|
564
|
+
if (traceMap.interop) {
|
|
565
|
+
// attach the run to a parent run tree
|
|
566
|
+
// - id: preserve
|
|
567
|
+
// - parent_run_id: use existing parent run id or hook to the provided run tree
|
|
568
|
+
// - dotted_order: append to the dotted_order of the parent run tree
|
|
569
|
+
// - trace_id: use from the existing run tree
|
|
570
|
+
if (traceMap.interop.type === "traceable") {
|
|
571
|
+
override = {
|
|
572
|
+
id: override.id,
|
|
573
|
+
parent_run_id: override.parent_run_id ?? traceMap.interop.parentRunTree.id,
|
|
574
|
+
dotted_order: [
|
|
575
|
+
traceMap.interop.parentRunTree.dotted_order,
|
|
576
|
+
override.dotted_order,
|
|
577
|
+
]
|
|
578
|
+
.filter(Boolean)
|
|
579
|
+
.join("."),
|
|
580
|
+
trace_id: traceMap.interop.parentRunTree.trace_id,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
else if (traceMap.interop.type === "user") {
|
|
584
|
+
// Allow user to specify custom trace ID = run ID of the root run
|
|
585
|
+
// - id: use user provided run ID if root run, otherwise preserve
|
|
586
|
+
// - parent_run_id: use user provided run ID if root run, otherwise preserve
|
|
587
|
+
// - dotted_order: replace the trace_id with the user provided run ID
|
|
588
|
+
// - trace_id: use user provided run ID
|
|
589
|
+
const userTraceId = traceMap.interop.userTraceId ?? (0, uuid_1.v4)();
|
|
590
|
+
override = {
|
|
591
|
+
id: override.id === override.trace_id ? userTraceId : override.id,
|
|
592
|
+
parent_run_id: override.parent_run_id === override.trace_id
|
|
593
|
+
? userTraceId
|
|
594
|
+
: override.parent_run_id,
|
|
595
|
+
dotted_order: override.dotted_order.replace(override.trace_id, userTraceId),
|
|
596
|
+
trace_id: userTraceId,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
sampled.push([override, task.item.run]);
|
|
601
|
+
task.item.sent = true;
|
|
602
|
+
}
|
|
603
|
+
const children = traceMap.childMap[task.item.id] ?? [];
|
|
604
|
+
queue.push(...children.map((child) => {
|
|
605
|
+
return {
|
|
606
|
+
item: child,
|
|
607
|
+
dottedOrder: [
|
|
608
|
+
task.dottedOrder,
|
|
609
|
+
convertToDottedOrderFormat(child.startTime, child.id, child.executionOrder),
|
|
610
|
+
].join("."),
|
|
611
|
+
traceId: task.traceId,
|
|
612
|
+
};
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
Promise.all(sampled.map(([override, value]) => this.client.createRun({ ...value, ...override }))).then(() => resultCallback({ code: 0 }), (error) => resultCallback({ code: 1, error }));
|
|
617
|
+
}
|
|
618
|
+
async shutdown() {
|
|
619
|
+
// find nodes which are incomplete
|
|
620
|
+
const incompleteNodes = Object.values(this.traceByMap).flatMap((trace) => Object.values(trace.nodeMap).filter((i) => !i.sent));
|
|
621
|
+
if (incompleteNodes.length > 0) {
|
|
622
|
+
console.warn("Some incomplete nodes were found before shutdown and not sent to LangSmith.");
|
|
623
|
+
}
|
|
624
|
+
await this.client?.awaitPendingTraceBatches();
|
|
625
|
+
}
|
|
626
|
+
async forceFlush() {
|
|
627
|
+
await this.client?.awaitPendingTraceBatches();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
exports.AISDKExporter = AISDKExporter;
|