langsmith 0.3.82 → 0.3.83-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/wrappers/anthropic.cjs +303 -0
- package/dist/wrappers/anthropic.d.ts +81 -0
- package/dist/wrappers/anthropic.js +299 -0
- package/dist/wrappers/index.cjs +1 -0
- package/dist/wrappers/index.d.ts +1 -0
- package/dist/wrappers/index.js +1 -0
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -13,4 +13,4 @@ var uuid_js_1 = require("./uuid.cjs");
|
|
|
13
13
|
Object.defineProperty(exports, "uuid7", { enumerable: true, get: function () { return uuid_js_1.uuid7; } });
|
|
14
14
|
Object.defineProperty(exports, "uuid7FromTime", { enumerable: true, get: function () { return uuid_js_1.uuid7FromTime; } });
|
|
15
15
|
// Update using yarn bump-version
|
|
16
|
-
exports.__version__ = "0.3.
|
|
16
|
+
exports.__version__ = "0.3.83-rc.1";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ 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
6
|
export { uuid7, uuid7FromTime } from "./uuid.js";
|
|
7
|
-
export declare const __version__ = "0.3.
|
|
7
|
+
export declare const __version__ = "0.3.83-rc.1";
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
|
|
|
4
4
|
export { getDefaultProjectName } from "./utils/project.js";
|
|
5
5
|
export { uuid7, uuid7FromTime } from "./uuid.js";
|
|
6
6
|
// Update using yarn bump-version
|
|
7
|
-
export const __version__ = "0.3.
|
|
7
|
+
export const __version__ = "0.3.83-rc.1";
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapAnthropic = void 0;
|
|
4
|
+
const traceable_js_1 = require("../traceable.cjs");
|
|
5
|
+
const TRACED_INVOCATION_KEYS = ["top_k", "top_p", "stream", "thinking"];
|
|
6
|
+
/**
|
|
7
|
+
* Create usage metadata from Anthropic's token usage format.
|
|
8
|
+
*/
|
|
9
|
+
function createUsageMetadata(anthropicUsage) {
|
|
10
|
+
if (!anthropicUsage) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const inputTokens = anthropicUsage.input_tokens ?? 0;
|
|
14
|
+
const outputTokens = anthropicUsage.output_tokens ?? 0;
|
|
15
|
+
const totalTokens = inputTokens + outputTokens;
|
|
16
|
+
// Anthropic provides cache tokens separately
|
|
17
|
+
const cacheReadTokens = anthropicUsage.cache_read_input_tokens ?? 0;
|
|
18
|
+
const cacheCreationTokens = anthropicUsage.cache_creation_input_tokens ?? 0;
|
|
19
|
+
const inputTokenDetails = {};
|
|
20
|
+
if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
|
|
21
|
+
inputTokenDetails.cache_read = cacheReadTokens + cacheCreationTokens;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
input_tokens: inputTokens,
|
|
25
|
+
output_tokens: outputTokens,
|
|
26
|
+
total_tokens: totalTokens,
|
|
27
|
+
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
28
|
+
input_token_details: inputTokenDetails,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Process Anthropic message outputs
|
|
34
|
+
*/
|
|
35
|
+
function processMessageOutput(outputs) {
|
|
36
|
+
const message = outputs;
|
|
37
|
+
const result = { ...message };
|
|
38
|
+
delete result.type;
|
|
39
|
+
if (message.usage) {
|
|
40
|
+
result.usage_metadata = createUsageMetadata(message.usage);
|
|
41
|
+
delete result.usage;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Accumulate a single content block delta into the content array.
|
|
47
|
+
*/
|
|
48
|
+
function accumulateContentBlockDelta(content, event) {
|
|
49
|
+
const block = content[event.index];
|
|
50
|
+
if (!block)
|
|
51
|
+
return;
|
|
52
|
+
if (block.type === "text" && event.delta.type === "text_delta") {
|
|
53
|
+
block.text += event.delta.text;
|
|
54
|
+
}
|
|
55
|
+
else if (block.type === "tool_use" &&
|
|
56
|
+
event.delta.type === "input_json_delta") {
|
|
57
|
+
// Accumulate JSON input for tool use
|
|
58
|
+
const toolBlock = block;
|
|
59
|
+
toolBlock._partial_json =
|
|
60
|
+
(toolBlock._partial_json ?? "") + event.delta.partial_json;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Aggregate streaming chunks into a complete message response
|
|
65
|
+
*/
|
|
66
|
+
const messageAggregator = (chunks) => {
|
|
67
|
+
if (!chunks || chunks.length === 0) {
|
|
68
|
+
return {
|
|
69
|
+
role: "assistant",
|
|
70
|
+
content: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
let message = {
|
|
74
|
+
role: "assistant",
|
|
75
|
+
content: [],
|
|
76
|
+
model: "",
|
|
77
|
+
stop_reason: null,
|
|
78
|
+
stop_sequence: null,
|
|
79
|
+
};
|
|
80
|
+
// Track usage
|
|
81
|
+
let usage = {
|
|
82
|
+
input_tokens: 0,
|
|
83
|
+
output_tokens: 0,
|
|
84
|
+
};
|
|
85
|
+
for (const chunk of chunks) {
|
|
86
|
+
switch (chunk.type) {
|
|
87
|
+
case "message_start":
|
|
88
|
+
// Initialize message
|
|
89
|
+
message = {
|
|
90
|
+
id: chunk.message.id,
|
|
91
|
+
role: chunk.message.role,
|
|
92
|
+
content: [],
|
|
93
|
+
model: chunk.message.model,
|
|
94
|
+
stop_reason: chunk.message.stop_reason,
|
|
95
|
+
stop_sequence: chunk.message.stop_sequence,
|
|
96
|
+
};
|
|
97
|
+
// Capture initial usage
|
|
98
|
+
if (chunk.message.usage) {
|
|
99
|
+
usage = {
|
|
100
|
+
input_tokens: chunk.message.usage.input_tokens,
|
|
101
|
+
output_tokens: chunk.message.usage.output_tokens,
|
|
102
|
+
cache_read_input_tokens: chunk.message.usage.cache_read_input_tokens,
|
|
103
|
+
cache_creation_input_tokens: chunk.message.usage.cache_creation_input_tokens,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case "content_block_start":
|
|
108
|
+
// Add new content block
|
|
109
|
+
if (message.content) {
|
|
110
|
+
message.content[chunk.index] =
|
|
111
|
+
chunk.content_block;
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case "content_block_delta":
|
|
115
|
+
// Accumulate delta
|
|
116
|
+
if (message.content) {
|
|
117
|
+
accumulateContentBlockDelta(message.content, chunk);
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case "content_block_stop":
|
|
121
|
+
// Finalize content block
|
|
122
|
+
if (message.content) {
|
|
123
|
+
const block = message.content[chunk.index];
|
|
124
|
+
if (block?.type === "tool_use") {
|
|
125
|
+
const toolBlock = block;
|
|
126
|
+
if (toolBlock._partial_json) {
|
|
127
|
+
try {
|
|
128
|
+
toolBlock.input = JSON.parse(toolBlock._partial_json);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Keep partial JSON as-is if parsing fails
|
|
132
|
+
toolBlock.input = toolBlock._partial_json;
|
|
133
|
+
}
|
|
134
|
+
delete toolBlock._partial_json;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
case "message_delta":
|
|
140
|
+
// Update message metadata
|
|
141
|
+
message.stop_reason = chunk.delta.stop_reason;
|
|
142
|
+
message.stop_sequence = chunk.delta.stop_sequence ?? null;
|
|
143
|
+
if (chunk.usage) {
|
|
144
|
+
usage.output_tokens = chunk.usage.output_tokens;
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
case "message_stop":
|
|
148
|
+
// Message complete
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Build final output
|
|
153
|
+
const result = {
|
|
154
|
+
...message,
|
|
155
|
+
};
|
|
156
|
+
delete result.type;
|
|
157
|
+
// Add usage metadata
|
|
158
|
+
result.usage_metadata = createUsageMetadata(usage);
|
|
159
|
+
return result;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Wraps an Anthropic client's completion methods, enabling automatic LangSmith
|
|
163
|
+
* tracing. Method signatures are unchanged, with the exception that you can pass
|
|
164
|
+
* an additional and optional "langsmithExtra" field within the second parameter.
|
|
165
|
+
*
|
|
166
|
+
* @param anthropic An Anthropic client instance.
|
|
167
|
+
* @param options LangSmith options.
|
|
168
|
+
* @returns The wrapped client.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
173
|
+
* import { wrapAnthropic } from "langsmith/wrappers/anthropic";
|
|
174
|
+
*
|
|
175
|
+
* const anthropic = wrapAnthropic(new Anthropic());
|
|
176
|
+
*
|
|
177
|
+
* // Non-streaming
|
|
178
|
+
* const message = await anthropic.messages.create({
|
|
179
|
+
* model: "claude-sonnet-4-20250514",
|
|
180
|
+
* max_tokens: 1024,
|
|
181
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* // Streaming with create()
|
|
185
|
+
* const stream = await anthropic.messages.create({
|
|
186
|
+
* model: "claude-sonnet-4-20250514",
|
|
187
|
+
* max_tokens: 1024,
|
|
188
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
189
|
+
* stream: true,
|
|
190
|
+
* });
|
|
191
|
+
* for await (const event of stream) {
|
|
192
|
+
* // process events
|
|
193
|
+
* }
|
|
194
|
+
*
|
|
195
|
+
* // Streaming with stream()
|
|
196
|
+
* const messageStream = anthropic.messages.stream({
|
|
197
|
+
* model: "claude-sonnet-4-20250514",
|
|
198
|
+
* max_tokens: 1024,
|
|
199
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
200
|
+
* });
|
|
201
|
+
* for await (const event of messageStream) {
|
|
202
|
+
* // process events
|
|
203
|
+
* }
|
|
204
|
+
* const finalMessage = await messageStream.finalMessage();
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
const wrapAnthropic = (anthropic, options) => {
|
|
208
|
+
if ((0, traceable_js_1.isTraceableFunction)(anthropic.messages.create) ||
|
|
209
|
+
(0, traceable_js_1.isTraceableFunction)(anthropic.messages.stream)) {
|
|
210
|
+
throw new Error("This instance of Anthropic client has been already wrapped once.");
|
|
211
|
+
}
|
|
212
|
+
const tracedAnthropicClient = { ...anthropic };
|
|
213
|
+
// Common configuration for messages.create
|
|
214
|
+
const messagesCreateConfig = {
|
|
215
|
+
name: "ChatAnthropic",
|
|
216
|
+
run_type: "llm",
|
|
217
|
+
aggregator: messageAggregator,
|
|
218
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
219
|
+
getInvocationParams: (payload) => {
|
|
220
|
+
if (typeof payload !== "object" || payload == null)
|
|
221
|
+
return undefined;
|
|
222
|
+
const params = payload;
|
|
223
|
+
const ls_stop = (typeof params.stop_sequences === "string"
|
|
224
|
+
? [params.stop_sequences]
|
|
225
|
+
: params.stop_sequences) ?? undefined;
|
|
226
|
+
const ls_invocation_params = {};
|
|
227
|
+
for (const [key, value] of Object.entries(params)) {
|
|
228
|
+
if (TRACED_INVOCATION_KEYS.includes(key)) {
|
|
229
|
+
ls_invocation_params[key] = value;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ls_provider: "anthropic",
|
|
234
|
+
ls_model_type: "chat",
|
|
235
|
+
ls_model_name: params.model,
|
|
236
|
+
ls_max_tokens: params.max_tokens ?? undefined,
|
|
237
|
+
ls_temperature: params.temperature ?? undefined,
|
|
238
|
+
ls_stop,
|
|
239
|
+
ls_invocation_params,
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
processOutputs: processMessageOutput,
|
|
243
|
+
...options,
|
|
244
|
+
};
|
|
245
|
+
// Create a new messages object preserving the prototype
|
|
246
|
+
tracedAnthropicClient.messages = Object.create(Object.getPrototypeOf(anthropic.messages));
|
|
247
|
+
// Copy all own properties
|
|
248
|
+
Object.assign(tracedAnthropicClient.messages, anthropic.messages);
|
|
249
|
+
// Wrap messages.create
|
|
250
|
+
tracedAnthropicClient.messages.create = (0, traceable_js_1.traceable)(anthropic.messages.create.bind(anthropic.messages), messagesCreateConfig);
|
|
251
|
+
// Wrap messages.stream
|
|
252
|
+
const originalStream = anthropic.messages.stream.bind(anthropic.messages);
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
|
+
const streamMethod = function (...args) {
|
|
255
|
+
const stream = originalStream(...args);
|
|
256
|
+
if ("finalMessage" in stream && typeof stream.finalMessage === "function") {
|
|
257
|
+
const originalFinalMessage = stream.finalMessage.bind(stream);
|
|
258
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
+
stream.finalMessage = async (...args) => {
|
|
260
|
+
for await (const _ of stream) {
|
|
261
|
+
// consume chunks
|
|
262
|
+
}
|
|
263
|
+
return originalFinalMessage(...args);
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
return stream;
|
|
267
|
+
};
|
|
268
|
+
tracedAnthropicClient.messages.stream = (0, traceable_js_1.traceable)(streamMethod, {
|
|
269
|
+
name: "ChatAnthropic",
|
|
270
|
+
run_type: "llm",
|
|
271
|
+
aggregator: messageAggregator,
|
|
272
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
273
|
+
getInvocationParams: messagesCreateConfig.getInvocationParams,
|
|
274
|
+
processOutputs: processMessageOutput,
|
|
275
|
+
...options,
|
|
276
|
+
});
|
|
277
|
+
// Wrap beta.messages if it exists
|
|
278
|
+
if (anthropic.beta &&
|
|
279
|
+
anthropic.beta.messages &&
|
|
280
|
+
typeof anthropic.beta.messages.create === "function") {
|
|
281
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
282
|
+
const tracedBeta = { ...anthropic.beta };
|
|
283
|
+
tracedBeta.messages = Object.create(Object.getPrototypeOf(anthropic.beta.messages));
|
|
284
|
+
Object.assign(tracedBeta.messages, anthropic.beta.messages);
|
|
285
|
+
// Wrap beta.messages.create
|
|
286
|
+
tracedBeta.messages.create = (0, traceable_js_1.traceable)(anthropic.beta.messages.create.bind(anthropic.beta.messages), messagesCreateConfig);
|
|
287
|
+
// Wrap beta.messages.stream if it exists
|
|
288
|
+
if (typeof anthropic.beta.messages.stream === "function") {
|
|
289
|
+
tracedBeta.messages.stream = (0, traceable_js_1.traceable)(anthropic.beta.messages.stream.bind(anthropic.beta.messages), {
|
|
290
|
+
name: "ChatAnthropic",
|
|
291
|
+
run_type: "llm",
|
|
292
|
+
aggregator: messageAggregator,
|
|
293
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
294
|
+
getInvocationParams: messagesCreateConfig.getInvocationParams,
|
|
295
|
+
processOutputs: processMessageOutput,
|
|
296
|
+
...options,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
tracedAnthropicClient.beta = tracedBeta;
|
|
300
|
+
}
|
|
301
|
+
return tracedAnthropicClient;
|
|
302
|
+
};
|
|
303
|
+
exports.wrapAnthropic = wrapAnthropic;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import type { Stream } from "@anthropic-ai/sdk/streaming";
|
|
3
|
+
import type { MessageStream } from "@anthropic-ai/sdk/lib/MessageStream";
|
|
4
|
+
import type { RunTreeConfig } from "../index.js";
|
|
5
|
+
type ExtraRunTreeConfig = Pick<Partial<RunTreeConfig>, "name" | "metadata" | "tags">;
|
|
6
|
+
type MessagesNamespace = {
|
|
7
|
+
create: (...args: any[]) => any;
|
|
8
|
+
stream: (...args: any[]) => any;
|
|
9
|
+
};
|
|
10
|
+
type AnthropicType = {
|
|
11
|
+
messages: MessagesNamespace;
|
|
12
|
+
beta?: {
|
|
13
|
+
messages?: MessagesNamespace;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type PatchedAnthropicClient<T extends AnthropicType> = T & {
|
|
17
|
+
messages: T["messages"] & {
|
|
18
|
+
create: {
|
|
19
|
+
(arg: Anthropic.MessageCreateParamsStreaming, arg2?: Anthropic.RequestOptions & {
|
|
20
|
+
langsmithExtra?: ExtraRunTreeConfig;
|
|
21
|
+
}): Promise<Stream<Anthropic.MessageStreamEvent>>;
|
|
22
|
+
} & {
|
|
23
|
+
(arg: Anthropic.MessageCreateParamsNonStreaming, arg2?: Anthropic.RequestOptions & {
|
|
24
|
+
langsmithExtra?: ExtraRunTreeConfig;
|
|
25
|
+
}): Promise<Anthropic.Message>;
|
|
26
|
+
};
|
|
27
|
+
stream: {
|
|
28
|
+
(arg: Anthropic.MessageStreamParams, arg2?: Anthropic.RequestOptions & {
|
|
29
|
+
langsmithExtra?: ExtraRunTreeConfig;
|
|
30
|
+
}): MessageStream;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Wraps an Anthropic client's completion methods, enabling automatic LangSmith
|
|
36
|
+
* tracing. Method signatures are unchanged, with the exception that you can pass
|
|
37
|
+
* an additional and optional "langsmithExtra" field within the second parameter.
|
|
38
|
+
*
|
|
39
|
+
* @param anthropic An Anthropic client instance.
|
|
40
|
+
* @param options LangSmith options.
|
|
41
|
+
* @returns The wrapped client.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
46
|
+
* import { wrapAnthropic } from "langsmith/wrappers/anthropic";
|
|
47
|
+
*
|
|
48
|
+
* const anthropic = wrapAnthropic(new Anthropic());
|
|
49
|
+
*
|
|
50
|
+
* // Non-streaming
|
|
51
|
+
* const message = await anthropic.messages.create({
|
|
52
|
+
* model: "claude-sonnet-4-20250514",
|
|
53
|
+
* max_tokens: 1024,
|
|
54
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Streaming with create()
|
|
58
|
+
* const stream = await anthropic.messages.create({
|
|
59
|
+
* model: "claude-sonnet-4-20250514",
|
|
60
|
+
* max_tokens: 1024,
|
|
61
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
62
|
+
* stream: true,
|
|
63
|
+
* });
|
|
64
|
+
* for await (const event of stream) {
|
|
65
|
+
* // process events
|
|
66
|
+
* }
|
|
67
|
+
*
|
|
68
|
+
* // Streaming with stream()
|
|
69
|
+
* const messageStream = anthropic.messages.stream({
|
|
70
|
+
* model: "claude-sonnet-4-20250514",
|
|
71
|
+
* max_tokens: 1024,
|
|
72
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
73
|
+
* });
|
|
74
|
+
* for await (const event of messageStream) {
|
|
75
|
+
* // process events
|
|
76
|
+
* }
|
|
77
|
+
* const finalMessage = await messageStream.finalMessage();
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare const wrapAnthropic: <T extends AnthropicType>(anthropic: T, options?: Partial<RunTreeConfig>) => PatchedAnthropicClient<T>;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { isTraceableFunction, traceable, } from "../traceable.js";
|
|
2
|
+
const TRACED_INVOCATION_KEYS = ["top_k", "top_p", "stream", "thinking"];
|
|
3
|
+
/**
|
|
4
|
+
* Create usage metadata from Anthropic's token usage format.
|
|
5
|
+
*/
|
|
6
|
+
function createUsageMetadata(anthropicUsage) {
|
|
7
|
+
if (!anthropicUsage) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const inputTokens = anthropicUsage.input_tokens ?? 0;
|
|
11
|
+
const outputTokens = anthropicUsage.output_tokens ?? 0;
|
|
12
|
+
const totalTokens = inputTokens + outputTokens;
|
|
13
|
+
// Anthropic provides cache tokens separately
|
|
14
|
+
const cacheReadTokens = anthropicUsage.cache_read_input_tokens ?? 0;
|
|
15
|
+
const cacheCreationTokens = anthropicUsage.cache_creation_input_tokens ?? 0;
|
|
16
|
+
const inputTokenDetails = {};
|
|
17
|
+
if (cacheReadTokens > 0 || cacheCreationTokens > 0) {
|
|
18
|
+
inputTokenDetails.cache_read = cacheReadTokens + cacheCreationTokens;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
input_tokens: inputTokens,
|
|
22
|
+
output_tokens: outputTokens,
|
|
23
|
+
total_tokens: totalTokens,
|
|
24
|
+
...(Object.keys(inputTokenDetails).length > 0 && {
|
|
25
|
+
input_token_details: inputTokenDetails,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Process Anthropic message outputs
|
|
31
|
+
*/
|
|
32
|
+
function processMessageOutput(outputs) {
|
|
33
|
+
const message = outputs;
|
|
34
|
+
const result = { ...message };
|
|
35
|
+
delete result.type;
|
|
36
|
+
if (message.usage) {
|
|
37
|
+
result.usage_metadata = createUsageMetadata(message.usage);
|
|
38
|
+
delete result.usage;
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Accumulate a single content block delta into the content array.
|
|
44
|
+
*/
|
|
45
|
+
function accumulateContentBlockDelta(content, event) {
|
|
46
|
+
const block = content[event.index];
|
|
47
|
+
if (!block)
|
|
48
|
+
return;
|
|
49
|
+
if (block.type === "text" && event.delta.type === "text_delta") {
|
|
50
|
+
block.text += event.delta.text;
|
|
51
|
+
}
|
|
52
|
+
else if (block.type === "tool_use" &&
|
|
53
|
+
event.delta.type === "input_json_delta") {
|
|
54
|
+
// Accumulate JSON input for tool use
|
|
55
|
+
const toolBlock = block;
|
|
56
|
+
toolBlock._partial_json =
|
|
57
|
+
(toolBlock._partial_json ?? "") + event.delta.partial_json;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Aggregate streaming chunks into a complete message response
|
|
62
|
+
*/
|
|
63
|
+
const messageAggregator = (chunks) => {
|
|
64
|
+
if (!chunks || chunks.length === 0) {
|
|
65
|
+
return {
|
|
66
|
+
role: "assistant",
|
|
67
|
+
content: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
let message = {
|
|
71
|
+
role: "assistant",
|
|
72
|
+
content: [],
|
|
73
|
+
model: "",
|
|
74
|
+
stop_reason: null,
|
|
75
|
+
stop_sequence: null,
|
|
76
|
+
};
|
|
77
|
+
// Track usage
|
|
78
|
+
let usage = {
|
|
79
|
+
input_tokens: 0,
|
|
80
|
+
output_tokens: 0,
|
|
81
|
+
};
|
|
82
|
+
for (const chunk of chunks) {
|
|
83
|
+
switch (chunk.type) {
|
|
84
|
+
case "message_start":
|
|
85
|
+
// Initialize message
|
|
86
|
+
message = {
|
|
87
|
+
id: chunk.message.id,
|
|
88
|
+
role: chunk.message.role,
|
|
89
|
+
content: [],
|
|
90
|
+
model: chunk.message.model,
|
|
91
|
+
stop_reason: chunk.message.stop_reason,
|
|
92
|
+
stop_sequence: chunk.message.stop_sequence,
|
|
93
|
+
};
|
|
94
|
+
// Capture initial usage
|
|
95
|
+
if (chunk.message.usage) {
|
|
96
|
+
usage = {
|
|
97
|
+
input_tokens: chunk.message.usage.input_tokens,
|
|
98
|
+
output_tokens: chunk.message.usage.output_tokens,
|
|
99
|
+
cache_read_input_tokens: chunk.message.usage.cache_read_input_tokens,
|
|
100
|
+
cache_creation_input_tokens: chunk.message.usage.cache_creation_input_tokens,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "content_block_start":
|
|
105
|
+
// Add new content block
|
|
106
|
+
if (message.content) {
|
|
107
|
+
message.content[chunk.index] =
|
|
108
|
+
chunk.content_block;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "content_block_delta":
|
|
112
|
+
// Accumulate delta
|
|
113
|
+
if (message.content) {
|
|
114
|
+
accumulateContentBlockDelta(message.content, chunk);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case "content_block_stop":
|
|
118
|
+
// Finalize content block
|
|
119
|
+
if (message.content) {
|
|
120
|
+
const block = message.content[chunk.index];
|
|
121
|
+
if (block?.type === "tool_use") {
|
|
122
|
+
const toolBlock = block;
|
|
123
|
+
if (toolBlock._partial_json) {
|
|
124
|
+
try {
|
|
125
|
+
toolBlock.input = JSON.parse(toolBlock._partial_json);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Keep partial JSON as-is if parsing fails
|
|
129
|
+
toolBlock.input = toolBlock._partial_json;
|
|
130
|
+
}
|
|
131
|
+
delete toolBlock._partial_json;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case "message_delta":
|
|
137
|
+
// Update message metadata
|
|
138
|
+
message.stop_reason = chunk.delta.stop_reason;
|
|
139
|
+
message.stop_sequence = chunk.delta.stop_sequence ?? null;
|
|
140
|
+
if (chunk.usage) {
|
|
141
|
+
usage.output_tokens = chunk.usage.output_tokens;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
case "message_stop":
|
|
145
|
+
// Message complete
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Build final output
|
|
150
|
+
const result = {
|
|
151
|
+
...message,
|
|
152
|
+
};
|
|
153
|
+
delete result.type;
|
|
154
|
+
// Add usage metadata
|
|
155
|
+
result.usage_metadata = createUsageMetadata(usage);
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Wraps an Anthropic client's completion methods, enabling automatic LangSmith
|
|
160
|
+
* tracing. Method signatures are unchanged, with the exception that you can pass
|
|
161
|
+
* an additional and optional "langsmithExtra" field within the second parameter.
|
|
162
|
+
*
|
|
163
|
+
* @param anthropic An Anthropic client instance.
|
|
164
|
+
* @param options LangSmith options.
|
|
165
|
+
* @returns The wrapped client.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
170
|
+
* import { wrapAnthropic } from "langsmith/wrappers/anthropic";
|
|
171
|
+
*
|
|
172
|
+
* const anthropic = wrapAnthropic(new Anthropic());
|
|
173
|
+
*
|
|
174
|
+
* // Non-streaming
|
|
175
|
+
* const message = await anthropic.messages.create({
|
|
176
|
+
* model: "claude-sonnet-4-20250514",
|
|
177
|
+
* max_tokens: 1024,
|
|
178
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
179
|
+
* });
|
|
180
|
+
*
|
|
181
|
+
* // Streaming with create()
|
|
182
|
+
* const stream = await anthropic.messages.create({
|
|
183
|
+
* model: "claude-sonnet-4-20250514",
|
|
184
|
+
* max_tokens: 1024,
|
|
185
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
186
|
+
* stream: true,
|
|
187
|
+
* });
|
|
188
|
+
* for await (const event of stream) {
|
|
189
|
+
* // process events
|
|
190
|
+
* }
|
|
191
|
+
*
|
|
192
|
+
* // Streaming with stream()
|
|
193
|
+
* const messageStream = anthropic.messages.stream({
|
|
194
|
+
* model: "claude-sonnet-4-20250514",
|
|
195
|
+
* max_tokens: 1024,
|
|
196
|
+
* messages: [{ role: "user", content: "Hello!" }],
|
|
197
|
+
* });
|
|
198
|
+
* for await (const event of messageStream) {
|
|
199
|
+
* // process events
|
|
200
|
+
* }
|
|
201
|
+
* const finalMessage = await messageStream.finalMessage();
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export const wrapAnthropic = (anthropic, options) => {
|
|
205
|
+
if (isTraceableFunction(anthropic.messages.create) ||
|
|
206
|
+
isTraceableFunction(anthropic.messages.stream)) {
|
|
207
|
+
throw new Error("This instance of Anthropic client has been already wrapped once.");
|
|
208
|
+
}
|
|
209
|
+
const tracedAnthropicClient = { ...anthropic };
|
|
210
|
+
// Common configuration for messages.create
|
|
211
|
+
const messagesCreateConfig = {
|
|
212
|
+
name: "ChatAnthropic",
|
|
213
|
+
run_type: "llm",
|
|
214
|
+
aggregator: messageAggregator,
|
|
215
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
216
|
+
getInvocationParams: (payload) => {
|
|
217
|
+
if (typeof payload !== "object" || payload == null)
|
|
218
|
+
return undefined;
|
|
219
|
+
const params = payload;
|
|
220
|
+
const ls_stop = (typeof params.stop_sequences === "string"
|
|
221
|
+
? [params.stop_sequences]
|
|
222
|
+
: params.stop_sequences) ?? undefined;
|
|
223
|
+
const ls_invocation_params = {};
|
|
224
|
+
for (const [key, value] of Object.entries(params)) {
|
|
225
|
+
if (TRACED_INVOCATION_KEYS.includes(key)) {
|
|
226
|
+
ls_invocation_params[key] = value;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
ls_provider: "anthropic",
|
|
231
|
+
ls_model_type: "chat",
|
|
232
|
+
ls_model_name: params.model,
|
|
233
|
+
ls_max_tokens: params.max_tokens ?? undefined,
|
|
234
|
+
ls_temperature: params.temperature ?? undefined,
|
|
235
|
+
ls_stop,
|
|
236
|
+
ls_invocation_params,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
processOutputs: processMessageOutput,
|
|
240
|
+
...options,
|
|
241
|
+
};
|
|
242
|
+
// Create a new messages object preserving the prototype
|
|
243
|
+
tracedAnthropicClient.messages = Object.create(Object.getPrototypeOf(anthropic.messages));
|
|
244
|
+
// Copy all own properties
|
|
245
|
+
Object.assign(tracedAnthropicClient.messages, anthropic.messages);
|
|
246
|
+
// Wrap messages.create
|
|
247
|
+
tracedAnthropicClient.messages.create = traceable(anthropic.messages.create.bind(anthropic.messages), messagesCreateConfig);
|
|
248
|
+
// Wrap messages.stream
|
|
249
|
+
const originalStream = anthropic.messages.stream.bind(anthropic.messages);
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
const streamMethod = function (...args) {
|
|
252
|
+
const stream = originalStream(...args);
|
|
253
|
+
if ("finalMessage" in stream && typeof stream.finalMessage === "function") {
|
|
254
|
+
const originalFinalMessage = stream.finalMessage.bind(stream);
|
|
255
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
256
|
+
stream.finalMessage = async (...args) => {
|
|
257
|
+
for await (const _ of stream) {
|
|
258
|
+
// consume chunks
|
|
259
|
+
}
|
|
260
|
+
return originalFinalMessage(...args);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return stream;
|
|
264
|
+
};
|
|
265
|
+
tracedAnthropicClient.messages.stream = traceable(streamMethod, {
|
|
266
|
+
name: "ChatAnthropic",
|
|
267
|
+
run_type: "llm",
|
|
268
|
+
aggregator: messageAggregator,
|
|
269
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
270
|
+
getInvocationParams: messagesCreateConfig.getInvocationParams,
|
|
271
|
+
processOutputs: processMessageOutput,
|
|
272
|
+
...options,
|
|
273
|
+
});
|
|
274
|
+
// Wrap beta.messages if it exists
|
|
275
|
+
if (anthropic.beta &&
|
|
276
|
+
anthropic.beta.messages &&
|
|
277
|
+
typeof anthropic.beta.messages.create === "function") {
|
|
278
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
279
|
+
const tracedBeta = { ...anthropic.beta };
|
|
280
|
+
tracedBeta.messages = Object.create(Object.getPrototypeOf(anthropic.beta.messages));
|
|
281
|
+
Object.assign(tracedBeta.messages, anthropic.beta.messages);
|
|
282
|
+
// Wrap beta.messages.create
|
|
283
|
+
tracedBeta.messages.create = traceable(anthropic.beta.messages.create.bind(anthropic.beta.messages), messagesCreateConfig);
|
|
284
|
+
// Wrap beta.messages.stream if it exists
|
|
285
|
+
if (typeof anthropic.beta.messages.stream === "function") {
|
|
286
|
+
tracedBeta.messages.stream = traceable(anthropic.beta.messages.stream.bind(anthropic.beta.messages), {
|
|
287
|
+
name: "ChatAnthropic",
|
|
288
|
+
run_type: "llm",
|
|
289
|
+
aggregator: messageAggregator,
|
|
290
|
+
argsConfigPath: [1, "langsmithExtra"],
|
|
291
|
+
getInvocationParams: messagesCreateConfig.getInvocationParams,
|
|
292
|
+
processOutputs: processMessageOutput,
|
|
293
|
+
...options,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
tracedAnthropicClient.beta = tracedBeta;
|
|
297
|
+
}
|
|
298
|
+
return tracedAnthropicClient;
|
|
299
|
+
};
|
package/dist/wrappers/index.cjs
CHANGED
|
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.wrapSDK = void 0;
|
|
18
18
|
__exportStar(require("./openai.cjs"), exports);
|
|
19
|
+
__exportStar(require("./anthropic.cjs"), exports);
|
|
19
20
|
var generic_js_1 = require("./generic.cjs");
|
|
20
21
|
Object.defineProperty(exports, "wrapSDK", { enumerable: true, get: function () { return generic_js_1.wrapSDK; } });
|
package/dist/wrappers/index.d.ts
CHANGED
package/dist/wrappers/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langsmith",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.83-rc.1",
|
|
4
4
|
"description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
|
|
5
5
|
"packageManager": "yarn@1.22.19",
|
|
6
6
|
"files": [
|
|
@@ -150,6 +150,7 @@
|
|
|
150
150
|
"devDependencies": {
|
|
151
151
|
"@ai-sdk/anthropic": "^2.0.17",
|
|
152
152
|
"@ai-sdk/openai": "^2.0.23",
|
|
153
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
153
154
|
"@babel/preset-env": "^7.22.4",
|
|
154
155
|
"@faker-js/faker": "^8.4.1",
|
|
155
156
|
"@jest/globals": "^29.5.0",
|