@mastra/arthur 0.0.0
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 +13 -0
- package/README.md +111 -0
- package/dist/index.cjs +237 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/openInferenceOTLPExporter.d.ts +7 -0
- package/dist/openInferenceOTLPExporter.d.ts.map +1 -0
- package/dist/tracing.d.ts +62 -0
- package/dist/tracing.d.ts.map +1 -0
- package/package.json +69 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @mastra/arthur
|
|
2
|
+
|
|
3
|
+
## 0.2.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added @mastra/arthur observability provider for exporting Mastra traces to Arthur AI using OpenInference semantic conventions. Supports zero-config setup via ARTHUR_API_KEY, ARTHUR_BASE_URL, and ARTHUR_TASK_ID environment variables. ([#14643](https://github.com/mastra-ai/mastra/pull/14643))
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`ac7baf6`](https://github.com/mastra-ai/mastra/commit/ac7baf66ef1db15e03975ef4ebb02724f015a391), [`0df8321`](https://github.com/mastra-ai/mastra/commit/0df832196eeb2450ab77ce887e8553abdd44c5a6), [`deb0888`](https://github.com/mastra-ai/mastra/commit/deb08881cd35468421dfb30bf5e2ec21cd1ca4e1), [`61109b3`](https://github.com/mastra-ai/mastra/commit/61109b34feb0e38d54bee4b8ca83eb7345b1d557), [`33f1ead`](https://github.com/mastra-ai/mastra/commit/33f1eadfa19c86953f593478e5fa371093b33779)]:
|
|
12
|
+
- @mastra/core@1.23.0-alpha.8
|
|
13
|
+
- @mastra/otel-exporter@1.0.13-alpha.0
|
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# @mastra/arthur - OpenTelemetry + OpenInference Tracing Exporter
|
|
2
|
+
|
|
3
|
+
Export Mastra traces to [Arthur AI](https://arthur.ai) using [OpenInference Semantic Conventions](https://github.com/Arize-ai/openinference/tree/main/spec).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mastra/arthur
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Add `ArthurExporter` to your Mastra configuration to export traces to Arthur. The exporter automatically reads credentials from environment variables, enabling zero-config setup.
|
|
14
|
+
|
|
15
|
+
### Zero-Config
|
|
16
|
+
|
|
17
|
+
Set environment variables:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
ARTHUR_API_KEY=your-api-key
|
|
21
|
+
ARTHUR_BASE_URL=https://app.arthur.ai
|
|
22
|
+
ARTHUR_TASK_ID=your-task-id # optional, associates traces with a specific Arthur task
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { ArthurExporter } from '@mastra/arthur';
|
|
27
|
+
import { Mastra } from '@mastra/core/mastra';
|
|
28
|
+
|
|
29
|
+
const mastra = new Mastra({
|
|
30
|
+
...,
|
|
31
|
+
observability: {
|
|
32
|
+
configs: {
|
|
33
|
+
arthur: {
|
|
34
|
+
serviceName: 'my-service',
|
|
35
|
+
exporters: [new ArthurExporter()],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Explicit Configuration
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { ArthurExporter } from '@mastra/arthur';
|
|
46
|
+
import { Mastra } from '@mastra/core/mastra';
|
|
47
|
+
|
|
48
|
+
const mastra = new Mastra({
|
|
49
|
+
...,
|
|
50
|
+
observability: {
|
|
51
|
+
configs: {
|
|
52
|
+
arthur: {
|
|
53
|
+
serviceName: 'my-service',
|
|
54
|
+
exporters: [
|
|
55
|
+
new ArthurExporter({
|
|
56
|
+
apiKey: 'your-api-key',
|
|
57
|
+
endpoint: 'https://app.arthur.ai',
|
|
58
|
+
}),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Optional Configuration
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
new ArthurExporter({
|
|
70
|
+
// Required at runtime (or set ARTHUR_API_KEY env var)
|
|
71
|
+
apiKey: 'your-api-key',
|
|
72
|
+
// Required at runtime (or set ARTHUR_BASE_URL env var)
|
|
73
|
+
endpoint: 'https://app.arthur.ai',
|
|
74
|
+
// Optional headers added to each OTLP request
|
|
75
|
+
headers: {
|
|
76
|
+
'x-custom-header': 'value',
|
|
77
|
+
},
|
|
78
|
+
// Optional log level for debugging
|
|
79
|
+
logLevel: 'debug',
|
|
80
|
+
// Optional batch size for the underlying BatchSpanProcessor
|
|
81
|
+
batchSize: 512,
|
|
82
|
+
// Optional timeout for span export
|
|
83
|
+
timeout: 30000,
|
|
84
|
+
// Optional resource attributes added to each span
|
|
85
|
+
resourceAttributes: {
|
|
86
|
+
'custom.attribute': 'value',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Custom metadata
|
|
92
|
+
|
|
93
|
+
Custom span attributes are serialized into the OpenInference `metadata` payload. Add them through `tracingOptions.metadata`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
await agent.generate(input, {
|
|
97
|
+
tracingOptions: {
|
|
98
|
+
metadata: {
|
|
99
|
+
companyId: 'acme-co',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## OpenInference Semantic Conventions
|
|
106
|
+
|
|
107
|
+
This exporter follows the [OpenInference Semantic Conventions](https://github.com/Arize-ai/openinference/tree/main/spec) for generative AI applications. All agent runs, tool calls, and LLM generations are automatically tagged with the correct span kinds and attributes.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
Apache 2.0
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var otelExporter = require('@mastra/otel-exporter');
|
|
4
|
+
var openinferenceGenai = require('@arizeai/openinference-genai');
|
|
5
|
+
var openinferenceSemanticConventions = require('@arizeai/openinference-semantic-conventions');
|
|
6
|
+
var exporterTraceOtlpProto = require('@opentelemetry/exporter-trace-otlp-proto');
|
|
7
|
+
var incubating = require('@opentelemetry/semantic-conventions/incubating');
|
|
8
|
+
|
|
9
|
+
// src/tracing.ts
|
|
10
|
+
var GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
|
|
11
|
+
var GEN_AI_USAGE_CACHED_INPUT_TOKENS = "gen_ai.usage.cached_input_tokens";
|
|
12
|
+
var GEN_AI_USAGE_CACHE_WRITE_TOKENS = "gen_ai.usage.cache_write_tokens";
|
|
13
|
+
var GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
|
|
14
|
+
var GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
|
|
15
|
+
var MASTRA_GENERAL_PREFIX = "mastra.";
|
|
16
|
+
var MASTRA_METADATA_PREFIX = "mastra.metadata.";
|
|
17
|
+
var MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
|
|
18
|
+
var MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
|
|
19
|
+
var MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
|
|
20
|
+
var MASTRA_SPAN_TYPE = "mastra.span.type";
|
|
21
|
+
var SPAN_TYPE_TO_KIND = {
|
|
22
|
+
// Model spans -> LLM
|
|
23
|
+
model_generation: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
|
|
24
|
+
model_step: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
|
|
25
|
+
model_chunk: openinferenceSemanticConventions.OpenInferenceSpanKind.LLM,
|
|
26
|
+
// Tool spans -> TOOL
|
|
27
|
+
tool_call: openinferenceSemanticConventions.OpenInferenceSpanKind.TOOL,
|
|
28
|
+
mcp_tool_call: openinferenceSemanticConventions.OpenInferenceSpanKind.TOOL,
|
|
29
|
+
// Agent spans -> AGENT
|
|
30
|
+
agent_run: openinferenceSemanticConventions.OpenInferenceSpanKind.AGENT
|
|
31
|
+
};
|
|
32
|
+
function convertUsageMetricsToOpenInference(attributes) {
|
|
33
|
+
const result = {};
|
|
34
|
+
const inputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS];
|
|
35
|
+
const outputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
|
|
36
|
+
if (inputTokens !== void 0) {
|
|
37
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT] = inputTokens;
|
|
38
|
+
}
|
|
39
|
+
if (outputTokens !== void 0) {
|
|
40
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
|
|
41
|
+
}
|
|
42
|
+
if (inputTokens !== void 0 && outputTokens !== void 0) {
|
|
43
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
|
|
44
|
+
}
|
|
45
|
+
const cachedInputTokens = attributes[GEN_AI_USAGE_CACHED_INPUT_TOKENS];
|
|
46
|
+
if (cachedInputTokens !== void 0) {
|
|
47
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cachedInputTokens;
|
|
48
|
+
}
|
|
49
|
+
const cacheWriteTokens = attributes[GEN_AI_USAGE_CACHE_WRITE_TOKENS];
|
|
50
|
+
if (cacheWriteTokens !== void 0) {
|
|
51
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheWriteTokens;
|
|
52
|
+
}
|
|
53
|
+
const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
|
|
54
|
+
if (reasoningTokens !== void 0) {
|
|
55
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
|
|
56
|
+
}
|
|
57
|
+
const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
|
|
58
|
+
if (audioInputTokens !== void 0) {
|
|
59
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
|
|
60
|
+
}
|
|
61
|
+
const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
|
|
62
|
+
if (audioOutputTokens !== void 0) {
|
|
63
|
+
result[openinferenceSemanticConventions.LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
function splitMastraAttributes(attributes) {
|
|
68
|
+
return Object.entries(attributes).reduce(
|
|
69
|
+
(acc, [key, value]) => {
|
|
70
|
+
if (key.startsWith(MASTRA_GENERAL_PREFIX)) {
|
|
71
|
+
if (key.startsWith(MASTRA_METADATA_PREFIX)) {
|
|
72
|
+
const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);
|
|
73
|
+
acc.mastraMetadata[strippedKey] = value;
|
|
74
|
+
} else {
|
|
75
|
+
acc.mastraOther[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return acc;
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
mastraMetadata: {},
|
|
82
|
+
mastraOther: {}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
var OpenInferenceOTLPTraceExporter = class extends exporterTraceOtlpProto.OTLPTraceExporter {
|
|
87
|
+
export(spans, resultCallback) {
|
|
88
|
+
const processedSpans = spans.map((span) => {
|
|
89
|
+
const attributes = { ...span.attributes ?? {} };
|
|
90
|
+
const mutableSpan = span;
|
|
91
|
+
const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
|
|
92
|
+
const processedAttributes = openinferenceGenai.convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);
|
|
93
|
+
if (processedAttributes) {
|
|
94
|
+
const threadId = mastraMetadata["threadId"];
|
|
95
|
+
if (threadId) {
|
|
96
|
+
delete mastraMetadata["threadId"];
|
|
97
|
+
processedAttributes[openinferenceSemanticConventions.SESSION_ID] = threadId;
|
|
98
|
+
}
|
|
99
|
+
if (mastraOther["mastra.tags"]) {
|
|
100
|
+
processedAttributes[openinferenceSemanticConventions.TAG_TAGS] = mastraOther["mastra.tags"];
|
|
101
|
+
delete mastraOther["mastra.tags"];
|
|
102
|
+
}
|
|
103
|
+
const userId = mastraMetadata["userId"];
|
|
104
|
+
if (userId) {
|
|
105
|
+
delete mastraMetadata["userId"];
|
|
106
|
+
processedAttributes[openinferenceSemanticConventions.USER_ID] = userId;
|
|
107
|
+
}
|
|
108
|
+
if (Object.keys(mastraMetadata).length > 0) {
|
|
109
|
+
try {
|
|
110
|
+
processedAttributes[openinferenceSemanticConventions.METADATA] = JSON.stringify(mastraMetadata);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const inputMessages = attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[incubating.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
|
|
115
|
+
if (inputMessages) {
|
|
116
|
+
processedAttributes[openinferenceSemanticConventions.INPUT_MIME_TYPE] = "application/json";
|
|
117
|
+
processedAttributes[openinferenceSemanticConventions.INPUT_VALUE] = inputMessages;
|
|
118
|
+
}
|
|
119
|
+
const outputMessages = attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[incubating.ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
|
|
120
|
+
if (outputMessages) {
|
|
121
|
+
processedAttributes[openinferenceSemanticConventions.OUTPUT_MIME_TYPE] = "application/json";
|
|
122
|
+
processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE] = outputMessages;
|
|
123
|
+
}
|
|
124
|
+
if (!processedAttributes[openinferenceSemanticConventions.INPUT_VALUE]) {
|
|
125
|
+
for (const key of Object.keys(mastraOther)) {
|
|
126
|
+
if (key.endsWith(".input")) {
|
|
127
|
+
processedAttributes[openinferenceSemanticConventions.INPUT_MIME_TYPE] = "application/json";
|
|
128
|
+
processedAttributes[openinferenceSemanticConventions.INPUT_VALUE] = mastraOther[key];
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE]) {
|
|
134
|
+
for (const key of Object.keys(mastraOther)) {
|
|
135
|
+
if (key.endsWith(".output")) {
|
|
136
|
+
processedAttributes[openinferenceSemanticConventions.OUTPUT_MIME_TYPE] = "application/json";
|
|
137
|
+
processedAttributes[openinferenceSemanticConventions.OUTPUT_VALUE] = mastraOther[key];
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const usageMetrics = convertUsageMetricsToOpenInference(attributes);
|
|
143
|
+
Object.assign(processedAttributes, usageMetrics);
|
|
144
|
+
mutableSpan.attributes = { ...processedAttributes, ...mastraOther };
|
|
145
|
+
const spanType = mastraOther[MASTRA_SPAN_TYPE];
|
|
146
|
+
if (typeof spanType === "string") {
|
|
147
|
+
mutableSpan.attributes[openinferenceSemanticConventions.SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? openinferenceSemanticConventions.OpenInferenceSpanKind.CHAIN;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return mutableSpan;
|
|
151
|
+
});
|
|
152
|
+
super.export(processedSpans, resultCallback);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/tracing.ts
|
|
157
|
+
var LOG_PREFIX = "[ArthurExporter]";
|
|
158
|
+
var ArthurExporter = class extends otelExporter.OtelExporter {
|
|
159
|
+
name = "arthur";
|
|
160
|
+
taskId;
|
|
161
|
+
/**
|
|
162
|
+
* @param config - Arthur exporter configuration. All fields are optional when
|
|
163
|
+
* the corresponding environment variables are set.
|
|
164
|
+
*/
|
|
165
|
+
constructor(config = {}) {
|
|
166
|
+
const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
|
|
167
|
+
const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
|
|
168
|
+
const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
|
|
169
|
+
const headers = {
|
|
170
|
+
...config.headers
|
|
171
|
+
};
|
|
172
|
+
let disabledReason;
|
|
173
|
+
if (!apiKey) {
|
|
174
|
+
disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
|
|
175
|
+
} else {
|
|
176
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
177
|
+
}
|
|
178
|
+
if (!disabledReason && !endpoint) {
|
|
179
|
+
disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
|
|
180
|
+
}
|
|
181
|
+
const tracesEndpoint = endpoint ? `${endpoint.replace(/\/+$/, "")}/api/v1/traces` : "http://disabled";
|
|
182
|
+
if (disabledReason) {
|
|
183
|
+
super({
|
|
184
|
+
...config,
|
|
185
|
+
provider: {
|
|
186
|
+
custom: {
|
|
187
|
+
endpoint: "http://disabled",
|
|
188
|
+
headers: {},
|
|
189
|
+
protocol: "http/protobuf"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
this.setDisabled(disabledReason);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
super({
|
|
197
|
+
exporter: new OpenInferenceOTLPTraceExporter({
|
|
198
|
+
url: tracesEndpoint,
|
|
199
|
+
headers
|
|
200
|
+
}),
|
|
201
|
+
...config,
|
|
202
|
+
resourceAttributes: {
|
|
203
|
+
...taskId ? { "arthur.task.id": taskId } : {},
|
|
204
|
+
...config.resourceAttributes
|
|
205
|
+
},
|
|
206
|
+
provider: {
|
|
207
|
+
custom: {
|
|
208
|
+
endpoint: tracesEndpoint,
|
|
209
|
+
headers,
|
|
210
|
+
protocol: "http/protobuf"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
this.taskId = taskId;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Called after construction with the observability config.
|
|
218
|
+
* Validates that traces can be routed to a task via either taskId or serviceName.
|
|
219
|
+
*/
|
|
220
|
+
init(options) {
|
|
221
|
+
super.init(options);
|
|
222
|
+
const serviceName = options.config?.serviceName;
|
|
223
|
+
if (this.taskId && serviceName) {
|
|
224
|
+
this.logger.warn(
|
|
225
|
+
`${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`
|
|
226
|
+
);
|
|
227
|
+
} else if (!this.taskId && !serviceName) {
|
|
228
|
+
this.logger.warn(
|
|
229
|
+
`${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
exports.ArthurExporter = ArthurExporter;
|
|
236
|
+
//# sourceMappingURL=index.cjs.map
|
|
237
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"names":["OpenInferenceSpanKind","ATTR_GEN_AI_USAGE_INPUT_TOKENS","ATTR_GEN_AI_USAGE_OUTPUT_TOKENS","LLM_TOKEN_COUNT_PROMPT","LLM_TOKEN_COUNT_COMPLETION","LLM_TOKEN_COUNT_TOTAL","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ","LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE","LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING","LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO","LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO","OTLPTraceExporter","convertGenAISpanAttributesToOpenInferenceSpanAttributes","SESSION_ID","TAG_TAGS","USER_ID","METADATA","ATTR_GEN_AI_INPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_ARGUMENTS","INPUT_MIME_TYPE","INPUT_VALUE","ATTR_GEN_AI_OUTPUT_MESSAGES","ATTR_GEN_AI_TOOL_CALL_RESULT","OUTPUT_MIME_TYPE","OUTPUT_VALUE","SemanticConventions","OtelExporter"],"mappings":"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","file":"index.cjs","sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHED_INPUT_TOKENS = 'gen_ai.usage.cached_input_tokens';\nconst GEN_AI_USAGE_CACHE_WRITE_TOKENS = 'gen_ai.usage.cache_write_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cachedInputTokens = attributes[GEN_AI_USAGE_CACHED_INPUT_TOKENS];\n if (cachedInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cachedInputTokens;\n }\n\n const cacheWriteTokens = attributes[GEN_AI_USAGE_CACHE_WRITE_TOKENS];\n if (cacheWriteTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheWriteTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${endpoint.replace(/\\/+$/, '')}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { OtelExporter } from '@mastra/otel-exporter';
|
|
2
|
+
import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';
|
|
3
|
+
import { SESSION_ID, TAG_TAGS, USER_ID, METADATA, INPUT_MIME_TYPE, INPUT_VALUE, OUTPUT_MIME_TYPE, OUTPUT_VALUE, OpenInferenceSpanKind, SemanticConventions, LLM_TOKEN_COUNT_PROMPT, LLM_TOKEN_COUNT_COMPLETION, LLM_TOKEN_COUNT_TOTAL, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ, LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE, LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO, LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO } from '@arizeai/openinference-semantic-conventions';
|
|
4
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
5
|
+
import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_TOOL_CALL_RESULT, ATTR_GEN_AI_USAGE_INPUT_TOKENS, ATTR_GEN_AI_USAGE_OUTPUT_TOKENS } from '@opentelemetry/semantic-conventions/incubating';
|
|
6
|
+
|
|
7
|
+
// src/tracing.ts
|
|
8
|
+
var GEN_AI_USAGE_REASONING_TOKENS = "gen_ai.usage.reasoning_tokens";
|
|
9
|
+
var GEN_AI_USAGE_CACHED_INPUT_TOKENS = "gen_ai.usage.cached_input_tokens";
|
|
10
|
+
var GEN_AI_USAGE_CACHE_WRITE_TOKENS = "gen_ai.usage.cache_write_tokens";
|
|
11
|
+
var GEN_AI_USAGE_AUDIO_INPUT_TOKENS = "gen_ai.usage.audio_input_tokens";
|
|
12
|
+
var GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = "gen_ai.usage.audio_output_tokens";
|
|
13
|
+
var MASTRA_GENERAL_PREFIX = "mastra.";
|
|
14
|
+
var MASTRA_METADATA_PREFIX = "mastra.metadata.";
|
|
15
|
+
var MASTRA_MODEL_STEP_INPUT = "mastra.model_step.input";
|
|
16
|
+
var MASTRA_MODEL_STEP_OUTPUT = "mastra.model_step.output";
|
|
17
|
+
var MASTRA_MODEL_CHUNK_OUTPUT = "mastra.model_chunk.output";
|
|
18
|
+
var MASTRA_SPAN_TYPE = "mastra.span.type";
|
|
19
|
+
var SPAN_TYPE_TO_KIND = {
|
|
20
|
+
// Model spans -> LLM
|
|
21
|
+
model_generation: OpenInferenceSpanKind.LLM,
|
|
22
|
+
model_step: OpenInferenceSpanKind.LLM,
|
|
23
|
+
model_chunk: OpenInferenceSpanKind.LLM,
|
|
24
|
+
// Tool spans -> TOOL
|
|
25
|
+
tool_call: OpenInferenceSpanKind.TOOL,
|
|
26
|
+
mcp_tool_call: OpenInferenceSpanKind.TOOL,
|
|
27
|
+
// Agent spans -> AGENT
|
|
28
|
+
agent_run: OpenInferenceSpanKind.AGENT
|
|
29
|
+
};
|
|
30
|
+
function convertUsageMetricsToOpenInference(attributes) {
|
|
31
|
+
const result = {};
|
|
32
|
+
const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];
|
|
33
|
+
const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
|
|
34
|
+
if (inputTokens !== void 0) {
|
|
35
|
+
result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;
|
|
36
|
+
}
|
|
37
|
+
if (outputTokens !== void 0) {
|
|
38
|
+
result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;
|
|
39
|
+
}
|
|
40
|
+
if (inputTokens !== void 0 && outputTokens !== void 0) {
|
|
41
|
+
result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;
|
|
42
|
+
}
|
|
43
|
+
const cachedInputTokens = attributes[GEN_AI_USAGE_CACHED_INPUT_TOKENS];
|
|
44
|
+
if (cachedInputTokens !== void 0) {
|
|
45
|
+
result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cachedInputTokens;
|
|
46
|
+
}
|
|
47
|
+
const cacheWriteTokens = attributes[GEN_AI_USAGE_CACHE_WRITE_TOKENS];
|
|
48
|
+
if (cacheWriteTokens !== void 0) {
|
|
49
|
+
result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheWriteTokens;
|
|
50
|
+
}
|
|
51
|
+
const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];
|
|
52
|
+
if (reasoningTokens !== void 0) {
|
|
53
|
+
result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;
|
|
54
|
+
}
|
|
55
|
+
const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];
|
|
56
|
+
if (audioInputTokens !== void 0) {
|
|
57
|
+
result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;
|
|
58
|
+
}
|
|
59
|
+
const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];
|
|
60
|
+
if (audioOutputTokens !== void 0) {
|
|
61
|
+
result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function splitMastraAttributes(attributes) {
|
|
66
|
+
return Object.entries(attributes).reduce(
|
|
67
|
+
(acc, [key, value]) => {
|
|
68
|
+
if (key.startsWith(MASTRA_GENERAL_PREFIX)) {
|
|
69
|
+
if (key.startsWith(MASTRA_METADATA_PREFIX)) {
|
|
70
|
+
const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);
|
|
71
|
+
acc.mastraMetadata[strippedKey] = value;
|
|
72
|
+
} else {
|
|
73
|
+
acc.mastraOther[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return acc;
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
mastraMetadata: {},
|
|
80
|
+
mastraOther: {}
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
var OpenInferenceOTLPTraceExporter = class extends OTLPTraceExporter {
|
|
85
|
+
export(spans, resultCallback) {
|
|
86
|
+
const processedSpans = spans.map((span) => {
|
|
87
|
+
const attributes = { ...span.attributes ?? {} };
|
|
88
|
+
const mutableSpan = span;
|
|
89
|
+
const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);
|
|
90
|
+
const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);
|
|
91
|
+
if (processedAttributes) {
|
|
92
|
+
const threadId = mastraMetadata["threadId"];
|
|
93
|
+
if (threadId) {
|
|
94
|
+
delete mastraMetadata["threadId"];
|
|
95
|
+
processedAttributes[SESSION_ID] = threadId;
|
|
96
|
+
}
|
|
97
|
+
if (mastraOther["mastra.tags"]) {
|
|
98
|
+
processedAttributes[TAG_TAGS] = mastraOther["mastra.tags"];
|
|
99
|
+
delete mastraOther["mastra.tags"];
|
|
100
|
+
}
|
|
101
|
+
const userId = mastraMetadata["userId"];
|
|
102
|
+
if (userId) {
|
|
103
|
+
delete mastraMetadata["userId"];
|
|
104
|
+
processedAttributes[USER_ID] = userId;
|
|
105
|
+
}
|
|
106
|
+
if (Object.keys(mastraMetadata).length > 0) {
|
|
107
|
+
try {
|
|
108
|
+
processedAttributes[METADATA] = JSON.stringify(mastraMetadata);
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const inputMessages = attributes[ATTR_GEN_AI_INPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ?? mastraOther[MASTRA_MODEL_STEP_INPUT];
|
|
113
|
+
if (inputMessages) {
|
|
114
|
+
processedAttributes[INPUT_MIME_TYPE] = "application/json";
|
|
115
|
+
processedAttributes[INPUT_VALUE] = inputMessages;
|
|
116
|
+
}
|
|
117
|
+
const outputMessages = attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ?? attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ?? mastraOther[MASTRA_MODEL_STEP_OUTPUT] ?? mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];
|
|
118
|
+
if (outputMessages) {
|
|
119
|
+
processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
|
|
120
|
+
processedAttributes[OUTPUT_VALUE] = outputMessages;
|
|
121
|
+
}
|
|
122
|
+
if (!processedAttributes[INPUT_VALUE]) {
|
|
123
|
+
for (const key of Object.keys(mastraOther)) {
|
|
124
|
+
if (key.endsWith(".input")) {
|
|
125
|
+
processedAttributes[INPUT_MIME_TYPE] = "application/json";
|
|
126
|
+
processedAttributes[INPUT_VALUE] = mastraOther[key];
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!processedAttributes[OUTPUT_VALUE]) {
|
|
132
|
+
for (const key of Object.keys(mastraOther)) {
|
|
133
|
+
if (key.endsWith(".output")) {
|
|
134
|
+
processedAttributes[OUTPUT_MIME_TYPE] = "application/json";
|
|
135
|
+
processedAttributes[OUTPUT_VALUE] = mastraOther[key];
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const usageMetrics = convertUsageMetricsToOpenInference(attributes);
|
|
141
|
+
Object.assign(processedAttributes, usageMetrics);
|
|
142
|
+
mutableSpan.attributes = { ...processedAttributes, ...mastraOther };
|
|
143
|
+
const spanType = mastraOther[MASTRA_SPAN_TYPE];
|
|
144
|
+
if (typeof spanType === "string") {
|
|
145
|
+
mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] = SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return mutableSpan;
|
|
149
|
+
});
|
|
150
|
+
super.export(processedSpans, resultCallback);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/tracing.ts
|
|
155
|
+
var LOG_PREFIX = "[ArthurExporter]";
|
|
156
|
+
var ArthurExporter = class extends OtelExporter {
|
|
157
|
+
name = "arthur";
|
|
158
|
+
taskId;
|
|
159
|
+
/**
|
|
160
|
+
* @param config - Arthur exporter configuration. All fields are optional when
|
|
161
|
+
* the corresponding environment variables are set.
|
|
162
|
+
*/
|
|
163
|
+
constructor(config = {}) {
|
|
164
|
+
const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;
|
|
165
|
+
const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;
|
|
166
|
+
const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;
|
|
167
|
+
const headers = {
|
|
168
|
+
...config.headers
|
|
169
|
+
};
|
|
170
|
+
let disabledReason;
|
|
171
|
+
if (!apiKey) {
|
|
172
|
+
disabledReason = `${LOG_PREFIX} API key is required. Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;
|
|
173
|
+
} else {
|
|
174
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
175
|
+
}
|
|
176
|
+
if (!disabledReason && !endpoint) {
|
|
177
|
+
disabledReason = `${LOG_PREFIX} Endpoint is required. Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;
|
|
178
|
+
}
|
|
179
|
+
const tracesEndpoint = endpoint ? `${endpoint.replace(/\/+$/, "")}/api/v1/traces` : "http://disabled";
|
|
180
|
+
if (disabledReason) {
|
|
181
|
+
super({
|
|
182
|
+
...config,
|
|
183
|
+
provider: {
|
|
184
|
+
custom: {
|
|
185
|
+
endpoint: "http://disabled",
|
|
186
|
+
headers: {},
|
|
187
|
+
protocol: "http/protobuf"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
this.setDisabled(disabledReason);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
super({
|
|
195
|
+
exporter: new OpenInferenceOTLPTraceExporter({
|
|
196
|
+
url: tracesEndpoint,
|
|
197
|
+
headers
|
|
198
|
+
}),
|
|
199
|
+
...config,
|
|
200
|
+
resourceAttributes: {
|
|
201
|
+
...taskId ? { "arthur.task.id": taskId } : {},
|
|
202
|
+
...config.resourceAttributes
|
|
203
|
+
},
|
|
204
|
+
provider: {
|
|
205
|
+
custom: {
|
|
206
|
+
endpoint: tracesEndpoint,
|
|
207
|
+
headers,
|
|
208
|
+
protocol: "http/protobuf"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
this.taskId = taskId;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Called after construction with the observability config.
|
|
216
|
+
* Validates that traces can be routed to a task via either taskId or serviceName.
|
|
217
|
+
*/
|
|
218
|
+
init(options) {
|
|
219
|
+
super.init(options);
|
|
220
|
+
const serviceName = options.config?.serviceName;
|
|
221
|
+
if (this.taskId && serviceName) {
|
|
222
|
+
this.logger.warn(
|
|
223
|
+
`${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ignoring the provided taskId.`
|
|
224
|
+
);
|
|
225
|
+
} else if (!this.taskId && !serviceName) {
|
|
226
|
+
this.logger.warn(
|
|
227
|
+
`${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export { ArthurExporter };
|
|
234
|
+
//# sourceMappingURL=index.js.map
|
|
235
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openInferenceOTLPExporter.ts","../src/tracing.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { convertGenAISpanAttributesToOpenInferenceSpanAttributes } from '@arizeai/openinference-genai';\nimport type { Mutable } from '@arizeai/openinference-genai/types';\nimport {\n INPUT_MIME_TYPE,\n INPUT_VALUE,\n LLM_TOKEN_COUNT_COMPLETION,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING,\n LLM_TOKEN_COUNT_PROMPT,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ,\n LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE,\n LLM_TOKEN_COUNT_TOTAL,\n METADATA,\n OpenInferenceSpanKind,\n OUTPUT_MIME_TYPE,\n OUTPUT_VALUE,\n SemanticConventions,\n SESSION_ID,\n TAG_TAGS,\n USER_ID,\n} from '@arizeai/openinference-semantic-conventions';\nimport type { ExportResult } from '@opentelemetry/core';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';\nimport type { ReadableSpan } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_GEN_AI_INPUT_MESSAGES,\n ATTR_GEN_AI_OUTPUT_MESSAGES,\n ATTR_GEN_AI_USAGE_INPUT_TOKENS,\n ATTR_GEN_AI_USAGE_OUTPUT_TOKENS,\n ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,\n ATTR_GEN_AI_TOOL_CALL_RESULT,\n} from '@opentelemetry/semantic-conventions/incubating';\n\n// GenAI usage attribute keys (not all are in @opentelemetry/semantic-conventions yet)\nconst GEN_AI_USAGE_REASONING_TOKENS = 'gen_ai.usage.reasoning_tokens';\nconst GEN_AI_USAGE_CACHED_INPUT_TOKENS = 'gen_ai.usage.cached_input_tokens';\nconst GEN_AI_USAGE_CACHE_WRITE_TOKENS = 'gen_ai.usage.cache_write_tokens';\nconst GEN_AI_USAGE_AUDIO_INPUT_TOKENS = 'gen_ai.usage.audio_input_tokens';\nconst GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS = 'gen_ai.usage.audio_output_tokens';\n\nconst MASTRA_GENERAL_PREFIX = 'mastra.';\nconst MASTRA_METADATA_PREFIX = 'mastra.metadata.';\nconst MASTRA_MODEL_STEP_INPUT = 'mastra.model_step.input';\nconst MASTRA_MODEL_STEP_OUTPUT = 'mastra.model_step.output';\nconst MASTRA_MODEL_CHUNK_OUTPUT = 'mastra.model_chunk.output';\nconst MASTRA_SPAN_TYPE = 'mastra.span.type';\n\n/**\n * Maps Mastra span types to OpenInference span kinds for proper trace categorization.\n *\n * Only non-CHAIN types are mapped here - all other span types default to CHAIN.\n */\nconst SPAN_TYPE_TO_KIND: Record<string, OpenInferenceSpanKind> = {\n // Model spans -> LLM\n model_generation: OpenInferenceSpanKind.LLM,\n model_step: OpenInferenceSpanKind.LLM,\n model_chunk: OpenInferenceSpanKind.LLM,\n // Tool spans -> TOOL\n tool_call: OpenInferenceSpanKind.TOOL,\n mcp_tool_call: OpenInferenceSpanKind.TOOL,\n // Agent spans -> AGENT\n agent_run: OpenInferenceSpanKind.AGENT,\n};\n\n/**\n * Converts GenAI usage metrics to OpenInference LLM token count attributes.\n * Maps from OTEL GenAI semantic conventions to OpenInference semantic conventions.\n *\n * @param attributes - The span attributes containing GenAI usage metrics\n * @returns OpenInference token count attributes\n */\nfunction convertUsageMetricsToOpenInference(attributes: Record<string, any>): Record<string, any> {\n const result: Record<string, any> = {};\n\n const inputTokens = attributes[ATTR_GEN_AI_USAGE_INPUT_TOKENS];\n const outputTokens = attributes[ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];\n\n // Core token counts\n if (inputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT] = inputTokens;\n }\n if (outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION] = outputTokens;\n }\n\n // Total tokens (compute if we have both input and output)\n if (inputTokens !== undefined && outputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_TOTAL] = inputTokens + outputTokens;\n }\n\n // Cache tokens (prompt details)\n const cachedInputTokens = attributes[GEN_AI_USAGE_CACHED_INPUT_TOKENS];\n if (cachedInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_READ] = cachedInputTokens;\n }\n\n const cacheWriteTokens = attributes[GEN_AI_USAGE_CACHE_WRITE_TOKENS];\n if (cacheWriteTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_CACHE_WRITE] = cacheWriteTokens;\n }\n\n // Reasoning tokens (completion details)\n const reasoningTokens = attributes[GEN_AI_USAGE_REASONING_TOKENS];\n if (reasoningTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING] = reasoningTokens;\n }\n\n // Audio tokens\n const audioInputTokens = attributes[GEN_AI_USAGE_AUDIO_INPUT_TOKENS];\n if (audioInputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_PROMPT_DETAILS_AUDIO] = audioInputTokens;\n }\n\n const audioOutputTokens = attributes[GEN_AI_USAGE_AUDIO_OUTPUT_TOKENS];\n if (audioOutputTokens !== undefined) {\n result[LLM_TOKEN_COUNT_COMPLETION_DETAILS_AUDIO] = audioOutputTokens;\n }\n\n return result;\n}\n\n/**\n * Splits Mastra span attributes into two groups:\n * - `metadata`: keys starting with \"mastra.metadata.\" (prefix removed)\n * - `other`: all remaining keys starting with \"mastra.\"\n *\n * Any attributes not starting with \"mastra.\" are ignored entirely.\n */\nfunction splitMastraAttributes(attributes: Record<string, any>): {\n mastraMetadata: Record<string, any>;\n mastraOther: Record<string, any>;\n} {\n return Object.entries(attributes).reduce(\n (acc, [key, value]) => {\n if (key.startsWith(MASTRA_GENERAL_PREFIX)) {\n if (key.startsWith(MASTRA_METADATA_PREFIX)) {\n const strippedKey = key.slice(MASTRA_METADATA_PREFIX.length);\n acc.mastraMetadata[strippedKey] = value;\n } else {\n acc.mastraOther[key] = value;\n }\n }\n return acc;\n },\n {\n mastraMetadata: {} as Record<string, any>,\n mastraOther: {} as Record<string, any>,\n },\n );\n}\n\nexport class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void) {\n const processedSpans = spans.map(span => {\n const attributes = { ...(span.attributes ?? {}) };\n const mutableSpan = span as Mutable<ReadableSpan>;\n\n const { mastraMetadata, mastraOther } = splitMastraAttributes(attributes);\n const processedAttributes = convertGenAISpanAttributesToOpenInferenceSpanAttributes(attributes);\n\n // only add processed attributes if conversion was successful\n if (processedAttributes) {\n const threadId = mastraMetadata['threadId'];\n if (threadId) {\n delete mastraMetadata['threadId'];\n processedAttributes[SESSION_ID] = threadId;\n }\n\n // Map mastra.tags to OpenInference native tag.tags convention (tags are only on root spans)\n if (mastraOther['mastra.tags']) {\n processedAttributes[TAG_TAGS] = mastraOther['mastra.tags'];\n delete mastraOther['mastra.tags'];\n }\n\n const userId = mastraMetadata['userId'];\n if (userId) {\n delete mastraMetadata['userId'];\n processedAttributes[USER_ID] = userId;\n }\n\n // Gather custom metadata into OpenInference metadata (flat best-effort)\n if (Object.keys(mastraMetadata).length > 0) {\n try {\n processedAttributes[METADATA] = JSON.stringify(mastraMetadata);\n } catch {\n // best-effort only\n }\n }\n\n const inputMessages =\n attributes[ATTR_GEN_AI_INPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] ??\n mastraOther[MASTRA_MODEL_STEP_INPUT];\n if (inputMessages) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = inputMessages;\n }\n const outputMessages =\n attributes[ATTR_GEN_AI_OUTPUT_MESSAGES] ??\n attributes[ATTR_GEN_AI_TOOL_CALL_RESULT] ??\n mastraOther[MASTRA_MODEL_STEP_OUTPUT] ??\n mastraOther[MASTRA_MODEL_CHUNK_OUTPUT];\n if (outputMessages) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = outputMessages;\n }\n\n // Map generic Mastra span input/output to OpenInference input/output\n // These are set by Mastra's gen-ai-semantics.ts for non-LLM/tool spans\n // (e.g., mastra.processor_run.input, mastra.workflow_run.input, etc.)\n if (!processedAttributes[INPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.input')) {\n processedAttributes[INPUT_MIME_TYPE] = 'application/json';\n processedAttributes[INPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n if (!processedAttributes[OUTPUT_VALUE]) {\n for (const key of Object.keys(mastraOther)) {\n if (key.endsWith('.output')) {\n processedAttributes[OUTPUT_MIME_TYPE] = 'application/json';\n processedAttributes[OUTPUT_VALUE] = mastraOther[key];\n break;\n }\n }\n }\n\n // Convert GenAI usage metrics to OpenInference token count attributes\n const usageMetrics = convertUsageMetricsToOpenInference(attributes);\n Object.assign(processedAttributes, usageMetrics);\n\n mutableSpan.attributes = { ...processedAttributes, ...mastraOther };\n\n // Set span kind based on mastra.span.type for proper trace categorization\n const spanType = mastraOther[MASTRA_SPAN_TYPE];\n if (typeof spanType === 'string') {\n mutableSpan.attributes[SemanticConventions.OPENINFERENCE_SPAN_KIND] =\n SPAN_TYPE_TO_KIND[spanType] ?? OpenInferenceSpanKind.CHAIN;\n }\n }\n\n return mutableSpan;\n });\n\n super.export(processedSpans, resultCallback);\n }\n}\n","import type { InitExporterOptions } from '@mastra/core/observability';\nimport { OtelExporter } from '@mastra/otel-exporter';\nimport type { OtelExporterConfig } from '@mastra/otel-exporter';\n\nimport { OpenInferenceOTLPTraceExporter } from './openInferenceOTLPExporter.js';\n\nconst LOG_PREFIX = '[ArthurExporter]';\n\nexport type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {\n /**\n * Arthur API key for authentication.\n * Falls back to ARTHUR_API_KEY environment variable.\n */\n apiKey?: string;\n /**\n * Arthur platform endpoint (e.g. https://app.arthur.ai).\n * Falls back to ARTHUR_BASE_URL environment variable.\n */\n endpoint?: string;\n /**\n * Arthur task ID to associate traces with.\n * Falls back to ARTHUR_TASK_ID environment variable.\n * At least one of taskId or serviceName (from the observability config) should be provided.\n */\n taskId?: string;\n /**\n * Optional headers to be added to each OTLP request.\n * Note: the Authorization header is managed internally when apiKey is provided.\n */\n headers?: Record<string, string>;\n};\n\n/**\n * Exports Mastra traces to Arthur AI using OpenInference semantic conventions.\n *\n * Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,\n * ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning\n * when required credentials are missing.\n *\n * @example\n * ```ts\n * const mastra = new Mastra({\n * observability: new Observability({\n * configs: {\n * arthur: {\n * serviceName: 'my-service',\n * exporters: [new ArthurExporter()],\n * },\n * },\n * }),\n * });\n * ```\n */\nexport class ArthurExporter extends OtelExporter {\n name = 'arthur';\n private taskId?: string;\n\n /**\n * @param config - Arthur exporter configuration. All fields are optional when\n * the corresponding environment variables are set.\n */\n constructor(config: ArthurExporterConfig = {}) {\n const apiKey = config.apiKey ?? process.env.ARTHUR_API_KEY;\n const endpoint = config.endpoint ?? process.env.ARTHUR_BASE_URL;\n const taskId = config.taskId ?? process.env.ARTHUR_TASK_ID;\n\n const headers: Record<string, string> = {\n ...config.headers,\n };\n\n let disabledReason: string | undefined;\n\n if (!apiKey) {\n disabledReason =\n `${LOG_PREFIX} API key is required. ` + `Set ARTHUR_API_KEY environment variable or pass apiKey in config.`;\n } else {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n if (!disabledReason && !endpoint) {\n disabledReason =\n `${LOG_PREFIX} Endpoint is required. ` + `Set ARTHUR_BASE_URL environment variable or pass endpoint in config.`;\n }\n\n // Ensure the endpoint ends with /api/v1/traces\n const tracesEndpoint = endpoint ? `${endpoint.replace(/\\/+$/, '')}/api/v1/traces` : 'http://disabled';\n\n if (disabledReason) {\n super({\n ...config,\n provider: {\n custom: {\n endpoint: 'http://disabled',\n headers: {},\n protocol: 'http/protobuf',\n },\n },\n });\n this.setDisabled(disabledReason);\n return;\n }\n\n super({\n exporter: new OpenInferenceOTLPTraceExporter({\n url: tracesEndpoint,\n headers,\n }),\n ...config,\n resourceAttributes: {\n ...(taskId ? { 'arthur.task.id': taskId } : {}),\n ...config.resourceAttributes,\n },\n provider: {\n custom: {\n endpoint: tracesEndpoint,\n headers,\n protocol: 'http/protobuf',\n },\n } satisfies OtelExporterConfig['provider'],\n } satisfies OtelExporterConfig);\n\n this.taskId = taskId;\n }\n\n /**\n * Called after construction with the observability config.\n * Validates that traces can be routed to a task via either taskId or serviceName.\n */\n init(options: InitExporterOptions) {\n super.init(options);\n\n const serviceName = options.config?.serviceName;\n\n if (this.taskId && serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Both taskId and serviceName are set. Arthur Engine will use serviceName to route traces, ` +\n `ignoring the provided taskId.`,\n );\n } else if (!this.taskId && !serviceName) {\n this.logger.warn(\n `${LOG_PREFIX} Neither taskId nor serviceName is set. Set ARTHUR_TASK_ID environment variable, ` +\n `pass taskId in config, or set serviceName in the observability config so Arthur Engine can route traces to a task.`,\n );\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExportResult } from '@opentelemetry/core';
|
|
2
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
3
|
+
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
4
|
+
export declare class OpenInferenceOTLPTraceExporter extends OTLPTraceExporter {
|
|
5
|
+
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=openInferenceOTLPExporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openInferenceOTLPExporter.d.ts","sourceRoot":"","sources":["../src/openInferenceOTLPExporter.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAgIlE,qBAAa,8BAA+B,SAAQ,iBAAiB;IACnE,MAAM,CAAC,KAAK,EAAE,YAAY,EAAE,EAAE,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI;CAgG7E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { InitExporterOptions } from '@mastra/core/observability';
|
|
2
|
+
import { OtelExporter } from '@mastra/otel-exporter';
|
|
3
|
+
import type { OtelExporterConfig } from '@mastra/otel-exporter';
|
|
4
|
+
export type ArthurExporterConfig = Omit<OtelExporterConfig, 'provider' | 'exporter'> & {
|
|
5
|
+
/**
|
|
6
|
+
* Arthur API key for authentication.
|
|
7
|
+
* Falls back to ARTHUR_API_KEY environment variable.
|
|
8
|
+
*/
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Arthur platform endpoint (e.g. https://app.arthur.ai).
|
|
12
|
+
* Falls back to ARTHUR_BASE_URL environment variable.
|
|
13
|
+
*/
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Arthur task ID to associate traces with.
|
|
17
|
+
* Falls back to ARTHUR_TASK_ID environment variable.
|
|
18
|
+
* At least one of taskId or serviceName (from the observability config) should be provided.
|
|
19
|
+
*/
|
|
20
|
+
taskId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional headers to be added to each OTLP request.
|
|
23
|
+
* Note: the Authorization header is managed internally when apiKey is provided.
|
|
24
|
+
*/
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Exports Mastra traces to Arthur AI using OpenInference semantic conventions.
|
|
29
|
+
*
|
|
30
|
+
* Supports zero-config setup via environment variables (ARTHUR_API_KEY, ARTHUR_BASE_URL,
|
|
31
|
+
* ARTHUR_TASK_ID) or explicit configuration. Automatically disables itself with a warning
|
|
32
|
+
* when required credentials are missing.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const mastra = new Mastra({
|
|
37
|
+
* observability: new Observability({
|
|
38
|
+
* configs: {
|
|
39
|
+
* arthur: {
|
|
40
|
+
* serviceName: 'my-service',
|
|
41
|
+
* exporters: [new ArthurExporter()],
|
|
42
|
+
* },
|
|
43
|
+
* },
|
|
44
|
+
* }),
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare class ArthurExporter extends OtelExporter {
|
|
49
|
+
name: string;
|
|
50
|
+
private taskId?;
|
|
51
|
+
/**
|
|
52
|
+
* @param config - Arthur exporter configuration. All fields are optional when
|
|
53
|
+
* the corresponding environment variables are set.
|
|
54
|
+
*/
|
|
55
|
+
constructor(config?: ArthurExporterConfig);
|
|
56
|
+
/**
|
|
57
|
+
* Called after construction with the observability config.
|
|
58
|
+
* Validates that traces can be routed to a task via either taskId or serviceName.
|
|
59
|
+
*/
|
|
60
|
+
init(options: InitExporterOptions): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=tracing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAMhE,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,UAAU,CAAC,GAAG;IACrF;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAC,CAAS;IAExB;;;OAGG;gBACS,MAAM,GAAE,oBAAyB;IA+D7C;;;OAGG;IACH,IAAI,CAAC,OAAO,EAAE,mBAAmB;CAiBlC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mastra/arthur",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Arthur AI observability provider for Mastra - exports traces using OpenInference semantic conventions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup --silent --config tsup.config.ts",
|
|
27
|
+
"build:watch": "pnpm build --watch",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest watch",
|
|
30
|
+
"lint": "eslint ."
|
|
31
|
+
},
|
|
32
|
+
"license": "Apache-2.0",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@arizeai/openinference-genai": "0.1.6",
|
|
35
|
+
"@arizeai/openinference-semantic-conventions": "^2.1.7",
|
|
36
|
+
"@mastra/otel-exporter": "workspace:*",
|
|
37
|
+
"@opentelemetry/core": "^2.6.0",
|
|
38
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.205.0",
|
|
39
|
+
"@opentelemetry/sdk-trace-base": "^2.6.0",
|
|
40
|
+
"@opentelemetry/semantic-conventions": "^1.40.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@internal/lint": "workspace:*",
|
|
44
|
+
"@internal/types-builder": "workspace:*",
|
|
45
|
+
"@mastra/core": "workspace:*",
|
|
46
|
+
"@types/node": "22.19.15",
|
|
47
|
+
"@vitest/coverage-v8": "catalog:",
|
|
48
|
+
"@vitest/ui": "catalog:",
|
|
49
|
+
"eslint": "^9.39.4",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "catalog:",
|
|
52
|
+
"vitest": "catalog:"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@mastra/core": ">=1.0.0-0 <2.0.0-0"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://mastra.ai",
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/mastra-ai/mastra.git",
|
|
61
|
+
"directory": "observability/arthur"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/mastra-ai/mastra/issues"
|
|
65
|
+
},
|
|
66
|
+
"engines": {
|
|
67
|
+
"node": ">=22.13.0"
|
|
68
|
+
}
|
|
69
|
+
}
|