langsmith 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/client.cjs +1 -1
  2. package/dist/client.js +1 -1
  3. package/dist/experimental/vercel/index.cjs +18 -637
  4. package/dist/experimental/vercel/index.d.ts +3 -257
  5. package/dist/experimental/vercel/index.js +2 -635
  6. package/dist/experimental/vercel/middleware.cjs +2 -78
  7. package/dist/experimental/vercel/middleware.js +1 -77
  8. package/dist/experimental/vercel/telemetry.cjs +462 -0
  9. package/dist/experimental/vercel/telemetry.d.ts +89 -0
  10. package/dist/experimental/vercel/telemetry.js +459 -0
  11. package/dist/experimental/vercel/utils.cjs +142 -35
  12. package/dist/experimental/vercel/utils.d.ts +28 -3
  13. package/dist/experimental/vercel/utils.js +140 -34
  14. package/dist/experimental/vercel/wrap.cjs +639 -0
  15. package/dist/experimental/vercel/wrap.d.ts +257 -0
  16. package/dist/experimental/vercel/wrap.js +635 -0
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/sandbox/client.cjs +21 -0
  21. package/dist/sandbox/client.d.ts +1 -0
  22. package/dist/sandbox/client.js +21 -0
  23. package/dist/sandbox/sandbox.cjs +12 -1
  24. package/dist/sandbox/sandbox.js +12 -1
  25. package/dist/sandbox/types.d.ts +12 -0
  26. package/dist/sandbox/ws_execute.cjs +11 -7
  27. package/dist/sandbox/ws_execute.d.ts +2 -1
  28. package/dist/sandbox/ws_execute.js +11 -7
  29. package/dist/utils/types.cjs +13 -0
  30. package/dist/utils/types.d.ts +2 -0
  31. package/dist/utils/types.js +8 -0
  32. package/dist/utils/vercel.cjs +68 -10
  33. package/dist/utils/vercel.d.ts +17 -2
  34. package/dist/utils/vercel.js +68 -10
  35. package/experimental/sandbox.cjs +1 -0
  36. package/experimental/sandbox.d.cts +1 -0
  37. package/experimental/sandbox.d.ts +1 -0
  38. package/experimental/sandbox.js +1 -0
  39. package/package.json +22 -7
