langsmith 0.3.66 → 0.3.68

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/README.md CHANGED
@@ -55,6 +55,7 @@ process.env.LANGSMITH_ENDPOINT = "https://api.smith.langchain.com";
55
55
  // process.env.LANGSMITH_ENDPOINT = "https://eu.api.smith.langchain.com"; // If signed up in the EU region
56
56
  process.env.LANGSMITH_API_KEY = "<YOUR-LANGSMITH-API-KEY>";
57
57
  // process.env.LANGSMITH_PROJECT = "My Project Name"; // Optional: "default" is used if not set
58
+ // process.env.LANGSMITH_WORKSPACE_ID = "<YOUR-WORKSPACE-ID>"; // Required for org-scoped API keys
58
59
  ```
59
60
 
60
61
  > **Tip:** Projects are groups of traces. All runs are logged to a project. If not specified, the project is set to `default`.
package/dist/client.cjs CHANGED
@@ -533,7 +533,7 @@ class Client {
533
533
  signal: AbortSignal.timeout(this.timeout_ms),
534
534
  ...this.fetchOptions,
535
535
  });
536
- await (0, error_js_1.raiseForStatus)(res, `Failed to fetch ${path}`);
536
+ await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
537
537
  return res;
538
538
  });
539
539
  return response;
@@ -556,7 +556,7 @@ class Client {
556
556
  signal: AbortSignal.timeout(this.timeout_ms),
557
557
  ...this.fetchOptions,
558
558
  });
559
- await (0, error_js_1.raiseForStatus)(res, `Failed to fetch ${path}`);
559
+ await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
560
560
  return res;
561
561
  });
562
562
  const items = transform
@@ -584,7 +584,7 @@ class Client {
584
584
  ...this.fetchOptions,
585
585
  body,
586
586
  });
587
- await (0, error_js_1.raiseForStatus)(res, `Failed to fetch ${path}`);
587
+ await (0, error_js_1.raiseForStatus)(res, `fetch ${path}`);
588
588
  return res;
589
589
  });
590
590
  const responseBody = await response.json();
@@ -1987,7 +1987,7 @@ class Client {
1987
1987
  }
1988
1988
  throw new Error("No projects found to resolve tenant.");
1989
1989
  }
1990
- async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, datasetVersion, referenceFree, metadata, } = {}) {
1990
+ async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, includeStats, datasetVersion, referenceFree, metadata, } = {}) {
1991
1991
  const params = new URLSearchParams();
1992
1992
  if (projectIds !== undefined) {
1993
1993
  for (const projectId of projectIds) {
@@ -2009,6 +2009,9 @@ class Client {
2009
2009
  });
2010
2010
  params.append("reference_dataset", dataset.id);
2011
2011
  }
2012
+ if (includeStats !== undefined) {
2013
+ params.append("include_stats", includeStats.toString());
2014
+ }
2012
2015
  if (datasetVersion !== undefined) {
2013
2016
  params.append("dataset_version", datasetVersion);
2014
2017
  }
package/dist/client.d.ts CHANGED
@@ -556,12 +556,13 @@ export declare class Client implements LangSmithTracingClientInterface {
556
556
  datasetName?: string;
557
557
  }): Promise<string>;
558
558
  private _getTenantId;
559
- listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, datasetVersion, referenceFree, metadata, }?: {
559
+ listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, includeStats, datasetVersion, referenceFree, metadata, }?: {
560
560
  projectIds?: string[];
561
561
  name?: string;
562
562
  nameContains?: string;
563
563
  referenceDatasetId?: string;
564
564
  referenceDatasetName?: string;
565
+ includeStats?: boolean;
565
566
  datasetVersion?: string;
566
567
  referenceFree?: boolean;
567
568
  metadata?: RecordStringAny;
package/dist/client.js CHANGED
@@ -495,7 +495,7 @@ export class Client {
495
495
  signal: AbortSignal.timeout(this.timeout_ms),
496
496
  ...this.fetchOptions,
497
497
  });
498
- await raiseForStatus(res, `Failed to fetch ${path}`);
498
+ await raiseForStatus(res, `fetch ${path}`);
499
499
  return res;
500
500
  });
501
501
  return response;
@@ -518,7 +518,7 @@ export class Client {
518
518
  signal: AbortSignal.timeout(this.timeout_ms),
519
519
  ...this.fetchOptions,
520
520
  });
521
- await raiseForStatus(res, `Failed to fetch ${path}`);
521
+ await raiseForStatus(res, `fetch ${path}`);
522
522
  return res;
523
523
  });
524
524
  const items = transform
@@ -546,7 +546,7 @@ export class Client {
546
546
  ...this.fetchOptions,
547
547
  body,
548
548
  });
549
- await raiseForStatus(res, `Failed to fetch ${path}`);
549
+ await raiseForStatus(res, `fetch ${path}`);
550
550
  return res;
551
551
  });
552
552
  const responseBody = await response.json();
@@ -1949,7 +1949,7 @@ export class Client {
1949
1949
  }
1950
1950
  throw new Error("No projects found to resolve tenant.");
1951
1951
  }
1952
- async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, datasetVersion, referenceFree, metadata, } = {}) {
1952
+ async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, includeStats, datasetVersion, referenceFree, metadata, } = {}) {
1953
1953
  const params = new URLSearchParams();
1954
1954
  if (projectIds !== undefined) {
1955
1955
  for (const projectId of projectIds) {
@@ -1971,6 +1971,9 @@ export class Client {
1971
1971
  });
1972
1972
  params.append("reference_dataset", dataset.id);
1973
1973
  }
1974
+ if (includeStats !== undefined) {
1975
+ params.append("include_stats", includeStats.toString());
1976
+ }
1974
1977
  if (datasetVersion !== undefined) {
1975
1978
  params.append("dataset_version", datasetVersion);
1976
1979
  }
@@ -203,7 +203,7 @@ const wrapAISDK = ({ wrapLanguageModel, generateText, streamText, streamObject,
203
203
  }
204
204
  const { content } = lastStep;
205
205
  return (0, utils_js_1.convertMessageToTracedFormat)({
206
- content,
206
+ content: content ?? outputs.outputs.text,
207
207
  role: "assistant",
208
208
  });
209
209
  }
@@ -331,8 +331,20 @@ const wrapAISDK = ({ wrapLanguageModel, generateText, streamText, streamObject,
331
331
  if (outputs.outputs == null || typeof outputs.outputs !== "object") {
332
332
  return outputs;
333
333
  }
334
- const content = await outputs.outputs.content;
335
- if (content == null || typeof content !== "object") {
334
+ let content;
335
+ if ("content" in outputs.outputs && outputs.outputs.content != null) {
336
+ content = await outputs.outputs.content;
337
+ }
338
+ else if ("text" in outputs.outputs &&
339
+ outputs.outputs.text != null) {
340
+ // AI SDK 4 shim
341
+ content = await outputs.outputs.text;
342
+ }
343
+ else {
344
+ return outputs;
345
+ }
346
+ if (content == null ||
347
+ !["object", "string"].includes(typeof content)) {
336
348
  return outputs;
337
349
  }
338
350
  return (0, utils_js_1.convertMessageToTracedFormat)({
@@ -199,7 +199,7 @@ const wrapAISDK = ({ wrapLanguageModel, generateText, streamText, streamObject,
199
199
  }
200
200
  const { content } = lastStep;
201
201
  return convertMessageToTracedFormat({
202
- content,
202
+ content: content ?? outputs.outputs.text,
203
203
  role: "assistant",
204
204
  });
205
205
  }
@@ -327,8 +327,20 @@ const wrapAISDK = ({ wrapLanguageModel, generateText, streamText, streamObject,
327
327
  if (outputs.outputs == null || typeof outputs.outputs !== "object") {
328
328
  return outputs;
329
329
  }
330
- const content = await outputs.outputs.content;
331
- if (content == null || typeof content !== "object") {
330
+ let content;
331
+ if ("content" in outputs.outputs && outputs.outputs.content != null) {
332
+ content = await outputs.outputs.content;
333
+ }
334
+ else if ("text" in outputs.outputs &&
335
+ outputs.outputs.text != null) {
336
+ // AI SDK 4 shim
337
+ content = await outputs.outputs.text;
338
+ }
339
+ else {
340
+ return outputs;
341
+ }
342
+ if (content == null ||
343
+ !["object", "string"].includes(typeof content)) {
332
344
  return outputs;
333
345
  }
334
346
  return convertMessageToTracedFormat({
@@ -26,13 +26,24 @@ const setUsageMetadataOnRunTree = (result, runTree) => {
26
26
  if (result.usage == null || typeof result.usage !== "object") {
27
27
  return;
28
28
  }
29
+ // Shim for AI SDK 4
30
+ const inputTokens = result.usage?.inputTokens ??
31
+ result.usage?.promptTokens;
32
+ const outputTokens = result.usage?.outputTokens ??
33
+ result.usage?.completionTokens;
34
+ let totalTokens = result.usage?.totalTokens;
35
+ if (typeof totalTokens !== "number" &&
36
+ typeof inputTokens === "number" &&
37
+ typeof outputTokens === "number") {
38
+ totalTokens = inputTokens + outputTokens;
39
+ }
29
40
  const langsmithUsage = {
30
- input_tokens: result.usage?.inputTokens,
31
- output_tokens: result.usage?.outputTokens,
32
- total_tokens: result.usage?.totalTokens,
41
+ input_tokens: inputTokens,
42
+ output_tokens: outputTokens,
43
+ total_tokens: totalTokens,
33
44
  };
34
- const inputTokenDetails = (0, vercel_js_1.extractInputTokenDetails)(result.providerMetadata ?? {}, result.usage?.cachedInputTokens);
35
- const outputTokenDetails = (0, vercel_js_1.extractOutputTokenDetails)(result.usage?.reasoningTokens);
45
+ const inputTokenDetails = (0, vercel_js_1.extractInputTokenDetails)(result.usage, result.providerMetadata);
46
+ const outputTokenDetails = (0, vercel_js_1.extractOutputTokenDetails)(result.usage, result.providerMetadata);
36
47
  runTree.extra = {
37
48
  ...runTree.extra,
38
49
  metadata: {
@@ -130,19 +141,35 @@ function LangSmithMiddleware(config) {
130
141
  try {
131
142
  const output = chunks.reduce((aggregated, chunk) => {
132
143
  if (chunk.type === "text-delta") {
133
- if (chunk.delta == null) {
144
+ if (chunk.delta != null) {
145
+ return {
146
+ ...aggregated,
147
+ content: aggregated.content + chunk.delta,
148
+ };
149
+ }
150
+ else if ("textDelta" in chunk &&
151
+ chunk.textDelta != null) {
152
+ // AI SDK 4 shim
153
+ return {
154
+ ...aggregated,
155
+ content: aggregated.content + chunk.textDelta,
156
+ };
157
+ }
158
+ else {
134
159
  return aggregated;
135
160
  }
136
- return {
137
- ...aggregated,
138
- content: aggregated.content + chunk.delta,
139
- };
140
161
  }
141
162
  else if (chunk.type === "tool-call") {
142
163
  const matchingToolCall = aggregated.tool_calls.find((call) => call.id === chunk.toolCallId);
143
164
  if (matchingToolCall != null) {
144
165
  return aggregated;
145
166
  }
167
+ let chunkArgs = chunk.input;
168
+ if (chunkArgs == null &&
169
+ "args" in chunk &&
170
+ typeof chunk.args === "string") {
171
+ chunkArgs = chunk.args;
172
+ }
146
173
  return {
147
174
  ...aggregated,
148
175
  tool_calls: [
@@ -152,7 +179,7 @@ function LangSmithMiddleware(config) {
152
179
  type: "function",
153
180
  function: {
154
181
  name: chunk.toolName,
155
- arguments: chunk.input,
182
+ arguments: chunkArgs,
156
183
  },
157
184
  },
158
185
  ],
@@ -23,13 +23,24 @@ const setUsageMetadataOnRunTree = (result, runTree) => {
23
23
  if (result.usage == null || typeof result.usage !== "object") {
24
24
  return;
25
25
  }
26
+ // Shim for AI SDK 4
27
+ const inputTokens = result.usage?.inputTokens ??
28
+ result.usage?.promptTokens;
29
+ const outputTokens = result.usage?.outputTokens ??
30
+ result.usage?.completionTokens;
31
+ let totalTokens = result.usage?.totalTokens;
32
+ if (typeof totalTokens !== "number" &&
33
+ typeof inputTokens === "number" &&
34
+ typeof outputTokens === "number") {
35
+ totalTokens = inputTokens + outputTokens;
36
+ }
26
37
  const langsmithUsage = {
27
- input_tokens: result.usage?.inputTokens,
28
- output_tokens: result.usage?.outputTokens,
29
- total_tokens: result.usage?.totalTokens,
38
+ input_tokens: inputTokens,
39
+ output_tokens: outputTokens,
40
+ total_tokens: totalTokens,
30
41
  };
31
- const inputTokenDetails = extractInputTokenDetails(result.providerMetadata ?? {}, result.usage?.cachedInputTokens);
32
- const outputTokenDetails = extractOutputTokenDetails(result.usage?.reasoningTokens);
42
+ const inputTokenDetails = extractInputTokenDetails(result.usage, result.providerMetadata);
43
+ const outputTokenDetails = extractOutputTokenDetails(result.usage, result.providerMetadata);
33
44
  runTree.extra = {
34
45
  ...runTree.extra,
35
46
  metadata: {
@@ -127,19 +138,35 @@ export function LangSmithMiddleware(config) {
127
138
  try {
128
139
  const output = chunks.reduce((aggregated, chunk) => {
129
140
  if (chunk.type === "text-delta") {
130
- if (chunk.delta == null) {
141
+ if (chunk.delta != null) {
142
+ return {
143
+ ...aggregated,
144
+ content: aggregated.content + chunk.delta,
145
+ };
146
+ }
147
+ else if ("textDelta" in chunk &&
148
+ chunk.textDelta != null) {
149
+ // AI SDK 4 shim
150
+ return {
151
+ ...aggregated,
152
+ content: aggregated.content + chunk.textDelta,
153
+ };
154
+ }
155
+ else {
131
156
  return aggregated;
132
157
  }
133
- return {
134
- ...aggregated,
135
- content: aggregated.content + chunk.delta,
136
- };
137
158
  }
138
159
  else if (chunk.type === "tool-call") {
139
160
  const matchingToolCall = aggregated.tool_calls.find((call) => call.id === chunk.toolCallId);
140
161
  if (matchingToolCall != null) {
141
162
  return aggregated;
142
163
  }
164
+ let chunkArgs = chunk.input;
165
+ if (chunkArgs == null &&
166
+ "args" in chunk &&
167
+ typeof chunk.args === "string") {
168
+ chunkArgs = chunk.args;
169
+ }
143
170
  return {
144
171
  ...aggregated,
145
172
  tool_calls: [
@@ -149,7 +176,7 @@ export function LangSmithMiddleware(config) {
149
176
  type: "function",
150
177
  function: {
151
178
  name: chunk.toolName,
152
- arguments: chunk.input,
179
+ arguments: chunkArgs,
153
180
  },
154
181
  },
155
182
  ],
@@ -109,14 +109,17 @@ const convertMessageToTracedFormat = (message) => {
109
109
  block.type === "tool-call");
110
110
  });
111
111
  const toolCalls = toolCallBlocks.map((block) => {
112
+ // AI SDK 4 shim
113
+ let toolArgs = block.input ?? (("args" in block && block.args) || undefined);
114
+ if (typeof toolArgs !== "string") {
115
+ toolArgs = JSON.stringify(toolArgs);
116
+ }
112
117
  return {
113
118
  id: block.toolCallId,
114
119
  type: "function",
115
120
  function: {
116
121
  name: block.toolName,
117
- arguments: typeof block.input !== "string"
118
- ? JSON.stringify(block.input)
119
- : block.input,
122
+ arguments: toolArgs,
120
123
  },
121
124
  };
122
125
  });
@@ -147,6 +150,21 @@ const convertMessageToTracedFormat = (message) => {
147
150
  });
148
151
  formattedMessage.content = newContent;
149
152
  }
153
+ else if (message.content == null && "text" in message) {
154
+ // AI SDK 4 shim
155
+ formattedMessage.content = message.text ?? "";
156
+ if ("toolCalls" in message &&
157
+ Array.isArray(message.toolCalls) &&
158
+ !("tool_calls" in formattedMessage)) {
159
+ formattedMessage.tool_calls = message.toolCalls.map((toolCall) => {
160
+ return {
161
+ id: toolCall.toolCallId,
162
+ type: "function",
163
+ function: { name: toolCall.toolName, arguments: toolCall.args },
164
+ };
165
+ });
166
+ }
167
+ }
150
168
  return formattedMessage;
151
169
  };
152
170
  exports.convertMessageToTracedFormat = convertMessageToTracedFormat;
@@ -105,14 +105,17 @@ export const convertMessageToTracedFormat = (message) => {
105
105
  block.type === "tool-call");
106
106
  });
107
107
  const toolCalls = toolCallBlocks.map((block) => {
108
+ // AI SDK 4 shim
109
+ let toolArgs = block.input ?? (("args" in block && block.args) || undefined);
110
+ if (typeof toolArgs !== "string") {
111
+ toolArgs = JSON.stringify(toolArgs);
112
+ }
108
113
  return {
109
114
  id: block.toolCallId,
110
115
  type: "function",
111
116
  function: {
112
117
  name: block.toolName,
113
- arguments: typeof block.input !== "string"
114
- ? JSON.stringify(block.input)
115
- : block.input,
118
+ arguments: toolArgs,
116
119
  },
117
120
  };
118
121
  });
@@ -143,5 +146,20 @@ export const convertMessageToTracedFormat = (message) => {
143
146
  });
144
147
  formattedMessage.content = newContent;
145
148
  }
149
+ else if (message.content == null && "text" in message) {
150
+ // AI SDK 4 shim
151
+ formattedMessage.content = message.text ?? "";
152
+ if ("toolCalls" in message &&
153
+ Array.isArray(message.toolCalls) &&
154
+ !("tool_calls" in formattedMessage)) {
155
+ formattedMessage.tool_calls = message.toolCalls.map((toolCall) => {
156
+ return {
157
+ id: toolCall.toolCallId,
158
+ type: "function",
159
+ function: { name: toolCall.toolName, arguments: toolCall.args },
160
+ };
161
+ });
162
+ }
163
+ }
146
164
  return formattedMessage;
147
165
  };
package/dist/index.cjs CHANGED
@@ -10,4 +10,4 @@ Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true
10
10
  var project_js_1 = require("./utils/project.cjs");
11
11
  Object.defineProperty(exports, "getDefaultProjectName", { enumerable: true, get: function () { return project_js_1.getDefaultProjectName; } });
12
12
  // Update using yarn bump-version
13
- exports.__version__ = "0.3.66";
13
+ exports.__version__ = "0.3.68";
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, }
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
- export declare const __version__ = "0.3.66";
6
+ export declare const __version__ = "0.3.68";
package/dist/index.js CHANGED
@@ -3,4 +3,4 @@ export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  export { getDefaultProjectName } from "./utils/project.js";
5
5
  // Update using yarn bump-version
6
- export const __version__ = "0.3.66";
6
+ export const __version__ = "0.3.68";
@@ -121,7 +121,7 @@ export declare class RunTree implements BaseRun {
121
121
  addEvent(event: RunEvent | string): void;
122
122
  static fromRunnableConfig(parentConfig: RunnableConfigLike, props: RunTreeConfig): RunTree;
123
123
  static fromDottedOrder(dottedOrder: string): RunTree | undefined;
124
- static fromHeaders(headers: Record<string, string | string[]> | HeadersLike, inheritArgs?: RunTreeConfig): RunTree | undefined;
124
+ static fromHeaders(headers: Record<string, string | string[]> | HeadersLike, inheritArgs?: Partial<RunTreeConfig>): RunTree | undefined;
125
125
  toHeaders(headers?: HeadersLike): {
126
126
  "langsmith-trace": string;
127
127
  baggage: string;
package/dist/schemas.d.ts CHANGED
@@ -485,7 +485,7 @@ export type InputTokenDetails = {
485
485
  * Since there was a cache miss, the cache was created from these tokens.
486
486
  */
487
487
  cache_creation?: number;
488
- };
488
+ } & Record<string, number>;
489
489
  /**
490
490
  * Breakdown of output token counts.
491
491
  *
@@ -503,7 +503,7 @@ export type OutputTokenDetails = {
503
503
  * OpenAI's o1 models) that are not returned as part of model output.
504
504
  */
505
505
  reasoning?: number;
506
- };
506
+ } & Record<string, number>;
507
507
  /**
508
508
  * Usage metadata for a message, such as token counts.
509
509
  */
@@ -93,21 +93,35 @@ async function raiseForStatus(response, context, consumeOnSuccess) {
93
93
  const errorData = await response.json();
94
94
  const errorCode = errorData?.error;
95
95
  if (errorCode === "org_scoped_key_requires_workspace") {
96
- throw new Error("This API key is org-scoped and requires workspace specification. " +
97
- "Please provide 'workspaceId' parameter, " +
98
- "or set LANGSMITH_WORKSPACE_ID environment variable.");
96
+ errorBody =
97
+ "This API key is org-scoped and requires workspace specification. " +
98
+ "Please provide 'workspaceId' parameter, " +
99
+ "or set LANGSMITH_WORKSPACE_ID environment variable.";
99
100
  }
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
102
  }
101
103
  catch (e) {
102
- throw new Error(`${response.status} ${response.statusText}`);
104
+ const errorWithStatus = new Error(`${response.status} ${response.statusText}`);
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ errorWithStatus.status = response?.status;
107
+ throw errorWithStatus;
103
108
  }
104
109
  }
105
- errorBody = await response.text();
106
- const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Server response: ${errorBody}`;
110
+ if (errorBody === undefined) {
111
+ try {
112
+ errorBody = await response.text();
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ }
115
+ catch (e) {
116
+ errorBody = "";
117
+ }
118
+ }
119
+ const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Message: ${errorBody}`;
107
120
  if (response.status === 409) {
108
121
  throw new LangSmithConflictError(fullMessage);
109
122
  }
110
123
  const err = new Error(fullMessage);
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
125
  err.status = response.status;
112
126
  throw err;
113
127
  }
@@ -86,21 +86,35 @@ export async function raiseForStatus(response, context, consumeOnSuccess) {
86
86
  const errorData = await response.json();
87
87
  const errorCode = errorData?.error;
88
88
  if (errorCode === "org_scoped_key_requires_workspace") {
89
- throw new Error("This API key is org-scoped and requires workspace specification. " +
90
- "Please provide 'workspaceId' parameter, " +
91
- "or set LANGSMITH_WORKSPACE_ID environment variable.");
89
+ errorBody =
90
+ "This API key is org-scoped and requires workspace specification. " +
91
+ "Please provide 'workspaceId' parameter, " +
92
+ "or set LANGSMITH_WORKSPACE_ID environment variable.";
92
93
  }
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
95
  }
94
96
  catch (e) {
95
- throw new Error(`${response.status} ${response.statusText}`);
97
+ const errorWithStatus = new Error(`${response.status} ${response.statusText}`);
98
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
+ errorWithStatus.status = response?.status;
100
+ throw errorWithStatus;
96
101
  }
97
102
  }
98
- errorBody = await response.text();
99
- const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Server response: ${errorBody}`;
103
+ if (errorBody === undefined) {
104
+ try {
105
+ errorBody = await response.text();
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ }
108
+ catch (e) {
109
+ errorBody = "";
110
+ }
111
+ }
112
+ const fullMessage = `Failed to ${context}. Received status [${response.status}]: ${response.statusText}. Message: ${errorBody}`;
100
113
  if (response.status === 409) {
101
114
  throw new LangSmithConflictError(fullMessage);
102
115
  }
