langsmith 0.3.36 → 0.3.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.
@@ -70,10 +70,10 @@ function parseHeadersString(headersStr) {
70
70
  */
71
71
  class LangSmithOTLPTraceExporter extends exporter_trace_otlp_proto_1.OTLPTraceExporter {
72
72
  constructor(config) {
73
- const lsEndpoint = (0, env_js_2.getEnvironmentVariable)("OTEL_EXPORTER_OTLP_ENDPOINT") ||
73
+ const defaultLsEndpoint = (0, env_js_2.getEnvironmentVariable)("OTEL_EXPORTER_OTLP_ENDPOINT") ||
74
74
  (0, env_js_2.getLangSmithEnvironmentVariable)("ENDPOINT") ||
75
75
  "https://api.smith.langchain.com";
76
- const defaultBaseUrl = lsEndpoint.replace(/\/$/, "");
76
+ const defaultBaseUrl = defaultLsEndpoint.replace(/\/$/, "");
77
77
  const defaultUrl = `${defaultBaseUrl}/otel/v1/traces`;
78
78
  // Configure headers with API key and project if available
79
79
  let defaultHeaderString = (0, env_js_2.getEnvironmentVariable)("OTEL_EXPORTER_OTLP_HEADERS") ?? "";
@@ -92,82 +92,100 @@ class LangSmithOTLPTraceExporter extends exporter_trace_otlp_proto_1.OTLPTraceEx
92
92
  headers: parseHeadersString(defaultHeaderString),
93
93
  ...config,
94
94
  });
95
+ Object.defineProperty(this, "transformExportedSpan", {
96
+ enumerable: true,
97
+ configurable: true,
98
+ writable: true,
99
+ value: void 0
100
+ });
101
+ this.transformExportedSpan = config?.transformExportedSpan;
95
102
  }