@@ -0,0 +1,89 @@
1
+ import { RunTreeConfig } from "../../run_trees.js";
2
+ import type { KVMap } from "../../schemas.js";
3
+ /**
4
+ * Configuration options for creating a LangSmith telemetry integration
5
+ * for the Vercel AI SDK.
6
+ */
7
+ export interface LangSmithTelemetryConfig {
8
+ /**
9
+ * Custom name for the root span. If not provided, defaults to the
10
+ * model display name or "generateText"/"streamText".
11
+ */
12
+ name?: string;
13
+ /**
14
+ * Run type for the root span. Defaults to "chain".
15
+ */
16
+ runType?: string;
17
+ /**
18
+ * Additional metadata to attach to all spans.
19
+ */
20
+ metadata?: KVMap;
21
+ /**
22
+ * Tags to attach to all spans.
23
+ */
24
+ tags?: string[];
25
+ /**
26
+ * Custom LangSmith client configuration.
27
+ */
28
+ client?: RunTreeConfig["client"];
29
+ /**
30
+ * Project name to log runs to.
31
+ */
32
+ projectName?: string;
33
+ /**
34
+ * Transform inputs before logging on the root span.
35
+ */
36
+ processInputs?: (inputs: KVMap) => KVMap;
37
+ /**
38
+ * Transform outputs before logging on the root span.
39
+ */
40
+ processOutputs?: (outputs: KVMap) => KVMap;
41
+ /**
42
+ * Transform inputs before logging on child LLM step spans.
43
+ */
44
+ processChildLLMRunInputs?: (inputs: KVMap) => KVMap;
45
+ /**
46
+ * Transform outputs before logging on child LLM step spans.
47
+ */
48
+ processChildLLMRunOutputs?: (outputs: KVMap) => KVMap;
49
+ /**
50
+ * Whether to include intermediate step details in the output.
51
+ */
52
+ traceResponseMetadata?: boolean;
53
+ /**
54
+ * Whether to include raw HTTP request/response bodies.
55
+ */
56
+ traceRawHttp?: boolean;
57
+ /**
58
+ * Additional RunTree configuration to pass through.
59
+ */
60
+ extra?: KVMap;
61
+ /**
62
+ * Whether to enable tracing.
63
+ * @default true if LANGSMITH_TRACING is set to "true"
64
+ */
65
+ tracingEnabled?: boolean;
66
+ }
67
+ /**
68
+ * Creates a LangSmith `Telemetry` for the Vercel AI SDK.
69
+ *
70
+ * This adapter implements the Vercel AI SDK's `Telemetry` interface
71
+ * and maps lifecycle events to LangSmith traces. It creates a root span for
72
+ * the entire generation, child LLM spans for each step, and tool spans for
73
+ * tool calls.
74
+ *
75
+ * ```ts
76
+ * import { generateText, registerTelemetry } from "ai";
77
+ * import { LangSmithTelemetry } from "langsmith/experimental/vercel";
78
+ *
79
+ * registerTelemetry(LangSmithTelemetry());
80
+ *
81
+ * const result = await generateText({
82
+ * model: openai("gpt-4o"),
83
+ * prompt: "Hello!",
84
+ * });
85
+ * ```
86
+ *
87
+ * @experimental Only available in Vercel AI SDK 7.
88
+ */
89
+ export declare function LangSmithTelemetry(config?: LangSmithTelemetryConfig): any;
@@ -0,0 +1,459 @@
1
+ import { isRunTree, RunTree } from "../../run_trees.js";
2
+ import { getCurrentRunTree, withRunTree } from "../../singletons/traceable.js";
3
+ import { isTracingEnabled } from "../../env.js";
4
+ import { convertMessageToTracedFormat } from "./utils.js";
5
+ import { setUsageMetadataOnRunTree } from "./utils.js";
6
+ import { isPrimitive, isRecord } from "../../utils/types.js";
7
+ function _formatMessages(messages) {
8
+ if (!Array.isArray(messages))
9
+ return messages;
10
+ return messages.map((msg) => convertMessageToTracedFormat(msg));
11
+ }
12
+ // oxlint-disable-next-line typescript/no-explicit-any
13
+ function _formatToolCalls(toolCalls) {
14
+ return toolCalls.map((tc) => ({
15
+ id: tc.toolCallId,
16
+ type: "function",
17
+ function: {
18
+ name: tc.toolName,
19
+ arguments: isPrimitive(tc.input)
20
+ ? String(tc.input)
21
+ : JSON.stringify(tc.input),
22
+ },
23
+ }));
24
+ }
25
+ function _formatStepOutput(
26
+ // oxlint-disable-next-line typescript/no-explicit-any
27
+ event, traceRawHttp) {
28
+ // Build an assistant-style message from the step result
29
+ const output = { role: "assistant" };
30
+ // Text content
31
+ if (event.content != null) {
32
+ output.content = event.content;
33
+ }
34
+ else if (event.text != null) {
35
+ output.content = event.text;
36
+ }
37
+ // Tool calls
38
+ if (Array.isArray(event.toolCalls) && event.toolCalls.length > 0) {
39
+ output.tool_calls = _formatToolCalls(event.toolCalls);
40
+ }
41
+ if (event.finishReason != null) {
42
+ output.finish_reason = event.finishReason;
43
+ }
44
+ if (traceRawHttp) {
45
+ if (event.request != null)
46
+ output.request = event.request;
47
+ if (event.response != null)
48
+ output.response = event.response;
49
+ }
50
+ return convertMessageToTracedFormat(output);
51
+ }
52
+ function _getLsAgentType(parentRunTree) {
53
+ if (isRunTree(parentRunTree) && parentRunTree.run_type === "tool") {
54
+ return "subagent";
55
+ }
56
+ return "root";
57
+ }
58
+ /**
59
+ * Creates a LangSmith `Telemetry` for the Vercel AI SDK.
60
+ *
61
+ * This adapter implements the Vercel AI SDK's `Telemetry` interface
62
+ * and maps lifecycle events to LangSmith traces. It creates a root span for
63
+ * the entire generation, child LLM spans for each step, and tool spans for
64
+ * tool calls.
65
+ *
66
+ * ```ts
67
+ * import { generateText, registerTelemetry } from "ai";
68
+ * import { LangSmithTelemetry } from "langsmith/experimental/vercel";
69
+ *
70
+ * registerTelemetry(LangSmithTelemetry());
71
+ *
72
+ * const result = await generateText({
73
+ * model: openai("gpt-4o"),
74
+ * prompt: "Hello!",
75
+ * });
76
+ * ```
77
+ *
78
+ * @experimental Only available in Vercel AI SDK 7.
79
+ */
80
+ export function LangSmithTelemetry(config) {
81
+ const { name: customName, runType = "chain", metadata: customMetadata, tags: customTags, client, projectName, processInputs, processOutputs, processChildLLMRunInputs, processChildLLMRunOutputs, traceResponseMetadata, traceRawHttp, tracingEnabled, extra: customExtra, } = config ?? {};
82
+ function getOpenStepOrRoot(state) {
83
+ let openStep;
84
+ state.stepRunTrees.forEach((stepRt) => {
85
+ if (stepRt.end_time == null) {
86
+ openStep = stepRt;
87
+ }
88
+ });
89
+ return openStep ?? state.rootRunTree;
90
+ }
91
+ async function finalizeOpenToolRuns(state, opts) {
92
+ const entries = Array.from(state.toolRunTrees.entries());
93
+ for (let i = 0; i < entries.length; i++) {
94
+ const [, toolRt] = entries[i];
95
+ if (toolRt.end_time == null) {
96
+ if (opts?.error != null) {
97
+ await toolRt.end(undefined, opts.error);
98
+ }
99
+ else {
100
+ await toolRt.end(opts?.note != null ? { note: opts.note } : undefined);
101
+ }
102
+ await toolRt.patchRun({ excludeInputs: true });
103
+ }
104
+ }
105
+ state.toolRunTrees.clear();
106
+ }
107
+ /** Per-generation state keyed by AI SDK `callId` (stable across nested calls). */
108
+ const invocationsByCallId = new Map();
109
+ const onStart = async (event) => {
110
+ if (!isTracingEnabled(tracingEnabled))
111
+ return;
112
+ if (!("callId" in event) || typeof event.callId !== "string")
113
+ return;
114
+ // If called within an existing traceable context, nest under it
115
+ const parentRunTree = getCurrentRunTree(true);
116
+ let inputs = {};
117
+ if (event.recordInputs !== false) {
118
+ if ("messages" in event && event.messages != null) {
119
+ inputs.messages = _formatMessages(event.messages);
120
+ }
121
+ if ("prompt" in event && event.prompt != null) {
122
+ inputs.prompt = event.prompt;
123
+ }
124
+ if ("instructions" in event && event.instructions != null) {
125
+ inputs.instructions = event.instructions;
126
+ }
127
+ if ("system" in event && event.system != null) {
128
+ inputs.system = event.system;
129
+ }
130
+ if ("tools" in event && event.tools != null) {
131
+ inputs.tools = Object.keys(event.tools);
132
+ }
133
+ if ("runtimeContext" in event && event.runtimeContext != null) {
134
+ inputs.runtimeContext = event.runtimeContext;
135
+ }
136
+ if ("toolsContext" in event && event.toolsContext != null) {
137
+ inputs.toolsContext = event.toolsContext;
138
+ }
139
+ // Apply user-provided input processing
140
+ if (processInputs) {
141
+ try {
142
+ inputs = processInputs(inputs);
143
+ }
144
+ catch (e) {
145
+ console.error("Error in processInputs, using raw inputs:", e);
146
+ }
147
+ }
148
+ }
149
+ const runTreeConfig = {
150
+ name: customName ?? event.functionId ?? event.provider,
151
+ run_type: runType,
152
+ inputs,
153
+ tracingEnabled: true,
154
+ extra: {
155
+ ...customExtra,
156
+ metadata: {
157
+ ...customMetadata,
158
+ ai_sdk_method: event.operationId,
159
+ ls_agent_type: _getLsAgentType(parentRunTree),
160
+ ls_model_name: event.modelId,
161
+ ls_provider: event.provider,
162
+ ls_integration: "vercel-ai-sdk-telemetry",
163
+ },
164
+ },
165
+ tags: customTags,
166
+ ...(client ? { client } : {}),
167
+ ...(projectName ? { project_name: projectName } : {}),
168
+ };
169
+ let rootRunTree;
170
+ if (isRunTree(parentRunTree)) {
171
+ rootRunTree = parentRunTree.createChild(runTreeConfig);
172
+ }
173
+ else {
174
+ rootRunTree = new RunTree(runTreeConfig);
175
+ }
176
+ await rootRunTree.postRun();
177
+ invocationsByCallId.set(event.callId, {
178
+ rootRunTree,
179
+ stepRunTrees: new Map(),
180
+ toolRunTrees: new Map(),
181
+ });
182
+ };
183
+ const onStepStart = async (event) => {
184
+ const state = invocationsByCallId.get(event.callId);
185
+ if (!state)
186
+ return;
187
+ const stepNumber = event.stepNumber ?? 0;
188
+ let inputs = {};
189
+ if (event.recordInputs !== false) {
190
+ if ("messages" in event && event.messages != null) {
191
+ inputs.messages = _formatMessages(event.messages);
192
+ }
193
+ if ("runtimeContext" in event && event.runtimeContext != null) {
194
+ inputs.runtimeContext = event.runtimeContext;
195
+ }
196
+ if ("toolsContext" in event && event.toolsContext != null) {
197
+ inputs.toolsContext = event.toolsContext;
198
+ }
199
+ if (processChildLLMRunInputs) {
200
+ try {
201
+ inputs = processChildLLMRunInputs(inputs);
202
+ }
203
+ catch (e) {
204
+ console.error("Error in processChildLLMRunInputs, using raw inputs:", e);
205
+ }
206
+ }
207
+ }
208
+ const stepRunTree = state.rootRunTree.createChild({
209
+ name: event.provider,
210
+ run_type: "llm",
211
+ inputs,
212
+ extra: { metadata: { step_number: stepNumber } },
213
+ });
214
+ state.stepRunTrees.set(stepNumber, stepRunTree);
215
+ await stepRunTree.postRun();
216
+ };
217
+ const onLanguageModelCallStart = async (event) => {
218
+ const state = invocationsByCallId.get(event.callId);
219
+ if (!state)
220
+ return;
221
+ const stepRunTree = getOpenStepOrRoot(state);
222
+ if (stepRunTree.run_type !== "llm")
223
+ return;
224
+ const prevParams = isRecord(stepRunTree.extra?.invocation_params)
225
+ ? stepRunTree.extra.invocation_params
226
+ : {};
227
+ const nextParams = { ...event };
228
+ // Remove properties that are already in the step run tree
229
+ delete nextParams.messages;
230
+ delete nextParams.provider;
231
+ delete nextParams.modelId;
232
+ // Remove telemetry options (except functionId)
233
+ delete nextParams.recordInputs;
234
+ delete nextParams.recordOutputs;
235
+ delete nextParams.includeToolsContext;
236
+ // Massage tools for LangSmith to render schema nicely
237
+ nextParams.tools = nextParams.tools?.map((tool) => {
238
+ const newTool = { ...tool };
239
+ if ("inputSchema" in newTool) {
240
+ newTool.input_schema = newTool.inputSchema;
241
+ delete newTool.inputSchema;
242
+ }
243
+ return newTool;
244
+ });
245
+ stepRunTree.extra = {
246
+ ...stepRunTree.extra,
247
+ invocation_params: { ...prevParams, ...nextParams },
248
+ };
249
+ };
250
+ const onToolExecutionStart = async (event) => {
251
+ const state = invocationsByCallId.get(event.callId);
252
+ if (!state)
253
+ return;
254
+ const parentRunTree = getOpenStepOrRoot(state);
255
+ let inputs = {};
256
+ if (event.recordInputs !== false) {
257
+ if (isRecord(event.toolCall.input)) {
258
+ inputs = { ...event.toolCall.input };
259
+ }
260
+ else if (typeof event.toolCall.input !== "undefined") {
261
+ inputs = { input: event.toolCall.input };
262
+ }
263
+ if ("toolContext" in event && event.toolContext != null) {
264
+ inputs.toolContext = event.toolContext;
265
+ }
266
+ }
267
+ else {
268
+ inputs = {};
269
+ }
270
+ const toolRunTree = parentRunTree.createChild({
271
+ name: event.toolCall.toolName,
272
+ run_type: "tool",
273
+ inputs,
274
+ extra: {
275
+ metadata: {
276
+ tool_call_id: event.toolCall.toolCallId,
277
+ ai_sdk_call_id: event.callId,
278
+ },
279
+ },
280
+ });
281
+ await toolRunTree.postRun();
282
+ state.toolRunTrees.set(event.toolCall.toolCallId, toolRunTree);
283
+ };
284
+ const onToolExecutionEnd = async (event) => {
285
+ const state = invocationsByCallId.get(event.callId);
286
+ if (!state)
287
+ return;
288
+ const toolRunTree = state.toolRunTrees.get(event.toolCall.toolCallId);
289
+ if (!toolRunTree)
290
+ return;
291
+ state.toolRunTrees.delete(event.toolCall.toolCallId);
292
+ let outputs;
293
+ let error;
294
+ if (event.recordOutputs !== false) {
295
+ if (event.toolOutput.type === "tool-result") {
296
+ outputs = { output: event.toolOutput.output };
297
+ }
298
+ else if (event.toolOutput.type === "tool-error") {
299
+ const err = event.toolOutput.error;
300
+ error = err instanceof Error ? err.message : String(err);
301
+ }
302
+ }
303
+ else {
304
+ outputs = {};
305
+ }
306
+ await toolRunTree.end(outputs, error, Math.floor(toolRunTree.start_time + event.toolExecutionMs));
307
+ await toolRunTree.patchRun({ excludeInputs: true });
308
+ };
309
+ const onStepFinish = async (event) => {
310
+ const state = invocationsByCallId.get(event.callId);
311
+ if (!state)
312
+ return;
313
+ const stepNumber = event.stepNumber ?? 0;
314
+ const stepRunTree = state.stepRunTrees.get(stepNumber);
315
+ if (!stepRunTree)
316
+ return;
317
+ let outputs = {};
318
+ if (event.recordOutputs !== false) {
319
+ outputs = _formatStepOutput(event, traceRawHttp);
320
+ if (processChildLLMRunOutputs) {
321
+ try {
322
+ outputs = processChildLLMRunOutputs(outputs);
323
+ }
324
+ catch (e) {
325
+ console.error("Error in processChildLLMRunOutputs, using raw outputs:", e);
326
+ }
327
+ }
328
+ }
329
+ // Set usage metadata
330
+ // @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
331
+ setUsageMetadataOnRunTree(event, stepRunTree);
332
+ await stepRunTree.end(outputs);
333
+ await stepRunTree.patchRun({ excludeInputs: true });
334
+ state.stepRunTrees.delete(stepNumber);
335
+ };
336
+ const onEnd = async (event) => {
337
+ if (!("callId" in event) || typeof event.callId !== "string")
338
+ return;
339
+ const state = invocationsByCallId.get(event.callId);
340
+ if (!state)
341
+ return;
342
+ const { rootRunTree } = state;
343
+ await finalizeOpenToolRuns(state, { note: "closed on finish" });
344
+ // Ensure any remaining step runs are closed
345
+ const remainingSteps = Array.from(state.stepRunTrees.entries());
346
+ for (let i = 0; i < remainingSteps.length; i++) {
347
+ const [stepNumber, stepRt] = remainingSteps[i];
348
+ if (stepRt.end_time == null) {
349
+ await stepRt.end({ note: "closed on finish" });
350
+ await stepRt.patchRun({ excludeInputs: true });
351
+ }
352
+ state.stepRunTrees.delete(stepNumber);
353
+ }
354
+ let outputs = {};
355
+ if (event.recordOutputs !== false) {
356
+ // Final result output
357
+ if ("text" in event && event.text != null) {
358
+ outputs.content = event.text;
359
+ }
360
+ else if ("content" in event && event.content != null) {
361
+ outputs.content = event.content;
362
+ }
363
+ if (outputs.content != null) {
364
+ outputs.role = "assistant";
365
+ }
366
+ if ("object" in event && event.object != null) {
367
+ outputs.object = event.object;
368
+ }
369
+ if ("toolCalls" in event &&
370
+ Array.isArray(event.toolCalls) &&
371
+ event.toolCalls.length > 0) {
372
+ outputs.tool_calls = _formatToolCalls(event.toolCalls);
373
+ }
374
+ if ("finishReason" in event && event.finishReason != null) {
375
+ outputs.finish_reason = event.finishReason;
376
+ }
377
+ if (traceResponseMetadata &&
378
+ "steps" in event &&
379
+ Array.isArray(event.steps)) {
380
+ outputs.steps = event.steps.map((step, idx) => ({
381
+ step_number: idx,
382
+ ..._formatStepOutput(step, traceRawHttp),
383
+ }));
384
+ }
385
+ if (processOutputs) {
386
+ try {
387
+ outputs = processOutputs(outputs);
388
+ }
389
+ catch (e) {
390
+ console.error("Error in processOutputs, using raw outputs:", e);
391
+ }
392
+ }
393
+ }
394
+ // Set aggregated usage on root
395
+ if ("totalUsage" in event && event.totalUsage != null) {
396
+ setUsageMetadataOnRunTree(
397
+ // @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
398
+ { usage: event.totalUsage, providerMetadata: event.providerMetadata }, rootRunTree);
399
+ }
400
+ else if ("usage" in event && event.usage != null) {
401
+ // @ts-expect-error SharedV4ProviderMetadata is not assignable to SharedV2ProviderMetadata
402
+ setUsageMetadataOnRunTree(event, rootRunTree);
403
+ }
404
+ await rootRunTree.end(outputs);
405
+ await rootRunTree.patchRun({ excludeInputs: true });
406
+ invocationsByCallId.delete(event.callId);
407
+ };
408
+ const onError = async (payload) => {
409
+ const callId = typeof payload === "object" &&
410
+ payload !== null &&
411
+ "callId" in payload &&
412
+ typeof payload.callId === "string"
413
+ ? payload.callId
414
+ : undefined;
415
+ const error = typeof payload === "object" && payload !== null && "error" in payload
416
+ ? payload.error
417
+ : payload;
418
+ if (callId === undefined)
419
+ return;
420
+ const state = invocationsByCallId.get(callId);
421
+ if (!state)
422
+ return;
423
+ const { rootRunTree } = state;
424
+ const errorMsg = error instanceof Error ? error.message : String(error);
425
+ await finalizeOpenToolRuns(state, { error: errorMsg });
426
+ // Close any open step runs with error
427
+ const errorSteps = Array.from(state.stepRunTrees.entries());
428
+ for (let i = 0; i < errorSteps.length; i++) {
429
+ const [stepNumber, stepRt] = errorSteps[i];
430
+ if (stepRt.end_time == null) {
431
+ await stepRt.end(undefined, errorMsg);
432
+ await stepRt.patchRun({ excludeInputs: true });
433
+ }
434
+ state.stepRunTrees.delete(stepNumber);
435
+ }
436
+ await rootRunTree.end(undefined, errorMsg);
437
+ await rootRunTree.patchRun({ excludeInputs: true });
438
+ invocationsByCallId.delete(callId);
439
+ };
440
+ const executeTool = async (params) => {
441
+ const state = invocationsByCallId.get(params.callId);
442
+ const toolRunTree = state?.toolRunTrees.get(params.toolCallId);
443
+ if (toolRunTree != null) {
444
+ return withRunTree(toolRunTree, () => params.execute());
445
+ }
446
+ return params.execute();
447
+ };
448
+ return {
449
+ onStart,
450
+ onStepStart,
451
+ onLanguageModelCallStart,
452
+ onToolExecutionStart,
453
+ onToolExecutionEnd,
454
+ onStepFinish,
455
+ onEnd,
456
+ onError,
457
+ executeTool,
458
+ };
459
+ }