103
116
  const err = new Error(fullMessage);
117
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
118
  err.status = response.status;
105
119
  throw err;
106
120
  }
@@ -3,17 +3,41 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractOutputTokenDetails = extractOutputTokenDetails;
4
4
  exports.extractInputTokenDetails = extractInputTokenDetails;
5
5
  exports.extractUsageMetadata = extractUsageMetadata;
6
- function extractOutputTokenDetails(reasoningTokens) {
6
+ function extractTraceableServiceTier(providerMetadata) {
7
+ if (providerMetadata?.openai != null &&
8
+ typeof providerMetadata.openai === "object") {
9
+ const openai = providerMetadata.openai;
10
+ if (openai.serviceTier != null &&
11
+ typeof openai.serviceTier === "string" &&
12
+ ["priority", "flex"].includes(openai.serviceTier)) {
13
+ return openai.serviceTier;
14
+ }
15
+ }
16
+ return undefined;
17
+ }
18
+ function extractOutputTokenDetails(usage, providerMetadata) {
19
+ const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
20
+ const outputTokenDetailsKeyPrefix = openAIServiceTier
21
+ ? `${openAIServiceTier}_`
22
+ : "";
7
23
  const outputTokenDetails = {};
8
- if (typeof reasoningTokens === "number") {
9
- outputTokenDetails.reasoning = reasoningTokens;
24
+ if (typeof usage?.reasoningTokens === "number") {
25
+ outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] =
26
+ usage.reasoningTokens;
27
+ }
28
+ if (openAIServiceTier && typeof usage?.outputTokens === "number") {
29
+ // Avoid counting reasoning tokens towards the output token count
30
+ // since service tier tokens are already priced differently
31
+ outputTokenDetails[openAIServiceTier] =
32
+ usage.outputTokens -
33
+ (outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] ?? 0);
10
34
  }
11
35
  return outputTokenDetails;
12
36
  }
