langsmith 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,4 +6,4 @@ Object.defineProperty(exports, "Client", { enumerable: true, get: function () {
6
6
  var run_trees_js_1 = require("./run_trees.cjs");
7
7
  Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () { return run_trees_js_1.RunTree; } });
8
8
  // Update using yarn bump-version
9
- exports.__version__ = "0.1.18";
9
+ exports.__version__ = "0.1.19";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
- export declare const __version__ = "0.1.18";
4
+ export declare const __version__ = "0.1.19";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  // Update using yarn bump-version
4
- export const __version__ = "0.1.18";
4
+ export const __version__ = "0.1.19";
@@ -43,7 +43,7 @@ function convertToDottedOrderFormat(epoch, runId) {
43
43
  }
44
44
  exports.convertToDottedOrderFormat = convertToDottedOrderFormat;
45
45
  class RunTree {
46
- constructor(config) {
46
+ constructor(originalConfig) {
47
47
  Object.defineProperty(this, "id", {
48
48
  enumerable: true,
49
49
  configurable: true,
@@ -159,7 +159,13 @@ class RunTree {
159
159
  value: void 0
160
160
  });
161
161
  const defaultConfig = RunTree.getDefaultConfig();
162
+ const { metadata, ...config } = originalConfig;
162
163
  const client = config.client ?? new client_js_1.Client();
164
+ const dedupedMetadata = {
165
+ ...metadata,
166
+ ...config?.extra?.metadata,
167
+ };
168
+ config.extra = { ...config.extra, metadata: dedupedMetadata };
163
169
  Object.assign(this, { ...defaultConfig, ...config, client });
164
170
  if (!this.trace_id) {
165
171
  if (this.parent_run) {
@@ -191,7 +197,7 @@ class RunTree {
191
197
  parentRun = langChainTracer?.getRun?.(parentRunId);
192
198
  projectName = langChainTracer?.projectName;
193
199
  }
194
- const deduppedTags = [
200
+ const dedupedTags = [
195
201
  ...new Set((parentRun?.tags ?? []).concat(config?.tags ?? [])),
196
202
  ];
197
203
  const dedupedMetadata = {
@@ -201,7 +207,7 @@ class RunTree {
201
207
  const rt = new RunTree({
202
208
  name: props?.name ?? "<lambda>",
203
209
  parent_run: parentRun,
204
- tags: deduppedTags,
210
+ tags: dedupedTags,
205
211
  extra: {
206
212
  metadata: dedupedMetadata,
207
213
  },
@@ -12,6 +12,7 @@ export interface RunTreeConfig {
12
12
  start_time?: number;
13
13
  end_time?: number;
14
14
  extra?: KVMap;
15
+ metadata?: KVMap;
15
16
  tags?: string[];
16
17
  error?: string;
17
18
  serialized?: object;
@@ -57,7 +58,7 @@ export declare class RunTree implements BaseRun {
57
58
  events?: KVMap[] | undefined;
58
59
  trace_id: string;
59
60
  dotted_order: string;
60
- constructor(config: RunTreeConfig);
61
+ constructor(originalConfig: RunTreeConfig);
61
62
  static fromRunnableConfig(config: RunnableConfigLike, props: {
62
63
  name: string;
63
64
  tags?: string[];
package/dist/run_trees.js CHANGED
@@ -16,7 +16,7 @@ export function convertToDottedOrderFormat(epoch, runId) {
16
16
  runId);
17
17
  }
18
18
  export class RunTree {
19
- constructor(config) {
19
+ constructor(originalConfig) {
20
20
  Object.defineProperty(this, "id", {
21
21
  enumerable: true,
22
22
  configurable: true,
@@ -132,7 +132,13 @@ export class RunTree {
132
132
  value: void 0
133
133
  });
134
134
  const defaultConfig = RunTree.getDefaultConfig();
135
+ const { metadata, ...config } = originalConfig;
135
136
  const client = config.client ?? new Client();
137
+ const dedupedMetadata = {
138
+ ...metadata,
139
+ ...config?.extra?.metadata,
140
+ };
141
+ config.extra = { ...config.extra, metadata: dedupedMetadata };
136
142
  Object.assign(this, { ...defaultConfig, ...config, client });
137
143
  if (!this.trace_id) {
138
144
  if (this.parent_run) {
@@ -164,7 +170,7 @@ export class RunTree {
164
170
  parentRun = langChainTracer?.getRun?.(parentRunId);
165
171
  projectName = langChainTracer?.projectName;
166
172
  }
167
- const deduppedTags = [
173
+ const dedupedTags = [
168
174
  ...new Set((parentRun?.tags ?? []).concat(config?.tags ?? [])),
169
175
  ];
170
176
  const dedupedMetadata = {
@@ -174,7 +180,7 @@ export class RunTree {
174
180
  const rt = new RunTree({
175
181
  name: props?.name ?? "<lambda>",
176
182
  parent_run: parentRun,
177
- tags: deduppedTags,
183
+ tags: dedupedTags,
178
184
  extra: {
179
185
  metadata: dedupedMetadata,
180
186
  },
@@ -24,12 +24,13 @@ const isAsyncIterable = (x) => x != null &&
24
24
  */
25
25
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
26
  function traceable(wrappedFunc, config) {
27
+ const { aggregator, ...runTreeConfig } = config ?? {};
27
28
  const traceableFunc = async (...args) => {
28
29
  let currentRunTree;
29
30
  let rawInputs;
30
31
  const ensuredConfig = {
31
32
  name: wrappedFunc.name || "<lambda>",
32
- ...config,
33
+ ...runTreeConfig,
33
34
  };
34
35
  const previousRunTree = asyncLocalStorage.getStore();
35
36
  if ((0, run_trees_js_1.isRunTree)(args[0])) {
@@ -79,7 +80,26 @@ function traceable(wrappedFunc, config) {
79
80
  chunks.push(chunk);
80
81
  yield chunk;
81
82
  }
82
- await currentRunTree.end({ outputs: chunks });
83
+ let finalOutputs;
84
+ if (aggregator !== undefined) {
85
+ try {
86
+ finalOutputs = await aggregator(chunks);
87
+ }
88
+ catch (e) {
89
+ console.error(`[ERROR]: LangSmith aggregation failed: `, e);
90
+ finalOutputs = chunks;
91
+ }
92
+ }
93
+ else {
94
+ finalOutputs = chunks;
95
+ }
96
+ if (typeof finalOutputs === "object" &&
97
+ !Array.isArray(finalOutputs)) {
98
+ await currentRunTree.end(finalOutputs);
99
+ }
100
+ else {
101
+ await currentRunTree.end({ outputs: finalOutputs });
102
+ }
83
103
  await currentRunTree.patchRun();
84
104
  }
85
105
  return resolve(wrapOutputForTracing());
@@ -112,7 +132,7 @@ function traceable(wrappedFunc, config) {
112
132
  });
113
133
  };
114
134
  Object.defineProperty(traceableFunc, "langsmith:traceable", {
115
- value: config,
135
+ value: runTreeConfig,
116
136
  });
117
137
  return traceableFunc;
118
138
  }
@@ -44,7 +44,9 @@ export type TraceableFunction<Func extends (...args: any[]) => any> = Func exten
44
44
  * @param config Additional metadata such as name, tags or providing
45
45
  * a custom LangSmith client instance
46
46
  */
47
- export declare function traceable<Func extends (...args: any[]) => any>(wrappedFunc: Func, config?: Partial<RunTreeConfig>): TraceableFunction<Func>;
47
+ export declare function traceable<Func extends (...args: any[]) => any>(wrappedFunc: Func, config?: Partial<RunTreeConfig> & {
48
+ aggregator?: (args: any[]) => any;
49
+ }): TraceableFunction<Func>;
48
50
  /**
49
51
  * Return the current run tree from within a traceable-wrapped function.
50
52
  * Will throw an error if called outside of a traceable function.
package/dist/traceable.js CHANGED
@@ -21,12 +21,13 @@ const isAsyncIterable = (x) => x != null &&
21
21
  */
22
22
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
23
  export function traceable(wrappedFunc, config) {
24
+ const { aggregator, ...runTreeConfig } = config ?? {};
24
25
  const traceableFunc = async (...args) => {
25
26
  let currentRunTree;
26
27
  let rawInputs;
27
28
  const ensuredConfig = {
28
29
  name: wrappedFunc.name || "<lambda>",
29
- ...config,
30
+ ...runTreeConfig,
30
31
  };
31
32
  const previousRunTree = asyncLocalStorage.getStore();
32
33
  if (isRunTree(args[0])) {
@@ -76,7 +77,26 @@ export function traceable(wrappedFunc, config) {
76
77
  chunks.push(chunk);
77
78
  yield chunk;
78
79
  }
79
- await currentRunTree.end({ outputs: chunks });
80
+ let finalOutputs;
81
+ if (aggregator !== undefined) {
82
+ try {
83
+ finalOutputs = await aggregator(chunks);
84
+ }
85
+ catch (e) {
86
+ console.error(`[ERROR]: LangSmith aggregation failed: `, e);
87
+ finalOutputs = chunks;
88
+ }
89
+ }
90
+ else {
91
+ finalOutputs = chunks;
92
+ }
93
+ if (typeof finalOutputs === "object" &&
94
+ !Array.isArray(finalOutputs)) {
95
+ await currentRunTree.end(finalOutputs);
96
+ }
97
+ else {
98
+ await currentRunTree.end({ outputs: finalOutputs });
99
+ }
80
100
  await currentRunTree.patchRun();
81
101
  }
82
102
  return resolve(wrapOutputForTracing());
@@ -109,7 +129,7 @@ export function traceable(wrappedFunc, config) {
109
129
  });
110
130
  };
111
131
  Object.defineProperty(traceableFunc, "langsmith:traceable", {
112
- value: config,
132
+ value: runTreeConfig,
113
133
  });
114
134
  return traceableFunc;
115
135
  }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./openai.cjs"), exports);
@@ -0,0 +1 @@
1
+ export * from "./openai.js";
@@ -0,0 +1 @@
1
+ export * from "./openai.js";
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapSDK = exports.wrapOpenAI = void 0;
4
+ const traceable_js_1 = require("../traceable.cjs");
5
+ function _combineChatCompletionChoices(choices) {
6
+ const reversedChoices = choices.slice().reverse();
7
+ const message = {
8
+ role: "assistant",
9
+ content: "",
10
+ };
11
+ for (const c of reversedChoices) {
12
+ if (c.delta.role) {
13
+ message["role"] = c.delta.role;
14
+ break;
15
+ }
16
+ }
17
+ const toolCalls = {};
18
+ for (const c of choices) {
19
+ if (c.delta.content) {
20
+ message.content = message.content.concat(c.delta.content);
21
+ }
22
+ if (c.delta.function_call) {
23
+ if (!message.function_call) {
24
+ message.function_call = { name: "", arguments: "" };
25
+ }
26
+ if (c.delta.function_call.name) {
27
+ message.function_call.name += c.delta.function_call.name;
28
+ }
29
+ if (c.delta.function_call.arguments) {
30
+ message.function_call.arguments += c.delta.function_call.arguments;
31
+ }
32
+ }
33
+ if (c.delta.tool_calls) {
34
+ for (const tool_call of c.delta.tool_calls) {
35
+ if (!toolCalls[c.index]) {
36
+ toolCalls[c.index] = [];
37
+ }
38
+ toolCalls[c.index].push(tool_call);
39
+ }
40
+ }
41
+ }
42
+ if (Object.keys(toolCalls).length > 0) {
43
+ message.tool_calls = [...Array(Object.keys(toolCalls).length)];
44
+ for (const [index, toolCallChunks] of Object.entries(toolCalls)) {
45
+ const idx = parseInt(index);
46
+ message.tool_calls[idx] = {
47
+ index: idx,
48
+ id: toolCallChunks.find((c) => c.id)?.id || null,
49
+ type: toolCallChunks.find((c) => c.type)?.type || null,
50
+ };
51
+ for (const chunk of toolCallChunks) {
52
+ if (chunk.function) {
53
+ if (!message.tool_calls[idx].function) {
54
+ message.tool_calls[idx].function = {
55
+ name: "",
56
+ arguments: "",
57
+ };
58
+ }
59
+ if (chunk.function.name) {
60
+ message.tool_calls[idx].function.name += chunk.function.name;
61
+ }
62
+ if (chunk.function.arguments) {
63
+ message.tool_calls[idx].function.arguments +=
64
+ chunk.function.arguments;
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ return {
71
+ index: choices[0].index,
72
+ finish_reason: reversedChoices.find((c) => c.finish_reason) || null,
73
+ message: message,
74
+ };
75
+ }
76
+ async function extractLangSmithExtraAndCall(openAIMethod, args, defaultRunConfig) {
77
+ if (args[1]?.langsmithExtra !== undefined) {
78
+ const { langsmithExtra, ...openAIOptions } = args[1];
79
+ const wrappedMethod = (0, traceable_js_1.traceable)(openAIMethod, {
80
+ ...defaultRunConfig,
81
+ ...langsmithExtra,
82
+ });
83
+ const finalArgs = [args[0]];
84
+ if (args.length > 2) {
85
+ finalArgs.push(openAIOptions);
86
+ finalArgs.push(args.slice(2));
87
+ }
88
+ else if (Object.keys(openAIOptions).length !== 0) {
89
+ finalArgs.push(openAIOptions);
90
+ }
91
+ return wrappedMethod(...finalArgs);
92
+ }
93
+ const wrappedMethod = (0, traceable_js_1.traceable)(openAIMethod, defaultRunConfig);
94
+ return wrappedMethod(...args);
95
+ }
96
+ /**
97
+ * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
98
+ * tracing. Method signatures are unchanged, with the exception that you can pass
99
+ * an additional and optional "langsmithExtra" field within the second parameter.
100
+ * @param openai An OpenAI client instance.
101
+ * @param options LangSmith options.
102
+ * @example
103
+ * ```ts
104
+ * const patchedStream = await patchedClient.chat.completions.create(
105
+ * {
106
+ * messages: [{ role: "user", content: `Say 'foo'` }],
107
+ * model: "gpt-3.5-turbo",
108
+ * stream: true,
109
+ * },
110
+ * {
111
+ * langsmithExtra: {
112
+ * metadata: {
113
+ * additional_data: "bar",
114
+ * },
115
+ * },
116
+ * },
117
+ * );
118
+ * ```
119
+ */
120
+ const wrapOpenAI = (openai, options) => {
121
+ const originalChatCompletionsFn = openai.chat.completions.create.bind(openai.chat.completions);
122
+ openai.chat.completions.create = async (...args) => {
123
+ const aggregator = (chunks) => {
124
+ if (!chunks || chunks.length === 0) {
125
+ return { choices: [{ message: { role: "assistant", content: "" } }] };
126
+ }
127
+ const choicesByIndex = {};
128
+ for (const chunk of chunks) {
129
+ for (const choice of chunk.choices) {
130
+ if (choicesByIndex[choice.index] === undefined) {
131
+ choicesByIndex[choice.index] = [];
132
+ }
133
+ choicesByIndex[choice.index].push(choice);
134
+ }
135
+ }
136
+ const aggregatedOutput = chunks[chunks.length - 1];
137
+ aggregatedOutput.choices = Object.values(choicesByIndex).map((choices) => _combineChatCompletionChoices(choices));
138
+ return aggregatedOutput;
139
+ };
140
+ const defaultRunConfig = {
141
+ name: "ChatOpenAI",
142
+ run_type: "llm",
143
+ aggregator,
144
+ ...options,
145
+ };
146
+ return extractLangSmithExtraAndCall(originalChatCompletionsFn, args, defaultRunConfig);
147
+ };
148
+ const originalCompletionsFn = openai.completions.create.bind(openai.chat.completions);
149
+ openai.completions.create = async (...args) => {
150
+ const aggregator = (allChunks) => {
151
+ if (allChunks.length === 0) {
152
+ return { choices: [{ text: "" }] };
153
+ }
154
+ const allContent = [];
155
+ for (const chunk of allChunks) {
156
+ const content = chunk.choices[0].text;
157
+ if (content != null) {
158
+ allContent.push(content);
159
+ }
160
+ }
161
+ const content = allContent.join("");
162
+ const aggregatedOutput = allChunks[allChunks.length - 1];
163
+ aggregatedOutput.choices = [
164
+ { ...aggregatedOutput.choices[0], text: content },
165
+ ];
166
+ return aggregatedOutput;
167
+ };
168
+ const defaultRunConfig = {
169
+ name: "OpenAI",
170
+ run_type: "llm",
171
+ aggregator,
172
+ ...options,
173
+ };
174
+ return extractLangSmithExtraAndCall(originalCompletionsFn, args, defaultRunConfig);
175
+ };
176
+ return openai;
177
+ };
178
+ exports.wrapOpenAI = wrapOpenAI;
179
+ const _wrapClient = (sdk, runName, options) => {
180
+ return new Proxy(sdk, {
181
+ get(target, propKey, receiver) {
182
+ const originalValue = target[propKey];
183
+ if (typeof originalValue === "function") {
184
+ return (0, traceable_js_1.traceable)(originalValue.bind(target), Object.assign({ name: [runName, propKey.toString()].join("."), run_type: "llm" }, options?.client));
185
+ }
186
+ else if (originalValue != null &&
187
+ !Array.isArray(originalValue) &&
188
+ // eslint-disable-next-line no-instanceof/no-instanceof
189
+ !(originalValue instanceof Date) &&
190
+ typeof originalValue === "object") {
191
+ return _wrapClient(originalValue, [runName, propKey.toString()].join("."), options);
192
+ }
193
+ else {
194
+ return Reflect.get(target, propKey, receiver);
195
+ }
196
+ },
197
+ });
198
+ };
199
+ /**
200
+ * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
201
+ * Method signatures are unchanged.
202
+ *
203
+ * Note that this will wrap and trace ALL SDK methods, not just
204
+ * LLM completion methods. If the passed SDK contains other methods,
205
+ * we recommend using the wrapped instance for LLM calls only.
206
+ * @param sdk An arbitrary SDK instance.
207
+ * @param options LangSmith options.
208
+ * @returns
209
+ */
210
+ const wrapSDK = (sdk, options) => {
211
+ return _wrapClient(sdk, options?.runName ?? sdk.constructor?.name, {
212
+ client: options?.client,
213
+ });
214
+ };
215
+ exports.wrapSDK = wrapSDK;
@@ -0,0 +1,83 @@
1
+ import type { OpenAI } from "openai";
2
+ import type { Client, RunTreeConfig } from "../index.js";
3
+ import { type RunnableConfigLike } from "../run_trees.js";
4
+ import { type RunTreeLike } from "../traceable.js";
5
+ type OpenAIType = {
6
+ chat: {
7
+ completions: {
8
+ create: (...args: any[]) => any;
9
+ };
10
+ };
11
+ completions: {
12
+ create: (...args: any[]) => any;
13
+ };
14
+ };
15
+ type PatchedOpenAIClient<T extends OpenAIType> = {
16
+ [P in keyof T]: T[P];
17
+ } & {
18
+ chat: {
19
+ completions: {
20
+ create: {
21
+ (arg: OpenAI.ChatCompletionCreateParamsStreaming, arg2?: OpenAI.RequestOptions & {
22
+ langsmithExtra?: RunnableConfigLike | RunTreeLike;
23
+ }): Promise<AsyncGenerator<OpenAI.ChatCompletionChunk>>;
24
+ } & {
25
+ (arg: OpenAI.ChatCompletionCreateParamsNonStreaming, arg2?: OpenAI.RequestOptions & {
26
+ langsmithExtra?: RunnableConfigLike | RunTreeLike;
27
+ }): Promise<OpenAI.ChatCompletionChunk>;
28
+ };
29
+ };
30
+ };
31
+ completions: {
32
+ create: {
33
+ (arg: OpenAI.CompletionCreateParamsStreaming, arg2?: OpenAI.RequestOptions & {
34
+ langsmithExtra?: RunnableConfigLike | RunTreeLike;
35
+ }): Promise<AsyncGenerator<OpenAI.Completion>>;
36
+ } & {
37
+ (arg: OpenAI.CompletionCreateParamsNonStreaming, arg2?: OpenAI.RequestOptions & {
38
+ langsmithExtra?: RunnableConfigLike | RunTreeLike;
39
+ }): Promise<OpenAI.Completion>;
40
+ };
41
+ };
42
+ };
43
+ /**
44
+ * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
45
+ * tracing. Method signatures are unchanged, with the exception that you can pass
46
+ * an additional and optional "langsmithExtra" field within the second parameter.
47
+ * @param openai An OpenAI client instance.
48
+ * @param options LangSmith options.
49
+ * @example
50
+ * ```ts
51
+ * const patchedStream = await patchedClient.chat.completions.create(
52
+ * {
53
+ * messages: [{ role: "user", content: `Say 'foo'` }],
54
+ * model: "gpt-3.5-turbo",
55
+ * stream: true,
56
+ * },
57
+ * {
58
+ * langsmithExtra: {
59
+ * metadata: {
60
+ * additional_data: "bar",
61
+ * },
62
+ * },
63
+ * },
64
+ * );
65
+ * ```
66
+ */
67
+ export declare const wrapOpenAI: <T extends OpenAIType>(openai: T, options?: Partial<RunTreeConfig>) => PatchedOpenAIClient<T>;
68
+ /**
69
+ * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
70
+ * Method signatures are unchanged.
71
+ *
72
+ * Note that this will wrap and trace ALL SDK methods, not just
73
+ * LLM completion methods. If the passed SDK contains other methods,
74
+ * we recommend using the wrapped instance for LLM calls only.
75
+ * @param sdk An arbitrary SDK instance.
76
+ * @param options LangSmith options.
77
+ * @returns
78
+ */
79
+ export declare const wrapSDK: <T extends object>(sdk: T, options?: {
80
+ client?: Client;
81
+ runName?: string;
82
+ }) => T;
83
+ export {};
@@ -0,0 +1,210 @@
1
+ import { traceable } from "../traceable.js";
2
+ function _combineChatCompletionChoices(choices) {
3
+ const reversedChoices = choices.slice().reverse();
4
+ const message = {
5
+ role: "assistant",
6
+ content: "",
7
+ };
8
+ for (const c of reversedChoices) {
9
+ if (c.delta.role) {
10
+ message["role"] = c.delta.role;
11
+ break;
12
+ }
13
+ }
14
+ const toolCalls = {};
15
+ for (const c of choices) {
16
+ if (c.delta.content) {
17
+ message.content = message.content.concat(c.delta.content);
18
+ }
19
+ if (c.delta.function_call) {
20
+ if (!message.function_call) {
21
+ message.function_call = { name: "", arguments: "" };
22
+ }
23
+ if (c.delta.function_call.name) {
24
+ message.function_call.name += c.delta.function_call.name;
25
+ }
26
+ if (c.delta.function_call.arguments) {
27
+ message.function_call.arguments += c.delta.function_call.arguments;
28
+ }
29
+ }
30
+ if (c.delta.tool_calls) {
31
+ for (const tool_call of c.delta.tool_calls) {
32
+ if (!toolCalls[c.index]) {
33
+ toolCalls[c.index] = [];
34
+ }
35
+ toolCalls[c.index].push(tool_call);
36
+ }
37
+ }
38
+ }
39
+ if (Object.keys(toolCalls).length > 0) {
40
+ message.tool_calls = [...Array(Object.keys(toolCalls).length)];
41
+ for (const [index, toolCallChunks] of Object.entries(toolCalls)) {
42
+ const idx = parseInt(index);
43
+ message.tool_calls[idx] = {
44
+ index: idx,
45
+ id: toolCallChunks.find((c) => c.id)?.id || null,
46
+ type: toolCallChunks.find((c) => c.type)?.type || null,
47
+ };
48
+ for (const chunk of toolCallChunks) {
49
+ if (chunk.function) {
50
+ if (!message.tool_calls[idx].function) {
51
+ message.tool_calls[idx].function = {
52
+ name: "",
53
+ arguments: "",
54
+ };
55
+ }
56
+ if (chunk.function.name) {
57
+ message.tool_calls[idx].function.name += chunk.function.name;
58
+ }
59
+ if (chunk.function.arguments) {
60
+ message.tool_calls[idx].function.arguments +=
61
+ chunk.function.arguments;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return {
68
+ index: choices[0].index,
69
+ finish_reason: reversedChoices.find((c) => c.finish_reason) || null,
70
+ message: message,
71
+ };
72
+ }
73
+ async function extractLangSmithExtraAndCall(openAIMethod, args, defaultRunConfig) {
74
+ if (args[1]?.langsmithExtra !== undefined) {
75
+ const { langsmithExtra, ...openAIOptions } = args[1];
76
+ const wrappedMethod = traceable(openAIMethod, {
77
+ ...defaultRunConfig,
78
+ ...langsmithExtra,
79
+ });
80
+ const finalArgs = [args[0]];
81
+ if (args.length > 2) {
82
+ finalArgs.push(openAIOptions);
83
+ finalArgs.push(args.slice(2));
84
+ }
85
+ else if (Object.keys(openAIOptions).length !== 0) {
86
+ finalArgs.push(openAIOptions);
87
+ }
88
+ return wrappedMethod(...finalArgs);
89
+ }
90
+ const wrappedMethod = traceable(openAIMethod, defaultRunConfig);
91
+ return wrappedMethod(...args);
92
+ }
93
+ /**
94
+ * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
95
+ * tracing. Method signatures are unchanged, with the exception that you can pass
96
+ * an additional and optional "langsmithExtra" field within the second parameter.
97
+ * @param openai An OpenAI client instance.
98
+ * @param options LangSmith options.
99
+ * @example
100
+ * ```ts
101
+ * const patchedStream = await patchedClient.chat.completions.create(
102
+ * {
103
+ * messages: [{ role: "user", content: `Say 'foo'` }],
104
+ * model: "gpt-3.5-turbo",
105
+ * stream: true,
106
+ * },
107
+ * {
108
+ * langsmithExtra: {
109
+ * metadata: {
110
+ * additional_data: "bar",
111
+ * },
112
+ * },
113
+ * },
114
+ * );
115
+ * ```
116
+ */
117
+ export const wrapOpenAI = (openai, options) => {
118
+ const originalChatCompletionsFn = openai.chat.completions.create.bind(openai.chat.completions);
119
+ openai.chat.completions.create = async (...args) => {
120
+ const aggregator = (chunks) => {
121
+ if (!chunks || chunks.length === 0) {
122
+ return { choices: [{ message: { role: "assistant", content: "" } }] };
123
+ }
124
+ const choicesByIndex = {};
125
+ for (const chunk of chunks) {
126
+ for (const choice of chunk.choices) {
127
+ if (choicesByIndex[choice.index] === undefined) {
128
+ choicesByIndex[choice.index] = [];
129
+ }
130
+ choicesByIndex[choice.index].push(choice);
131
+ }
132
+ }
133
+ const aggregatedOutput = chunks[chunks.length - 1];
134
+ aggregatedOutput.choices = Object.values(choicesByIndex).map((choices) => _combineChatCompletionChoices(choices));
135
+ return aggregatedOutput;
136
+ };
137
+ const defaultRunConfig = {
138
+ name: "ChatOpenAI",
139
+ run_type: "llm",
140
+ aggregator,
141
+ ...options,
142
+ };
143
+ return extractLangSmithExtraAndCall(originalChatCompletionsFn, args, defaultRunConfig);
144
+ };
145
+ const originalCompletionsFn = openai.completions.create.bind(openai.chat.completions);
146
+ openai.completions.create = async (...args) => {
147
+ const aggregator = (allChunks) => {
148
+ if (allChunks.length === 0) {
149
+ return { choices: [{ text: "" }] };
150
+ }
151
+ const allContent = [];
152
+ for (const chunk of allChunks) {
153
+ const content = chunk.choices[0].text;
154
+ if (content != null) {
155
+ allContent.push(content);
156
+ }
157
+ }
158
+ const content = allContent.join("");
159
+ const aggregatedOutput = allChunks[allChunks.length - 1];
160
+ aggregatedOutput.choices = [
161
+ { ...aggregatedOutput.choices[0], text: content },
162
+ ];
163
+ return aggregatedOutput;
164
+ };
165
+ const defaultRunConfig = {
166
+ name: "OpenAI",
167
+ run_type: "llm",
168
+ aggregator,
169
+ ...options,
170
+ };
171
+ return extractLangSmithExtraAndCall(originalCompletionsFn, args, defaultRunConfig);
172
+ };
173
+ return openai;
174
+ };
175
+ const _wrapClient = (sdk, runName, options) => {
176
+ return new Proxy(sdk, {
177
+ get(target, propKey, receiver) {
178
+ const originalValue = target[propKey];
179
+ if (typeof originalValue === "function") {
180
+ return traceable(originalValue.bind(target), Object.assign({ name: [runName, propKey.toString()].join("."), run_type: "llm" }, options?.client));
181
+ }
182
+ else if (originalValue != null &&
183
+ !Array.isArray(originalValue) &&
184
+ // eslint-disable-next-line no-instanceof/no-instanceof
185
+ !(originalValue instanceof Date) &&
186
+ typeof originalValue === "object") {
187
+ return _wrapClient(originalValue, [runName, propKey.toString()].join("."), options);
188
+ }
189
+ else {
190
+ return Reflect.get(target, propKey, receiver);
191
+ }
192
+ },
193
+ });
194
+ };
195
+ /**
196
+ * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
197
+ * Method signatures are unchanged.
198
+ *
199
+ * Note that this will wrap and trace ALL SDK methods, not just
200
+ * LLM completion methods. If the passed SDK contains other methods,
201
+ * we recommend using the wrapped instance for LLM calls only.
202
+ * @param sdk An arbitrary SDK instance.
203
+ * @param options LangSmith options.
204
+ * @returns
205
+ */
206
+ export const wrapSDK = (sdk, options) => {
207
+ return _wrapClient(sdk, options?.runName ?? sdk.constructor?.name, {
208
+ client: options?.client,
209
+ });
210
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
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": [
@@ -29,6 +29,10 @@
29
29
  "wrappers.js",
30
30
  "wrappers.d.ts",
31
31
  "wrappers.d.cts",
32
+ "wrappers/openai.cjs",
33
+ "wrappers/openai.js",
34
+ "wrappers/openai.d.ts",
35
+ "wrappers/openai.d.cts",
32
36
  "index.cjs",
33
37
  "index.js",
34
38
  "index.d.ts",
@@ -70,6 +74,13 @@
70
74
  "url": "https://github.com/langchain-ai/langsmith-sdk/issues"
71
75
  },
72
76
  "homepage": "https://github.com/langchain-ai/langsmith-sdk#readme",
77
+ "dependencies": {
78
+ "@types/uuid": "^9.0.1",
79
+ "commander": "^10.0.1",
80
+ "p-queue": "^6.6.2",
81
+ "p-retry": "4",
82
+ "uuid": "^9.0.0"
83
+ },
73
84
  "devDependencies": {
74
85
  "@babel/preset-env": "^7.22.4",
75
86
  "@jest/globals": "^29.5.0",
@@ -88,18 +99,19 @@
88
99
  "eslint-plugin-no-instanceof": "^1.0.1",
89
100
  "eslint-plugin-prettier": "^4.2.1",
90
101
  "jest": "^29.5.0",
91
- "openai": "^4.28.0",
102
+ "openai": "^4.38.5",
92
103
  "prettier": "^2.8.8",
93
104
  "ts-jest": "^29.1.0",
94
105
  "ts-node": "^10.9.1",
95
106
  "typescript": "^5.0.4"
96
107
  },
97
- "dependencies": {
98
- "@types/uuid": "^9.0.1",
99
- "commander": "^10.0.1",
100
- "p-queue": "^6.6.2",
101
- "p-retry": "4",
102
- "uuid": "^9.0.0"
108
+ "peerDependencies": {
109
+ "openai": "*"
110
+ },
111
+ "peerDependenciesMeta": {
112
+ "openai": {
113
+ "optional": true
114
+ }
103
115
  },
104
116
  "lint-staged": {
105
117
  "**/*.{ts,tsx}": [
@@ -171,6 +183,15 @@
171
183
  "import": "./wrappers.js",
172
184
  "require": "./wrappers.cjs"
173
185
  },
186
+ "./wrappers/openai": {
187
+ "types": {
188
+ "import": "./wrappers/openai.d.ts",
189
+ "require": "./wrappers/openai.d.cts",
190
+ "default": "./wrappers/openai.d.ts"
191
+ },
192
+ "import": "./wrappers/openai.js",
193
+ "require": "./wrappers/openai.cjs"
194
+ },
174
195
  "./package.json": "./package.json"
175
196
  }
176
197
  }
@@ -0,0 +1 @@
1
+ module.exports = require('../dist/wrappers/openai.cjs');
@@ -0,0 +1 @@
1
+ export * from '../dist/wrappers/openai.js'
@@ -0,0 +1 @@
1
+ export * from '../dist/wrappers/openai.js'
@@ -0,0 +1 @@
1
+ export * from '../dist/wrappers/openai.js'
package/wrappers.cjs CHANGED
@@ -1 +1 @@
1
- module.exports = require('./dist/wrappers.cjs');
1
+ module.exports = require('./dist/wrappers/index.cjs');
package/wrappers.d.cts CHANGED
@@ -1 +1 @@
1
- export * from './dist/wrappers.js'
1
+ export * from './dist/wrappers/index.js'
package/wrappers.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './dist/wrappers.js'
1
+ export * from './dist/wrappers/index.js'
package/wrappers.js CHANGED
@@ -1 +1 @@
1
- export * from './dist/wrappers.js'
1
+ export * from './dist/wrappers/index.js'
package/dist/wrappers.cjs DELETED
@@ -1,54 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wrapSDK = exports.wrapOpenAI = void 0;
4
- const traceable_js_1 = require("./traceable.cjs");
5
- /**
6
- * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
7
- * tracing. Method signatures are unchanged.
8
- * @param openai An OpenAI client instance.
9
- * @param options LangSmith options.
10
- * @returns
11
- */
12
- const wrapOpenAI = (openai, options) => {
13
- openai.chat.completions.create = (0, traceable_js_1.traceable)(openai.chat.completions.create.bind(openai.chat.completions), Object.assign({ name: "ChatOpenAI", run_type: "llm" }, options?.client));
14
- openai.completions.create = (0, traceable_js_1.traceable)(openai.completions.create.bind(openai.completions), Object.assign({ name: "OpenAI", run_type: "llm" }, options?.client));
15
- return openai;
16
- };
17
- exports.wrapOpenAI = wrapOpenAI;
18
- const _wrapClient = (sdk, runName, options) => {
19
- return new Proxy(sdk, {
20
- get(target, propKey, receiver) {
21
- const originalValue = target[propKey];
22
- if (typeof originalValue === "function") {
23
- return (0, traceable_js_1.traceable)(originalValue.bind(target), Object.assign({ name: [runName, propKey.toString()].join("."), run_type: "llm" }, options?.client));
24
- }
25
- else if (originalValue != null &&
26
- !Array.isArray(originalValue) &&
27
- // eslint-disable-next-line no-instanceof/no-instanceof
28
- !(originalValue instanceof Date) &&
29
- typeof originalValue === "object") {
30
- return _wrapClient(originalValue, [runName, propKey.toString()].join("."), options);
31
- }
32
- else {
33
- return Reflect.get(target, propKey, receiver);
34
- }
35
- },
36
- });
37
- };
38
- /**
39
- * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
40
- * Method signatures are unchanged.
41
- *
42
- * Note that this will wrap and trace ALL SDK methods, not just
43
- * LLM completion methods. If the passed SDK contains other methods,
44
- * we recommend using the wrapped instance for LLM calls only.
45
- * @param sdk An arbitrary SDK instance.
46
- * @param options LangSmith options.
47
- * @returns
48
- */
49
- const wrapSDK = (sdk, options) => {
50
- return _wrapClient(sdk, options?.runName ?? sdk.constructor?.name, {
51
- client: options?.client,
52
- });
53
- };
54
- exports.wrapSDK = wrapSDK;
@@ -1,37 +0,0 @@
1
- import type { Client } from "./index.js";
2
- type OpenAIType = {
3
- chat: {
4
- completions: {
5
- create: (...args: any[]) => any;
6
- };
7
- };
8
- completions: {
9
- create: (...args: any[]) => any;
10
- };
11
- };
12
- /**
13
- * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
14
- * tracing. Method signatures are unchanged.
15
- * @param openai An OpenAI client instance.
16
- * @param options LangSmith options.
17
- * @returns
18
- */
19
- export declare const wrapOpenAI: <T extends OpenAIType>(openai: T, options?: {
20
- client?: Client;
21
- }) => T;
22
- /**
23
- * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
24
- * Method signatures are unchanged.
25
- *
26
- * Note that this will wrap and trace ALL SDK methods, not just
27
- * LLM completion methods. If the passed SDK contains other methods,
28
- * we recommend using the wrapped instance for LLM calls only.
29
- * @param sdk An arbitrary SDK instance.
30
- * @param options LangSmith options.
31
- * @returns
32
- */
33
- export declare const wrapSDK: <T extends object>(sdk: T, options?: {
34
- client?: Client;
35
- runName?: string;
36
- }) => T;
37
- export {};
package/dist/wrappers.js DELETED
@@ -1,49 +0,0 @@
1
- import { traceable } from "./traceable.js";
2
- /**
3
- * Wraps an OpenAI client's completion methods, enabling automatic LangSmith
4
- * tracing. Method signatures are unchanged.
5
- * @param openai An OpenAI client instance.
6
- * @param options LangSmith options.
7
- * @returns
8
- */
9
- export const wrapOpenAI = (openai, options) => {
10
- openai.chat.completions.create = traceable(openai.chat.completions.create.bind(openai.chat.completions), Object.assign({ name: "ChatOpenAI", run_type: "llm" }, options?.client));
11
- openai.completions.create = traceable(openai.completions.create.bind(openai.completions), Object.assign({ name: "OpenAI", run_type: "llm" }, options?.client));
12
- return openai;
13
- };
14
- const _wrapClient = (sdk, runName, options) => {
15
- return new Proxy(sdk, {
16
- get(target, propKey, receiver) {
17
- const originalValue = target[propKey];
18
- if (typeof originalValue === "function") {
19
- return traceable(originalValue.bind(target), Object.assign({ name: [runName, propKey.toString()].join("."), run_type: "llm" }, options?.client));
20
- }
21
- else if (originalValue != null &&
22
- !Array.isArray(originalValue) &&
23
- // eslint-disable-next-line no-instanceof/no-instanceof
24
- !(originalValue instanceof Date) &&
25
- typeof originalValue === "object") {
26
- return _wrapClient(originalValue, [runName, propKey.toString()].join("."), options);
27
- }
28
- else {
29
- return Reflect.get(target, propKey, receiver);
30
- }
31
- },
32
- });
33
- };
34
- /**
35
- * Wrap an arbitrary SDK, enabling automatic LangSmith tracing.
36
- * Method signatures are unchanged.
37
- *
38
- * Note that this will wrap and trace ALL SDK methods, not just
39
- * LLM completion methods. If the passed SDK contains other methods,
40
- * we recommend using the wrapped instance for LLM calls only.
41
- * @param sdk An arbitrary SDK instance.
42
- * @param options LangSmith options.
43
- * @returns
44
- */
45
- export const wrapSDK = (sdk, options) => {
46
- return _wrapClient(sdk, options?.runName ?? sdk.constructor?.name, {
47
- client: options?.client,
48
- });
49
- };