@mastra/braintrust 1.3.0 → 1.3.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/CHANGELOG.md +16 -0
- package/dist/index.cjs +550 -634
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +548 -631
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts.map +1 -1
- package/package.json +9 -8
package/dist/index.cjs
CHANGED
|
@@ -1,652 +1,568 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _mastra_core_observability = require("@mastra/core/observability");
|
|
3
|
+
let _mastra_core_utils = require("@mastra/core/utils");
|
|
4
|
+
let _mastra_observability = require("@mastra/observability");
|
|
5
|
+
let braintrust = require("braintrust");
|
|
6
|
+
//#region src/formatter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Message format conversion utilities for Braintrust.
|
|
9
|
+
*
|
|
10
|
+
* Converts AI SDK message format (v4/v5) to OpenAI Chat Completion format,
|
|
11
|
+
* which Braintrust requires for proper rendering of threads.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Remove null and undefined values from an object (shallow)
|
|
15
|
+
*/
|
|
11
16
|
function removeNullish(obj) {
|
|
12
|
-
|
|
17
|
+
return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null));
|
|
13
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Converts a content part to a string representation.
|
|
21
|
+
* Handles text, image, file, reasoning, and other content types.
|
|
22
|
+
*/
|
|
14
23
|
function convertContentPart(part) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return null;
|
|
39
|
-
case "tool-result":
|
|
40
|
-
return null;
|
|
41
|
-
default: {
|
|
42
|
-
const unknownPart = part;
|
|
43
|
-
if (typeof unknownPart.text === "string") {
|
|
44
|
-
return unknownPart.text;
|
|
45
|
-
}
|
|
46
|
-
if (typeof unknownPart.content === "string") {
|
|
47
|
-
return unknownPart.content;
|
|
48
|
-
}
|
|
49
|
-
return `[${unknownPart.type || "unknown"}]`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
24
|
+
if (!part || typeof part !== "object") return null;
|
|
25
|
+
switch (part.type) {
|
|
26
|
+
case "text": return part.text || null;
|
|
27
|
+
case "image": return "[image]";
|
|
28
|
+
case "file": {
|
|
29
|
+
const filePart = part;
|
|
30
|
+
if (filePart.filename || filePart.name) return `[file: ${filePart.filename || filePart.name}]`;
|
|
31
|
+
return "[file]";
|
|
32
|
+
}
|
|
33
|
+
case "reasoning": {
|
|
34
|
+
const reasoningPart = part;
|
|
35
|
+
if (typeof reasoningPart.text === "string" && reasoningPart.text.length > 0) return `[reasoning: ${reasoningPart.text.substring(0, 100)}${reasoningPart.text.length > 100 ? "..." : ""}]`;
|
|
36
|
+
return "[reasoning]";
|
|
37
|
+
}
|
|
38
|
+
case "tool-call": return null;
|
|
39
|
+
case "tool-result": return null;
|
|
40
|
+
default: {
|
|
41
|
+
const unknownPart = part;
|
|
42
|
+
if (typeof unknownPart.text === "string") return unknownPart.text;
|
|
43
|
+
if (typeof unknownPart.content === "string") return unknownPart.content;
|
|
44
|
+
return `[${unknownPart.type || "unknown"}]`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
52
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Serializes tool result data to a string for OpenAI format.
|
|
50
|
+
*/
|
|
53
51
|
function serializeToolResult(resultData) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return JSON.stringify(resultData);
|
|
66
|
-
} catch {
|
|
67
|
-
return "[unserializable result]";
|
|
68
|
-
}
|
|
52
|
+
if (typeof resultData === "string") return resultData;
|
|
53
|
+
if (resultData && typeof resultData === "object" && "value" in resultData) {
|
|
54
|
+
const valueData = resultData.value;
|
|
55
|
+
return typeof valueData === "string" ? valueData : JSON.stringify(valueData);
|
|
56
|
+
}
|
|
57
|
+
if (resultData === void 0 || resultData === null) return "";
|
|
58
|
+
try {
|
|
59
|
+
return JSON.stringify(resultData);
|
|
60
|
+
} catch {
|
|
61
|
+
return "[unserializable result]";
|
|
62
|
+
}
|
|
69
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Converts AI SDK message format to OpenAI Chat Completion format for Braintrust.
|
|
66
|
+
*
|
|
67
|
+
* Supports both AI SDK v4 and v5 formats:
|
|
68
|
+
* - v4 uses 'args' for tool calls and 'result' for tool results
|
|
69
|
+
* - v5 uses 'input' for tool calls and 'output' for tool results
|
|
70
|
+
*
|
|
71
|
+
* AI SDK format:
|
|
72
|
+
* { role: "user", content: [{ type: "text", text: "hello" }] }
|
|
73
|
+
* { role: "assistant", content: [{ type: "text", text: "..." }, { type: "tool-call", toolCallId: "...", toolName: "...", args: {...} }] }
|
|
74
|
+
* { role: "tool", content: [{ type: "tool-result", toolCallId: "...", result: {...} }] }
|
|
75
|
+
*
|
|
76
|
+
* OpenAI format (what Braintrust expects):
|
|
77
|
+
* { role: "user", content: "hello" }
|
|
78
|
+
* { role: "assistant", content: "...", tool_calls: [{ id: "...", type: "function", function: { name: "...", arguments: "..." } }] }
|
|
79
|
+
* { role: "tool", content: "result", tool_call_id: "..." }
|
|
80
|
+
*/
|
|
70
81
|
function convertAISDKMessage(message) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (toolResult) {
|
|
127
|
-
const resultData = toolResult.output ?? toolResult.result;
|
|
128
|
-
const resultContent = serializeToolResult(resultData);
|
|
129
|
-
return {
|
|
130
|
-
role: "tool",
|
|
131
|
-
content: resultContent,
|
|
132
|
-
tool_call_id: toolResult.toolCallId
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return message;
|
|
82
|
+
if (!message || typeof message !== "object") return message;
|
|
83
|
+
const { role, content, ...rest } = message;
|
|
84
|
+
if (typeof content === "string") return message;
|
|
85
|
+
if (Array.isArray(content)) {
|
|
86
|
+
if (content.length === 0) return {
|
|
87
|
+
role,
|
|
88
|
+
content: "",
|
|
89
|
+
...rest
|
|
90
|
+
};
|
|
91
|
+
if (role === "user" || role === "system") {
|
|
92
|
+
const contentParts = content.map((part) => convertContentPart(part)).filter(Boolean);
|
|
93
|
+
return {
|
|
94
|
+
role,
|
|
95
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
96
|
+
...rest
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (role === "assistant") {
|
|
100
|
+
const contentParts = content.filter((part) => part?.type !== "tool-call").map((part) => convertContentPart(part)).filter(Boolean);
|
|
101
|
+
const toolCallParts = content.filter((part) => part?.type === "tool-call");
|
|
102
|
+
const result = {
|
|
103
|
+
role,
|
|
104
|
+
content: contentParts.length > 0 ? contentParts.join("\n") : "",
|
|
105
|
+
...rest
|
|
106
|
+
};
|
|
107
|
+
if (toolCallParts.length > 0) result.tool_calls = toolCallParts.map((tc) => {
|
|
108
|
+
const toolCall = tc;
|
|
109
|
+
const toolCallId = toolCall.toolCallId;
|
|
110
|
+
const toolName = toolCall.toolName;
|
|
111
|
+
const args = toolCall.args ?? toolCall.input;
|
|
112
|
+
let argsString;
|
|
113
|
+
if (typeof args === "string") argsString = args;
|
|
114
|
+
else if (args !== void 0 && args !== null) argsString = JSON.stringify(args);
|
|
115
|
+
else argsString = "{}";
|
|
116
|
+
return {
|
|
117
|
+
id: toolCallId,
|
|
118
|
+
type: "function",
|
|
119
|
+
function: {
|
|
120
|
+
name: toolName,
|
|
121
|
+
arguments: argsString
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
if (role === "tool") {
|
|
128
|
+
const toolResult = content.find((part) => part?.type === "tool-result");
|
|
129
|
+
if (toolResult) return {
|
|
130
|
+
role: "tool",
|
|
131
|
+
content: serializeToolResult(toolResult.output ?? toolResult.result),
|
|
132
|
+
tool_call_id: toolResult.toolCallId
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return message;
|
|
138
137
|
}
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/metrics.ts
|
|
140
|
+
/**
|
|
141
|
+
* Formats UsageStats to Braintrust's expected metric format.
|
|
142
|
+
*/
|
|
141
143
|
function formatUsageMetrics(usage) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;
|
|
151
|
-
}
|
|
152
|
-
if (usage?.outputDetails?.reasoning !== void 0) {
|
|
153
|
-
metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;
|
|
154
|
-
}
|
|
155
|
-
if (usage?.inputDetails?.cacheRead !== void 0) {
|
|
156
|
-
metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;
|
|
157
|
-
}
|
|
158
|
-
if (usage?.inputDetails?.cacheWrite !== void 0) {
|
|
159
|
-
metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;
|
|
160
|
-
}
|
|
161
|
-
return metrics;
|
|
144
|
+
const metrics = {};
|
|
145
|
+
if (usage?.inputTokens !== void 0) metrics.prompt_tokens = usage.inputTokens;
|
|
146
|
+
if (usage?.outputTokens !== void 0) metrics.completion_tokens = usage.outputTokens;
|
|
147
|
+
if (metrics.prompt_tokens !== void 0 && metrics.completion_tokens !== void 0) metrics.tokens = metrics.prompt_tokens + metrics.completion_tokens;
|
|
148
|
+
if (usage?.outputDetails?.reasoning !== void 0) metrics.completion_reasoning_tokens = usage.outputDetails.reasoning;
|
|
149
|
+
if (usage?.inputDetails?.cacheRead !== void 0) metrics.prompt_cached_tokens = usage.inputDetails.cacheRead;
|
|
150
|
+
if (usage?.inputDetails?.cacheWrite !== void 0) metrics.prompt_cache_creation_tokens = usage.inputDetails.cacheWrite;
|
|
151
|
+
return metrics;
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/thread-reconstruction.ts
|
|
155
|
+
/**
|
|
156
|
+
* Thread view reconstruction for Braintrust.
|
|
157
|
+
*
|
|
158
|
+
* Reconstructs LLM output in OpenAI Chat Completion format by examining
|
|
159
|
+
* child MODEL_STEP and TOOL_CALL spans. This enables Braintrust's Thread
|
|
160
|
+
* view to properly display the full conversation flow including tool calls.
|
|
161
|
+
*
|
|
162
|
+
* See THREAD_VIEW_RECONSTRUCTION.md for details.
|
|
163
|
+
*/
|
|
164
|
+
/**
|
|
165
|
+
* Reconstruct the Thread view output from accumulated threadData.
|
|
166
|
+
* Converts to OpenAI Chat Completion message format.
|
|
167
|
+
*/
|
|
165
168
|
function reconstructThreadOutput(threadData, originalOutput) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (originalText && lastMessage.role === "tool") {
|
|
209
|
-
messages.push({
|
|
210
|
-
role: "assistant",
|
|
211
|
-
content: originalText
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return messages;
|
|
169
|
+
const messages = [];
|
|
170
|
+
const sortedSteps = [...threadData].sort((a, b) => a.stepIndex - b.stepIndex);
|
|
171
|
+
for (const step of sortedSteps) {
|
|
172
|
+
const sortedToolCalls = step.toolCalls ? [...step.toolCalls].sort((a, b) => {
|
|
173
|
+
if (!a.startTime || !b.startTime) return 0;
|
|
174
|
+
return a.startTime.getTime() - b.startTime.getTime();
|
|
175
|
+
}) : [];
|
|
176
|
+
if (sortedToolCalls.length > 0) {
|
|
177
|
+
messages.push({
|
|
178
|
+
role: "assistant",
|
|
179
|
+
content: step.text || "",
|
|
180
|
+
tool_calls: sortedToolCalls.map((tc) => {
|
|
181
|
+
const cleanArgs = tc.args && typeof tc.args === "object" && !Array.isArray(tc.args) ? removeNullish(tc.args) : tc.args;
|
|
182
|
+
return {
|
|
183
|
+
id: tc.toolCallId,
|
|
184
|
+
type: "function",
|
|
185
|
+
function: {
|
|
186
|
+
name: tc.toolName,
|
|
187
|
+
arguments: typeof cleanArgs === "string" ? cleanArgs : JSON.stringify(cleanArgs)
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
for (const tc of sortedToolCalls) if (tc.result !== void 0) messages.push({
|
|
193
|
+
role: "tool",
|
|
194
|
+
content: typeof tc.result === "string" ? tc.result : JSON.stringify(tc.result),
|
|
195
|
+
tool_call_id: tc.toolCallId
|
|
196
|
+
});
|
|
197
|
+
} else if (step.text) messages.push({
|
|
198
|
+
role: "assistant",
|
|
199
|
+
content: step.text
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (messages.length > 0) {
|
|
203
|
+
const lastMessage = messages[messages.length - 1];
|
|
204
|
+
const originalText = originalOutput?.text;
|
|
205
|
+
if (originalText && lastMessage.role === "tool") messages.push({
|
|
206
|
+
role: "assistant",
|
|
207
|
+
content: originalText
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return messages;
|
|
216
211
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/tracing.ts
|
|
214
|
+
const DEFAULT_SPAN_TYPE = "task";
|
|
215
|
+
const SPAN_TYPE_EXCEPTIONS = {
|
|
216
|
+
[_mastra_core_observability.SpanType.MODEL_GENERATION]: "llm",
|
|
217
|
+
[_mastra_core_observability.SpanType.TOOL_CALL]: "tool",
|
|
218
|
+
[_mastra_core_observability.SpanType.MCP_TOOL_CALL]: "tool",
|
|
219
|
+
[_mastra_core_observability.SpanType.PROVIDER_TOOL_CALL]: "tool",
|
|
220
|
+
[_mastra_core_observability.SpanType.WORKFLOW_CONDITIONAL_EVAL]: "function",
|
|
221
|
+
[_mastra_core_observability.SpanType.WORKFLOW_WAIT_EVENT]: "function"
|
|
227
222
|
};
|
|
228
223
|
function mapSpanType(spanType) {
|
|
229
|
-
|
|
224
|
+
return SPAN_TYPE_EXCEPTIONS[spanType] ?? DEFAULT_SPAN_TYPE;
|
|
230
225
|
}
|
|
231
|
-
var BraintrustExporter = class extends
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
]
|
|
572
|
-
}
|
|
573
|
-
];
|
|
574
|
-
}
|
|
575
|
-
toToolCallOutput(span) {
|
|
576
|
-
return {
|
|
577
|
-
role: "tool",
|
|
578
|
-
content: serializeToolResult(span.output),
|
|
579
|
-
tool_call_id: this.getToolCallId(span)
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
buildSpanPayload(span, isCreate = true) {
|
|
583
|
-
const payload = {};
|
|
584
|
-
const isToolType = this.isToolSpan(span);
|
|
585
|
-
if (span.input !== void 0) {
|
|
586
|
-
payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);
|
|
587
|
-
}
|
|
588
|
-
if (span.output !== void 0) {
|
|
589
|
-
payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);
|
|
590
|
-
}
|
|
591
|
-
if (isCreate && span.isRootSpan && span.tags?.length) {
|
|
592
|
-
payload.tags = span.tags;
|
|
593
|
-
}
|
|
594
|
-
payload.metrics = {};
|
|
595
|
-
payload.metadata = {
|
|
596
|
-
...span.metadata,
|
|
597
|
-
spanType: span.type
|
|
598
|
-
};
|
|
599
|
-
if (isCreate) {
|
|
600
|
-
payload.metadata["mastra-trace-id"] = span.traceId;
|
|
601
|
-
}
|
|
602
|
-
const attributes = span.attributes ?? {};
|
|
603
|
-
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
604
|
-
const modelAttr = attributes;
|
|
605
|
-
if (modelAttr.model !== void 0) {
|
|
606
|
-
payload.metadata.model = modelAttr.model;
|
|
607
|
-
}
|
|
608
|
-
if (modelAttr.provider !== void 0) {
|
|
609
|
-
payload.metadata.provider = modelAttr.provider;
|
|
610
|
-
}
|
|
611
|
-
if (modelAttr.responseModel !== void 0) {
|
|
612
|
-
payload.metadata.model = modelAttr.responseModel;
|
|
613
|
-
}
|
|
614
|
-
payload.metrics = formatUsageMetrics(modelAttr.usage);
|
|
615
|
-
if (modelAttr.completionStartTime) {
|
|
616
|
-
payload.metrics.time_to_first_token = (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1e3;
|
|
617
|
-
}
|
|
618
|
-
if (modelAttr.parameters !== void 0) {
|
|
619
|
-
payload.metadata.modelParameters = modelAttr.parameters;
|
|
620
|
-
}
|
|
621
|
-
const otherAttributes = utils.omitKeys(attributes, [
|
|
622
|
-
"model",
|
|
623
|
-
"responseModel",
|
|
624
|
-
"usage",
|
|
625
|
-
"parameters",
|
|
626
|
-
"completionStartTime"
|
|
627
|
-
]);
|
|
628
|
-
payload.metadata = {
|
|
629
|
-
...payload.metadata,
|
|
630
|
-
...otherAttributes
|
|
631
|
-
};
|
|
632
|
-
} else {
|
|
633
|
-
payload.metadata = {
|
|
634
|
-
...payload.metadata,
|
|
635
|
-
...attributes
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
if (span.errorInfo) {
|
|
639
|
-
payload.error = span.errorInfo.message;
|
|
640
|
-
payload.metadata.errorDetails = span.errorInfo;
|
|
641
|
-
}
|
|
642
|
-
if (Object.keys(payload.metrics).length === 0) {
|
|
643
|
-
delete payload.metrics;
|
|
644
|
-
}
|
|
645
|
-
payload.metadata = removeNullish(payload.metadata);
|
|
646
|
-
return payload;
|
|
647
|
-
}
|
|
226
|
+
var BraintrustExporter = class extends _mastra_observability.TrackingExporter {
|
|
227
|
+
name = "braintrust";
|
|
228
|
+
#useProvidedLogger;
|
|
229
|
+
#providedLogger;
|
|
230
|
+
#localLogger;
|
|
231
|
+
constructor(config = {}) {
|
|
232
|
+
const resolvedApiKey = config.apiKey ?? process.env.BRAINTRUST_API_KEY;
|
|
233
|
+
const resolvedEndpoint = config.endpoint ?? process.env.BRAINTRUST_ENDPOINT;
|
|
234
|
+
super({
|
|
235
|
+
...config,
|
|
236
|
+
apiKey: resolvedApiKey,
|
|
237
|
+
endpoint: resolvedEndpoint
|
|
238
|
+
});
|
|
239
|
+
this.#useProvidedLogger = !!config.braintrustLogger;
|
|
240
|
+
if (this.#useProvidedLogger) this.#providedLogger = config.braintrustLogger;
|
|
241
|
+
else {
|
|
242
|
+
if (!this.config.apiKey) {
|
|
243
|
+
this.setDisabled(`Missing required API key. Set BRAINTRUST_API_KEY environment variable or pass apiKey in config.`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.#localLogger = void 0;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async getLocalLogger() {
|
|
250
|
+
if (this.#localLogger) return this.#localLogger;
|
|
251
|
+
try {
|
|
252
|
+
const logger = await (0, braintrust.initLogger)({
|
|
253
|
+
projectName: this.config.projectName ?? "mastra-tracing",
|
|
254
|
+
apiKey: this.config.apiKey,
|
|
255
|
+
appUrl: this.config.endpoint,
|
|
256
|
+
...this.config.tuningParameters
|
|
257
|
+
});
|
|
258
|
+
this.#localLogger = logger;
|
|
259
|
+
return logger;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
this.logger.error("Braintrust exporter: Failed to initialize logger", { error: err });
|
|
262
|
+
this.setDisabled("Failed to initialize Braintrust logger");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async onScoreEvent(event) {
|
|
266
|
+
if (this.isDisabled) return;
|
|
267
|
+
const { score } = event;
|
|
268
|
+
const rowId = score.spanId ?? score.traceId;
|
|
269
|
+
if (!rowId) {
|
|
270
|
+
this.logger.debug("Braintrust exporter: skipping score with no spanId or traceId", { scorerId: score.scorerId });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const logger = this.#useProvidedLogger ? this.#providedLogger : await this.getLocalLogger();
|
|
274
|
+
if (!logger) return;
|
|
275
|
+
const name = score.scorerName ?? score.scorerId;
|
|
276
|
+
try {
|
|
277
|
+
logger.logFeedback({
|
|
278
|
+
id: rowId,
|
|
279
|
+
scores: { [name]: score.score },
|
|
280
|
+
...score.reason ? { comment: score.reason } : {},
|
|
281
|
+
metadata: {
|
|
282
|
+
scorerId: score.scorerId,
|
|
283
|
+
...score.scoreSource ? { scoreSource: score.scoreSource } : {},
|
|
284
|
+
...score.metadata ?? {}
|
|
285
|
+
},
|
|
286
|
+
source: "external"
|
|
287
|
+
});
|
|
288
|
+
} catch (err) {
|
|
289
|
+
this.logger.error("Braintrust exporter: Failed to submit score", {
|
|
290
|
+
error: err,
|
|
291
|
+
traceId: score.traceId,
|
|
292
|
+
spanId: score.spanId,
|
|
293
|
+
scorerId: score.scorerId
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
startSpan(args) {
|
|
298
|
+
const { parent, span } = args;
|
|
299
|
+
const payload = this.buildSpanPayload(span);
|
|
300
|
+
const braintrustSpan = parent.startSpan({
|
|
301
|
+
spanId: span.id,
|
|
302
|
+
name: span.name,
|
|
303
|
+
type: mapSpanType(span.type),
|
|
304
|
+
startTime: span.startTime.getTime() / 1e3,
|
|
305
|
+
event: {
|
|
306
|
+
id: span.id,
|
|
307
|
+
...payload
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
const isModelGeneration = span.type === _mastra_core_observability.SpanType.MODEL_GENERATION;
|
|
311
|
+
return {
|
|
312
|
+
span: braintrustSpan,
|
|
313
|
+
spanType: span.type,
|
|
314
|
+
threadData: isModelGeneration ? [] : void 0,
|
|
315
|
+
pendingToolResults: isModelGeneration ? /* @__PURE__ */ new Map() : void 0
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
async _buildRoot(_args) {
|
|
319
|
+
if (this.#useProvidedLogger) {
|
|
320
|
+
let externalSpan;
|
|
321
|
+
try {
|
|
322
|
+
externalSpan = this.config.currentSpan?.();
|
|
323
|
+
} catch (err) {
|
|
324
|
+
this.logger.error("Braintrust exporter: Failed to resolve configured currentSpan", { error: err });
|
|
325
|
+
}
|
|
326
|
+
externalSpan ??= (0, braintrust.currentSpan)();
|
|
327
|
+
if (externalSpan && externalSpan.id) return externalSpan;
|
|
328
|
+
else return this.#providedLogger;
|
|
329
|
+
} else return this.getLocalLogger();
|
|
330
|
+
}
|
|
331
|
+
async _buildSpan(args) {
|
|
332
|
+
const { span, traceData } = args;
|
|
333
|
+
if (span.isRootSpan) {
|
|
334
|
+
const root = traceData.getRoot();
|
|
335
|
+
if (root) return this.startSpan({
|
|
336
|
+
parent: root,
|
|
337
|
+
span
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
const parent = traceData.getParent(args);
|
|
341
|
+
if (parent) {
|
|
342
|
+
const parentSpan = "span" in parent ? parent.span : parent;
|
|
343
|
+
return this.startSpan({
|
|
344
|
+
parent: parentSpan,
|
|
345
|
+
span
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async _buildEvent(args) {
|
|
351
|
+
const spanData = await this._buildSpan(args);
|
|
352
|
+
if (!spanData) return;
|
|
353
|
+
spanData.span.end({ endTime: args.span.startTime.getTime() / 1e3 });
|
|
354
|
+
return spanData.span;
|
|
355
|
+
}
|
|
356
|
+
async _updateSpan(args) {
|
|
357
|
+
const { span, traceData } = args;
|
|
358
|
+
const spanData = traceData.getSpan({ spanId: span.id });
|
|
359
|
+
if (!spanData) return;
|
|
360
|
+
spanData.span.log(this.buildSpanPayload(span, false));
|
|
361
|
+
}
|
|
362
|
+
async _finishSpan(args) {
|
|
363
|
+
const { span, traceData } = args;
|
|
364
|
+
const spanData = traceData.getSpan({ spanId: span.id });
|
|
365
|
+
if (!spanData) return;
|
|
366
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_STEP) this.accumulateModelStepData(span, traceData);
|
|
367
|
+
else if (span.type === _mastra_core_observability.SpanType.TOOL_CALL) this.accumulateToolCallResult(span, traceData);
|
|
368
|
+
const payload = span.type === _mastra_core_observability.SpanType.MODEL_GENERATION ? this.buildModelGenerationPayload(span, spanData) : this.buildSpanPayload(span, false);
|
|
369
|
+
spanData.span.log(payload);
|
|
370
|
+
if (span.endTime) spanData.span.end({ endTime: span.endTime.getTime() / 1e3 });
|
|
371
|
+
else spanData.span.end();
|
|
372
|
+
}
|
|
373
|
+
async _abortSpan(args) {
|
|
374
|
+
const { span: spanData, reason } = args;
|
|
375
|
+
spanData.span.log({
|
|
376
|
+
error: reason.message,
|
|
377
|
+
metadata: { errorDetails: reason }
|
|
378
|
+
});
|
|
379
|
+
spanData.span.end();
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Walk up the tree to find the MODEL_GENERATION ancestor span.
|
|
383
|
+
* Returns the BraintrustSpanData if found, undefined otherwise.
|
|
384
|
+
*/
|
|
385
|
+
findModelGenerationAncestor(spanId, traceData) {
|
|
386
|
+
let currentId = spanId;
|
|
387
|
+
while (currentId) {
|
|
388
|
+
const parentId = traceData.getParentId({ spanId: currentId });
|
|
389
|
+
if (!parentId) return void 0;
|
|
390
|
+
const parentSpanData = traceData.getSpan({ spanId: parentId });
|
|
391
|
+
if (parentSpanData?.spanType === _mastra_core_observability.SpanType.MODEL_GENERATION) return parentSpanData;
|
|
392
|
+
currentId = parentId;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Accumulate MODEL_STEP data to the parent MODEL_GENERATION's threadData.
|
|
397
|
+
* Called when a MODEL_STEP span ends.
|
|
398
|
+
*/
|
|
399
|
+
accumulateModelStepData(span, traceData) {
|
|
400
|
+
const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);
|
|
401
|
+
if (!modelGenSpanData?.threadData) return;
|
|
402
|
+
const output = span.output;
|
|
403
|
+
const attributes = span.attributes;
|
|
404
|
+
const stepData = {
|
|
405
|
+
stepSpanId: span.id,
|
|
406
|
+
stepIndex: attributes?.stepIndex ?? 0,
|
|
407
|
+
text: output?.text,
|
|
408
|
+
toolCalls: output?.toolCalls?.map((tc) => ({
|
|
409
|
+
toolCallId: tc.toolCallId,
|
|
410
|
+
toolName: tc.toolName,
|
|
411
|
+
args: tc.args
|
|
412
|
+
}))
|
|
413
|
+
};
|
|
414
|
+
modelGenSpanData.threadData.push(stepData);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Store TOOL_CALL result in parent MODEL_GENERATION's pendingToolResults.
|
|
418
|
+
* Called when a TOOL_CALL span ends.
|
|
419
|
+
* Results are merged into threadData when MODEL_GENERATION ends.
|
|
420
|
+
*/
|
|
421
|
+
accumulateToolCallResult(span, traceData) {
|
|
422
|
+
const modelGenSpanData = this.findModelGenerationAncestor(span.id, traceData);
|
|
423
|
+
if (!modelGenSpanData?.pendingToolResults) return;
|
|
424
|
+
const toolCallId = span.input?.toolCallId;
|
|
425
|
+
if (!toolCallId) return;
|
|
426
|
+
modelGenSpanData.pendingToolResults.set(toolCallId, {
|
|
427
|
+
result: span.output,
|
|
428
|
+
startTime: span.startTime
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Build the payload for MODEL_GENERATION span, reconstructing output from threadData if available.
|
|
433
|
+
*/
|
|
434
|
+
buildModelGenerationPayload(span, spanData) {
|
|
435
|
+
const basePayload = this.buildSpanPayload(span, false);
|
|
436
|
+
const threadData = spanData.threadData;
|
|
437
|
+
if (!threadData || threadData.length === 0) return basePayload;
|
|
438
|
+
if (spanData.pendingToolResults && spanData.pendingToolResults.size > 0) {
|
|
439
|
+
for (const step of threadData) if (step.toolCalls) for (const toolCall of step.toolCalls) {
|
|
440
|
+
const pendingResult = spanData.pendingToolResults.get(toolCall.toolCallId);
|
|
441
|
+
if (pendingResult) {
|
|
442
|
+
toolCall.result = pendingResult.result;
|
|
443
|
+
toolCall.startTime = pendingResult.startTime;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (!threadData.some((step) => step.toolCalls && step.toolCalls.length > 0)) return basePayload;
|
|
448
|
+
const reconstructedOutput = reconstructThreadOutput(threadData, span.output);
|
|
449
|
+
return {
|
|
450
|
+
...basePayload,
|
|
451
|
+
output: reconstructedOutput
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Transforms MODEL_GENERATION input to Braintrust Thread view format.
|
|
456
|
+
* Converts AI SDK messages (v4/v5) to OpenAI Chat Completion format, which Braintrust requires
|
|
457
|
+
* for proper rendering of threads (fixes #11023).
|
|
458
|
+
*/
|
|
459
|
+
transformInput(input, spanType) {
|
|
460
|
+
if (spanType === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
461
|
+
if (Array.isArray(input)) return input.map((msg) => convertAISDKMessage(msg));
|
|
462
|
+
if (input && typeof input === "object" && "messages" in input && Array.isArray(input.messages)) return input.messages.map((msg) => convertAISDKMessage(msg));
|
|
463
|
+
}
|
|
464
|
+
return input;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Transforms MODEL_GENERATION output to Braintrust Thread view format.
|
|
468
|
+
*/
|
|
469
|
+
transformOutput(output, spanType) {
|
|
470
|
+
if (spanType === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
471
|
+
if (!output || typeof output !== "object") return output;
|
|
472
|
+
const { text, ...rest } = output;
|
|
473
|
+
return {
|
|
474
|
+
role: "assistant",
|
|
475
|
+
content: text,
|
|
476
|
+
...removeNullish(rest)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return output;
|
|
480
|
+
}
|
|
481
|
+
isToolSpan(span) {
|
|
482
|
+
return span.type === _mastra_core_observability.SpanType.TOOL_CALL || span.type === _mastra_core_observability.SpanType.MCP_TOOL_CALL || span.type === _mastra_core_observability.SpanType.PROVIDER_TOOL_CALL;
|
|
483
|
+
}
|
|
484
|
+
getToolName(span) {
|
|
485
|
+
if (span.entityName) return span.entityName;
|
|
486
|
+
return /'([^']+)'/.exec(span.name)?.[1] ?? span.name;
|
|
487
|
+
}
|
|
488
|
+
getToolCallId(span) {
|
|
489
|
+
return span.attributes?.toolCallId ?? span.input?.toolCallId ?? span.id;
|
|
490
|
+
}
|
|
491
|
+
toToolCallInput(span) {
|
|
492
|
+
const args = span.input;
|
|
493
|
+
let argsString;
|
|
494
|
+
if (typeof args === "string") argsString = args;
|
|
495
|
+
else try {
|
|
496
|
+
argsString = JSON.stringify(args ?? {});
|
|
497
|
+
} catch {
|
|
498
|
+
argsString = "[unserializable result]";
|
|
499
|
+
}
|
|
500
|
+
return [{
|
|
501
|
+
role: "assistant",
|
|
502
|
+
content: "",
|
|
503
|
+
tool_calls: [{
|
|
504
|
+
id: this.getToolCallId(span),
|
|
505
|
+
type: "function",
|
|
506
|
+
function: {
|
|
507
|
+
name: this.getToolName(span),
|
|
508
|
+
arguments: argsString
|
|
509
|
+
}
|
|
510
|
+
}]
|
|
511
|
+
}];
|
|
512
|
+
}
|
|
513
|
+
toToolCallOutput(span) {
|
|
514
|
+
return {
|
|
515
|
+
role: "tool",
|
|
516
|
+
content: serializeToolResult(span.output),
|
|
517
|
+
tool_call_id: this.getToolCallId(span)
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
buildSpanPayload(span, isCreate = true) {
|
|
521
|
+
const payload = {};
|
|
522
|
+
const isToolType = this.isToolSpan(span);
|
|
523
|
+
if (span.input !== void 0) payload.input = isToolType ? this.toToolCallInput(span) : this.transformInput(span.input, span.type);
|
|
524
|
+
if (span.output !== void 0) payload.output = isToolType ? this.toToolCallOutput(span) : this.transformOutput(span.output, span.type);
|
|
525
|
+
if (isCreate && span.isRootSpan && span.tags?.length) payload.tags = span.tags;
|
|
526
|
+
payload.metrics = {};
|
|
527
|
+
payload.metadata = {
|
|
528
|
+
...span.metadata,
|
|
529
|
+
spanType: span.type
|
|
530
|
+
};
|
|
531
|
+
if (isCreate) payload.metadata["mastra-trace-id"] = span.traceId;
|
|
532
|
+
const attributes = span.attributes ?? {};
|
|
533
|
+
if (span.type === _mastra_core_observability.SpanType.MODEL_GENERATION) {
|
|
534
|
+
const modelAttr = attributes;
|
|
535
|
+
if (modelAttr.model !== void 0) payload.metadata.model = modelAttr.model;
|
|
536
|
+
if (modelAttr.provider !== void 0) payload.metadata.provider = modelAttr.provider;
|
|
537
|
+
if (modelAttr.responseModel !== void 0) payload.metadata.model = modelAttr.responseModel;
|
|
538
|
+
payload.metrics = formatUsageMetrics(modelAttr.usage);
|
|
539
|
+
if (modelAttr.completionStartTime) payload.metrics.time_to_first_token = (modelAttr.completionStartTime.getTime() - span.startTime.getTime()) / 1e3;
|
|
540
|
+
if (modelAttr.parameters !== void 0) payload.metadata.modelParameters = modelAttr.parameters;
|
|
541
|
+
const otherAttributes = (0, _mastra_core_utils.omitKeys)(attributes, [
|
|
542
|
+
"model",
|
|
543
|
+
"responseModel",
|
|
544
|
+
"usage",
|
|
545
|
+
"parameters",
|
|
546
|
+
"completionStartTime"
|
|
547
|
+
]);
|
|
548
|
+
payload.metadata = {
|
|
549
|
+
...payload.metadata,
|
|
550
|
+
...otherAttributes
|
|
551
|
+
};
|
|
552
|
+
} else payload.metadata = {
|
|
553
|
+
...payload.metadata,
|
|
554
|
+
...attributes
|
|
555
|
+
};
|
|
556
|
+
if (span.errorInfo) {
|
|
557
|
+
payload.error = span.errorInfo.message;
|
|
558
|
+
payload.metadata.errorDetails = span.errorInfo;
|
|
559
|
+
}
|
|
560
|
+
if (Object.keys(payload.metrics).length === 0) delete payload.metrics;
|
|
561
|
+
payload.metadata = removeNullish(payload.metadata);
|
|
562
|
+
return payload;
|
|
563
|
+
}
|
|
648
564
|
};
|
|
649
|
-
|
|
565
|
+
//#endregion
|
|
650
566
|
exports.BraintrustExporter = BraintrustExporter;
|
|
651
|
-
|
|
567
|
+
|
|
652
568
|
//# sourceMappingURL=index.cjs.map
|