13
- function extractInputTokenDetails(providerMetadata, cachedTokenUsage) {
37
+ function extractInputTokenDetails(usage, providerMetadata) {
14
38
  const inputTokenDetails = {};
15
- if (providerMetadata.anthropic != null &&
16
- typeof providerMetadata.anthropic === "object") {
39
+ if (providerMetadata?.anthropic != null &&
40
+ typeof providerMetadata?.anthropic === "object") {
17
41
  const anthropic = providerMetadata.anthropic;
18
42
  if (anthropic.usage != null && typeof anthropic.usage === "object") {
19
43
  // Raw usage from Anthropic returned in AI SDK 5
@@ -54,15 +78,28 @@ function extractInputTokenDetails(providerMetadata, cachedTokenUsage) {
54
78
  }
55
79
  return inputTokenDetails;
56
80
  }
57
- else if (providerMetadata.openai != null &&
58
- typeof providerMetadata.openai === "object") {
59
- const openai = providerMetadata.openai;
60
- if (openai.cachedPromptTokens != null &&
61
- typeof openai.cachedPromptTokens === "number") {
62
- inputTokenDetails.cache_read = openai.cachedPromptTokens;
81
+ else if (providerMetadata?.openai != null &&
82
+ typeof providerMetadata?.openai === "object") {
83
+ const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
84
+ const outputTokenDetailsKeyPrefix = openAIServiceTier
85
+ ? `${openAIServiceTier}_`
86
+ : "";
87
+ if (typeof usage?.cachedInputTokens === "number") {
88
+ inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
89
+ usage.cachedInputTokens;
90
+ }
91
+ else if ("cachedPromptTokens" in providerMetadata.openai &&
92
+ providerMetadata.openai.cachedPromptTokens != null &&
93
+ typeof providerMetadata.openai.cachedPromptTokens === "number") {
94
+ inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
95
+ providerMetadata.openai.cachedPromptTokens;
63
96
  }
64
- else if (typeof cachedTokenUsage === "number") {
65
- inputTokenDetails.cache_read = cachedTokenUsage;
97
+ if (openAIServiceTier && typeof usage?.inputTokens === "number") {
98
+ // Avoid counting cached input tokens towards the input token count
99
+ // since service tier tokens are already priced differently
100
+ inputTokenDetails[openAIServiceTier] =
101
+ usage.inputTokens -
102
+ (inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] ?? 0);
66
103
  }
67
104
  }
68
105
  return inputTokenDetails;
@@ -96,9 +133,9 @@ function extractUsageMetadata(span) {
96
133
  if (typeof span.attributes["ai.response.providerMetadata"] === "string") {
97
134
  try {
98
135
  const providerMetadata = JSON.parse(span.attributes["ai.response.providerMetadata"]);
99
- usageMetadata.input_token_details = extractInputTokenDetails(providerMetadata, typeof span.attributes["ai.usage.cachedInputTokens"] === "number"
100
- ? span.attributes["ai.usage.cachedInputTokens"]
101
- : undefined);
136
+ usageMetadata.input_token_details = extractInputTokenDetails(typeof span.attributes["ai.usage.cachedInputTokens"] === "number"
137
+ ? { cachedInputTokens: span.attributes["ai.usage.cachedInputTokens"] }
138
+ : undefined, providerMetadata);
102
139
  if (providerMetadata.anthropic != null &&
103
140
  typeof providerMetadata.anthropic === "object") {
104
141
  // AI SDK does not include Anthropic cache tokens in their stated input token
@@ -1,6 +1,7 @@
1
+ import type { LanguageModelV2Usage } from "@ai-sdk/provider";
1
2
  import { KVMap } from "../schemas.js";
2
- export declare function extractOutputTokenDetails(reasoningTokens?: number): Record<string, number>;
3
- export declare function extractInputTokenDetails(providerMetadata: Record<string, unknown>, cachedTokenUsage?: number): Record<string, number>;
3
+ export declare function extractOutputTokenDetails(usage?: Partial<LanguageModelV2Usage>, providerMetadata?: Record<string, unknown>): Record<string, number>;
4
+ export declare function extractInputTokenDetails(usage?: Partial<LanguageModelV2Usage>, providerMetadata?: Record<string, unknown>): Record<string, number>;
4
5
  export declare function extractUsageMetadata(span?: {
5
6
  status?: {
6
7
  code: number;
@@ -1,14 +1,38 @@
1
- export function extractOutputTokenDetails(reasoningTokens) {
1
+ function extractTraceableServiceTier(providerMetadata) {
2
+ if (providerMetadata?.openai != null &&
3
+ typeof providerMetadata.openai === "object") {
4
+ const openai = providerMetadata.openai;
5
+ if (openai.serviceTier != null &&
6
+ typeof openai.serviceTier === "string" &&
7
+ ["priority", "flex"].includes(openai.serviceTier)) {
8
+ return openai.serviceTier;
9
+ }
10
+ }
11
+ return undefined;
12
+ }
13
+ export function extractOutputTokenDetails(usage, providerMetadata) {
14
+ const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
15
+ const outputTokenDetailsKeyPrefix = openAIServiceTier
16
+ ? `${openAIServiceTier}_`
17
+ : "";
2
18
  const outputTokenDetails = {};
3
- if (typeof reasoningTokens === "number") {
4
- outputTokenDetails.reasoning = reasoningTokens;
19
+ if (typeof usage?.reasoningTokens === "number") {
20
+ outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] =
21
+ usage.reasoningTokens;
22
+ }
23
+ if (openAIServiceTier && typeof usage?.outputTokens === "number") {
24
+ // Avoid counting reasoning tokens towards the output token count
25
+ // since service tier tokens are already priced differently
26
+ outputTokenDetails[openAIServiceTier] =
27
+ usage.outputTokens -
28
+ (outputTokenDetails[`${outputTokenDetailsKeyPrefix}reasoning`] ?? 0);
5
29
  }
6
30
  return outputTokenDetails;
7
31
  }
8
- export function extractInputTokenDetails(providerMetadata, cachedTokenUsage) {
32
+ export function extractInputTokenDetails(usage, providerMetadata) {
9
33
  const inputTokenDetails = {};
10
- if (providerMetadata.anthropic != null &&
11
- typeof providerMetadata.anthropic === "object") {
34
+ if (providerMetadata?.anthropic != null &&
35
+ typeof providerMetadata?.anthropic === "object") {
12
36
  const anthropic = providerMetadata.anthropic;
13
37
  if (anthropic.usage != null && typeof anthropic.usage === "object") {
14
38
  // Raw usage from Anthropic returned in AI SDK 5
@@ -49,15 +73,28 @@ export function extractInputTokenDetails(providerMetadata, cachedTokenUsage) {
49
73
  }
50
74
  return inputTokenDetails;
51
75
  }
52
- else if (providerMetadata.openai != null &&
53
- typeof providerMetadata.openai === "object") {
54
- const openai = providerMetadata.openai;
55
- if (openai.cachedPromptTokens != null &&
56
- typeof openai.cachedPromptTokens === "number") {
57
- inputTokenDetails.cache_read = openai.cachedPromptTokens;
76
+ else if (providerMetadata?.openai != null &&
77
+ typeof providerMetadata?.openai === "object") {
78
+ const openAIServiceTier = extractTraceableServiceTier(providerMetadata ?? {});
79
+ const outputTokenDetailsKeyPrefix = openAIServiceTier
80
+ ? `${openAIServiceTier}_`
81
+ : "";
82
+ if (typeof usage?.cachedInputTokens === "number") {
83
+ inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
84
+ usage.cachedInputTokens;
85
+ }
86
+ else if ("cachedPromptTokens" in providerMetadata.openai &&
87
+ providerMetadata.openai.cachedPromptTokens != null &&
88
+ typeof providerMetadata.openai.cachedPromptTokens === "number") {
89
+ inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] =
90
+ providerMetadata.openai.cachedPromptTokens;
58
91
  }
59
- else if (typeof cachedTokenUsage === "number") {
60
- inputTokenDetails.cache_read = cachedTokenUsage;
92
+ if (openAIServiceTier && typeof usage?.inputTokens === "number") {
93
+ // Avoid counting cached input tokens towards the input token count
94
+ // since service tier tokens are already priced differently
95
+ inputTokenDetails[openAIServiceTier] =
96
+ usage.inputTokens -
97
+ (inputTokenDetails[`${outputTokenDetailsKeyPrefix}cache_read`] ?? 0);
61
98
  }
62
99
  }
63
100
  return inputTokenDetails;
@@ -91,9 +128,9 @@ export function extractUsageMetadata(span) {
91
128
  if (typeof span.attributes["ai.response.providerMetadata"] === "string") {
92
129
  try {
93
130
  const providerMetadata = JSON.parse(span.attributes["ai.response.providerMetadata"]);
94
- usageMetadata.input_token_details = extractInputTokenDetails(providerMetadata, typeof span.attributes["ai.usage.cachedInputTokens"] === "number"
95
- ? span.attributes["ai.usage.cachedInputTokens"]
96
- : undefined);
131
+ usageMetadata.input_token_details = extractInputTokenDetails(typeof span.attributes["ai.usage.cachedInputTokens"] === "number"
132
+ ? { cachedInputTokens: span.attributes["ai.usage.cachedInputTokens"] }
133
+ : undefined, providerMetadata);
97
134
  if (providerMetadata.anthropic != null &&
98
135
  typeof providerMetadata.anthropic === "object") {
99
136
  // AI SDK does not include Anthropic cache tokens in their stated input token
package/dist/vercel.cjs CHANGED
@@ -265,6 +265,7 @@ function getParentSpanId(span) {
265
265
  return (span.parentSpanId ?? span.parentSpanContext?.spanId ?? undefined);
266
266
  }
267
267
  /**
268
+ * @deprecated Use `wrapAISDK` from `langsmith/experimental/vercel` instead.
268
269
  * OpenTelemetry trace exporter for Vercel AI SDK.
269
270
  *
270
271
  * @example
package/dist/vercel.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { generateText } from "ai";
2
2
  import { Client } from "./index.js";
3
+ /** @deprecated Use `wrapAISDK` from `langsmith/experimental/vercel` instead. */
3
4
  export type AITelemetrySettings = Exclude<Parameters<typeof generateText>[0]["experimental_telemetry"], undefined>;
5
+ /** @deprecated Use `wrapAISDK` from `langsmith/experimental/vercel` instead. */
4
6
  export interface TelemetrySettings extends AITelemetrySettings {
5
7
  /** ID of the run sent to LangSmith */
6
8
  runId?: string;
@@ -9,6 +11,7 @@ export interface TelemetrySettings extends AITelemetrySettings {
9
11
  }
10
12
  export declare const parseStrippedIsoTime: (stripped: string) => string;
11
13
  /**
14
+ * @deprecated Use `wrapAISDK` from `langsmith/experimental/vercel` instead.
12
15
  * OpenTelemetry trace exporter for Vercel AI SDK.
13
16
  *
14
17
  * @example
package/dist/vercel.js CHANGED
@@ -261,6 +261,7 @@ function getParentSpanId(span) {
261
261
  return (span.parentSpanId ?? span.parentSpanContext?.spanId ?? undefined);
262
262
  }
263
263
  /**
264
+ * @deprecated Use `wrapAISDK` from `langsmith/experimental/vercel` instead.
264
265
  * OpenTelemetry trace exporter for Vercel AI SDK.
265
266
  *
266
267
  * @example
@@ -129,6 +129,12 @@ const textAggregator = (allChunks
129
129
  };
130
130
  function processChatCompletion(outputs) {
131
131
  const chatCompletion = outputs;
132
+ const recognizedServiceTier = ["priority", "flex"].includes(chatCompletion.service_tier ?? "")
133
+ ? chatCompletion.service_tier
134
+ : undefined;
135
+ const serviceTierPrefix = recognizedServiceTier
136
+ ? `${recognizedServiceTier}_`
137
+ : "";
132
138
  // copy the original object, minus usage
133
139
  const result = { ...chatCompletion };
134
140
  const usage = chatCompletion.usage;
@@ -138,7 +144,7 @@ function processChatCompletion(outputs) {
138
144
  audio: usage.prompt_tokens_details?.audio_tokens,
139
145
  }),
140
146
  ...(usage.prompt_tokens_details?.cached_tokens !== null && {
141
- cache_read: usage.prompt_tokens_details?.cached_tokens,
147
+ [`${serviceTierPrefix}cache_read`]: usage.prompt_tokens_details?.cached_tokens,
142
148
  }),
143
149
  };
144
150
  const outputTokenDetails = {
@@ -146,9 +152,20 @@ function processChatCompletion(outputs) {
146
152
  audio: usage.completion_tokens_details?.audio_tokens,
147
153
  }),
148
154
  ...(usage.completion_tokens_details?.reasoning_tokens !== null && {
149
- reasoning: usage.completion_tokens_details?.reasoning_tokens,
155
+ [`${serviceTierPrefix}reasoning`]: usage.completion_tokens_details?.reasoning_tokens,
150
156
  }),
151
157
  };
158
+ if (recognizedServiceTier) {
159
+ // Avoid counting cache read and reasoning tokens towards the
160
+ // service tier token count since service tier tokens are already
161
+ // priced differently
162
+ inputTokenDetails[recognizedServiceTier] =
163
+ usage.prompt_tokens -
164
+ (inputTokenDetails[`${serviceTierPrefix}cache_read`] ?? 0);
165
+ outputTokenDetails[recognizedServiceTier] =
166
+ usage.completion_tokens -
167
+ (outputTokenDetails[`${serviceTierPrefix}reasoning`] ?? 0);
168
+ }
152
169
  result.usage_metadata = {
153
170
  input_tokens: usage.prompt_tokens ?? 0,
154
171
  output_tokens: usage.completion_tokens ?? 0,
@@ -126,6 +126,12 @@ const textAggregator = (allChunks
126
126
  };
127
127
  function processChatCompletion(outputs) {
128
128
  const chatCompletion = outputs;
129
+ const recognizedServiceTier = ["priority", "flex"].includes(chatCompletion.service_tier ?? "")
130
+ ? chatCompletion.service_tier
131
+ : undefined;
132
+ const serviceTierPrefix = recognizedServiceTier
133
+ ? `${recognizedServiceTier}_`
134
+ : "";
129
135
  // copy the original object, minus usage
130
136
  const result = { ...chatCompletion };
131
137
  const usage = chatCompletion.usage;
@@ -135,7 +141,7 @@ function processChatCompletion(outputs) {
135
141
  audio: usage.prompt_tokens_details?.audio_tokens,
136
142
  }),
137
143
  ...(usage.prompt_tokens_details?.cached_tokens !== null && {
138
- cache_read: usage.prompt_tokens_details?.cached_tokens,
144
+ [`${serviceTierPrefix}cache_read`]: usage.prompt_tokens_details?.cached_tokens,
139
145
  }),
140
146
  };
141
147
  const outputTokenDetails = {
@@ -143,9 +149,20 @@ function processChatCompletion(outputs) {
143
149
  audio: usage.completion_tokens_details?.audio_tokens,
144
150
  }),
145
151
  ...(usage.completion_tokens_details?.reasoning_tokens !== null && {
146
- reasoning: usage.completion_tokens_details?.reasoning_tokens,
152
+ [`${serviceTierPrefix}reasoning`]: usage.completion_tokens_details?.reasoning_tokens,
147
153
  }),
148
154
  };
155
+ if (recognizedServiceTier) {
156
+ // Avoid counting cache read and reasoning tokens towards the
157
+ // service tier token count since service tier tokens are already
158
+ // priced differently
159
+ inputTokenDetails[recognizedServiceTier] =
160
+ usage.prompt_tokens -
161
+ (inputTokenDetails[`${serviceTierPrefix}cache_read`] ?? 0);
162
+ outputTokenDetails[recognizedServiceTier] =
163
+ usage.completion_tokens -
164
+ (outputTokenDetails[`${serviceTierPrefix}reasoning`] ?? 0);
165
+ }
149
166
  result.usage_metadata = {
150
167
  input_tokens: usage.prompt_tokens ?? 0,
151
168
  output_tokens: usage.completion_tokens ?? 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.3.66",
3
+ "version": "0.3.68",
4
4
  "description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
5
5
  "packageManager": "yarn@1.22.19",
6
6
  "files": [
@@ -149,7 +149,7 @@
149
149
  },
150
150
  "devDependencies": {
151
151
  "@ai-sdk/anthropic": "^2.0.1",
152
- "@ai-sdk/openai": "^2.0.10",
152
+ "@ai-sdk/openai": "^2.0.23",
153
153
  "@babel/preset-env": "^7.22.4",
154
154
  "@faker-js/faker": "^8.4.1",
155
155
  "@jest/globals": "^29.5.0",