96
103
  export(spans, resultCallback) {
97
104
  if (!(0, env_js_1.isTracingEnabled)()) {
98
105
  return resultCallback({ code: 0 });
99
106
  }
100
- for (const span of spans) {
101
- if (!span.attributes[constants.GENAI_PROMPT]) {
102
- if (span.attributes["ai.prompt"]) {
103
- span.attributes[constants.GENAI_PROMPT] =
104
- span.attributes["ai.prompt"];
107
+ const runExport = async () => {
108
+ for (let span of spans) {
109
+ if (this.transformExportedSpan) {
110
+ span = await this.transformExportedSpan(span);
105
111
  }
106
- if (span.attributes["ai.prompt.messages"] &&
107
- typeof span.attributes["ai.prompt.messages"] === "string") {
108
- let messages;
109
- try {
110
- messages = JSON.parse(span.attributes["ai.prompt.messages"]);
112
+ if (!span.attributes[constants.GENAI_PROMPT]) {
113
+ if (span.attributes["ai.prompt"]) {
114
+ span.attributes[constants.GENAI_PROMPT] =
115
+ span.attributes["ai.prompt"];
111
116
  }
112
- catch (e) {
113
- console.error("Failed to parse ai.prompt.messages", e);
117
+ if (span.attributes["ai.prompt.messages"] &&
118
+ typeof span.attributes["ai.prompt.messages"] === "string") {
119
+ let messages;
120
+ try {
121
+ messages = JSON.parse(span.attributes["ai.prompt.messages"]);
122
+ }
123
+ catch (e) {
124
+ console.error("Failed to parse ai.prompt.messages", e);
125
+ }
126
+ if (messages && Array.isArray(messages)) {
127
+ span.attributes[constants.GENAI_PROMPT] = JSON.stringify({
128
+ input: messages,
129
+ });
130
+ }
114
131
  }
115
- if (messages && Array.isArray(messages)) {
116
- span.attributes[constants.GENAI_PROMPT] = JSON.stringify({
117
- input: messages,
118
- });
132
+ if (span.attributes["ai.toolCall.args"]) {
133
+ span.attributes[constants.GENAI_PROMPT] =
134
+ span.attributes["ai.toolCall.args"];
119
135
  }
120
136
  }
121
- if (span.attributes["ai.toolCall.args"]) {
122
- span.attributes[constants.GENAI_PROMPT] =
123
- span.attributes["ai.toolCall.args"];
124
- }
125
- }
126
- // Iterate over all attributes starting with "ai.telemetry.metadata"
127
- for (const [key, value] of Object.entries(span.attributes)) {
128
- if (key.startsWith("ai.telemetry.metadata.")) {
129
- const metadataKey = key.replace("ai.telemetry.metadata.", "");
130
- span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
131
- value;
132
- delete span.attributes[key];
133
- }
134
- }
135
- if (!span.attributes[constants.GENAI_COMPLETION]) {
136
- if (span.attributes["ai.response.text"]) {
137
- span.attributes[constants.GENAI_COMPLETION] =
138
- span.attributes["ai.response.text"];
139
- }
140
- if (span.attributes["ai.response.choices"]) {
141
- span.attributes[constants.GENAI_COMPLETION] =
142
- span.attributes["ai.response.choices"];
137
+ // Iterate over all attributes starting with "ai.telemetry.metadata"
138
+ for (const [key, value] of Object.entries(span.attributes)) {
139
+ if (key.startsWith("ai.telemetry.metadata.")) {
140
+ const metadataKey = key.replace("ai.telemetry.metadata.", "");
141
+ span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
142
+ value;
143
+ delete span.attributes[key];
144
+ }
143
145
  }
144
- if (span.attributes["ai.response.object"]) {
145
- span.attributes[constants.GENAI_COMPLETION] =
146
- span.attributes["ai.response.object"];
146
+ if (!span.attributes[constants.GENAI_COMPLETION]) {
147
+ if (span.attributes["ai.response.text"]) {
148
+ span.attributes[constants.GENAI_COMPLETION] =
149
+ span.attributes["ai.response.text"];
150
+ }
151
+ if (span.attributes["ai.response.choices"]) {
152
+ span.attributes[constants.GENAI_COMPLETION] =
153
+ span.attributes["ai.response.choices"];
154
+ }
155
+ if (span.attributes["ai.response.object"]) {
156
+ span.attributes[constants.GENAI_COMPLETION] =
157
+ span.attributes["ai.response.object"];
158
+ }
159
+ if (span.attributes["ai.response.toolCalls"]) {
160
+ span.attributes[constants.GENAI_COMPLETION] =
161
+ span.attributes["ai.response.toolCalls"];
162
+ }
163
+ if (span.attributes["ai.toolCall.result"]) {
164
+ span.attributes[constants.GENAI_COMPLETION] =
165
+ span.attributes["ai.toolCall.result"];
166
+ }
147
167
  }
148
- if (span.attributes["ai.response.toolCalls"]) {
149
- span.attributes[constants.GENAI_COMPLETION] =
150
- span.attributes["ai.response.toolCalls"];
168
+ if (typeof span.attributes["ai.operationId"] === "string" &&
169
+ constants.AI_SDK_LLM_OPERATIONS.includes(span.attributes["ai.operationId"])) {
170
+ span.attributes[constants.LANGSMITH_RUN_TYPE] = "llm";
151
171
  }
152
- if (span.attributes["ai.toolCall.result"]) {
153
- span.attributes[constants.GENAI_COMPLETION] =
154
- span.attributes["ai.toolCall.result"];
172
+ else if (typeof span.attributes["ai.operationId"] === "string" &&
173
+ constants.AI_SDK_TOOL_OPERATIONS.includes(span.attributes["ai.operationId"])) {
174
+ span.attributes[constants.LANGSMITH_RUN_TYPE] = "tool";
175
+ if (span.attributes["ai.toolCall.name"]) {
176
+ span.attributes[constants.LANGSMITH_NAME] =
177
+ span.attributes["ai.toolCall.name"];
178
+ }
155
179
  }
156
- }
157
- if (typeof span.attributes["ai.operationId"] === "string" &&
158
- constants.AI_SDK_LLM_OPERATIONS.includes(span.attributes["ai.operationId"])) {
159
- span.attributes[constants.LANGSMITH_RUN_TYPE] = "llm";
160
- }
161
- else if (typeof span.attributes["ai.operationId"] === "string" &&
162
- constants.AI_SDK_TOOL_OPERATIONS.includes(span.attributes["ai.operationId"])) {
163
- span.attributes[constants.LANGSMITH_RUN_TYPE] = "tool";
164
- if (span.attributes["ai.toolCall.name"]) {
165
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
- span.name = span.attributes["ai.toolCall.name"];
180
+ if (span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`]) {
181
+ span.attributes[constants.LANGSMITH_NAME] =
182
+ span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`];
183
+ delete span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`];
167
184
  }
168
185
  }
169
- }
170
- super.export(spans, resultCallback);
186
+ super.export(spans, resultCallback);
187
+ };
188
+ void runExport();
171
189
  }
172
190
  }
173
191
  exports.LangSmithOTLPTraceExporter = LangSmithOTLPTraceExporter;
@@ -1,5 +1,30 @@
1
1
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
2
2
  import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
3
+ export type LangSmithOTLPTraceExporterConfig = ConstructorParameters<typeof OTLPTraceExporter>[0] & {
4
+ /**
5
+ * A function that takes an exported span and returns a transformed version of it.
6
+ * May be used to add or remove attributes from the span.
7
+ *
8
+ * For example, to add a custom attribute to the span, you can do:
9
+ *
10
+ * ```ts
11
+ * import { LangSmithOTLPTraceExporter } from "langsmith/experimental/otel/exporter";
12
+ *
13
+ * const exporter = new LangSmithOTLPTraceExporter({
14
+ * transformExportedSpan: (span) => {
15
+ * if (span.name === "foo") {
16
+ * span.attributes["langsmith.metadata.bar"] = "baz";
17
+ * }
18
+ * return span;
19
+ * }
20
+ * });
21
+ * ```
22
+ *
23
+ * @param span - The span to transform.
24
+ * @returns A transformed version of the span.
25
+ */
26
+ transformExportedSpan?: (span: ReadableSpan) => ReadableSpan | Promise<ReadableSpan>;
27
+ };
3
28
  /**
4
29
  * LangSmith OpenTelemetry trace exporter that extends the standard OTLP trace exporter
5
30
  * with LangSmith-specific configuration and span attribute transformations.
@@ -16,6 +41,7 @@ import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
16
41
  * Any provided config will override these defaults.
17
42
  */
18
43
  export declare class LangSmithOTLPTraceExporter extends OTLPTraceExporter {
19
- constructor(config?: ConstructorParameters<typeof OTLPTraceExporter>[0]);
44
+ private transformExportedSpan?;
45
+ constructor(config?: LangSmithOTLPTraceExporterConfig);
20
46
  export(spans: ReadableSpan[], resultCallback: Parameters<OTLPTraceExporter["export"]>[1]): void;
21
47
  }
@@ -34,10 +34,10 @@ function parseHeadersString(headersStr) {
34
34
  */
35
35
  export class LangSmithOTLPTraceExporter extends OTLPTraceExporter {
36
36
  constructor(config) {
37
- const lsEndpoint = getEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ||
37
+ const defaultLsEndpoint = getEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") ||
38
38
  getLangSmithEnvironmentVariable("ENDPOINT") ||
39
39
  "https://api.smith.langchain.com";
40
- const defaultBaseUrl = lsEndpoint.replace(/\/$/, "");
40
+ const defaultBaseUrl = defaultLsEndpoint.replace(/\/$/, "");
41
41
  const defaultUrl = `${defaultBaseUrl}/otel/v1/traces`;
42
42
  // Configure headers with API key and project if available
43
43
  let defaultHeaderString = getEnvironmentVariable("OTEL_EXPORTER_OTLP_HEADERS") ?? "";
@@ -56,81 +56,99 @@ export class LangSmithOTLPTraceExporter extends OTLPTraceExporter {
56
56
  headers: parseHeadersString(defaultHeaderString),
57
57
  ...config,
58
58
  });
59
+ Object.defineProperty(this, "transformExportedSpan", {
60
+ enumerable: true,
61
+ configurable: true,
62
+ writable: true,
63
+ value: void 0
64
+ });
65
+ this.transformExportedSpan = config?.transformExportedSpan;
59
66
  }
60
67
  export(spans, resultCallback) {
61
68
  if (!isTracingEnabled()) {
62
69
  return resultCallback({ code: 0 });
63
70
  }
64
- for (const span of spans) {
65
- if (!span.attributes[constants.GENAI_PROMPT]) {
66
- if (span.attributes["ai.prompt"]) {
67
- span.attributes[constants.GENAI_PROMPT] =
68
- span.attributes["ai.prompt"];
71
+ const runExport = async () => {
72
+ for (let span of spans) {
73
+ if (this.transformExportedSpan) {
74
+ span = await this.transformExportedSpan(span);
69
75
  }
70
- if (span.attributes["ai.prompt.messages"] &&
71
- typeof span.attributes["ai.prompt.messages"] === "string") {
72
- let messages;
73
- try {
74
- messages = JSON.parse(span.attributes["ai.prompt.messages"]);
76
+ if (!span.attributes[constants.GENAI_PROMPT]) {
77
+ if (span.attributes["ai.prompt"]) {
78
+ span.attributes[constants.GENAI_PROMPT] =
79
+ span.attributes["ai.prompt"];
75
80
  }
76
- catch (e) {
77
- console.error("Failed to parse ai.prompt.messages", e);
81
+ if (span.attributes["ai.prompt.messages"] &&
82
+ typeof span.attributes["ai.prompt.messages"] === "string") {
83
+ let messages;
84
+ try {
85
+ messages = JSON.parse(span.attributes["ai.prompt.messages"]);
86
+ }
87
+ catch (e) {
88
+ console.error("Failed to parse ai.prompt.messages", e);
89
+ }
90
+ if (messages && Array.isArray(messages)) {
91
+ span.attributes[constants.GENAI_PROMPT] = JSON.stringify({
92
+ input: messages,
93
+ });
94
+ }
78
95
  }
79
- if (messages && Array.isArray(messages)) {
80
- span.attributes[constants.GENAI_PROMPT] = JSON.stringify({
81
- input: messages,
82
- });
96
+ if (span.attributes["ai.toolCall.args"]) {
97
+ span.attributes[constants.GENAI_PROMPT] =
98
+ span.attributes["ai.toolCall.args"];
83
99
  }
84
100
  }
85
- if (span.attributes["ai.toolCall.args"]) {
86
- span.attributes[constants.GENAI_PROMPT] =
87
- span.attributes["ai.toolCall.args"];
88
- }
89
- }
90
- // Iterate over all attributes starting with "ai.telemetry.metadata"
91
- for (const [key, value] of Object.entries(span.attributes)) {
92
- if (key.startsWith("ai.telemetry.metadata.")) {
93
- const metadataKey = key.replace("ai.telemetry.metadata.", "");
94
- span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
95
- value;
96
- delete span.attributes[key];
97
- }
98
- }
99
- if (!span.attributes[constants.GENAI_COMPLETION]) {
100
- if (span.attributes["ai.response.text"]) {
101
- span.attributes[constants.GENAI_COMPLETION] =
102
- span.attributes["ai.response.text"];
103
- }
104
- if (span.attributes["ai.response.choices"]) {
105
- span.attributes[constants.GENAI_COMPLETION] =
106
- span.attributes["ai.response.choices"];
101
+ // Iterate over all attributes starting with "ai.telemetry.metadata"
102
+ for (const [key, value] of Object.entries(span.attributes)) {
103
+ if (key.startsWith("ai.telemetry.metadata.")) {
104
+ const metadataKey = key.replace("ai.telemetry.metadata.", "");
105
+ span.attributes[`${constants.LANGSMITH_METADATA}.${metadataKey}`] =
106
+ value;
107
+ delete span.attributes[key];
108
+ }
107
109
  }
108
- if (span.attributes["ai.response.object"]) {
109
- span.attributes[constants.GENAI_COMPLETION] =
110
- span.attributes["ai.response.object"];
110
+ if (!span.attributes[constants.GENAI_COMPLETION]) {
111
+ if (span.attributes["ai.response.text"]) {
112
+ span.attributes[constants.GENAI_COMPLETION] =
113
+ span.attributes["ai.response.text"];
114
+ }
115
+ if (span.attributes["ai.response.choices"]) {
116
+ span.attributes[constants.GENAI_COMPLETION] =
117
+ span.attributes["ai.response.choices"];
118
+ }
119
+ if (span.attributes["ai.response.object"]) {
120
+ span.attributes[constants.GENAI_COMPLETION] =
121
+ span.attributes["ai.response.object"];
122
+ }
123
+ if (span.attributes["ai.response.toolCalls"]) {
124
+ span.attributes[constants.GENAI_COMPLETION] =
125
+ span.attributes["ai.response.toolCalls"];
126
+ }
127
+ if (span.attributes["ai.toolCall.result"]) {
128
+ span.attributes[constants.GENAI_COMPLETION] =
129
+ span.attributes["ai.toolCall.result"];
130
+ }
111
131
  }
112
- if (span.attributes["ai.response.toolCalls"]) {
113
- span.attributes[constants.GENAI_COMPLETION] =
114
- span.attributes["ai.response.toolCalls"];
132
+ if (typeof span.attributes["ai.operationId"] === "string" &&
133
+ constants.AI_SDK_LLM_OPERATIONS.includes(span.attributes["ai.operationId"])) {
134
+ span.attributes[constants.LANGSMITH_RUN_TYPE] = "llm";
115
135
  }
116
- if (span.attributes["ai.toolCall.result"]) {
117
- span.attributes[constants.GENAI_COMPLETION] =
118
- span.attributes["ai.toolCall.result"];
136
+ else if (typeof span.attributes["ai.operationId"] === "string" &&
137
+ constants.AI_SDK_TOOL_OPERATIONS.includes(span.attributes["ai.operationId"])) {
138
+ span.attributes[constants.LANGSMITH_RUN_TYPE] = "tool";
139
+ if (span.attributes["ai.toolCall.name"]) {
140
+ span.attributes[constants.LANGSMITH_NAME] =
141
+ span.attributes["ai.toolCall.name"];
142
+ }
119
143
  }
120
- }
121
- if (typeof span.attributes["ai.operationId"] === "string" &&
122
- constants.AI_SDK_LLM_OPERATIONS.includes(span.attributes["ai.operationId"])) {
123
- span.attributes[constants.LANGSMITH_RUN_TYPE] = "llm";
124
- }
125
- else if (typeof span.attributes["ai.operationId"] === "string" &&
126
- constants.AI_SDK_TOOL_OPERATIONS.includes(span.attributes["ai.operationId"])) {
127
- span.attributes[constants.LANGSMITH_RUN_TYPE] = "tool";
128
- if (span.attributes["ai.toolCall.name"]) {
129
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
- span.name = span.attributes["ai.toolCall.name"];
144
+ if (span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`]) {
145
+ span.attributes[constants.LANGSMITH_NAME] =
146
+ span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`];
147
+ delete span.attributes[`${constants.LANGSMITH_METADATA}.ls_run_name`];
131
148
  }
132
149
  }
133
- }
134
- super.export(spans, resultCallback);
150
+ super.export(spans, resultCallback);
151
+ };
152
+ void runExport();
135
153
  }
136
154
  }
@@ -40,7 +40,8 @@ const otel_js_1 = require("../../singletons/otel.cjs");
40
40
  * initializeOTEL({ globalTracerProvider: customProvider });
41
41
  * ```
42
42
  */
43
- const initializeOTEL = ({ globalTracerProvider, globalContextManager, } = {}) => {
43
+ const initializeOTEL = (config = {}) => {
44
+ const { globalTracerProvider, globalContextManager, exporterConfig } = config;
44
45
  const otel = {
45
46
  trace: api_1.trace,
46
47
  context: api_1.context,
@@ -51,7 +52,7 @@ const initializeOTEL = ({ globalTracerProvider, globalContextManager, } = {}) =>
51
52
  contextManager.enable();
52
53
  api_1.context.setGlobalContextManager(contextManager);
53
54
  }
54
- const DEFAULT_LANGSMITH_SPAN_EXPORTER = new exporter_js_1.LangSmithOTLPTraceExporter({});
55
+ const DEFAULT_LANGSMITH_SPAN_EXPORTER = new exporter_js_1.LangSmithOTLPTraceExporter(exporterConfig);
55
56
  const DEFAULT_LANGSMITH_SPAN_PROCESSOR = new sdk_trace_base_1.BatchSpanProcessor(DEFAULT_LANGSMITH_SPAN_EXPORTER);
56
57
  if (!globalTracerProvider) {
57
58
  const DEFAULT_LANGSMITH_TRACER_PROVIDER = new sdk_trace_base_1.BasicTracerProvider({
@@ -1,6 +1,25 @@
1
1
  import { type TracerProvider, type ContextManager } from "@opentelemetry/api";
2
2
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
3
- import { LangSmithOTLPTraceExporter } from "./exporter.js";
3
+ import { LangSmithOTLPTraceExporter, LangSmithOTLPTraceExporterConfig } from "./exporter.js";
4
+ /**
5
+ * Configuration options for initializing OpenTelemetry with LangSmith.
6
+ */
7
+ export type InitializeOTELConfig = {
8
+ /**
9
+ * Optional custom OTEL TracerProvider to use instead of
10
+ * creating and globally setting a new one.
11
+ */
12
+ globalTracerProvider?: TracerProvider;
13
+ /**
14
+ * Optional custom OTEL ContextManager to use instead of
15
+ * creating and globally setting a new one with AsyncHooksContextManager.
16
+ */
17
+ globalContextManager?: ContextManager;
18
+ /**
19
+ * Optional configuration passed to the default LangSmith OTLP trace exporter.
20
+ */
21
+ exporterConfig?: LangSmithOTLPTraceExporterConfig;
22
+ };
4
23
  /**
5
24
  * Initializes OpenTelemetry with LangSmith-specific configuration for tracing.
6
25
  *
@@ -32,10 +51,7 @@ import { LangSmithOTLPTraceExporter } from "./exporter.js";
32
51
  * initializeOTEL({ globalTracerProvider: customProvider });
33
52
  * ```
34
53
  */
35
- export declare const initializeOTEL: ({ globalTracerProvider, globalContextManager, }?: {
36
- globalTracerProvider?: TracerProvider;
37
- globalContextManager?: ContextManager;
38
- }) => {
54
+ export declare const initializeOTEL: (config?: InitializeOTELConfig) => {
39
55
  DEFAULT_LANGSMITH_TRACER_PROVIDER: TracerProvider;
40
56
  DEFAULT_LANGSMITH_SPAN_PROCESSOR: BatchSpanProcessor;
41
57
  DEFAULT_LANGSMITH_SPAN_EXPORTER: LangSmithOTLPTraceExporter;
@@ -4,7 +4,7 @@
4
4
  import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks";
5
5
  import { trace as otel_trace, context as otel_context, } from "@opentelemetry/api";
6
6
  import { BatchSpanProcessor, BasicTracerProvider, } from "@opentelemetry/sdk-trace-base";
7
- import { LangSmithOTLPTraceExporter } from "./exporter.js";
7
+ import { LangSmithOTLPTraceExporter, } from "./exporter.js";
8
8
  import { setDefaultOTLPTracerComponents, setOTELInstances, } from "../../singletons/otel.js";
9
9
  /**
10
10
  * Initializes OpenTelemetry with LangSmith-specific configuration for tracing.
@@ -37,7 +37,8 @@ import { setDefaultOTLPTracerComponents, setOTELInstances, } from "../../singlet
37
37
  * initializeOTEL({ globalTracerProvider: customProvider });
38
38
  * ```
39
39
  */
40
- export const initializeOTEL = ({ globalTracerProvider, globalContextManager, } = {}) => {
40
+ export const initializeOTEL = (config = {}) => {
41
+ const { globalTracerProvider, globalContextManager, exporterConfig } = config;
41
42
  const otel = {
42
43
  trace: otel_trace,
43
44
  context: otel_context,
@@ -48,7 +49,7 @@ export const initializeOTEL = ({ globalTracerProvider, globalContextManager, } =
48
49
  contextManager.enable();
49
50
  otel_context.setGlobalContextManager(contextManager);
50
51
  }
51
- const DEFAULT_LANGSMITH_SPAN_EXPORTER = new LangSmithOTLPTraceExporter({});
52
+ const DEFAULT_LANGSMITH_SPAN_EXPORTER = new LangSmithOTLPTraceExporter(exporterConfig);
52
53
  const DEFAULT_LANGSMITH_SPAN_PROCESSOR = new BatchSpanProcessor(DEFAULT_LANGSMITH_SPAN_EXPORTER);
53
54
  if (!globalTracerProvider) {
54
55
  const DEFAULT_LANGSMITH_TRACER_PROVIDER = new BasicTracerProvider({
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.36";
13
+ exports.__version__ = "0.3.38";
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.36";
6
+ export declare const __version__ = "0.3.38";
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.36";
6
+ export const __version__ = "0.3.38";
@@ -4,7 +4,6 @@ exports.ROOT = exports.AsyncLocalStorageProviderSingleton = void 0;
4
4
  exports.getCurrentRunTree = getCurrentRunTree;
5
5
  exports.withRunTree = withRunTree;
6
6
  exports.isTraceableFunction = isTraceableFunction;
7
- const run_trees_js_1 = require("../run_trees.cjs");
8
7
  class MockAsyncLocalStorage {
9
8
  getStore() {
10
9
  return undefined;
@@ -31,7 +30,7 @@ class AsyncLocalStorageProvider {
31
30
  exports.AsyncLocalStorageProviderSingleton = new AsyncLocalStorageProvider();
32
31
  function getCurrentRunTree(permitAbsentRunTree = false) {
33
32
  const runTree = exports.AsyncLocalStorageProviderSingleton.getInstance().getStore();
34
- if (!permitAbsentRunTree && !(0, run_trees_js_1.isRunTree)(runTree)) {
33
+ if (!permitAbsentRunTree && runTree === undefined) {
35
34
  throw new Error("Could not get the current run tree.\n\nPlease make sure you are calling this method within a traceable function and that tracing is enabled.");
36
35
  }
37
36
  return runTree;
@@ -1,8 +1,8 @@
1
- import { RunTree } from "../run_trees.js";
2
- import { TraceableFunction } from "./types.js";
1
+ import type { RunTree } from "../run_trees.js";
2
+ import type { ContextPlaceholder, TraceableFunction } from "./types.js";
3
3
  interface AsyncLocalStorageInterface {
4
- getStore: () => RunTree | undefined;
5
- run: (context: RunTree | undefined, fn: () => void) => void;
4
+ getStore: () => RunTree | ContextPlaceholder | undefined;
5
+ run: (context: RunTree | ContextPlaceholder | undefined, fn: () => void) => void;
6
6
  }
7
7
  declare class AsyncLocalStorageProvider {
8
8
  getInstance(): AsyncLocalStorageInterface;
@@ -1,4 +1,3 @@
1
- import { isRunTree } from "../run_trees.js";
2
1
  class MockAsyncLocalStorage {
3
2
  getStore() {
4
3
  return undefined;
@@ -25,7 +24,7 @@ class AsyncLocalStorageProvider {
25
24
  export const AsyncLocalStorageProviderSingleton = new AsyncLocalStorageProvider();
26
25
  export function getCurrentRunTree(permitAbsentRunTree = false) {
27
26
  const runTree = AsyncLocalStorageProviderSingleton.getInstance().getStore();
28
- if (!permitAbsentRunTree && !isRunTree(runTree)) {
27
+ if (!permitAbsentRunTree && runTree === undefined) {
29
28
  throw new Error("Could not get the current run tree.\n\nPlease make sure you are calling this method within a traceable function and that tracing is enabled.");
30
29
  }
31
30
  return runTree;
@@ -1,2 +1,3 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const constants_js_1 = require("./constants.cjs");
@@ -1,5 +1,6 @@
1
1
  import { RunTree, RunnableConfigLike } from "../run_trees.js";
2
2
  import { ROOT } from "./traceable.js";
3
+ import { _LC_CONTEXT_VARIABLES_KEY } from "./constants.js";
3
4
  type SmartPromise<T> = T extends AsyncGenerator ? T : T extends Promise<unknown> ? T : Promise<T>;
4
5
  type WrapArgReturnPair<Pair> = Pair extends [
5
6
  infer Args extends any[],
@@ -37,4 +38,7 @@ export type TraceableFunction<Func extends (...args: any[]) => any> = (Func exte
37
38
  [K in keyof Func]: Func[K];
38
39
  };
39
40
  export type RunTreeLike = RunTree;
41
+ export type ContextPlaceholder = {
42
+ [_LC_CONTEXT_VARIABLES_KEY]?: Record<string, unknown>;
43
+ };
40
44
  export {};
@@ -1 +1 @@
1
- export {};
1
+ import { _LC_CONTEXT_VARIABLES_KEY } from "./constants.js";
@@ -143,7 +143,7 @@ const handleRunAttachments = (rawInputs, extractAttachments) => {
143
143
  };
144
144
  const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs, extractAttachments) => {
145
145
  if (!(0, env_js_1.isTracingEnabled)(runTree.tracingEnabled)) {
146
- return undefined;
146
+ return {};
147
147
  }
148
148
  const [attached, args] = handleRunAttachments(inputs, extractAttachments);
149
149
  runTree.attachments = attached;
@@ -364,7 +364,7 @@ function traceable(wrappedFunc, config) {
364
364
  for (let i = 0; i < processedArgs.length; i++) {
365
365
  processedArgs[i] = convertSerializableArg(processedArgs[i]);
366
366
  }
367
- const [currentRunTree, rawInputs] = (() => {
367
+ const [currentContext, rawInputs] = (() => {
368
368
  const [firstArg, ...restArgs] = processedArgs;
369
369
  // used for handoff between LangChain.JS and traceable functions
370
370
  if ((0, run_trees_js_1.isRunnableConfigLike)(firstArg)) {
@@ -391,24 +391,32 @@ function traceable(wrappedFunc, config) {
391
391
  // Node.JS uses AsyncLocalStorage (ALS) and AsyncResource
392
392
  // to allow storing context
393
393
  const prevRunFromStore = asyncLocalStorage.getStore();
394
- if ((0, run_trees_js_1.isRunTree)(prevRunFromStore)) {
395
- return [
396
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
397
- processedArgs,
398
- ];
399
- }
400
- const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
394
+ let lc_contextVars;
401
395
  // If a context var is set by LangChain outside of a traceable,
402
396
  // it will be an object with a single property and we should copy
403
397
  // context vars over into the new run tree.
404
398
  if (prevRunFromStore !== undefined &&
405
399
  constants_js_1._LC_CONTEXT_VARIABLES_KEY in prevRunFromStore) {
406
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
- currentRunTree[constants_js_1._LC_CONTEXT_VARIABLES_KEY] =
408
- prevRunFromStore[constants_js_1._LC_CONTEXT_VARIABLES_KEY];
400
+ lc_contextVars = prevRunFromStore[constants_js_1._LC_CONTEXT_VARIABLES_KEY];
401
+ }
402
+ if ((0, run_trees_js_1.isRunTree)(prevRunFromStore)) {
403
+ const currentRunTree = getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
404
+ if (lc_contextVars) {
405
+ (currentRunTree ?? {})[constants_js_1._LC_CONTEXT_VARIABLES_KEY] =
406
+ lc_contextVars;
407
+ }
408
+ return [currentRunTree, processedArgs];
409
+ }
410
+ const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
411
+ if (lc_contextVars) {
412
+ (currentRunTree ?? {})[constants_js_1._LC_CONTEXT_VARIABLES_KEY] =
413
+ lc_contextVars;
409
414
  }
410
415
  return [currentRunTree, processedArgs];
411
416
  })();
417
+ const currentRunTree = (0, run_trees_js_1.isRunTree)(currentContext)
418
+ ? currentContext
419
+ : undefined;
412
420
  const otelContextManager = maybeCreateOtelContext(currentRunTree, config?.tracer);
413
421
  const otel_context = (0, otel_js_1.getOTELContext)();
414
422
  const runWithContext = () => {
@@ -648,10 +656,10 @@ function traceable(wrappedFunc, config) {
648
656
  };
649
657
  // Wrap with OTEL context if available, similar to Python's implementation
650
658
  if (otelContextManager) {
651
- return asyncLocalStorage.run(currentRunTree, () => otelContextManager(runWithContext));
659
+ return asyncLocalStorage.run(currentContext, () => otelContextManager(runWithContext));
652
660
  }
653
661
  else {
654
- return asyncLocalStorage.run(currentRunTree, runWithContext);
662
+ return asyncLocalStorage.run(currentContext, runWithContext);
655
663
  }
656
664
  };
657
665
  Object.defineProperty(traceableFunc, "langsmith:traceable", {
@@ -1,22 +1,8 @@
1
1
  import { RunTreeConfig } from "./run_trees.js";
2
2
  import { Attachments, InvocationParamsSchema, KVMap } from "./schemas.js";
3
- import { TraceableFunction } from "./singletons/types.js";
3
+ import type { TraceableFunction } from "./singletons/types.js";
4
4
  import { OTELTracer } from "./experimental/otel/types.js";
5
- /**
6
- * Higher-order function that takes function as input and returns a
7
- * "TraceableFunction" - a wrapped version of the input that
8
- * automatically handles tracing. If the returned traceable function calls any
9
- * traceable functions, those are automatically traced as well.
10
- *
11
- * The returned TraceableFunction can accept a run tree or run tree config as
12
- * its first argument. If omitted, it will default to the caller's run tree,
13
- * or will be treated as a root run.
14
- *
15
- * @param wrappedFunc Targeted function to be traced
16
- * @param config Additional metadata such as name, tags or providing
17
- * a custom LangSmith client instance
18
- */
19
- export declare function traceable<Func extends (...args: any[]) => any>(wrappedFunc: Func, config?: Partial<Omit<RunTreeConfig, "inputs" | "outputs">> & {
5
+ export type TraceableConfig<Func extends (...args: any[]) => any> = Partial<Omit<RunTreeConfig, "inputs" | "outputs">> & {
20
6
  aggregator?: (args: any[]) => any;
21
7
  argsConfigPath?: [number] | [number, string];
22
8
  tracer?: OTELTracer;
@@ -54,6 +40,21 @@ export declare function traceable<Func extends (...args: any[]) => any>(wrappedF
54
40
  * @returns Transformed key-value map
55
41
  */
56
42
  processOutputs?: (outputs: Readonly<KVMap>) => KVMap;
57
- }): TraceableFunction<Func>;
43
+ };
44
+ /**
45
+ * Higher-order function that takes function as input and returns a
46
+ * "TraceableFunction" - a wrapped version of the input that
47
+ * automatically handles tracing. If the returned traceable function calls any
48
+ * traceable functions, those are automatically traced as well.
49
+ *
50
+ * The returned TraceableFunction can accept a run tree or run tree config as
51
+ * its first argument. If omitted, it will default to the caller's run tree,
52
+ * or will be treated as a root run.
53
+ *
54
+ * @param wrappedFunc Targeted function to be traced
55
+ * @param config Additional metadata such as name, tags or providing
56
+ * a custom LangSmith client instance
57
+ */
58
+ export declare function traceable<Func extends (...args: any[]) => any>(wrappedFunc: Func, config?: TraceableConfig<Func>): TraceableFunction<Func>;
58
59
  export { getCurrentRunTree, isTraceableFunction, withRunTree, ROOT, } from "./singletons/traceable.js";
59
60
  export type { RunTreeLike, TraceableFunction } from "./singletons/types.js";
package/dist/traceable.js CHANGED
@@ -139,7 +139,7 @@ const handleRunAttachments = (rawInputs, extractAttachments) => {
139
139
  };
140
140
  const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs, extractAttachments) => {
141
141
  if (!isTracingEnabled(runTree.tracingEnabled)) {
142
- return undefined;
142
+ return {};
143
143
  }
144
144
  const [attached, args] = handleRunAttachments(inputs, extractAttachments);
145
145
  runTree.attachments = attached;
@@ -360,7 +360,7 @@ export function traceable(wrappedFunc, config) {
360
360
  for (let i = 0; i < processedArgs.length; i++) {
361
361
  processedArgs[i] = convertSerializableArg(processedArgs[i]);
362
362
  }
363
- const [currentRunTree, rawInputs] = (() => {
363
+ const [currentContext, rawInputs] = (() => {
364
364
  const [firstArg, ...restArgs] = processedArgs;
365
365
  // used for handoff between LangChain.JS and traceable functions
366
366
  if (isRunnableConfigLike(firstArg)) {
@@ -387,24 +387,32 @@ export function traceable(wrappedFunc, config) {
387
387
  // Node.JS uses AsyncLocalStorage (ALS) and AsyncResource
388
388
  // to allow storing context
389
389
  const prevRunFromStore = asyncLocalStorage.getStore();
390
- if (isRunTree(prevRunFromStore)) {
391
- return [
392
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn),
393
- processedArgs,
394
- ];
395
- }
396
- const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
390
+ let lc_contextVars;
397
391
  // If a context var is set by LangChain outside of a traceable,
398
392
  // it will be an object with a single property and we should copy
399
393
  // context vars over into the new run tree.
400
394
  if (prevRunFromStore !== undefined &&
401
395
  _LC_CONTEXT_VARIABLES_KEY in prevRunFromStore) {
402
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
403
- currentRunTree[_LC_CONTEXT_VARIABLES_KEY] =
404
- prevRunFromStore[_LC_CONTEXT_VARIABLES_KEY];
396
+ lc_contextVars = prevRunFromStore[_LC_CONTEXT_VARIABLES_KEY];
397
+ }
398
+ if (isRunTree(prevRunFromStore)) {
399
+ const currentRunTree = getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
400
+ if (lc_contextVars) {
401
+ (currentRunTree ?? {})[_LC_CONTEXT_VARIABLES_KEY] =
402
+ lc_contextVars;
403
+ }
404
+ return [currentRunTree, processedArgs];
405
+ }
406
+ const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), processedArgs, config?.getInvocationParams, processInputsFn, extractAttachmentsFn);
407
+ if (lc_contextVars) {
408
+ (currentRunTree ?? {})[_LC_CONTEXT_VARIABLES_KEY] =
409
+ lc_contextVars;
405
410
  }
406
411
  return [currentRunTree, processedArgs];
407
412
  })();
413
+ const currentRunTree = isRunTree(currentContext)
414
+ ? currentContext
415
+ : undefined;
408
416
  const otelContextManager = maybeCreateOtelContext(currentRunTree, config?.tracer);
409
417
  const otel_context = getOTELContext();
410
418
  const runWithContext = () => {
@@ -644,10 +652,10 @@ export function traceable(wrappedFunc, config) {
644
652
  };
645
653
  // Wrap with OTEL context if available, similar to Python's implementation
646
654
  if (otelContextManager) {
647
- return asyncLocalStorage.run(currentRunTree, () => otelContextManager(runWithContext));
655
+ return asyncLocalStorage.run(currentContext, () => otelContextManager(runWithContext));
648
656
  }
649
657
  else {
650
- return asyncLocalStorage.run(currentRunTree, runWithContext);
658
+ return asyncLocalStorage.run(currentContext, runWithContext);
651
659
  }
652
660
  };
653
661
  Object.defineProperty(traceableFunc, "langsmith:traceable", {
@@ -93,6 +93,19 @@ const chatAggregator = (chunks) => {
93
93
  aggregatedOutput.choices = Object.values(choicesByIndex).map((choices) => _combineChatCompletionChoices(choices));
94
94
  return aggregatedOutput;
95
95
  };
96
+ const responsesAggregator = (events) => {
97
+ if (!events || events.length === 0) {
98
+ return {};
99
+ }
100
+ // Find the response.completed event which contains the final response
101
+ for (const event of events) {
102
+ if (event.type === "response.completed" && event.response) {
103
+ return event.response;
104
+ }
105
+ }
106
+ // If no completed event found, return the last event
107
+ return events[events.length - 1] || {};
108
+ };
96
109
  const textAggregator = (allChunks
97
110
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
111
  ) => {
@@ -187,74 +200,50 @@ const wrapOpenAI = (openai, options) => {
187
200
  // Some internal OpenAI methods call each other, so we need to preserve original
188
201
  // OpenAI methods.
189
202
  const tracedOpenAIClient = { ...openai };
190
- if (openai.beta &&
191
- openai.beta.chat &&
192
- openai.beta.chat.completions &&
193
- typeof openai.beta.chat.completions.parse === "function") {
194
- tracedOpenAIClient.beta = {
195
- ...openai.beta,
196
- chat: {
197
- ...openai.beta.chat,
198
- completions: {
199
- ...openai.beta.chat.completions,
200
- parse: (0, traceable_js_1.traceable)(openai.beta.chat.completions.parse.bind(openai.beta.chat.completions), {
201
- name: "ChatOpenAI",
202
- run_type: "llm",
203
- aggregator: chatAggregator,
204
- argsConfigPath: [1, "langsmithExtra"],
205
- getInvocationParams: (payload) => {
206
- if (typeof payload !== "object" || payload == null)
207
- return undefined;
208
- // we can safely do so, as the types are not exported in TSC
209
- const params = payload;
210
- const ls_stop = (typeof params.stop === "string"
211
- ? [params.stop]
212
- : params.stop) ?? undefined;
213
- return {
214
- ls_provider: "openai",
215
- ls_model_type: "chat",
216
- ls_model_name: params.model,
217
- ls_max_tokens: params.max_tokens ?? undefined,
218
- ls_temperature: params.temperature ?? undefined,
219
- ls_stop,
220
- };
221
- },
222
- ...options,
223
- }),
224
- },
225
- },
226
- };
203
+ const chatCompletionParseMetadata = {
204
+ name: "ChatOpenAI",
205
+ run_type: "llm",
206
+ aggregator: chatAggregator,
207
+ argsConfigPath: [1, "langsmithExtra"],
208
+ getInvocationParams: (payload) => {
209
+ if (typeof payload !== "object" || payload == null)
210
+ return undefined;
211
+ // we can safely do so, as the types are not exported in TSC
212
+ const params = payload;
213
+ const ls_stop = (typeof params.stop === "string" ? [params.stop] : params.stop) ??
214
+ undefined;
215
+ return {
216
+ ls_provider: "openai",
217
+ ls_model_type: "chat",
218
+ ls_model_name: params.model,
219
+ ls_max_tokens: params.max_completion_tokens ?? params.max_tokens ?? undefined,
220
+ ls_temperature: params.temperature ?? undefined,
221
+ ls_stop,
222
+ };
223
+ },
224
+ processOutputs: processChatCompletion,
225
+ ...options,
226
+ };
227
+ if (openai.beta) {
228
+ tracedOpenAIClient.beta = openai.beta;
229
+ if (openai.beta.chat &&
230
+ openai.beta.chat.completions &&
231
+ typeof openai.beta.chat.completions.parse === "function") {
232
+ tracedOpenAIClient.beta.chat.completions.parse = (0, traceable_js_1.traceable)(openai.beta.chat.completions.parse.bind(openai.beta.chat.completions), chatCompletionParseMetadata);
233
+ }
227
234
  }
228
235
  tracedOpenAIClient.chat = {
229
236
  ...openai.chat,
230
- completions: {
231
- ...openai.chat.completions,
232
- create: (0, traceable_js_1.traceable)(openai.chat.completions.create.bind(openai.chat.completions), {
233
- name: "ChatOpenAI",
234
- run_type: "llm",
235
- aggregator: chatAggregator,
236
- argsConfigPath: [1, "langsmithExtra"],
237
- getInvocationParams: (payload) => {
238
- if (typeof payload !== "object" || payload == null)
239
- return undefined;
240
- // we can safely do so, as the types are not exported in TSC
241
- const params = payload;
242
- const ls_stop = (typeof params.stop === "string" ? [params.stop] : params.stop) ??
243
- undefined;
244
- return {
245
- ls_provider: "openai",
246
- ls_model_type: "chat",
247
- ls_model_name: params.model,
248
- ls_max_tokens: params.max_tokens ?? undefined,
249
- ls_temperature: params.temperature ?? undefined,
250
- ls_stop,
251
- };
252
- },
253
- processOutputs: processChatCompletion,
254
- ...options,
255
- }),
256
- },
237
+ completions: Object.create(Object.getPrototypeOf(openai.chat.completions)),
257
238
  };
239
+ // Copy all own properties and then wrap specific methods
240
+ Object.assign(tracedOpenAIClient.chat.completions, openai.chat.completions);
241
+ // Wrap chat.completions.create
242
+ tracedOpenAIClient.chat.completions.create = (0, traceable_js_1.traceable)(openai.chat.completions.create.bind(openai.chat.completions), chatCompletionParseMetadata);
243
+ // Wrap chat.completions.parse if it exists
244
+ if (typeof openai.chat.completions.parse === "function") {
245
+ tracedOpenAIClient.chat.completions.parse = (0, traceable_js_1.traceable)(openai.chat.completions.parse.bind(openai.chat.completions), chatCompletionParseMetadata);
246
+ }
258
247
  tracedOpenAIClient.completions = {
259
248
  ...openai.completions,
260
249
  create: (0, traceable_js_1.traceable)(openai.completions.create.bind(openai.completions), {
@@ -281,6 +270,38 @@ const wrapOpenAI = (openai, options) => {
281
270
  ...options,
282
271
  }),
283
272
  };
273
+ // Add responses API support if it exists
274
+ if (openai.responses) {
275
+ // Create a new object with the same prototype to preserve all methods
276
+ tracedOpenAIClient.responses = Object.create(Object.getPrototypeOf(openai.responses));
277
+ // Copy all own properties
278
+ if (tracedOpenAIClient.responses) {
279
+ Object.assign(tracedOpenAIClient.responses, openai.responses);
280
+ }
281
+ // Wrap responses.create method
282
+ if (tracedOpenAIClient.responses &&
283
+ typeof tracedOpenAIClient.responses.create === "function") {
284
+ tracedOpenAIClient.responses.create = (0, traceable_js_1.traceable)(openai.responses.create.bind(openai.responses), {
285
+ name: "ChatOpenAI",
286
+ run_type: "llm",
287
+ aggregator: responsesAggregator,
288
+ argsConfigPath: [1, "langsmithExtra"],
289
+ getInvocationParams: (payload) => {
290
+ if (typeof payload !== "object" || payload == null)
291
+ return undefined;
292
+ // Handle responses API parameters
293
+ const params = payload;
294
+ return {
295
+ ls_provider: "openai",
296
+ ls_model_type: "llm",
297
+ ls_model_name: params.model || "unknown",
298
+ };
299
+ },
300
+ processOutputs: processChatCompletion,
301
+ ...options,
302
+ });
303
+ }
304
+ }
284
305
  return tracedOpenAIClient;
285
306
  };
286
307
  exports.wrapOpenAI = wrapOpenAI;
@@ -1,22 +1,21 @@
1
1
  import { OpenAI } from "openai";
2
- import type { APIPromise } from "openai/core";
2
+ import type { APIPromise } from "openai";
3
3
  import type { RunTreeConfig } from "../index.js";
4
4
  type OpenAIType = {
5
- beta?: {
6
- chat?: {
7
- completions?: {
8
- parse?: (...args: any[]) => any;
9
- };
10
- };
11
- };
5
+ beta?: any;
12
6
  chat: {
13
7
  completions: {
14
8
  create: (...args: any[]) => any;
9
+ parse: (...args: any[]) => any;
15
10
  };
16
11
  };
17
12
  completions: {
18
13
  create: (...args: any[]) => any;
19
14
  };
15
+ responses?: {
16
+ create: (...args: any[]) => any;
17
+ retrieve: (...args: any[]) => any;
18
+ };
20
19
  };
21
20
  type ExtraRunTreeConfig = Pick<Partial<RunTreeConfig>, "name" | "metadata" | "tags">;
22
21
  type PatchedOpenAIClient<T extends OpenAIType> = T & {
@@ -1,4 +1,4 @@
1
- import { isTraceableFunction, traceable } from "../traceable.js";
1
+ import { isTraceableFunction, traceable, } from "../traceable.js";
2
2
  function _combineChatCompletionChoices(choices
3
3
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
4
  ) {
@@ -90,6 +90,19 @@ const chatAggregator = (chunks) => {
90
90
  aggregatedOutput.choices = Object.values(choicesByIndex).map((choices) => _combineChatCompletionChoices(choices));
91
91
  return aggregatedOutput;
92
92
  };
93
+ const responsesAggregator = (events) => {
94
+ if (!events || events.length === 0) {
95
+ return {};
96
+ }
97
+ // Find the response.completed event which contains the final response
98
+ for (const event of events) {
99
+ if (event.type === "response.completed" && event.response) {
100
+ return event.response;
101
+ }
102
+ }
103
+ // If no completed event found, return the last event
104
+ return events[events.length - 1] || {};
105
+ };
93
106
  const textAggregator = (allChunks
94
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
108
  ) => {
@@ -184,74 +197,50 @@ export const wrapOpenAI = (openai, options) => {
184
197
  // Some internal OpenAI methods call each other, so we need to preserve original
185
198
  // OpenAI methods.
186
199
  const tracedOpenAIClient = { ...openai };
187
- if (openai.beta &&
188
- openai.beta.chat &&
189
- openai.beta.chat.completions &&
190
- typeof openai.beta.chat.completions.parse === "function") {
191
- tracedOpenAIClient.beta = {
192
- ...openai.beta,
193
- chat: {
194
- ...openai.beta.chat,
195
- completions: {
196
- ...openai.beta.chat.completions,
197
- parse: traceable(openai.beta.chat.completions.parse.bind(openai.beta.chat.completions), {
198
- name: "ChatOpenAI",
199
- run_type: "llm",
200
- aggregator: chatAggregator,
201
- argsConfigPath: [1, "langsmithExtra"],
202
- getInvocationParams: (payload) => {
203
- if (typeof payload !== "object" || payload == null)
204
- return undefined;
205
- // we can safely do so, as the types are not exported in TSC
206
- const params = payload;
207
- const ls_stop = (typeof params.stop === "string"
208
- ? [params.stop]
209
- : params.stop) ?? undefined;
210
- return {
211
- ls_provider: "openai",
212
- ls_model_type: "chat",
213
- ls_model_name: params.model,
214
- ls_max_tokens: params.max_tokens ?? undefined,
215
- ls_temperature: params.temperature ?? undefined,
216
- ls_stop,
217
- };
218
- },
219
- ...options,
220
- }),
221
- },
222
- },
223
- };
200
+ const chatCompletionParseMetadata = {
201
+ name: "ChatOpenAI",
202
+ run_type: "llm",
203
+ aggregator: chatAggregator,
204
+ argsConfigPath: [1, "langsmithExtra"],
205
+ getInvocationParams: (payload) => {
206
+ if (typeof payload !== "object" || payload == null)
207
+ return undefined;
208
+ // we can safely do so, as the types are not exported in TSC
209
+ const params = payload;
210
+ const ls_stop = (typeof params.stop === "string" ? [params.stop] : params.stop) ??
211
+ undefined;
212
+ return {
213
+ ls_provider: "openai",
214
+ ls_model_type: "chat",
215
+ ls_model_name: params.model,
216
+ ls_max_tokens: params.max_completion_tokens ?? params.max_tokens ?? undefined,
217
+ ls_temperature: params.temperature ?? undefined,
218
+ ls_stop,
219
+ };
220
+ },
221
+ processOutputs: processChatCompletion,
222
+ ...options,
223
+ };
224
+ if (openai.beta) {
225
+ tracedOpenAIClient.beta = openai.beta;
226
+ if (openai.beta.chat &&
227
+ openai.beta.chat.completions &&
228
+ typeof openai.beta.chat.completions.parse === "function") {
229
+ tracedOpenAIClient.beta.chat.completions.parse = traceable(openai.beta.chat.completions.parse.bind(openai.beta.chat.completions), chatCompletionParseMetadata);
230
+ }
224
231
  }
225
232
  tracedOpenAIClient.chat = {
226
233
  ...openai.chat,
227
- completions: {
228
- ...openai.chat.completions,
229
- create: traceable(openai.chat.completions.create.bind(openai.chat.completions), {
230
- name: "ChatOpenAI",
231
- run_type: "llm",
232
- aggregator: chatAggregator,
233
- argsConfigPath: [1, "langsmithExtra"],
234
- getInvocationParams: (payload) => {
235
- if (typeof payload !== "object" || payload == null)
236
- return undefined;
237
- // we can safely do so, as the types are not exported in TSC
238
- const params = payload;
239
- const ls_stop = (typeof params.stop === "string" ? [params.stop] : params.stop) ??
240
- undefined;
241
- return {
242
- ls_provider: "openai",
243
- ls_model_type: "chat",
244
- ls_model_name: params.model,
245
- ls_max_tokens: params.max_tokens ?? undefined,
246
- ls_temperature: params.temperature ?? undefined,
247
- ls_stop,
248
- };
249
- },
250
- processOutputs: processChatCompletion,
251
- ...options,
252
- }),
253
- },
234
+ completions: Object.create(Object.getPrototypeOf(openai.chat.completions)),
254
235
  };
236
+ // Copy all own properties and then wrap specific methods
237
+ Object.assign(tracedOpenAIClient.chat.completions, openai.chat.completions);
238
+ // Wrap chat.completions.create
239
+ tracedOpenAIClient.chat.completions.create = traceable(openai.chat.completions.create.bind(openai.chat.completions), chatCompletionParseMetadata);
240
+ // Wrap chat.completions.parse if it exists
241
+ if (typeof openai.chat.completions.parse === "function") {
242
+ tracedOpenAIClient.chat.completions.parse = traceable(openai.chat.completions.parse.bind(openai.chat.completions), chatCompletionParseMetadata);
243
+ }
255
244
  tracedOpenAIClient.completions = {
256
245
  ...openai.completions,
257
246
  create: traceable(openai.completions.create.bind(openai.completions), {
@@ -278,5 +267,37 @@ export const wrapOpenAI = (openai, options) => {
278
267
  ...options,
279
268
  }),
280
269
  };
270
+ // Add responses API support if it exists
271
+ if (openai.responses) {
272
+ // Create a new object with the same prototype to preserve all methods
273
+ tracedOpenAIClient.responses = Object.create(Object.getPrototypeOf(openai.responses));
274
+ // Copy all own properties
275
+ if (tracedOpenAIClient.responses) {
276
+ Object.assign(tracedOpenAIClient.responses, openai.responses);
277
+ }
278
+ // Wrap responses.create method
279
+ if (tracedOpenAIClient.responses &&
280
+ typeof tracedOpenAIClient.responses.create === "function") {
281
+ tracedOpenAIClient.responses.create = traceable(openai.responses.create.bind(openai.responses), {
282
+ name: "ChatOpenAI",
283
+ run_type: "llm",
284
+ aggregator: responsesAggregator,
285
+ argsConfigPath: [1, "langsmithExtra"],
286
+ getInvocationParams: (payload) => {
287
+ if (typeof payload !== "object" || payload == null)
288
+ return undefined;
289
+ // Handle responses API parameters
290
+ const params = payload;
291
+ return {
292
+ ls_provider: "openai",
293
+ ls_model_type: "llm",
294
+ ls_model_name: params.model || "unknown",
295
+ };
296
+ },
297
+ processOutputs: processChatCompletion,
298
+ ...options,
299
+ });
300
+ }
301
+ }
281
302
  return tracedOpenAIClient;
282
303
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.3.36",
3
+ "version": "0.3.38",
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": [
@@ -145,9 +145,9 @@
145
145
  "@faker-js/faker": "^8.4.1",
146
146
  "@jest/globals": "^29.5.0",
147
147
  "@jest/reporters": "^29.7.0",
148
- "@langchain/core": "^0.3.14",
149
- "@langchain/langgraph": "^0.2.20",
150
- "@langchain/openai": "^0.3.11",
148
+ "@langchain/core": "^0.3.61",
149
+ "@langchain/langgraph": "^0.3.6",
150
+ "@langchain/openai": "^0.5.16",
151
151
  "@opentelemetry/api": "^1.9.0",
152
152
  "@opentelemetry/auto-instrumentations-node": "^0.58.0",
153
153
  "@opentelemetry/sdk-node": "^0.200.0",
@@ -155,6 +155,7 @@
155
155
  "@opentelemetry/sdk-trace-node": "^2.0.0",
156
156
  "@tsconfig/recommended": "^1.0.2",
157
157
  "@types/jest": "^29.5.1",
158
+ "@types/node-fetch": "^2.6.12",
158
159
  "@typescript-eslint/eslint-plugin": "^5.59.8",
159
160
  "@typescript-eslint/parser": "^5.59.8",
160
161
  "ai": "^4.3.10",
@@ -167,9 +168,9 @@
167
168
  "eslint-plugin-no-instanceof": "^1.0.1",
168
169
  "eslint-plugin-prettier": "^4.2.1",
169
170
  "jest": "^29.5.0",
170
- "langchain": "^0.3.3",
171
+ "langchain": "^0.3.29",
171
172
  "node-fetch": "^2.7.0",
172
- "openai": "^4.67.3",
173
+ "openai": "^5.8.2",
173
174
  "prettier": "^2.8.8",
174
175
  "ts-jest": "^29.1.0",
175
176
  "ts-node": "^10.9.1",
@@ -180,10 +181,10 @@
180
181
  "zod": "^3.23.8"
181
182
  },
182
183
  "peerDependencies": {
183
- "openai": "*",
184
184
  "@opentelemetry/api": "*",
185
185
  "@opentelemetry/exporter-trace-otlp-proto": "*",
186
- "@opentelemetry/sdk-trace-base": "*"
186
+ "@opentelemetry/sdk-trace-base": "*",
187
+ "openai": "*"
187
188
  },
188
189
  "peerDependenciesMeta": {
189
190
  "openai": {