@llmops/sdk 0.5.3-beta.1 → 0.5.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-exporter-B5vkJgjX.cjs +212 -0
- package/dist/agents-exporter-BBBF9nsM.mjs +206 -0
- package/dist/agents-exporter-COsWSriQ.d.mts +153 -0
- package/dist/agents-exporter-DhxpM6aO.d.cts +153 -0
- package/dist/agents.cjs +3 -0
- package/dist/agents.d.cts +2 -0
- package/dist/agents.d.mts +2 -0
- package/dist/agents.mjs +3 -0
- package/dist/express.d.cts +2 -2
- package/dist/express.d.mts +2 -2
- package/dist/express.mjs +1 -1
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.mts +1 -1
- package/dist/{index-CoflKbMf.d.mts → index-BJayNg2a.d.mts} +1 -1
- package/dist/{index-DQVdCbkh.d.cts → index-R2T6Kr7h.d.cts} +1 -1
- package/dist/index.cjs +7 -211
- package/dist/index.d.cts +3 -154
- package/dist/index.d.mts +3 -154
- package/dist/index.mjs +7 -211
- package/dist/nextjs.d.cts +1 -1
- package/dist/nextjs.d.mts +1 -1
- package/package.json +13 -3
- /package/dist/{express-Dz3xwxkh.mjs → express-ClEIbLM9.mjs} +0 -0
- /package/dist/{index-BvYAMh37.d.mts → index-BDb3GYHs.d.cts} +0 -0
- /package/dist/{index-DdwqBi1V.d.cts → index-BjzrptNm.d.mts} +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/telemetry/agents-exporter.ts
|
|
3
|
+
function normalizeTraceId(id) {
|
|
4
|
+
return id.startsWith("trace_") ? id.slice(6) : id;
|
|
5
|
+
}
|
|
6
|
+
function normalizeSpanId(id) {
|
|
7
|
+
return id.startsWith("span_") ? id.slice(5) : id;
|
|
8
|
+
}
|
|
9
|
+
function isoToNano(iso) {
|
|
10
|
+
if (!iso) return "0";
|
|
11
|
+
const ms = new Date(iso).getTime();
|
|
12
|
+
if (isNaN(ms)) return "0";
|
|
13
|
+
return (BigInt(ms) * BigInt(1e6)).toString();
|
|
14
|
+
}
|
|
15
|
+
function toOtlpValue(value) {
|
|
16
|
+
if (typeof value === "string") return { stringValue: value };
|
|
17
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
18
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
19
|
+
if (Array.isArray(value)) return { arrayValue: { values: value.map((v) => toOtlpValue(v)) } };
|
|
20
|
+
if (typeof value === "object" && value !== null) try {
|
|
21
|
+
return { stringValue: JSON.stringify(value) };
|
|
22
|
+
} catch {
|
|
23
|
+
return { stringValue: String(value) };
|
|
24
|
+
}
|
|
25
|
+
return { stringValue: String(value) };
|
|
26
|
+
}
|
|
27
|
+
function kv(key, value) {
|
|
28
|
+
return {
|
|
29
|
+
key,
|
|
30
|
+
value: toOtlpValue(value)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Derive a human-readable span name from the span data type.
|
|
35
|
+
*/
|
|
36
|
+
function deriveSpanName(data) {
|
|
37
|
+
switch (data.type) {
|
|
38
|
+
case "agent": return `Agent: ${data.name}`;
|
|
39
|
+
case "function": return `Tool: ${data.name}`;
|
|
40
|
+
case "generation": return data.model ? `Generation: ${data.model}` : "Generation";
|
|
41
|
+
case "response": return "Response";
|
|
42
|
+
case "handoff": return `Handoff: ${data.from_agent ?? "?"} → ${data.to_agent ?? "?"}`;
|
|
43
|
+
case "guardrail": return `Guardrail: ${data.name}`;
|
|
44
|
+
case "custom": return `Custom: ${data.name}`;
|
|
45
|
+
case "transcription": return data.model ? `Transcription: ${data.model}` : "Transcription";
|
|
46
|
+
case "speech": return data.model ? `Speech: ${data.model}` : "Speech";
|
|
47
|
+
case "speech_group": return "Speech Group";
|
|
48
|
+
case "mcp_tools": return `MCP Tools: ${data.server ?? "unknown"}`;
|
|
49
|
+
default: return "Unknown Span";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convert span data fields to OTLP attributes that our UI's getSpanType() recognizes.
|
|
54
|
+
*/
|
|
55
|
+
function convertSpanDataToAttributes(data, error) {
|
|
56
|
+
const attrs = [kv("openai.agents.span_type", data.type)];
|
|
57
|
+
switch (data.type) {
|
|
58
|
+
case "generation":
|
|
59
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
60
|
+
if (data.model) {
|
|
61
|
+
attrs.push(kv("gen_ai.request.model", data.model));
|
|
62
|
+
attrs.push(kv("gen_ai.system", "openai"));
|
|
63
|
+
}
|
|
64
|
+
if (data.usage?.input_tokens != null) attrs.push(kv("gen_ai.usage.input_tokens", data.usage.input_tokens));
|
|
65
|
+
if (data.usage?.output_tokens != null) attrs.push(kv("gen_ai.usage.output_tokens", data.usage.output_tokens));
|
|
66
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", JSON.stringify(data.input)));
|
|
67
|
+
if (data.output) attrs.push(kv("gen_ai.completion", JSON.stringify(data.output)));
|
|
68
|
+
if (data.model_config) attrs.push(kv("gen_ai.request.model_config", JSON.stringify(data.model_config)));
|
|
69
|
+
break;
|
|
70
|
+
case "agent":
|
|
71
|
+
attrs.push(kv("openai.agents.agent.name", data.name));
|
|
72
|
+
if (data.tools?.length) attrs.push(kv("openai.agents.agent.tools", JSON.stringify(data.tools)));
|
|
73
|
+
if (data.handoffs?.length) attrs.push(kv("openai.agents.agent.handoffs", JSON.stringify(data.handoffs)));
|
|
74
|
+
if (data.output_type) attrs.push(kv("openai.agents.agent.output_type", data.output_type));
|
|
75
|
+
break;
|
|
76
|
+
case "function":
|
|
77
|
+
attrs.push(kv("gen_ai.tool.name", data.name));
|
|
78
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
79
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
80
|
+
if (data.mcp_data) attrs.push(kv("openai.agents.function.mcp_data", data.mcp_data));
|
|
81
|
+
break;
|
|
82
|
+
case "handoff":
|
|
83
|
+
if (data.from_agent) attrs.push(kv("openai.agents.handoff.from_agent", data.from_agent));
|
|
84
|
+
if (data.to_agent) attrs.push(kv("openai.agents.handoff.to_agent", data.to_agent));
|
|
85
|
+
break;
|
|
86
|
+
case "guardrail":
|
|
87
|
+
attrs.push(kv("llmops.guardrail.action", data.triggered ? "triggered" : "passed"));
|
|
88
|
+
attrs.push(kv("openai.agents.guardrail.name", data.name));
|
|
89
|
+
break;
|
|
90
|
+
case "response":
|
|
91
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
92
|
+
if (data.response_id) attrs.push(kv("openai.agents.response.id", data.response_id));
|
|
93
|
+
break;
|
|
94
|
+
case "custom":
|
|
95
|
+
attrs.push(kv("openai.agents.custom.name", data.name));
|
|
96
|
+
attrs.push(kv("openai.agents.custom.data", JSON.stringify(data.data)));
|
|
97
|
+
break;
|
|
98
|
+
case "transcription":
|
|
99
|
+
attrs.push(kv("gen_ai.operation.name", "transcription"));
|
|
100
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
101
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
102
|
+
break;
|
|
103
|
+
case "speech":
|
|
104
|
+
attrs.push(kv("gen_ai.operation.name", "speech"));
|
|
105
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
106
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
107
|
+
break;
|
|
108
|
+
case "speech_group":
|
|
109
|
+
if (data.input) attrs.push(kv("openai.agents.speech_group.input", data.input));
|
|
110
|
+
break;
|
|
111
|
+
case "mcp_tools":
|
|
112
|
+
attrs.push(kv("gen_ai.tool.name", `mcp:${data.server ?? "unknown"}`));
|
|
113
|
+
if (data.result) attrs.push(kv("openai.agents.mcp.tools", JSON.stringify(data.result)));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (error) {
|
|
117
|
+
attrs.push(kv("error.message", error.message));
|
|
118
|
+
if (error.data) attrs.push(kv("error.data", JSON.stringify(error.data)));
|
|
119
|
+
}
|
|
120
|
+
return attrs;
|
|
121
|
+
}
|
|
122
|
+
function buildResourceSpans(traceId, trace, spans) {
|
|
123
|
+
const resourceAttrs = [kv("service.name", "@openai/agents")];
|
|
124
|
+
if (trace) {
|
|
125
|
+
resourceAttrs.push(kv("openai.agents.trace.name", trace.name));
|
|
126
|
+
if (trace.groupId) resourceAttrs.push(kv("openai.agents.trace.group_id", trace.groupId));
|
|
127
|
+
if (trace.metadata && Object.keys(trace.metadata).length > 0) resourceAttrs.push(kv("openai.agents.trace.metadata", JSON.stringify(trace.metadata)));
|
|
128
|
+
}
|
|
129
|
+
const normalizedTraceId = normalizeTraceId(traceId);
|
|
130
|
+
return {
|
|
131
|
+
resource: { attributes: resourceAttrs },
|
|
132
|
+
scopeSpans: [{
|
|
133
|
+
scope: { name: "@llmops/agents-exporter" },
|
|
134
|
+
spans: spans.map((span) => {
|
|
135
|
+
const isError = !!span.error;
|
|
136
|
+
return {
|
|
137
|
+
traceId: normalizedTraceId,
|
|
138
|
+
spanId: normalizeSpanId(span.spanId),
|
|
139
|
+
parentSpanId: span.parentId ? normalizeSpanId(span.parentId) : void 0,
|
|
140
|
+
name: deriveSpanName(span.spanData),
|
|
141
|
+
kind: 1,
|
|
142
|
+
startTimeUnixNano: isoToNano(span.startedAt),
|
|
143
|
+
endTimeUnixNano: isoToNano(span.endedAt),
|
|
144
|
+
attributes: convertSpanDataToAttributes(span.spanData, span.error),
|
|
145
|
+
events: [],
|
|
146
|
+
status: isError ? {
|
|
147
|
+
code: 2,
|
|
148
|
+
message: span.error.message
|
|
149
|
+
} : { code: 1 }
|
|
150
|
+
};
|
|
151
|
+
})
|
|
152
|
+
}]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create a TracingExporter for @openai/agents that sends traces to LLMOps.
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* ```typescript
|
|
160
|
+
* import { createLLMOpsAgentsExporter } from '@llmops/sdk';
|
|
161
|
+
* import { setTraceProcessors, BatchTraceProcessor } from '@openai/agents';
|
|
162
|
+
*
|
|
163
|
+
* setTraceProcessors([
|
|
164
|
+
* new BatchTraceProcessor(createLLMOpsAgentsExporter({
|
|
165
|
+
* baseURL: 'http://localhost:5177',
|
|
166
|
+
* apiKey: process.env.LLMOPS_API_KEY!,
|
|
167
|
+
* }))
|
|
168
|
+
* ]);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
function createLLMOpsAgentsExporter(config) {
|
|
172
|
+
const url = `${config.baseURL.replace(/\/$/, "")}/api/otlp/v1/traces`;
|
|
173
|
+
return { async export(items, signal) {
|
|
174
|
+
const traces = [];
|
|
175
|
+
const spans = [];
|
|
176
|
+
for (const item of items) if (item.type === "trace") traces.push(item);
|
|
177
|
+
else spans.push(item);
|
|
178
|
+
if (spans.length === 0) return;
|
|
179
|
+
const traceMap = /* @__PURE__ */ new Map();
|
|
180
|
+
for (const t of traces) traceMap.set(t.traceId, t);
|
|
181
|
+
const spansByTrace = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const s of spans) {
|
|
183
|
+
const group = spansByTrace.get(s.traceId) ?? [];
|
|
184
|
+
group.push(s);
|
|
185
|
+
spansByTrace.set(s.traceId, group);
|
|
186
|
+
}
|
|
187
|
+
const resourceSpans = [];
|
|
188
|
+
for (const [traceId, traceSpans] of spansByTrace) {
|
|
189
|
+
const trace = traceMap.get(traceId);
|
|
190
|
+
resourceSpans.push(buildResourceSpans(traceId, trace, traceSpans));
|
|
191
|
+
}
|
|
192
|
+
if (resourceSpans.length === 0) return;
|
|
193
|
+
await fetch(url, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: {
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
198
|
+
...config.headers ?? {}
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({ resourceSpans }),
|
|
201
|
+
signal
|
|
202
|
+
});
|
|
203
|
+
} };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//#endregion
|
|
207
|
+
Object.defineProperty(exports, 'createLLMOpsAgentsExporter', {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
get: function () {
|
|
210
|
+
return createLLMOpsAgentsExporter;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//#region src/telemetry/agents-exporter.ts
|
|
2
|
+
function normalizeTraceId(id) {
|
|
3
|
+
return id.startsWith("trace_") ? id.slice(6) : id;
|
|
4
|
+
}
|
|
5
|
+
function normalizeSpanId(id) {
|
|
6
|
+
return id.startsWith("span_") ? id.slice(5) : id;
|
|
7
|
+
}
|
|
8
|
+
function isoToNano(iso) {
|
|
9
|
+
if (!iso) return "0";
|
|
10
|
+
const ms = new Date(iso).getTime();
|
|
11
|
+
if (isNaN(ms)) return "0";
|
|
12
|
+
return (BigInt(ms) * BigInt(1e6)).toString();
|
|
13
|
+
}
|
|
14
|
+
function toOtlpValue(value) {
|
|
15
|
+
if (typeof value === "string") return { stringValue: value };
|
|
16
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
17
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
18
|
+
if (Array.isArray(value)) return { arrayValue: { values: value.map((v) => toOtlpValue(v)) } };
|
|
19
|
+
if (typeof value === "object" && value !== null) try {
|
|
20
|
+
return { stringValue: JSON.stringify(value) };
|
|
21
|
+
} catch {
|
|
22
|
+
return { stringValue: String(value) };
|
|
23
|
+
}
|
|
24
|
+
return { stringValue: String(value) };
|
|
25
|
+
}
|
|
26
|
+
function kv(key, value) {
|
|
27
|
+
return {
|
|
28
|
+
key,
|
|
29
|
+
value: toOtlpValue(value)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Derive a human-readable span name from the span data type.
|
|
34
|
+
*/
|
|
35
|
+
function deriveSpanName(data) {
|
|
36
|
+
switch (data.type) {
|
|
37
|
+
case "agent": return `Agent: ${data.name}`;
|
|
38
|
+
case "function": return `Tool: ${data.name}`;
|
|
39
|
+
case "generation": return data.model ? `Generation: ${data.model}` : "Generation";
|
|
40
|
+
case "response": return "Response";
|
|
41
|
+
case "handoff": return `Handoff: ${data.from_agent ?? "?"} → ${data.to_agent ?? "?"}`;
|
|
42
|
+
case "guardrail": return `Guardrail: ${data.name}`;
|
|
43
|
+
case "custom": return `Custom: ${data.name}`;
|
|
44
|
+
case "transcription": return data.model ? `Transcription: ${data.model}` : "Transcription";
|
|
45
|
+
case "speech": return data.model ? `Speech: ${data.model}` : "Speech";
|
|
46
|
+
case "speech_group": return "Speech Group";
|
|
47
|
+
case "mcp_tools": return `MCP Tools: ${data.server ?? "unknown"}`;
|
|
48
|
+
default: return "Unknown Span";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Convert span data fields to OTLP attributes that our UI's getSpanType() recognizes.
|
|
53
|
+
*/
|
|
54
|
+
function convertSpanDataToAttributes(data, error) {
|
|
55
|
+
const attrs = [kv("openai.agents.span_type", data.type)];
|
|
56
|
+
switch (data.type) {
|
|
57
|
+
case "generation":
|
|
58
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
59
|
+
if (data.model) {
|
|
60
|
+
attrs.push(kv("gen_ai.request.model", data.model));
|
|
61
|
+
attrs.push(kv("gen_ai.system", "openai"));
|
|
62
|
+
}
|
|
63
|
+
if (data.usage?.input_tokens != null) attrs.push(kv("gen_ai.usage.input_tokens", data.usage.input_tokens));
|
|
64
|
+
if (data.usage?.output_tokens != null) attrs.push(kv("gen_ai.usage.output_tokens", data.usage.output_tokens));
|
|
65
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", JSON.stringify(data.input)));
|
|
66
|
+
if (data.output) attrs.push(kv("gen_ai.completion", JSON.stringify(data.output)));
|
|
67
|
+
if (data.model_config) attrs.push(kv("gen_ai.request.model_config", JSON.stringify(data.model_config)));
|
|
68
|
+
break;
|
|
69
|
+
case "agent":
|
|
70
|
+
attrs.push(kv("openai.agents.agent.name", data.name));
|
|
71
|
+
if (data.tools?.length) attrs.push(kv("openai.agents.agent.tools", JSON.stringify(data.tools)));
|
|
72
|
+
if (data.handoffs?.length) attrs.push(kv("openai.agents.agent.handoffs", JSON.stringify(data.handoffs)));
|
|
73
|
+
if (data.output_type) attrs.push(kv("openai.agents.agent.output_type", data.output_type));
|
|
74
|
+
break;
|
|
75
|
+
case "function":
|
|
76
|
+
attrs.push(kv("gen_ai.tool.name", data.name));
|
|
77
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
78
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
79
|
+
if (data.mcp_data) attrs.push(kv("openai.agents.function.mcp_data", data.mcp_data));
|
|
80
|
+
break;
|
|
81
|
+
case "handoff":
|
|
82
|
+
if (data.from_agent) attrs.push(kv("openai.agents.handoff.from_agent", data.from_agent));
|
|
83
|
+
if (data.to_agent) attrs.push(kv("openai.agents.handoff.to_agent", data.to_agent));
|
|
84
|
+
break;
|
|
85
|
+
case "guardrail":
|
|
86
|
+
attrs.push(kv("llmops.guardrail.action", data.triggered ? "triggered" : "passed"));
|
|
87
|
+
attrs.push(kv("openai.agents.guardrail.name", data.name));
|
|
88
|
+
break;
|
|
89
|
+
case "response":
|
|
90
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
91
|
+
if (data.response_id) attrs.push(kv("openai.agents.response.id", data.response_id));
|
|
92
|
+
break;
|
|
93
|
+
case "custom":
|
|
94
|
+
attrs.push(kv("openai.agents.custom.name", data.name));
|
|
95
|
+
attrs.push(kv("openai.agents.custom.data", JSON.stringify(data.data)));
|
|
96
|
+
break;
|
|
97
|
+
case "transcription":
|
|
98
|
+
attrs.push(kv("gen_ai.operation.name", "transcription"));
|
|
99
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
100
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
101
|
+
break;
|
|
102
|
+
case "speech":
|
|
103
|
+
attrs.push(kv("gen_ai.operation.name", "speech"));
|
|
104
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
105
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
106
|
+
break;
|
|
107
|
+
case "speech_group":
|
|
108
|
+
if (data.input) attrs.push(kv("openai.agents.speech_group.input", data.input));
|
|
109
|
+
break;
|
|
110
|
+
case "mcp_tools":
|
|
111
|
+
attrs.push(kv("gen_ai.tool.name", `mcp:${data.server ?? "unknown"}`));
|
|
112
|
+
if (data.result) attrs.push(kv("openai.agents.mcp.tools", JSON.stringify(data.result)));
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (error) {
|
|
116
|
+
attrs.push(kv("error.message", error.message));
|
|
117
|
+
if (error.data) attrs.push(kv("error.data", JSON.stringify(error.data)));
|
|
118
|
+
}
|
|
119
|
+
return attrs;
|
|
120
|
+
}
|
|
121
|
+
function buildResourceSpans(traceId, trace, spans) {
|
|
122
|
+
const resourceAttrs = [kv("service.name", "@openai/agents")];
|
|
123
|
+
if (trace) {
|
|
124
|
+
resourceAttrs.push(kv("openai.agents.trace.name", trace.name));
|
|
125
|
+
if (trace.groupId) resourceAttrs.push(kv("openai.agents.trace.group_id", trace.groupId));
|
|
126
|
+
if (trace.metadata && Object.keys(trace.metadata).length > 0) resourceAttrs.push(kv("openai.agents.trace.metadata", JSON.stringify(trace.metadata)));
|
|
127
|
+
}
|
|
128
|
+
const normalizedTraceId = normalizeTraceId(traceId);
|
|
129
|
+
return {
|
|
130
|
+
resource: { attributes: resourceAttrs },
|
|
131
|
+
scopeSpans: [{
|
|
132
|
+
scope: { name: "@llmops/agents-exporter" },
|
|
133
|
+
spans: spans.map((span) => {
|
|
134
|
+
const isError = !!span.error;
|
|
135
|
+
return {
|
|
136
|
+
traceId: normalizedTraceId,
|
|
137
|
+
spanId: normalizeSpanId(span.spanId),
|
|
138
|
+
parentSpanId: span.parentId ? normalizeSpanId(span.parentId) : void 0,
|
|
139
|
+
name: deriveSpanName(span.spanData),
|
|
140
|
+
kind: 1,
|
|
141
|
+
startTimeUnixNano: isoToNano(span.startedAt),
|
|
142
|
+
endTimeUnixNano: isoToNano(span.endedAt),
|
|
143
|
+
attributes: convertSpanDataToAttributes(span.spanData, span.error),
|
|
144
|
+
events: [],
|
|
145
|
+
status: isError ? {
|
|
146
|
+
code: 2,
|
|
147
|
+
message: span.error.message
|
|
148
|
+
} : { code: 1 }
|
|
149
|
+
};
|
|
150
|
+
})
|
|
151
|
+
}]
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Create a TracingExporter for @openai/agents that sends traces to LLMOps.
|
|
156
|
+
*
|
|
157
|
+
* Usage:
|
|
158
|
+
* ```typescript
|
|
159
|
+
* import { createLLMOpsAgentsExporter } from '@llmops/sdk';
|
|
160
|
+
* import { setTraceProcessors, BatchTraceProcessor } from '@openai/agents';
|
|
161
|
+
*
|
|
162
|
+
* setTraceProcessors([
|
|
163
|
+
* new BatchTraceProcessor(createLLMOpsAgentsExporter({
|
|
164
|
+
* baseURL: 'http://localhost:5177',
|
|
165
|
+
* apiKey: process.env.LLMOPS_API_KEY!,
|
|
166
|
+
* }))
|
|
167
|
+
* ]);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
function createLLMOpsAgentsExporter(config) {
|
|
171
|
+
const url = `${config.baseURL.replace(/\/$/, "")}/api/otlp/v1/traces`;
|
|
172
|
+
return { async export(items, signal) {
|
|
173
|
+
const traces = [];
|
|
174
|
+
const spans = [];
|
|
175
|
+
for (const item of items) if (item.type === "trace") traces.push(item);
|
|
176
|
+
else spans.push(item);
|
|
177
|
+
if (spans.length === 0) return;
|
|
178
|
+
const traceMap = /* @__PURE__ */ new Map();
|
|
179
|
+
for (const t of traces) traceMap.set(t.traceId, t);
|
|
180
|
+
const spansByTrace = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const s of spans) {
|
|
182
|
+
const group = spansByTrace.get(s.traceId) ?? [];
|
|
183
|
+
group.push(s);
|
|
184
|
+
spansByTrace.set(s.traceId, group);
|
|
185
|
+
}
|
|
186
|
+
const resourceSpans = [];
|
|
187
|
+
for (const [traceId, traceSpans] of spansByTrace) {
|
|
188
|
+
const trace = traceMap.get(traceId);
|
|
189
|
+
resourceSpans.push(buildResourceSpans(traceId, trace, traceSpans));
|
|
190
|
+
}
|
|
191
|
+
if (resourceSpans.length === 0) return;
|
|
192
|
+
await fetch(url, {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: {
|
|
195
|
+
"Content-Type": "application/json",
|
|
196
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
197
|
+
...config.headers ?? {}
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({ resourceSpans }),
|
|
200
|
+
signal
|
|
201
|
+
});
|
|
202
|
+
} };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
export { createLLMOpsAgentsExporter as t };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
//#region src/telemetry/agents-exporter.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* LLMOps Tracing Exporter for @openai/agents
|
|
4
|
+
*
|
|
5
|
+
* Implements the TracingExporter interface from @openai/agents-core, converting
|
|
6
|
+
* the agents framework's Trace/Span format to OTLP JSON and sending it to
|
|
7
|
+
* the LLMOps OTLP ingestion endpoint.
|
|
8
|
+
*
|
|
9
|
+
* No dependency on @openai/agents — types are defined inline using structural
|
|
10
|
+
* compatibility (same pattern as the OTel exporter).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createLLMOpsAgentsExporter } from '@llmops/sdk';
|
|
15
|
+
* import { setTraceProcessors, BatchTraceProcessor } from '@openai/agents';
|
|
16
|
+
*
|
|
17
|
+
* const exporter = createLLMOpsAgentsExporter({
|
|
18
|
+
* baseURL: 'http://localhost:5177',
|
|
19
|
+
* apiKey: process.env.LLMOPS_API_KEY!,
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* setTraceProcessors([new BatchTraceProcessor(exporter)]);
|
|
23
|
+
*
|
|
24
|
+
* // All @openai/agents traces now flow to LLMOps automatically
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
/** Matches TracingExporter from @openai/agents-core */
|
|
28
|
+
interface AgentsTracingExporter {
|
|
29
|
+
export(items: (AgentsTrace | AgentsSpan)[], signal?: AbortSignal): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** Matches Trace from @openai/agents-core */
|
|
32
|
+
interface AgentsTrace {
|
|
33
|
+
type: 'trace';
|
|
34
|
+
traceId: string;
|
|
35
|
+
name: string;
|
|
36
|
+
groupId?: string | null;
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/** Matches SpanError from @openai/agents-core */
|
|
40
|
+
interface AgentsSpanError {
|
|
41
|
+
message: string;
|
|
42
|
+
data?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
/** Matches Span from @openai/agents-core */
|
|
45
|
+
interface AgentsSpan {
|
|
46
|
+
type: 'trace.span';
|
|
47
|
+
spanId: string;
|
|
48
|
+
traceId: string;
|
|
49
|
+
parentId?: string | null;
|
|
50
|
+
startedAt?: string | null;
|
|
51
|
+
endedAt?: string | null;
|
|
52
|
+
error?: AgentsSpanError | null;
|
|
53
|
+
spanData: AgentsSpanData;
|
|
54
|
+
}
|
|
55
|
+
type AgentSpanData = {
|
|
56
|
+
type: 'agent';
|
|
57
|
+
name: string;
|
|
58
|
+
handoffs?: string[];
|
|
59
|
+
tools?: string[];
|
|
60
|
+
output_type?: string;
|
|
61
|
+
};
|
|
62
|
+
type FunctionSpanData = {
|
|
63
|
+
type: 'function';
|
|
64
|
+
name: string;
|
|
65
|
+
input: string;
|
|
66
|
+
output: string;
|
|
67
|
+
mcp_data?: string;
|
|
68
|
+
};
|
|
69
|
+
type GenerationUsageData = {
|
|
70
|
+
input_tokens?: number;
|
|
71
|
+
output_tokens?: number;
|
|
72
|
+
details?: Record<string, unknown> | null;
|
|
73
|
+
[key: string]: unknown;
|
|
74
|
+
};
|
|
75
|
+
type GenerationSpanData = {
|
|
76
|
+
type: 'generation';
|
|
77
|
+
input?: Record<string, unknown>[];
|
|
78
|
+
output?: Record<string, unknown>[];
|
|
79
|
+
model?: string;
|
|
80
|
+
model_config?: Record<string, unknown>;
|
|
81
|
+
usage?: GenerationUsageData;
|
|
82
|
+
};
|
|
83
|
+
type ResponseSpanData = {
|
|
84
|
+
type: 'response';
|
|
85
|
+
response_id?: string;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
};
|
|
88
|
+
type HandoffSpanData = {
|
|
89
|
+
type: 'handoff';
|
|
90
|
+
from_agent?: string;
|
|
91
|
+
to_agent?: string;
|
|
92
|
+
};
|
|
93
|
+
type CustomSpanData = {
|
|
94
|
+
type: 'custom';
|
|
95
|
+
name: string;
|
|
96
|
+
data: Record<string, unknown>;
|
|
97
|
+
};
|
|
98
|
+
type GuardrailSpanData = {
|
|
99
|
+
type: 'guardrail';
|
|
100
|
+
name: string;
|
|
101
|
+
triggered: boolean;
|
|
102
|
+
};
|
|
103
|
+
type TranscriptionSpanData = {
|
|
104
|
+
type: 'transcription';
|
|
105
|
+
input: unknown;
|
|
106
|
+
output?: string;
|
|
107
|
+
model?: string;
|
|
108
|
+
model_config?: Record<string, unknown>;
|
|
109
|
+
};
|
|
110
|
+
type SpeechSpanData = {
|
|
111
|
+
type: 'speech';
|
|
112
|
+
input?: string;
|
|
113
|
+
output: unknown;
|
|
114
|
+
model?: string;
|
|
115
|
+
model_config?: Record<string, unknown>;
|
|
116
|
+
};
|
|
117
|
+
type SpeechGroupSpanData = {
|
|
118
|
+
type: 'speech_group';
|
|
119
|
+
input?: string;
|
|
120
|
+
};
|
|
121
|
+
type MCPListToolsSpanData = {
|
|
122
|
+
type: 'mcp_tools';
|
|
123
|
+
server?: string;
|
|
124
|
+
result?: string[];
|
|
125
|
+
};
|
|
126
|
+
type AgentsSpanData = AgentSpanData | FunctionSpanData | GenerationSpanData | ResponseSpanData | HandoffSpanData | CustomSpanData | GuardrailSpanData | TranscriptionSpanData | SpeechSpanData | SpeechGroupSpanData | MCPListToolsSpanData;
|
|
127
|
+
interface LLMOpsAgentsExporterConfig {
|
|
128
|
+
/** LLMOps server base URL (e.g. http://localhost:5177) */
|
|
129
|
+
baseURL: string;
|
|
130
|
+
/** Environment secret or API key for authentication */
|
|
131
|
+
apiKey: string;
|
|
132
|
+
/** Custom headers to include in requests */
|
|
133
|
+
headers?: Record<string, string>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Create a TracingExporter for @openai/agents that sends traces to LLMOps.
|
|
137
|
+
*
|
|
138
|
+
* Usage:
|
|
139
|
+
* ```typescript
|
|
140
|
+
* import { createLLMOpsAgentsExporter } from '@llmops/sdk';
|
|
141
|
+
* import { setTraceProcessors, BatchTraceProcessor } from '@openai/agents';
|
|
142
|
+
*
|
|
143
|
+
* setTraceProcessors([
|
|
144
|
+
* new BatchTraceProcessor(createLLMOpsAgentsExporter({
|
|
145
|
+
* baseURL: 'http://localhost:5177',
|
|
146
|
+
* apiKey: process.env.LLMOPS_API_KEY!,
|
|
147
|
+
* }))
|
|
148
|
+
* ]);
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function createLLMOpsAgentsExporter(config: LLMOpsAgentsExporterConfig): AgentsTracingExporter;
|
|
152
|
+
//#endregion
|
|
153
|
+
export { AgentsTracingExporter as a, AgentsTrace as i, AgentsSpanData as n, LLMOpsAgentsExporterConfig as o, AgentsSpanError as r, createLLMOpsAgentsExporter as s, AgentsSpan as t };
|