@llmops/sdk 0.5.2 → 0.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/express.d.cts +2 -2
- package/dist/express.d.mts +2 -2
- package/dist/hono.d.cts +1 -1
- package/dist/hono.d.mts +1 -1
- package/dist/{index-BkLZoEW_.d.mts → index-BvYAMh37.d.mts} +10 -2
- package/dist/{index-CHeoSGK0.d.cts → index-CoflKbMf.d.mts} +1 -1
- package/dist/{index-bdFT7Yxj.d.mts → index-DQVdCbkh.d.cts} +1 -1
- package/dist/{index-De3QdpKX.d.cts → index-DdwqBi1V.d.cts} +10 -2
- package/dist/index.cjs +350 -8
- package/dist/index.d.cts +244 -3
- package/dist/index.d.mts +244 -3
- package/dist/index.mjs +349 -9
- package/dist/nextjs.d.cts +1 -1
- package/dist/nextjs.d.mts +1 -1
- package/package.json +3 -3
package/dist/express.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./index-
|
|
2
|
-
import { t as createLLMOpsMiddleware } from "./index-
|
|
1
|
+
import "./index-DdwqBi1V.cjs";
|
|
2
|
+
import { t as createLLMOpsMiddleware } from "./index-DQVdCbkh.cjs";
|
|
3
3
|
export { createLLMOpsMiddleware };
|
package/dist/express.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./index-
|
|
2
|
-
import { t as createLLMOpsMiddleware } from "./index-
|
|
1
|
+
import "./index-BvYAMh37.mjs";
|
|
2
|
+
import { t as createLLMOpsMiddleware } from "./index-CoflKbMf.mjs";
|
|
3
3
|
export { createLLMOpsMiddleware };
|
package/dist/hono.d.cts
CHANGED
package/dist/hono.d.mts
CHANGED
|
@@ -6,11 +6,19 @@ type ProviderConfig = {
|
|
|
6
6
|
apiKey: string;
|
|
7
7
|
fetch: typeof globalThis.fetch;
|
|
8
8
|
};
|
|
9
|
+
type TraceContext = {
|
|
10
|
+
traceId?: string;
|
|
11
|
+
spanName?: string;
|
|
12
|
+
traceName?: string;
|
|
13
|
+
};
|
|
14
|
+
type ProviderOptions = {
|
|
15
|
+
traceContext?: () => TraceContext | null;
|
|
16
|
+
};
|
|
9
17
|
type LLMOpsClient = {
|
|
10
18
|
handler: (request: Request) => Promise<Response>;
|
|
11
19
|
config: ValidatedLLMOpsConfig;
|
|
12
|
-
provider: () => ProviderConfig;
|
|
20
|
+
provider: (options?: ProviderOptions) => ProviderConfig;
|
|
13
21
|
};
|
|
14
22
|
declare const createLLMOps: (config?: LLMOpsConfig) => LLMOpsClient;
|
|
15
23
|
//#endregion
|
|
16
|
-
export { createLLMOps as n, LLMOpsClient as t };
|
|
24
|
+
export { createLLMOps as i, ProviderOptions as n, TraceContext as r, LLMOpsClient as t };
|
|
@@ -6,11 +6,19 @@ type ProviderConfig = {
|
|
|
6
6
|
apiKey: string;
|
|
7
7
|
fetch: typeof globalThis.fetch;
|
|
8
8
|
};
|
|
9
|
+
type TraceContext = {
|
|
10
|
+
traceId?: string;
|
|
11
|
+
spanName?: string;
|
|
12
|
+
traceName?: string;
|
|
13
|
+
};
|
|
14
|
+
type ProviderOptions = {
|
|
15
|
+
traceContext?: () => TraceContext | null;
|
|
16
|
+
};
|
|
9
17
|
type LLMOpsClient = {
|
|
10
18
|
handler: (request: Request) => Promise<Response>;
|
|
11
19
|
config: ValidatedLLMOpsConfig;
|
|
12
|
-
provider: () => ProviderConfig;
|
|
20
|
+
provider: (options?: ProviderOptions) => ProviderConfig;
|
|
13
21
|
};
|
|
14
22
|
declare const createLLMOps: (config?: LLMOpsConfig) => LLMOpsClient;
|
|
15
23
|
//#endregion
|
|
16
|
-
export { createLLMOps as n, LLMOpsClient as t };
|
|
24
|
+
export { createLLMOps as i, ProviderOptions as n, TraceContext as r, LLMOpsClient as t };
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_express = require('./express-B-wbCza5.cjs');
|
|
2
|
-
require("@llmops/core");
|
|
2
|
+
let __llmops_core = require("@llmops/core");
|
|
3
3
|
let __llmops_app = require("@llmops/app");
|
|
4
4
|
|
|
5
5
|
//#region src/lib/auth/client.ts
|
|
@@ -19,24 +19,366 @@ const createLLMOps = (config) => {
|
|
|
19
19
|
const { app, config: validatedConfig } = (0, __llmops_app.createApp)(config);
|
|
20
20
|
const handler = async (req) => app.fetch(req, void 0, void 0);
|
|
21
21
|
const basePath = validatedConfig.basePath;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const createInternalFetch = (getTraceContext) => {
|
|
23
|
+
return (input, init) => {
|
|
24
|
+
const request = new Request(input, init);
|
|
25
|
+
const url = new URL(request.url);
|
|
26
|
+
if (basePath && basePath !== "/" && url.pathname.startsWith(basePath)) url.pathname = url.pathname.slice(basePath.length) || "/";
|
|
27
|
+
if (getTraceContext) {
|
|
28
|
+
const ctx = getTraceContext();
|
|
29
|
+
if (ctx) {
|
|
30
|
+
const headers = new Headers(request.headers);
|
|
31
|
+
if (ctx.traceId) headers.set(__llmops_core.LLMOPS_TRACE_ID_HEADER, ctx.traceId);
|
|
32
|
+
if (ctx.traceName) headers.set(__llmops_core.LLMOPS_TRACE_NAME_HEADER, ctx.traceName);
|
|
33
|
+
if (ctx.spanName) headers.set(__llmops_core.LLMOPS_SPAN_NAME_HEADER, ctx.spanName);
|
|
34
|
+
return handler(new Request(url.toString(), {
|
|
35
|
+
method: request.method,
|
|
36
|
+
headers,
|
|
37
|
+
body: request.body,
|
|
38
|
+
duplex: "half"
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return handler(new Request(url.toString(), request));
|
|
43
|
+
};
|
|
27
44
|
};
|
|
28
45
|
return {
|
|
29
46
|
handler,
|
|
30
47
|
config: Object.freeze(validatedConfig),
|
|
31
|
-
provider: () => ({
|
|
48
|
+
provider: (options) => ({
|
|
32
49
|
baseURL: `http://localhost${basePath}/api/genai/v1`,
|
|
33
50
|
apiKey: "llmops",
|
|
34
|
-
fetch:
|
|
51
|
+
fetch: createInternalFetch(options?.traceContext)
|
|
35
52
|
})
|
|
36
53
|
};
|
|
37
54
|
};
|
|
38
55
|
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/telemetry/exporter.ts
|
|
58
|
+
let ExportResultCode = /* @__PURE__ */ function(ExportResultCode$1) {
|
|
59
|
+
ExportResultCode$1[ExportResultCode$1["SUCCESS"] = 0] = "SUCCESS";
|
|
60
|
+
ExportResultCode$1[ExportResultCode$1["FAILED"] = 1] = "FAILED";
|
|
61
|
+
return ExportResultCode$1;
|
|
62
|
+
}({});
|
|
63
|
+
/**
|
|
64
|
+
* Convert a value to OTLP attribute value format
|
|
65
|
+
*/
|
|
66
|
+
function toOtlpValue$1(value) {
|
|
67
|
+
if (typeof value === "string") return { stringValue: value };
|
|
68
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
69
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
70
|
+
if (Array.isArray(value)) return { arrayValue: { values: value.map((v) => toOtlpValue$1(v)) } };
|
|
71
|
+
if (typeof value === "object" && value !== null) try {
|
|
72
|
+
return { stringValue: JSON.stringify(value) };
|
|
73
|
+
} catch {
|
|
74
|
+
return { stringValue: String(value) };
|
|
75
|
+
}
|
|
76
|
+
return { stringValue: String(value) };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert HrTime [seconds, nanoseconds] to nanosecond string
|
|
80
|
+
*/
|
|
81
|
+
function hrTimeToNano(hrTime) {
|
|
82
|
+
return (BigInt(hrTime[0]) * BigInt(1e9) + BigInt(hrTime[1])).toString();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create an OTel SpanExporter that sends spans to an LLMOps server.
|
|
86
|
+
*/
|
|
87
|
+
function createLLMOpsSpanExporter(config) {
|
|
88
|
+
const url = `${config.baseURL.replace(/\/$/, "")}/api/otlp/v1/traces`;
|
|
89
|
+
return {
|
|
90
|
+
export(spans, resultCallback) {
|
|
91
|
+
const resourceMap = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const span of spans) {
|
|
93
|
+
const resourceKey = JSON.stringify(span.resource.attributes);
|
|
94
|
+
const group = resourceMap.get(resourceKey) ?? [];
|
|
95
|
+
group.push(span);
|
|
96
|
+
resourceMap.set(resourceKey, group);
|
|
97
|
+
}
|
|
98
|
+
const body = { resourceSpans: Array.from(resourceMap.entries()).map(([resourceKey, group]) => {
|
|
99
|
+
const resourceAttrs = JSON.parse(resourceKey);
|
|
100
|
+
const scopeMap = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const span of group) {
|
|
102
|
+
const scopeKey = `${span.instrumentationLibrary.name}:${span.instrumentationLibrary.version ?? ""}`;
|
|
103
|
+
const scopeGroup = scopeMap.get(scopeKey) ?? [];
|
|
104
|
+
scopeGroup.push(span);
|
|
105
|
+
scopeMap.set(scopeKey, scopeGroup);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
resource: { attributes: Object.entries(resourceAttrs).filter(([, v]) => v !== void 0 && v !== null).map(([key, value]) => ({
|
|
109
|
+
key,
|
|
110
|
+
value: toOtlpValue$1(value)
|
|
111
|
+
})) },
|
|
112
|
+
scopeSpans: Array.from(scopeMap.entries()).map(([, scopeGroup]) => ({
|
|
113
|
+
scope: {
|
|
114
|
+
name: scopeGroup[0].instrumentationLibrary.name,
|
|
115
|
+
version: scopeGroup[0].instrumentationLibrary.version ?? void 0
|
|
116
|
+
},
|
|
117
|
+
spans: scopeGroup.map((span) => {
|
|
118
|
+
const ctx = span.spanContext();
|
|
119
|
+
return {
|
|
120
|
+
traceId: ctx.traceId,
|
|
121
|
+
spanId: ctx.spanId,
|
|
122
|
+
parentSpanId: span.parentSpanId || void 0,
|
|
123
|
+
name: span.name,
|
|
124
|
+
kind: span.kind,
|
|
125
|
+
startTimeUnixNano: hrTimeToNano(span.startTime),
|
|
126
|
+
endTimeUnixNano: hrTimeToNano(span.endTime),
|
|
127
|
+
attributes: Object.entries(span.attributes).filter(([, v]) => v !== void 0 && v !== null).map(([key, value]) => ({
|
|
128
|
+
key,
|
|
129
|
+
value: toOtlpValue$1(value)
|
|
130
|
+
})),
|
|
131
|
+
events: span.events.map((event) => ({
|
|
132
|
+
name: event.name,
|
|
133
|
+
timeUnixNano: hrTimeToNano(event.time),
|
|
134
|
+
attributes: event.attributes ? Object.entries(event.attributes).filter(([, v]) => v !== void 0 && v !== null).map(([key, value]) => ({
|
|
135
|
+
key,
|
|
136
|
+
value: toOtlpValue$1(value)
|
|
137
|
+
})) : []
|
|
138
|
+
})),
|
|
139
|
+
status: {
|
|
140
|
+
code: span.status.code,
|
|
141
|
+
message: span.status.message ?? void 0
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
})
|
|
145
|
+
}))
|
|
146
|
+
};
|
|
147
|
+
}) };
|
|
148
|
+
fetch(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
153
|
+
...config.headers ?? {}
|
|
154
|
+
},
|
|
155
|
+
body: JSON.stringify(body)
|
|
156
|
+
}).then((res) => {
|
|
157
|
+
if (res.ok) resultCallback({ code: ExportResultCode.SUCCESS });
|
|
158
|
+
else resultCallback({
|
|
159
|
+
code: ExportResultCode.FAILED,
|
|
160
|
+
error: /* @__PURE__ */ new Error(`OTLP export failed: ${res.status}`)
|
|
161
|
+
});
|
|
162
|
+
}).catch((error) => {
|
|
163
|
+
resultCallback({
|
|
164
|
+
code: ExportResultCode.FAILED,
|
|
165
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
async shutdown() {},
|
|
170
|
+
async forceFlush() {}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/telemetry/agents-exporter.ts
|
|
176
|
+
function normalizeTraceId(id) {
|
|
177
|
+
return id.startsWith("trace_") ? id.slice(6) : id;
|
|
178
|
+
}
|
|
179
|
+
function normalizeSpanId(id) {
|
|
180
|
+
return id.startsWith("span_") ? id.slice(5) : id;
|
|
181
|
+
}
|
|
182
|
+
function isoToNano(iso) {
|
|
183
|
+
if (!iso) return "0";
|
|
184
|
+
const ms = new Date(iso).getTime();
|
|
185
|
+
if (isNaN(ms)) return "0";
|
|
186
|
+
return (BigInt(ms) * BigInt(1e6)).toString();
|
|
187
|
+
}
|
|
188
|
+
function toOtlpValue(value) {
|
|
189
|
+
if (typeof value === "string") return { stringValue: value };
|
|
190
|
+
if (typeof value === "number") return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
191
|
+
if (typeof value === "boolean") return { boolValue: value };
|
|
192
|
+
if (Array.isArray(value)) return { arrayValue: { values: value.map((v) => toOtlpValue(v)) } };
|
|
193
|
+
if (typeof value === "object" && value !== null) try {
|
|
194
|
+
return { stringValue: JSON.stringify(value) };
|
|
195
|
+
} catch {
|
|
196
|
+
return { stringValue: String(value) };
|
|
197
|
+
}
|
|
198
|
+
return { stringValue: String(value) };
|
|
199
|
+
}
|
|
200
|
+
function kv(key, value) {
|
|
201
|
+
return {
|
|
202
|
+
key,
|
|
203
|
+
value: toOtlpValue(value)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Derive a human-readable span name from the span data type.
|
|
208
|
+
*/
|
|
209
|
+
function deriveSpanName(data) {
|
|
210
|
+
switch (data.type) {
|
|
211
|
+
case "agent": return `Agent: ${data.name}`;
|
|
212
|
+
case "function": return `Tool: ${data.name}`;
|
|
213
|
+
case "generation": return data.model ? `Generation: ${data.model}` : "Generation";
|
|
214
|
+
case "response": return "Response";
|
|
215
|
+
case "handoff": return `Handoff: ${data.from_agent ?? "?"} → ${data.to_agent ?? "?"}`;
|
|
216
|
+
case "guardrail": return `Guardrail: ${data.name}`;
|
|
217
|
+
case "custom": return `Custom: ${data.name}`;
|
|
218
|
+
case "transcription": return data.model ? `Transcription: ${data.model}` : "Transcription";
|
|
219
|
+
case "speech": return data.model ? `Speech: ${data.model}` : "Speech";
|
|
220
|
+
case "speech_group": return "Speech Group";
|
|
221
|
+
case "mcp_tools": return `MCP Tools: ${data.server ?? "unknown"}`;
|
|
222
|
+
default: return "Unknown Span";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Convert span data fields to OTLP attributes that our UI's getSpanType() recognizes.
|
|
227
|
+
*/
|
|
228
|
+
function convertSpanDataToAttributes(data, error) {
|
|
229
|
+
const attrs = [kv("openai.agents.span_type", data.type)];
|
|
230
|
+
switch (data.type) {
|
|
231
|
+
case "generation":
|
|
232
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
233
|
+
if (data.model) {
|
|
234
|
+
attrs.push(kv("gen_ai.request.model", data.model));
|
|
235
|
+
attrs.push(kv("gen_ai.system", "openai"));
|
|
236
|
+
}
|
|
237
|
+
if (data.usage?.input_tokens != null) attrs.push(kv("gen_ai.usage.input_tokens", data.usage.input_tokens));
|
|
238
|
+
if (data.usage?.output_tokens != null) attrs.push(kv("gen_ai.usage.output_tokens", data.usage.output_tokens));
|
|
239
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", JSON.stringify(data.input)));
|
|
240
|
+
if (data.output) attrs.push(kv("gen_ai.completion", JSON.stringify(data.output)));
|
|
241
|
+
if (data.model_config) attrs.push(kv("gen_ai.request.model_config", JSON.stringify(data.model_config)));
|
|
242
|
+
break;
|
|
243
|
+
case "agent":
|
|
244
|
+
attrs.push(kv("openai.agents.agent.name", data.name));
|
|
245
|
+
if (data.tools?.length) attrs.push(kv("openai.agents.agent.tools", JSON.stringify(data.tools)));
|
|
246
|
+
if (data.handoffs?.length) attrs.push(kv("openai.agents.agent.handoffs", JSON.stringify(data.handoffs)));
|
|
247
|
+
if (data.output_type) attrs.push(kv("openai.agents.agent.output_type", data.output_type));
|
|
248
|
+
break;
|
|
249
|
+
case "function":
|
|
250
|
+
attrs.push(kv("gen_ai.tool.name", data.name));
|
|
251
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
252
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
253
|
+
if (data.mcp_data) attrs.push(kv("openai.agents.function.mcp_data", data.mcp_data));
|
|
254
|
+
break;
|
|
255
|
+
case "handoff":
|
|
256
|
+
if (data.from_agent) attrs.push(kv("openai.agents.handoff.from_agent", data.from_agent));
|
|
257
|
+
if (data.to_agent) attrs.push(kv("openai.agents.handoff.to_agent", data.to_agent));
|
|
258
|
+
break;
|
|
259
|
+
case "guardrail":
|
|
260
|
+
attrs.push(kv("llmops.guardrail.action", data.triggered ? "triggered" : "passed"));
|
|
261
|
+
attrs.push(kv("openai.agents.guardrail.name", data.name));
|
|
262
|
+
break;
|
|
263
|
+
case "response":
|
|
264
|
+
attrs.push(kv("gen_ai.operation.name", "chat"));
|
|
265
|
+
if (data.response_id) attrs.push(kv("openai.agents.response.id", data.response_id));
|
|
266
|
+
break;
|
|
267
|
+
case "custom":
|
|
268
|
+
attrs.push(kv("openai.agents.custom.name", data.name));
|
|
269
|
+
attrs.push(kv("openai.agents.custom.data", JSON.stringify(data.data)));
|
|
270
|
+
break;
|
|
271
|
+
case "transcription":
|
|
272
|
+
attrs.push(kv("gen_ai.operation.name", "transcription"));
|
|
273
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
274
|
+
if (data.output) attrs.push(kv("ai.response.text", data.output));
|
|
275
|
+
break;
|
|
276
|
+
case "speech":
|
|
277
|
+
attrs.push(kv("gen_ai.operation.name", "speech"));
|
|
278
|
+
if (data.model) attrs.push(kv("gen_ai.request.model", data.model));
|
|
279
|
+
if (data.input) attrs.push(kv("ai.prompt.messages", data.input));
|
|
280
|
+
break;
|
|
281
|
+
case "speech_group":
|
|
282
|
+
if (data.input) attrs.push(kv("openai.agents.speech_group.input", data.input));
|
|
283
|
+
break;
|
|
284
|
+
case "mcp_tools":
|
|
285
|
+
attrs.push(kv("gen_ai.tool.name", `mcp:${data.server ?? "unknown"}`));
|
|
286
|
+
if (data.result) attrs.push(kv("openai.agents.mcp.tools", JSON.stringify(data.result)));
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (error) {
|
|
290
|
+
attrs.push(kv("error.message", error.message));
|
|
291
|
+
if (error.data) attrs.push(kv("error.data", JSON.stringify(error.data)));
|
|
292
|
+
}
|
|
293
|
+
return attrs;
|
|
294
|
+
}
|
|
295
|
+
function buildResourceSpans(traceId, trace, spans) {
|
|
296
|
+
const resourceAttrs = [kv("service.name", "@openai/agents")];
|
|
297
|
+
if (trace) {
|
|
298
|
+
resourceAttrs.push(kv("openai.agents.trace.name", trace.name));
|
|
299
|
+
if (trace.groupId) resourceAttrs.push(kv("openai.agents.trace.group_id", trace.groupId));
|
|
300
|
+
if (trace.metadata && Object.keys(trace.metadata).length > 0) resourceAttrs.push(kv("openai.agents.trace.metadata", JSON.stringify(trace.metadata)));
|
|
301
|
+
}
|
|
302
|
+
const normalizedTraceId = normalizeTraceId(traceId);
|
|
303
|
+
return {
|
|
304
|
+
resource: { attributes: resourceAttrs },
|
|
305
|
+
scopeSpans: [{
|
|
306
|
+
scope: { name: "@llmops/agents-exporter" },
|
|
307
|
+
spans: spans.map((span) => {
|
|
308
|
+
const isError = !!span.error;
|
|
309
|
+
return {
|
|
310
|
+
traceId: normalizedTraceId,
|
|
311
|
+
spanId: normalizeSpanId(span.spanId),
|
|
312
|
+
parentSpanId: span.parentId ? normalizeSpanId(span.parentId) : void 0,
|
|
313
|
+
name: deriveSpanName(span.spanData),
|
|
314
|
+
kind: 1,
|
|
315
|
+
startTimeUnixNano: isoToNano(span.startedAt),
|
|
316
|
+
endTimeUnixNano: isoToNano(span.endedAt),
|
|
317
|
+
attributes: convertSpanDataToAttributes(span.spanData, span.error),
|
|
318
|
+
events: [],
|
|
319
|
+
status: isError ? {
|
|
320
|
+
code: 2,
|
|
321
|
+
message: span.error.message
|
|
322
|
+
} : { code: 1 }
|
|
323
|
+
};
|
|
324
|
+
})
|
|
325
|
+
}]
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Create a TracingExporter for @openai/agents that sends traces to LLMOps.
|
|
330
|
+
*
|
|
331
|
+
* Usage:
|
|
332
|
+
* ```typescript
|
|
333
|
+
* import { createLLMOpsAgentsExporter } from '@llmops/sdk';
|
|
334
|
+
* import { setTraceProcessors, BatchTraceProcessor } from '@openai/agents';
|
|
335
|
+
*
|
|
336
|
+
* setTraceProcessors([
|
|
337
|
+
* new BatchTraceProcessor(createLLMOpsAgentsExporter({
|
|
338
|
+
* baseURL: 'http://localhost:5177',
|
|
339
|
+
* apiKey: process.env.LLMOPS_API_KEY!,
|
|
340
|
+
* }))
|
|
341
|
+
* ]);
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
function createLLMOpsAgentsExporter(config) {
|
|
345
|
+
const url = `${config.baseURL.replace(/\/$/, "")}/api/otlp/v1/traces`;
|
|
346
|
+
return { async export(items, signal) {
|
|
347
|
+
const traces = [];
|
|
348
|
+
const spans = [];
|
|
349
|
+
for (const item of items) if (item.type === "trace") traces.push(item);
|
|
350
|
+
else spans.push(item);
|
|
351
|
+
if (spans.length === 0) return;
|
|
352
|
+
const traceMap = /* @__PURE__ */ new Map();
|
|
353
|
+
for (const t of traces) traceMap.set(t.traceId, t);
|
|
354
|
+
const spansByTrace = /* @__PURE__ */ new Map();
|
|
355
|
+
for (const s of spans) {
|
|
356
|
+
const group = spansByTrace.get(s.traceId) ?? [];
|
|
357
|
+
group.push(s);
|
|
358
|
+
spansByTrace.set(s.traceId, group);
|
|
359
|
+
}
|
|
360
|
+
const resourceSpans = [];
|
|
361
|
+
for (const [traceId, traceSpans] of spansByTrace) {
|
|
362
|
+
const trace = traceMap.get(traceId);
|
|
363
|
+
resourceSpans.push(buildResourceSpans(traceId, trace, traceSpans));
|
|
364
|
+
}
|
|
365
|
+
if (resourceSpans.length === 0) return;
|
|
366
|
+
await fetch(url, {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: {
|
|
369
|
+
"Content-Type": "application/json",
|
|
370
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
371
|
+
...config.headers ?? {}
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({ resourceSpans }),
|
|
374
|
+
signal
|
|
375
|
+
});
|
|
376
|
+
} };
|
|
377
|
+
}
|
|
378
|
+
|
|
39
379
|
//#endregion
|
|
40
380
|
exports.AuthFeatureNotAvailableError = AuthFeatureNotAvailableError;
|
|
381
|
+
exports.createLLMOpsAgentsExporter = createLLMOpsAgentsExporter;
|
|
41
382
|
exports.createLLMOpsMiddleware = require_express.createLLMOpsMiddleware;
|
|
383
|
+
exports.createLLMOpsSpanExporter = createLLMOpsSpanExporter;
|
|
42
384
|
exports.llmops = createLLMOps;
|