langsmith 0.4.8 → 0.4.10
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/experimental/anthropic/context.cjs +187 -0
- package/dist/experimental/anthropic/context.d.ts +5 -0
- package/dist/experimental/anthropic/context.js +183 -0
- package/dist/experimental/anthropic/index.cjs +82 -863
- package/dist/experimental/anthropic/index.d.ts +1 -1
- package/dist/experimental/anthropic/index.js +83 -864
- package/dist/experimental/anthropic/messages.cjs +102 -0
- package/dist/experimental/anthropic/messages.d.ts +6 -0
- package/dist/experimental/anthropic/messages.js +96 -0
- package/dist/experimental/anthropic/types.cjs +1 -0
- package/dist/experimental/anthropic/types.d.ts +50 -37
- package/dist/experimental/anthropic/types.js +1 -0
- package/dist/experimental/anthropic/usage.cjs +180 -0
- package/dist/experimental/anthropic/usage.d.ts +1 -0
- package/dist/experimental/anthropic/usage.js +175 -0
- package/dist/experimental/anthropic/utils.cjs +14 -0
- package/dist/experimental/anthropic/utils.d.ts +1 -1
- package/dist/experimental/anthropic/utils.js +13 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas.d.ts +1 -0
- package/dist/utils/usage.cjs +6 -7
- package/dist/utils/usage.js +6 -7
- package/experimental/anthropic.cjs +1 -0
- package/experimental/anthropic.d.cts +1 -0
- package/experimental/anthropic.d.ts +1 -0
- package/experimental/anthropic.js +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flattenContentBlocks = flattenContentBlocks;
|
|
4
|
+
exports.convertFromAnthropicMessage = convertFromAnthropicMessage;
|
|
5
|
+
exports.isTaskTool = isTaskTool;
|
|
6
|
+
exports.isToolBlock = isToolBlock;
|
|
7
|
+
const utils_js_1 = require("./utils.cjs");
|
|
8
|
+
/**
|
|
9
|
+
* Converts SDK content blocks into serializable objects.
|
|
10
|
+
* Matches Python's flatten_content_blocks behavior.
|
|
11
|
+
*/
|
|
12
|
+
function flattenContentBlocks(content) {
|
|
13
|
+
if (!Array.isArray(content)) {
|
|
14
|
+
return content;
|
|
15
|
+
}
|
|
16
|
+
return content.map((block) => {
|
|
17
|
+
if (!block || typeof block !== "object" || !("type" in block)) {
|
|
18
|
+
return block;
|
|
19
|
+
}
|
|
20
|
+
const blockType = block.type;
|
|
21
|
+
switch (blockType) {
|
|
22
|
+
case "text":
|
|
23
|
+
return { type: "text", text: block.text || "" };
|
|
24
|
+
case "thinking":
|
|
25
|
+
return {
|
|
26
|
+
type: "thinking",
|
|
27
|
+
thinking: block.thinking || "",
|
|
28
|
+
signature: block.signature || "",
|
|
29
|
+
};
|
|
30
|
+
case "tool_use":
|
|
31
|
+
return {
|
|
32
|
+
type: "tool_use",
|
|
33
|
+
id: block.id,
|
|
34
|
+
name: block.name,
|
|
35
|
+
input: block.input,
|
|
36
|
+
};
|
|
37
|
+
case "tool_result":
|
|
38
|
+
return {
|
|
39
|
+
type: "tool_result",
|
|
40
|
+
tool_use_id: block.tool_use_id,
|
|
41
|
+
content: block.content,
|
|
42
|
+
is_error: block.is_error || false,
|
|
43
|
+
};
|
|
44
|
+
default:
|
|
45
|
+
return block;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Utility function to convert an Anthropic message to a LangSmith-compatible message.
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function convertFromAnthropicMessage(sdkMessage) {
|
|
54
|
+
if (sdkMessage == null)
|
|
55
|
+
return [];
|
|
56
|
+
if (typeof sdkMessage === "string") {
|
|
57
|
+
return [{ content: sdkMessage, role: "user" }];
|
|
58
|
+
}
|
|
59
|
+
if ((0, utils_js_1.isIterable)(sdkMessage)) {
|
|
60
|
+
return Array.from(sdkMessage).flatMap(convertFromAnthropicMessage);
|
|
61
|
+
}
|
|
62
|
+
if (typeof sdkMessage !== "object" ||
|
|
63
|
+
sdkMessage == null ||
|
|
64
|
+
!("message" in sdkMessage) ||
|
|
65
|
+
(sdkMessage.type !== "assistant" && sdkMessage.type !== "user")) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
const { role = sdkMessage.type, content, ...rest } = sdkMessage.message;
|
|
69
|
+
const flattened = flattenContentBlocks(content);
|
|
70
|
+
const toolResultBlocks = role === "user" && Array.isArray(flattened)
|
|
71
|
+
? flattened.filter(isToolResultBlock)
|
|
72
|
+
: [];
|
|
73
|
+
if (toolResultBlocks.length > 0) {
|
|
74
|
+
return toolResultBlocks.map((block) => ({ ...block, role: "tool" }));
|
|
75
|
+
}
|
|
76
|
+
return [{ ...rest, content: flattened, role }];
|
|
77
|
+
}
|
|
78
|
+
function isToolResultBlock(block) {
|
|
79
|
+
if (typeof block !== "object" || block == null)
|
|
80
|
+
return false;
|
|
81
|
+
if (!("type" in block))
|
|
82
|
+
return false;
|
|
83
|
+
return block.type === "tool_result";
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Type assertion to check if a tool is a Task tool
|
|
87
|
+
* @param tool - The tool to check
|
|
88
|
+
* @returns True if the tool is a Task tool, false otherwise
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
function isTaskTool(tool) {
|
|
92
|
+
return tool.type === "tool_use" && tool.name === "Task";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Type-assertion to check for tool blocks
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
function isToolBlock(block) {
|
|
99
|
+
if (!block || typeof block !== "object")
|
|
100
|
+
return false;
|
|
101
|
+
return block.type === "tool_use";
|
|
102
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BetaContentBlock } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Converts SDK content blocks into serializable objects.
|
|
4
|
+
* Matches Python's flatten_content_blocks behavior.
|
|
5
|
+
*/
|
|
6
|
+
export declare function flattenContentBlocks(content: BetaContentBlock[] | unknown): Array<Record<string, unknown>> | unknown;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isIterable } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Converts SDK content blocks into serializable objects.
|
|
4
|
+
* Matches Python's flatten_content_blocks behavior.
|
|
5
|
+
*/
|
|
6
|
+
export function flattenContentBlocks(content) {
|
|
7
|
+
if (!Array.isArray(content)) {
|
|
8
|
+
return content;
|
|
9
|
+
}
|
|
10
|
+
return content.map((block) => {
|
|
11
|
+
if (!block || typeof block !== "object" || !("type" in block)) {
|
|
12
|
+
return block;
|
|
13
|
+
}
|
|
14
|
+
const blockType = block.type;
|
|
15
|
+
switch (blockType) {
|
|
16
|
+
case "text":
|
|
17
|
+
return { type: "text", text: block.text || "" };
|
|
18
|
+
case "thinking":
|
|
19
|
+
return {
|
|
20
|
+
type: "thinking",
|
|
21
|
+
thinking: block.thinking || "",
|
|
22
|
+
signature: block.signature || "",
|
|
23
|
+
};
|
|
24
|
+
case "tool_use":
|
|
25
|
+
return {
|
|
26
|
+
type: "tool_use",
|
|
27
|
+
id: block.id,
|
|
28
|
+
name: block.name,
|
|
29
|
+
input: block.input,
|
|
30
|
+
};
|
|
31
|
+
case "tool_result":
|
|
32
|
+
return {
|
|
33
|
+
type: "tool_result",
|
|
34
|
+
tool_use_id: block.tool_use_id,
|
|
35
|
+
content: block.content,
|
|
36
|
+
is_error: block.is_error || false,
|
|
37
|
+
};
|
|
38
|
+
default:
|
|
39
|
+
return block;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Utility function to convert an Anthropic message to a LangSmith-compatible message.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export function convertFromAnthropicMessage(sdkMessage) {
|
|
48
|
+
if (sdkMessage == null)
|
|
49
|
+
return [];
|
|
50
|
+
if (typeof sdkMessage === "string") {
|
|
51
|
+
return [{ content: sdkMessage, role: "user" }];
|
|
52
|
+
}
|
|
53
|
+
if (isIterable(sdkMessage)) {
|
|
54
|
+
return Array.from(sdkMessage).flatMap(convertFromAnthropicMessage);
|
|
55
|
+
}
|
|
56
|
+
if (typeof sdkMessage !== "object" ||
|
|
57
|
+
sdkMessage == null ||
|
|
58
|
+
!("message" in sdkMessage) ||
|
|
59
|
+
(sdkMessage.type !== "assistant" && sdkMessage.type !== "user")) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const { role = sdkMessage.type, content, ...rest } = sdkMessage.message;
|
|
63
|
+
const flattened = flattenContentBlocks(content);
|
|
64
|
+
const toolResultBlocks = role === "user" && Array.isArray(flattened)
|
|
65
|
+
? flattened.filter(isToolResultBlock)
|
|
66
|
+
: [];
|
|
67
|
+
if (toolResultBlocks.length > 0) {
|
|
68
|
+
return toolResultBlocks.map((block) => ({ ...block, role: "tool" }));
|
|
69
|
+
}
|
|
70
|
+
return [{ ...rest, content: flattened, role }];
|
|
71
|
+
}
|
|
72
|
+
function isToolResultBlock(block) {
|
|
73
|
+
if (typeof block !== "object" || block == null)
|
|
74
|
+
return false;
|
|
75
|
+
if (!("type" in block))
|
|
76
|
+
return false;
|
|
77
|
+
return block.type === "tool_result";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Type assertion to check if a tool is a Task tool
|
|
81
|
+
* @param tool - The tool to check
|
|
82
|
+
* @returns True if the tool is a Task tool, false otherwise
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export function isTaskTool(tool) {
|
|
86
|
+
return tool.type === "tool_use" && tool.name === "Task";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Type-assertion to check for tool blocks
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
export function isToolBlock(block) {
|
|
93
|
+
if (!block || typeof block !== "object")
|
|
94
|
+
return false;
|
|
95
|
+
return block.type === "tool_use";
|
|
96
|
+
}
|
|
@@ -1,37 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
1
|
+
export type SDKAssistantMessage = {
|
|
2
|
+
type: "assistant";
|
|
3
|
+
message: {
|
|
4
|
+
id: string;
|
|
5
|
+
role?: string;
|
|
6
|
+
content: Record<string, any>[];
|
|
7
|
+
usage?: Record<string, any>;
|
|
8
|
+
model?: string;
|
|
9
|
+
};
|
|
10
|
+
parent_tool_use_id: string | null;
|
|
11
|
+
};
|
|
12
|
+
export type SDKSystemMessage = {
|
|
13
|
+
type: "system";
|
|
14
|
+
};
|
|
15
|
+
export type SDKUserMessage = {
|
|
16
|
+
type: "user";
|
|
17
|
+
message: {
|
|
18
|
+
role?: string;
|
|
19
|
+
content: Record<string, any> | Record<string, any>[] | string;
|
|
20
|
+
usage?: Record<string, any>;
|
|
21
|
+
model?: string;
|
|
22
|
+
};
|
|
23
|
+
session_id: string;
|
|
24
|
+
tool_use_result?: unknown;
|
|
25
|
+
parent_tool_use_id: string | null;
|
|
26
|
+
};
|
|
27
|
+
export type SDKResultMessage = {
|
|
28
|
+
type: "result";
|
|
29
|
+
modelUsage: ModelUsage;
|
|
30
|
+
total_cost_usd: number | null;
|
|
31
|
+
is_error: boolean | null;
|
|
32
|
+
num_turns: number | null;
|
|
33
|
+
session_id: string | null;
|
|
34
|
+
duration_ms: number | null;
|
|
35
|
+
duration_api_ms: number | null;
|
|
36
|
+
usage: Record<string, any>;
|
|
37
|
+
};
|
|
38
|
+
export type SDKMessage = SDKAssistantMessage | SDKUserMessage | SDKSystemMessage | SDKResultMessage;
|
|
39
|
+
export type ModelUsage = {
|
|
40
|
+
[key: string]: any;
|
|
41
|
+
};
|
|
42
|
+
export type QueryOptions = {
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
};
|
|
45
|
+
export type BetaContentBlock = {
|
|
46
|
+
[key: string]: any;
|
|
47
|
+
};
|
|
48
|
+
export type BetaToolUseBlock = {
|
|
49
|
+
[key: string]: any;
|
|
50
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.aggregateUsageFromModelUsage = aggregateUsageFromModelUsage;
|
|
4
|
+
exports.extractUsageFromMessage = extractUsageFromMessage;
|
|
5
|
+
exports.correctUsageFromResults = correctUsageFromResults;
|
|
6
|
+
const usage_js_1 = require("../../utils/usage.cjs");
|
|
7
|
+
const utils_js_1 = require("./utils.cjs");
|
|
8
|
+
/**
|
|
9
|
+
* Aggregates usage from modelUsage breakdown (includes all models, including hidden ones).
|
|
10
|
+
* This provides accurate totals when multiple models are used.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
function aggregateUsageFromModelUsage(modelUsage) {
|
|
14
|
+
const metrics = {};
|
|
15
|
+
let totalInputTokens = 0;
|
|
16
|
+
let totalOutputTokens = 0;
|
|
17
|
+
let totalCacheReadTokens = 0;
|
|
18
|
+
let totalCacheCreationTokens = 0;
|
|
19
|
+
// Aggregate across all models
|
|
20
|
+
for (const modelStats of Object.values(modelUsage)) {
|
|
21
|
+
totalInputTokens += modelStats.inputTokens || 0;
|
|
22
|
+
totalOutputTokens += modelStats.outputTokens || 0;
|
|
23
|
+
totalCacheReadTokens += modelStats.cacheReadInputTokens || 0;
|
|
24
|
+
totalCacheCreationTokens += modelStats.cacheCreationInputTokens || 0;
|
|
25
|
+
}
|
|
26
|
+
// Build input_token_details if we have cache tokens
|
|
27
|
+
if (totalCacheReadTokens > 0 || totalCacheCreationTokens > 0) {
|
|
28
|
+
metrics.input_token_details = {
|
|
29
|
+
cache_read: totalCacheReadTokens,
|
|
30
|
+
cache_creation: totalCacheCreationTokens,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Sum all input tokens (new + cache read + cache creation)
|
|
34
|
+
const totalPromptTokens = totalInputTokens + totalCacheReadTokens + totalCacheCreationTokens;
|
|
35
|
+
metrics.input_tokens = totalPromptTokens;
|
|
36
|
+
metrics.output_tokens = totalOutputTokens;
|
|
37
|
+
metrics.total_tokens = totalPromptTokens + totalOutputTokens;
|
|
38
|
+
return metrics;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts and normalizes usage metrics from a Claude Agent SDK message.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function extractUsageFromMessage(message) {
|
|
45
|
+
const metrics = {};
|
|
46
|
+
// Assistant messages contain usage in message.message.usage
|
|
47
|
+
// Result messages contain usage in message.usage
|
|
48
|
+
let usage;
|
|
49
|
+
if (message.type === "assistant") {
|
|
50
|
+
usage = message.message?.usage;
|
|
51
|
+
}
|
|
52
|
+
else if (message.type === "result") {
|
|
53
|
+
usage = message.usage;
|
|
54
|
+
}
|
|
55
|
+
if (!usage || typeof usage !== "object") {
|
|
56
|
+
return metrics;
|
|
57
|
+
}
|
|
58
|
+
// Standard token counts - use LangSmith's expected field names
|
|
59
|
+
const inputTokens = (0, utils_js_1.getNumberProperty)(usage, "input_tokens") || 0;
|
|
60
|
+
const outputTokens = (0, utils_js_1.getNumberProperty)(usage, "output_tokens") || 0;
|
|
61
|
+
// Get cache tokens
|
|
62
|
+
const cacheRead = (0, utils_js_1.getNumberProperty)(usage, "cache_read_input_tokens") || 0;
|
|
63
|
+
const cacheCreation = (0, utils_js_1.getNumberProperty)(usage, "cache_creation_input_tokens") || 0;
|
|
64
|
+
// Build input_token_details if we have cache tokens
|
|
65
|
+
if (cacheRead > 0 || cacheCreation > 0) {
|
|
66
|
+
const inputTokenDetails = (0, usage_js_1.convertAnthropicUsageToInputTokenDetails)(usage);
|
|
67
|
+
if (Object.keys(inputTokenDetails).length > 0) {
|
|
68
|
+
metrics.input_token_details = inputTokenDetails;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Sum cache tokens into input_tokens total (matching Python's sum_anthropic_tokens)
|
|
72
|
+
const totalInputTokens = inputTokens + cacheRead + cacheCreation;
|
|
73
|
+
metrics.input_tokens = totalInputTokens;
|
|
74
|
+
metrics.output_tokens = outputTokens;
|
|
75
|
+
metrics.total_tokens = totalInputTokens + outputTokens;
|
|
76
|
+
return metrics;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Corrects usage metrics for assistant runs based on the results of the runs.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
function correctUsageFromResults(resultUsages, assistantRuns) {
|
|
83
|
+
const runByModel = assistantRuns.reduce((acc, run) => {
|
|
84
|
+
const modelId = run.extra?.metadata?.ls_model_name;
|
|
85
|
+
if (!modelId)
|
|
86
|
+
return acc;
|
|
87
|
+
acc[modelId] ??= [];
|
|
88
|
+
acc[modelId].push(run);
|
|
89
|
+
return acc;
|
|
90
|
+
}, {});
|
|
91
|
+
const runUsageByModel = assistantRuns.reduce((acc, run) => {
|
|
92
|
+
const modelId = run.extra?.metadata?.ls_model_name;
|
|
93
|
+
if (!modelId)
|
|
94
|
+
return acc;
|
|
95
|
+
const usageMetadata = { ...run.extra?.metadata?.usage_metadata };
|
|
96
|
+
usageMetadata.input_tokens ??= 0;
|
|
97
|
+
usageMetadata.output_tokens ??= 0;
|
|
98
|
+
usageMetadata.total_tokens ??= 0;
|
|
99
|
+
usageMetadata.input_token_details = {
|
|
100
|
+
...usageMetadata.input_token_details,
|
|
101
|
+
};
|
|
102
|
+
usageMetadata.input_token_details.cache_read ??= 0;
|
|
103
|
+
usageMetadata.input_token_details.ephemeral_5m_input_tokens ??= 0;
|
|
104
|
+
usageMetadata.input_token_details.ephemeral_1h_input_tokens ??= 0;
|
|
105
|
+
acc[modelId] ??= {
|
|
106
|
+
input_tokens: 0,
|
|
107
|
+
output_tokens: 0,
|
|
108
|
+
total_tokens: 0,
|
|
109
|
+
input_token_details: {
|
|
110
|
+
cache_read: 0,
|
|
111
|
+
cache_creation: 0,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
acc[modelId].input_tokens += usageMetadata.input_tokens;
|
|
115
|
+
acc[modelId].output_tokens += usageMetadata.output_tokens;
|
|
116
|
+
acc[modelId].total_tokens += usageMetadata.total_tokens;
|
|
117
|
+
acc[modelId].input_token_details.cache_read +=
|
|
118
|
+
usageMetadata.input_token_details.cache_read;
|
|
119
|
+
acc[modelId].input_token_details.cache_creation +=
|
|
120
|
+
usageMetadata.input_token_details.ephemeral_5m_input_tokens;
|
|
121
|
+
acc[modelId].input_token_details.cache_creation +=
|
|
122
|
+
usageMetadata.input_token_details.ephemeral_1h_input_tokens;
|
|
123
|
+
return acc;
|
|
124
|
+
}, {});
|
|
125
|
+
const resultUsageMap = Object.fromEntries(Object.entries(resultUsages).map(([modelId, usage]) => [
|
|
126
|
+
modelId,
|
|
127
|
+
{
|
|
128
|
+
input_tokens: usage.inputTokens +
|
|
129
|
+
usage.cacheReadInputTokens +
|
|
130
|
+
usage.cacheCreationInputTokens,
|
|
131
|
+
output_tokens: usage.outputTokens,
|
|
132
|
+
total_tokens: usage.inputTokens +
|
|
133
|
+
usage.cacheReadInputTokens +
|
|
134
|
+
usage.cacheCreationInputTokens +
|
|
135
|
+
usage.outputTokens,
|
|
136
|
+
input_token_details: {
|
|
137
|
+
cache_read: usage.cacheReadInputTokens,
|
|
138
|
+
cache_creation: usage.cacheCreationInputTokens,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
]));
|
|
142
|
+
for (const modelId in resultUsageMap) {
|
|
143
|
+
const lastRun = runByModel[modelId]?.at(-1);
|
|
144
|
+
const runsUsage = runUsageByModel[modelId];
|
|
145
|
+
const resultUsage = resultUsageMap[modelId];
|
|
146
|
+
if (!runsUsage || !lastRun)
|
|
147
|
+
continue;
|
|
148
|
+
const difference = {
|
|
149
|
+
input_tokens: Math.max(0, resultUsage.input_tokens - runsUsage.input_tokens),
|
|
150
|
+
output_tokens: Math.max(0, resultUsage.output_tokens - runsUsage.output_tokens),
|
|
151
|
+
total_tokens: Math.max(0, resultUsage.total_tokens - runsUsage.total_tokens),
|
|
152
|
+
cache_read: Math.max(0, resultUsage.input_token_details.cache_read -
|
|
153
|
+
runsUsage.input_token_details.cache_read),
|
|
154
|
+
cache_creation: Math.max(0, resultUsage.input_token_details.cache_creation -
|
|
155
|
+
runsUsage.input_token_details.cache_creation),
|
|
156
|
+
};
|
|
157
|
+
if (Object.values(difference).some((value) => value > 0)) {
|
|
158
|
+
// apply difference to the last run
|
|
159
|
+
lastRun.extra ??= {};
|
|
160
|
+
lastRun.extra.metadata ??= {};
|
|
161
|
+
lastRun.extra.metadata.usage_metadata ??= {};
|
|
162
|
+
lastRun.extra.metadata.usage_metadata.input_tokens ??= 0;
|
|
163
|
+
lastRun.extra.metadata.usage_metadata.input_tokens +=
|
|
164
|
+
difference.input_tokens;
|
|
165
|
+
lastRun.extra.metadata.usage_metadata.output_tokens ??= 0;
|
|
166
|
+
lastRun.extra.metadata.usage_metadata.output_tokens +=
|
|
167
|
+
difference.output_tokens;
|
|
168
|
+
lastRun.extra.metadata.usage_metadata.total_tokens ??= 0;
|
|
169
|
+
lastRun.extra.metadata.usage_metadata.total_tokens +=
|
|
170
|
+
difference.total_tokens;
|
|
171
|
+
lastRun.extra.metadata.usage_metadata.input_token_details ??= {};
|
|
172
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.cache_read ??= 0;
|
|
173
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.cache_read +=
|
|
174
|
+
difference.cache_read;
|
|
175
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.ephemeral_5m_input_tokens ??= 0;
|
|
176
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.ephemeral_5m_input_tokens +=
|
|
177
|
+
difference.cache_creation;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { convertAnthropicUsageToInputTokenDetails } from "../../utils/usage.js";
|
|
2
|
+
import { getNumberProperty } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Aggregates usage from modelUsage breakdown (includes all models, including hidden ones).
|
|
5
|
+
* This provides accurate totals when multiple models are used.
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export function aggregateUsageFromModelUsage(modelUsage) {
|
|
9
|
+
const metrics = {};
|
|
10
|
+
let totalInputTokens = 0;
|
|
11
|
+
let totalOutputTokens = 0;
|
|
12
|
+
let totalCacheReadTokens = 0;
|
|
13
|
+
let totalCacheCreationTokens = 0;
|
|
14
|
+
// Aggregate across all models
|
|
15
|
+
for (const modelStats of Object.values(modelUsage)) {
|
|
16
|
+
totalInputTokens += modelStats.inputTokens || 0;
|
|
17
|
+
totalOutputTokens += modelStats.outputTokens || 0;
|
|
18
|
+
totalCacheReadTokens += modelStats.cacheReadInputTokens || 0;
|
|
19
|
+
totalCacheCreationTokens += modelStats.cacheCreationInputTokens || 0;
|
|
20
|
+
}
|
|
21
|
+
// Build input_token_details if we have cache tokens
|
|
22
|
+
if (totalCacheReadTokens > 0 || totalCacheCreationTokens > 0) {
|
|
23
|
+
metrics.input_token_details = {
|
|
24
|
+
cache_read: totalCacheReadTokens,
|
|
25
|
+
cache_creation: totalCacheCreationTokens,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
// Sum all input tokens (new + cache read + cache creation)
|
|
29
|
+
const totalPromptTokens = totalInputTokens + totalCacheReadTokens + totalCacheCreationTokens;
|
|
30
|
+
metrics.input_tokens = totalPromptTokens;
|
|
31
|
+
metrics.output_tokens = totalOutputTokens;
|
|
32
|
+
metrics.total_tokens = totalPromptTokens + totalOutputTokens;
|
|
33
|
+
return metrics;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Extracts and normalizes usage metrics from a Claude Agent SDK message.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function extractUsageFromMessage(message) {
|
|
40
|
+
const metrics = {};
|
|
41
|
+
// Assistant messages contain usage in message.message.usage
|
|
42
|
+
// Result messages contain usage in message.usage
|
|
43
|
+
let usage;
|
|
44
|
+
if (message.type === "assistant") {
|
|
45
|
+
usage = message.message?.usage;
|
|
46
|
+
}
|
|
47
|
+
else if (message.type === "result") {
|
|
48
|
+
usage = message.usage;
|
|
49
|
+
}
|
|
50
|
+
if (!usage || typeof usage !== "object") {
|
|
51
|
+
return metrics;
|
|
52
|
+
}
|
|
53
|
+
// Standard token counts - use LangSmith's expected field names
|
|
54
|
+
const inputTokens = getNumberProperty(usage, "input_tokens") || 0;
|
|
55
|
+
const outputTokens = getNumberProperty(usage, "output_tokens") || 0;
|
|
56
|
+
// Get cache tokens
|
|
57
|
+
const cacheRead = getNumberProperty(usage, "cache_read_input_tokens") || 0;
|
|
58
|
+
const cacheCreation = getNumberProperty(usage, "cache_creation_input_tokens") || 0;
|
|
59
|
+
// Build input_token_details if we have cache tokens
|
|
60
|
+
if (cacheRead > 0 || cacheCreation > 0) {
|
|
61
|
+
const inputTokenDetails = convertAnthropicUsageToInputTokenDetails(usage);
|
|
62
|
+
if (Object.keys(inputTokenDetails).length > 0) {
|
|
63
|
+
metrics.input_token_details = inputTokenDetails;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Sum cache tokens into input_tokens total (matching Python's sum_anthropic_tokens)
|
|
67
|
+
const totalInputTokens = inputTokens + cacheRead + cacheCreation;
|
|
68
|
+
metrics.input_tokens = totalInputTokens;
|
|
69
|
+
metrics.output_tokens = outputTokens;
|
|
70
|
+
metrics.total_tokens = totalInputTokens + outputTokens;
|
|
71
|
+
return metrics;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Corrects usage metrics for assistant runs based on the results of the runs.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
export function correctUsageFromResults(resultUsages, assistantRuns) {
|
|
78
|
+
const runByModel = assistantRuns.reduce((acc, run) => {
|
|
79
|
+
const modelId = run.extra?.metadata?.ls_model_name;
|
|
80
|
+
if (!modelId)
|
|
81
|
+
return acc;
|
|
82
|
+
acc[modelId] ??= [];
|
|
83
|
+
acc[modelId].push(run);
|
|
84
|
+
return acc;
|
|
85
|
+
}, {});
|
|
86
|
+
const runUsageByModel = assistantRuns.reduce((acc, run) => {
|
|
87
|
+
const modelId = run.extra?.metadata?.ls_model_name;
|
|
88
|
+
if (!modelId)
|
|
89
|
+
return acc;
|
|
90
|
+
const usageMetadata = { ...run.extra?.metadata?.usage_metadata };
|
|
91
|
+
usageMetadata.input_tokens ??= 0;
|
|
92
|
+
usageMetadata.output_tokens ??= 0;
|
|
93
|
+
usageMetadata.total_tokens ??= 0;
|
|
94
|
+
usageMetadata.input_token_details = {
|
|
95
|
+
...usageMetadata.input_token_details,
|
|
96
|
+
};
|
|
97
|
+
usageMetadata.input_token_details.cache_read ??= 0;
|
|
98
|
+
usageMetadata.input_token_details.ephemeral_5m_input_tokens ??= 0;
|
|
99
|
+
usageMetadata.input_token_details.ephemeral_1h_input_tokens ??= 0;
|
|
100
|
+
acc[modelId] ??= {
|
|
101
|
+
input_tokens: 0,
|
|
102
|
+
output_tokens: 0,
|
|
103
|
+
total_tokens: 0,
|
|
104
|
+
input_token_details: {
|
|
105
|
+
cache_read: 0,
|
|
106
|
+
cache_creation: 0,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
acc[modelId].input_tokens += usageMetadata.input_tokens;
|
|
110
|
+
acc[modelId].output_tokens += usageMetadata.output_tokens;
|
|
111
|
+
acc[modelId].total_tokens += usageMetadata.total_tokens;
|
|
112
|
+
acc[modelId].input_token_details.cache_read +=
|
|
113
|
+
usageMetadata.input_token_details.cache_read;
|
|
114
|
+
acc[modelId].input_token_details.cache_creation +=
|
|
115
|
+
usageMetadata.input_token_details.ephemeral_5m_input_tokens;
|
|
116
|
+
acc[modelId].input_token_details.cache_creation +=
|
|
117
|
+
usageMetadata.input_token_details.ephemeral_1h_input_tokens;
|
|
118
|
+
return acc;
|
|
119
|
+
}, {});
|
|
120
|
+
const resultUsageMap = Object.fromEntries(Object.entries(resultUsages).map(([modelId, usage]) => [
|
|
121
|
+
modelId,
|
|
122
|
+
{
|
|
123
|
+
input_tokens: usage.inputTokens +
|
|
124
|
+
usage.cacheReadInputTokens +
|
|
125
|
+
usage.cacheCreationInputTokens,
|
|
126
|
+
output_tokens: usage.outputTokens,
|
|
127
|
+
total_tokens: usage.inputTokens +
|
|
128
|
+
usage.cacheReadInputTokens +
|
|
129
|
+
usage.cacheCreationInputTokens +
|
|
130
|
+
usage.outputTokens,
|
|
131
|
+
input_token_details: {
|
|
132
|
+
cache_read: usage.cacheReadInputTokens,
|
|
133
|
+
cache_creation: usage.cacheCreationInputTokens,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
]));
|
|
137
|
+
for (const modelId in resultUsageMap) {
|
|
138
|
+
const lastRun = runByModel[modelId]?.at(-1);
|
|
139
|
+
const runsUsage = runUsageByModel[modelId];
|
|
140
|
+
const resultUsage = resultUsageMap[modelId];
|
|
141
|
+
if (!runsUsage || !lastRun)
|
|
142
|
+
continue;
|
|
143
|
+
const difference = {
|
|
144
|
+
input_tokens: Math.max(0, resultUsage.input_tokens - runsUsage.input_tokens),
|
|
145
|
+
output_tokens: Math.max(0, resultUsage.output_tokens - runsUsage.output_tokens),
|
|
146
|
+
total_tokens: Math.max(0, resultUsage.total_tokens - runsUsage.total_tokens),
|
|
147
|
+
cache_read: Math.max(0, resultUsage.input_token_details.cache_read -
|
|
148
|
+
runsUsage.input_token_details.cache_read),
|
|
149
|
+
cache_creation: Math.max(0, resultUsage.input_token_details.cache_creation -
|
|
150
|
+
runsUsage.input_token_details.cache_creation),
|
|
151
|
+
};
|
|
152
|
+
if (Object.values(difference).some((value) => value > 0)) {
|
|
153
|
+
// apply difference to the last run
|
|
154
|
+
lastRun.extra ??= {};
|
|
155
|
+
lastRun.extra.metadata ??= {};
|
|
156
|
+
lastRun.extra.metadata.usage_metadata ??= {};
|
|
157
|
+
lastRun.extra.metadata.usage_metadata.input_tokens ??= 0;
|
|
158
|
+
lastRun.extra.metadata.usage_metadata.input_tokens +=
|
|
159
|
+
difference.input_tokens;
|
|
160
|
+
lastRun.extra.metadata.usage_metadata.output_tokens ??= 0;
|
|
161
|
+
lastRun.extra.metadata.usage_metadata.output_tokens +=
|
|
162
|
+
difference.output_tokens;
|
|
163
|
+
lastRun.extra.metadata.usage_metadata.total_tokens ??= 0;
|
|
164
|
+
lastRun.extra.metadata.usage_metadata.total_tokens +=
|
|
165
|
+
difference.total_tokens;
|
|
166
|
+
lastRun.extra.metadata.usage_metadata.input_token_details ??= {};
|
|
167
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.cache_read ??= 0;
|
|
168
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.cache_read +=
|
|
169
|
+
difference.cache_read;
|
|
170
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.ephemeral_5m_input_tokens ??= 0;
|
|
171
|
+
lastRun.extra.metadata.usage_metadata.input_token_details.ephemeral_5m_input_tokens +=
|
|
172
|
+
difference.cache_creation;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|