@opensearch-project/agent-health 0.3.0 → 0.4.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/README.md +43 -2
- package/cli/dist/index.js +1300 -112
- package/deployment/cloudformation/agent-health-observability.yaml +571 -0
- package/dist/assets/index-BOIP5L7h.js +246 -0
- package/dist/assets/index-CU9YKpAL.css +1 -0
- package/dist/index.html +2 -2
- package/lib/dist/config/index.js +50 -2
- package/lib/dist/index.js +66 -6
- package/package.json +25 -8
- package/server/dist/app.js +1276 -101
- package/server/dist/index.js +1429 -130
- package/dist/assets/index-EvPLSTAS.js +0 -267
- package/dist/assets/index-RXasQKUs.css +0 -1
package/server/dist/app.js
CHANGED
|
@@ -87,6 +87,8 @@ var init_config = __esm({
|
|
|
87
87
|
// OpenAI-compatible (optional - for OpenAI-compatible judge/agent endpoints)
|
|
88
88
|
openaiCompatibleApiKey: getEnvVar("OPENAI_COMPATIBLE_API_KEY", ""),
|
|
89
89
|
openaiCompatibleEndpoint: getEnvVar("OPENAI_COMPATIBLE_ENDPOINT", "http://localhost:4000/v1/chat/completions"),
|
|
90
|
+
// Observio Sample Agent endpoint
|
|
91
|
+
observioEndpoint: getEnvVar("OBSERVIO_ENDPOINT", "http://localhost:3001/run-agent"),
|
|
90
92
|
// Claude Code Telemetry (optional - for OTEL traces from Claude Code)
|
|
91
93
|
claudeCodeTelemetryEnabled: getEnvVar("CLAUDE_CODE_TELEMETRY_ENABLED", "false") === "true",
|
|
92
94
|
otelExporterEndpoint: getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT", ""),
|
|
@@ -151,6 +153,16 @@ var init_constants = __esm({
|
|
|
151
153
|
description: "Invokes the Claude Code CLI. Server-only \u2014 use the CLI or benchmark runner.",
|
|
152
154
|
serverOnly: true
|
|
153
155
|
},
|
|
156
|
+
"strands": {
|
|
157
|
+
label: "Amazon Strands",
|
|
158
|
+
description: "Amazon Strands agent framework via Bedrock Agent Runtime API. Server-only \u2014 requires AWS SDK.",
|
|
159
|
+
serverOnly: true
|
|
160
|
+
},
|
|
161
|
+
"langgraph": {
|
|
162
|
+
label: "LangGraph (REST)",
|
|
163
|
+
description: "LangGraph agent via direct REST API. Use for non-AG-UI LangGraph instances.",
|
|
164
|
+
serverOnly: false
|
|
165
|
+
},
|
|
154
166
|
"mock": {
|
|
155
167
|
label: "Mock",
|
|
156
168
|
description: "Built-in demo agent for testing. No real endpoint needed.",
|
|
@@ -170,6 +182,15 @@ var init_constants = __esm({
|
|
|
170
182
|
headers: {},
|
|
171
183
|
useTraces: false
|
|
172
184
|
},
|
|
185
|
+
{
|
|
186
|
+
key: "observio",
|
|
187
|
+
name: "Observio Sample Agent",
|
|
188
|
+
endpoint: ENV_CONFIG.observioEndpoint || "http://localhost:3001/run-agent",
|
|
189
|
+
description: "Observio sample agent \u2014 ReAct pattern with LangGraph and Bedrock. Start with: cd observio-sample-agent && npm run start:ag-ui",
|
|
190
|
+
connectorType: "agui-streaming",
|
|
191
|
+
headers: {},
|
|
192
|
+
useTraces: true
|
|
193
|
+
},
|
|
173
194
|
{
|
|
174
195
|
key: "claude-code",
|
|
175
196
|
name: "Claude Code",
|
|
@@ -179,6 +200,30 @@ var init_constants = __esm({
|
|
|
179
200
|
headers: {},
|
|
180
201
|
useTraces: ENV_CONFIG.claudeCodeTelemetryEnabled && !!ENV_CONFIG.otelExporterEndpoint,
|
|
181
202
|
connectorConfig: { env: getClaudeCodeConnectorEnv() }
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
key: "strands",
|
|
206
|
+
name: "Amazon Strands",
|
|
207
|
+
endpoint: "${STRANDS_AGENT_ID}",
|
|
208
|
+
description: "Amazon Strands agent framework (Bedrock Agent Runtime)",
|
|
209
|
+
connectorType: "strands",
|
|
210
|
+
headers: {},
|
|
211
|
+
useTraces: false,
|
|
212
|
+
connectorConfig: {
|
|
213
|
+
agentAliasId: "${STRANDS_ALIAS_ID:-TSTALIASID}",
|
|
214
|
+
region: "${AWS_REGION:-us-east-1}"
|
|
215
|
+
},
|
|
216
|
+
enabled: false
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: "langgraph-rest",
|
|
220
|
+
name: "LangGraph (REST)",
|
|
221
|
+
endpoint: "${LANGGRAPH_API_ENDPOINT:-http://localhost:8000}",
|
|
222
|
+
description: "LangGraph agent via direct REST API",
|
|
223
|
+
connectorType: "langgraph",
|
|
224
|
+
headers: {},
|
|
225
|
+
useTraces: false,
|
|
226
|
+
enabled: false
|
|
182
227
|
}
|
|
183
228
|
],
|
|
184
229
|
models: {
|
|
@@ -362,6 +407,7 @@ function mergeConfigs(userConfig, defaultConfig) {
|
|
|
362
407
|
...DEFAULT_SERVER_CONFIG,
|
|
363
408
|
...userConfig.server
|
|
364
409
|
};
|
|
410
|
+
const telemetry = userConfig.telemetry ?? {};
|
|
365
411
|
return {
|
|
366
412
|
server,
|
|
367
413
|
agents,
|
|
@@ -369,7 +415,8 @@ function mergeConfigs(userConfig, defaultConfig) {
|
|
|
369
415
|
connectors,
|
|
370
416
|
testCases,
|
|
371
417
|
reporters,
|
|
372
|
-
judge
|
|
418
|
+
judge,
|
|
419
|
+
telemetry
|
|
373
420
|
};
|
|
374
421
|
}
|
|
375
422
|
async function loadUserConfig(configPath) {
|
|
@@ -411,7 +458,8 @@ function loadConfigSync(cwd = process.cwd()) {
|
|
|
411
458
|
connectors: [],
|
|
412
459
|
testCases: [],
|
|
413
460
|
reporters: [["console"]],
|
|
414
|
-
judge: { provider: "bedrock", model: "claude-sonnet-4" }
|
|
461
|
+
judge: { provider: "bedrock", model: "claude-sonnet-4" },
|
|
462
|
+
telemetry: {}
|
|
415
463
|
};
|
|
416
464
|
}
|
|
417
465
|
function clearConfigCache() {
|
|
@@ -1129,7 +1177,8 @@ var ClaudeCodeReader = class {
|
|
|
1129
1177
|
if (s.session_completed) completedSessions++;
|
|
1130
1178
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
1131
1179
|
totalToolCalls += toolCallCount;
|
|
1132
|
-
const
|
|
1180
|
+
const _d = new Date(s.start_time);
|
|
1181
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
1133
1182
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
1134
1183
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
1135
1184
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2017,7 +2066,8 @@ var KiroReader = class {
|
|
|
2017
2066
|
if (s.session_completed) completedSessions++;
|
|
2018
2067
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
2019
2068
|
totalToolCalls += toolCallCount;
|
|
2020
|
-
const
|
|
2069
|
+
const _d = new Date(s.start_time);
|
|
2070
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
2021
2071
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
2022
2072
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
2023
2073
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2362,7 +2412,8 @@ var CodexReader = class {
|
|
|
2362
2412
|
if (s.session_completed) completedSessions++;
|
|
2363
2413
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
2364
2414
|
totalToolCalls += toolCallCount;
|
|
2365
|
-
const
|
|
2415
|
+
const _d = new Date(s.start_time);
|
|
2416
|
+
const date = `${_d.getFullYear()}-${String(_d.getMonth() + 1).padStart(2, "0")}-${String(_d.getDate()).padStart(2, "0")}`;
|
|
2366
2417
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
2367
2418
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
2368
2419
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -2809,7 +2860,7 @@ function localDateStr(d) {
|
|
|
2809
2860
|
function filterByDate(sessions, range) {
|
|
2810
2861
|
if (!range?.from && !range?.to) return sessions;
|
|
2811
2862
|
return sessions.filter((s) => {
|
|
2812
|
-
const date = s.start_time
|
|
2863
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2813
2864
|
if (range.from && date < range.from) return false;
|
|
2814
2865
|
if (range.to && date > range.to) return false;
|
|
2815
2866
|
return true;
|
|
@@ -2835,7 +2886,7 @@ function computeStatsFromSessions(sessions, agent) {
|
|
|
2835
2886
|
if (s.session_completed) completedSessions++;
|
|
2836
2887
|
const toolCallCount = Object.values(s.tool_counts).reduce((a, b) => a + b, 0);
|
|
2837
2888
|
totalToolCalls += toolCallCount;
|
|
2838
|
-
const date = s.start_time
|
|
2889
|
+
const date = localDateStr(new Date(s.start_time));
|
|
2839
2890
|
if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
2840
2891
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
2841
2892
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -3004,7 +3055,7 @@ var CodingAgentRegistry = class {
|
|
|
3004
3055
|
}
|
|
3005
3056
|
const dailyCostMap = /* @__PURE__ */ new Map();
|
|
3006
3057
|
for (const s of sessions) {
|
|
3007
|
-
const date = s.start_time
|
|
3058
|
+
const date = localDateStr(new Date(s.start_time));
|
|
3008
3059
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
3009
3060
|
const key = `${date}:${s.agent}`;
|
|
3010
3061
|
const existing = dailyCostMap.get(key) ?? { date, cost: 0, agent: s.agent };
|
|
@@ -3032,7 +3083,7 @@ var CodingAgentRegistry = class {
|
|
|
3032
3083
|
if (!s.start_time) continue;
|
|
3033
3084
|
const d = new Date(s.start_time);
|
|
3034
3085
|
if (isNaN(d.getTime())) continue;
|
|
3035
|
-
activeDates.add(s.start_time
|
|
3086
|
+
activeDates.add(localDateStr(new Date(s.start_time)));
|
|
3036
3087
|
dowCounts[d.getDay()]++;
|
|
3037
3088
|
hourCounts[d.getHours()]++;
|
|
3038
3089
|
}
|
|
@@ -3059,7 +3110,7 @@ var CodingAgentRegistry = class {
|
|
|
3059
3110
|
}
|
|
3060
3111
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
3061
3112
|
for (const s of sessions) {
|
|
3062
|
-
const date = s.start_time
|
|
3113
|
+
const date = localDateStr(new Date(s.start_time));
|
|
3063
3114
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
3064
3115
|
const existing = dailyMap.get(date) ?? { date, messageCount: 0, sessionCount: 0, toolCallCount: 0 };
|
|
3065
3116
|
existing.messageCount += s.user_message_count + s.assistant_message_count;
|
|
@@ -3178,7 +3229,7 @@ var CodingAgentRegistry = class {
|
|
|
3178
3229
|
const totalDuration = data.sessions.reduce((s, sess) => s + sess.duration_minutes, 0);
|
|
3179
3230
|
const dailyCostMap = /* @__PURE__ */ new Map();
|
|
3180
3231
|
for (const s of data.sessions) {
|
|
3181
|
-
const date = s.start_time
|
|
3232
|
+
const date = localDateStr(new Date(s.start_time));
|
|
3182
3233
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) continue;
|
|
3183
3234
|
const key = `${date}:${s.agent}`;
|
|
3184
3235
|
const existing = dailyCostMap.get(key) ?? { date, cost: 0, agent: s.agent };
|
|
@@ -3387,7 +3438,7 @@ var CodingAgentRegistry = class {
|
|
|
3387
3438
|
const totalToolErrors = userSessions.reduce((s, sess) => s + sess.total_tool_errors, 0);
|
|
3388
3439
|
const totalDuration = userSessions.reduce((s, sess) => s + sess.duration_minutes, 0);
|
|
3389
3440
|
const agents = [...new Set(userSessions.map((s) => s.agent))];
|
|
3390
|
-
const activeDates = new Set(userSessions.map((s) => s.start_time
|
|
3441
|
+
const activeDates = new Set(userSessions.map((s) => localDateStr(new Date(s.start_time))));
|
|
3391
3442
|
const projectCounts = /* @__PURE__ */ new Map();
|
|
3392
3443
|
for (const s of userSessions) {
|
|
3393
3444
|
projectCounts.set(s.project_path, (projectCounts.get(s.project_path) ?? 0) + 1);
|
|
@@ -3495,9 +3546,10 @@ var RemoteAggregator = class extends CodingAgentRegistry {
|
|
|
3495
3546
|
filterByRange(sessions, range) {
|
|
3496
3547
|
if (!range?.from && !range?.to) return sessions;
|
|
3497
3548
|
return sessions.filter((s) => {
|
|
3498
|
-
const
|
|
3499
|
-
|
|
3500
|
-
if (range.
|
|
3549
|
+
const y = new Date(s.start_time);
|
|
3550
|
+
const date = `${y.getFullYear()}-${String(y.getMonth() + 1).padStart(2, "0")}-${String(y.getDate()).padStart(2, "0")}`;
|
|
3551
|
+
if (range.from && date < range.from) return false;
|
|
3552
|
+
if (range.to && date > range.to) return false;
|
|
3501
3553
|
return true;
|
|
3502
3554
|
});
|
|
3503
3555
|
}
|
|
@@ -4104,15 +4156,15 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4104
4156
|
if (!modelConfig) {
|
|
4105
4157
|
modelConfig = Object.values(config2.models).find((m) => m.model_id === modelId);
|
|
4106
4158
|
}
|
|
4107
|
-
const
|
|
4159
|
+
const provider2 = modelConfig?.provider || "bedrock";
|
|
4108
4160
|
const resolvedModelId = modelConfig?.model_id || modelId;
|
|
4109
|
-
debug("JudgeAPI", "Using provider:",
|
|
4110
|
-
if (
|
|
4161
|
+
debug("JudgeAPI", "Using provider:", provider2, "model:", resolvedModelId);
|
|
4162
|
+
if (provider2 === "demo") {
|
|
4111
4163
|
debug("JudgeAPI", "Demo provider - returning mock evaluation");
|
|
4112
4164
|
const mockResult = generateMockEvaluation(trajectory, expectedOutcomes);
|
|
4113
4165
|
return res.json(mockResult);
|
|
4114
4166
|
}
|
|
4115
|
-
if (
|
|
4167
|
+
if (provider2 === "openai-compatible") {
|
|
4116
4168
|
debug("JudgeAPI", "OpenAI-compatible provider - calling endpoint");
|
|
4117
4169
|
const result2 = await evaluateWithOpenAICompatible(
|
|
4118
4170
|
{ trajectory, expectedOutcomes, expectedTrajectory, logs },
|
|
@@ -4129,7 +4181,7 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4129
4181
|
res.json(result);
|
|
4130
4182
|
} catch (error) {
|
|
4131
4183
|
console.error("[JudgeAPI] Error during evaluation:", error);
|
|
4132
|
-
const
|
|
4184
|
+
const provider2 = (() => {
|
|
4133
4185
|
try {
|
|
4134
4186
|
const config2 = loadConfigSync();
|
|
4135
4187
|
const { modelId } = req.body;
|
|
@@ -4139,7 +4191,7 @@ router2.post("/api/judge", async (req, res) => {
|
|
|
4139
4191
|
return "bedrock";
|
|
4140
4192
|
}
|
|
4141
4193
|
})();
|
|
4142
|
-
const errorMessage =
|
|
4194
|
+
const errorMessage = provider2 === "openai-compatible" ? parseOpenAICompatibleError(error) : parseBedrockError(error);
|
|
4143
4195
|
res.status(500).json({
|
|
4144
4196
|
error: `Judge evaluation failed: ${errorMessage}`,
|
|
4145
4197
|
details: error.message
|
|
@@ -4430,13 +4482,13 @@ function getCustomAgents() {
|
|
|
4430
4482
|
}
|
|
4431
4483
|
|
|
4432
4484
|
// lib/hooks.ts
|
|
4433
|
-
async function executeBeforeRequestHook(hooks,
|
|
4485
|
+
async function executeBeforeRequestHook(hooks, context2, agentKey) {
|
|
4434
4486
|
if (!hooks?.beforeRequest) {
|
|
4435
|
-
return
|
|
4487
|
+
return context2;
|
|
4436
4488
|
}
|
|
4437
4489
|
debug("Hooks", `Executing beforeRequest hook for agent "${agentKey}"`);
|
|
4438
4490
|
try {
|
|
4439
|
-
const result = await hooks.beforeRequest(
|
|
4491
|
+
const result = await hooks.beforeRequest(context2);
|
|
4440
4492
|
if (!result || typeof result !== "object") {
|
|
4441
4493
|
throw new Error("beforeRequest hook must return an object with endpoint, payload, and headers");
|
|
4442
4494
|
}
|
|
@@ -4456,12 +4508,12 @@ async function executeBeforeRequestHook(hooks, context, agentKey) {
|
|
|
4456
4508
|
throw new Error(`beforeRequest hook failed for agent "${agentKey}": ${message}`);
|
|
4457
4509
|
}
|
|
4458
4510
|
}
|
|
4459
|
-
async function executeAfterResponseHook(hooks,
|
|
4511
|
+
async function executeAfterResponseHook(hooks, context2, agentKey) {
|
|
4460
4512
|
if (!hooks?.afterResponse) {
|
|
4461
|
-
return
|
|
4513
|
+
return context2;
|
|
4462
4514
|
}
|
|
4463
4515
|
try {
|
|
4464
|
-
const result = await hooks.afterResponse(
|
|
4516
|
+
const result = await hooks.afterResponse(context2);
|
|
4465
4517
|
if (!result || typeof result !== "object") {
|
|
4466
4518
|
throw new Error("afterResponse hook must return an object with response, trajectory, and optional runId");
|
|
4467
4519
|
}
|
|
@@ -4477,12 +4529,12 @@ async function executeAfterResponseHook(hooks, context, agentKey) {
|
|
|
4477
4529
|
throw new Error(`afterResponse hook failed for agent "${agentKey}": ${message}`);
|
|
4478
4530
|
}
|
|
4479
4531
|
}
|
|
4480
|
-
async function executeBuildTrajectoryHook(hooks,
|
|
4532
|
+
async function executeBuildTrajectoryHook(hooks, context2, agentKey) {
|
|
4481
4533
|
if (!hooks?.buildTrajectory) {
|
|
4482
4534
|
return null;
|
|
4483
4535
|
}
|
|
4484
4536
|
try {
|
|
4485
|
-
const result = await hooks.buildTrajectory(
|
|
4537
|
+
const result = await hooks.buildTrajectory(context2);
|
|
4486
4538
|
if (result !== null && !Array.isArray(result)) {
|
|
4487
4539
|
throw new Error("buildTrajectory hook must return an array of TrajectoryStep objects or null");
|
|
4488
4540
|
}
|
|
@@ -4533,6 +4585,36 @@ var agent_default = router3;
|
|
|
4533
4585
|
import { Router as Router4 } from "express";
|
|
4534
4586
|
|
|
4535
4587
|
// server/services/tracesService.ts
|
|
4588
|
+
function classifyOpenSearchError(error) {
|
|
4589
|
+
const statusCode = error.meta?.statusCode;
|
|
4590
|
+
const message = error.message || "Unknown error";
|
|
4591
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
4592
|
+
return {
|
|
4593
|
+
category: "auth",
|
|
4594
|
+
message: `Authentication failed (HTTP ${statusCode})`,
|
|
4595
|
+
suggestion: "AWS credentials may be expired or invalid. Run `aws sts get-caller-identity` to verify, then refresh with `aws sso login` or update your credentials."
|
|
4596
|
+
};
|
|
4597
|
+
}
|
|
4598
|
+
if (statusCode === 404) {
|
|
4599
|
+
return {
|
|
4600
|
+
category: "index_not_found",
|
|
4601
|
+
message: "Trace index not found",
|
|
4602
|
+
suggestion: "The OpenSearch traces index does not exist yet. Ensure your agents are configured to emit OpenTelemetry spans."
|
|
4603
|
+
};
|
|
4604
|
+
}
|
|
4605
|
+
if (message.includes("ECONNREFUSED") || message.includes("ENOTFOUND") || message.includes("getaddrinfo") || message.includes("Connection refused")) {
|
|
4606
|
+
return {
|
|
4607
|
+
category: "connection",
|
|
4608
|
+
message: `Cannot connect to OpenSearch: ${message}`,
|
|
4609
|
+
suggestion: "Check that the OpenSearch endpoint is correct and the cluster is reachable from your network."
|
|
4610
|
+
};
|
|
4611
|
+
}
|
|
4612
|
+
return {
|
|
4613
|
+
category: "unknown",
|
|
4614
|
+
message,
|
|
4615
|
+
suggestion: "Check the server logs for more details."
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4536
4618
|
function transformSpan(source) {
|
|
4537
4619
|
const attributes = {};
|
|
4538
4620
|
for (const [key, value] of Object.entries(source)) {
|
|
@@ -4587,11 +4669,11 @@ function transformSpan(source) {
|
|
|
4587
4669
|
};
|
|
4588
4670
|
}
|
|
4589
4671
|
async function fetchTraces(options, client, indexPattern = "otel-v1-apm-span-*") {
|
|
4590
|
-
const { traceId, runIds, startTime, endTime, size = 100, serviceName, textSearch, cursor } = options;
|
|
4672
|
+
const { traceId, runIds, sessionId, startTime, endTime, size = 100, serviceName, textSearch, cursor } = options;
|
|
4591
4673
|
const hasTimeRange = startTime || endTime;
|
|
4592
|
-
const hasIdFilter = traceId || runIds && runIds.length > 0;
|
|
4674
|
+
const hasIdFilter = traceId || runIds && runIds.length > 0 || sessionId;
|
|
4593
4675
|
if (!hasIdFilter && !hasTimeRange) {
|
|
4594
|
-
throw new Error("Either traceId, runIds, or time range is required");
|
|
4676
|
+
throw new Error("Either traceId, runIds, sessionId, or time range is required");
|
|
4595
4677
|
}
|
|
4596
4678
|
debug("TracesService", "Fetching traces:", { traceId, runIds: runIds?.length, serviceName, textSearch, size, cursor: cursor ? "present" : "none" });
|
|
4597
4679
|
const must = [];
|
|
@@ -4603,6 +4685,9 @@ async function fetchTraces(options, client, indexPattern = "otel-v1-apm-span-*")
|
|
|
4603
4685
|
terms: { "span.attributes.gen_ai@request@id": runIds }
|
|
4604
4686
|
});
|
|
4605
4687
|
}
|
|
4688
|
+
if (sessionId) {
|
|
4689
|
+
must.push({ term: { "span.attributes.session@id": sessionId } });
|
|
4690
|
+
}
|
|
4606
4691
|
if (startTime || endTime) {
|
|
4607
4692
|
const range = { "startTime": {} };
|
|
4608
4693
|
if (startTime) range["startTime"].gte = new Date(startTime).toISOString();
|
|
@@ -4674,7 +4759,13 @@ async function checkTracesHealth(client, indexPattern = "otel-v1-apm-span-*") {
|
|
|
4674
4759
|
return { status: "error", index: indexPattern };
|
|
4675
4760
|
}
|
|
4676
4761
|
} catch (error) {
|
|
4677
|
-
|
|
4762
|
+
const classified = classifyOpenSearchError(error);
|
|
4763
|
+
return {
|
|
4764
|
+
status: "error",
|
|
4765
|
+
error: classified.message,
|
|
4766
|
+
errorCategory: classified.category,
|
|
4767
|
+
suggestion: classified.suggestion
|
|
4768
|
+
};
|
|
4678
4769
|
}
|
|
4679
4770
|
}
|
|
4680
4771
|
|
|
@@ -6328,10 +6419,10 @@ function createOpenSearchClient(config2) {
|
|
|
6328
6419
|
region: config2.awsRegion,
|
|
6329
6420
|
service: config2.awsService || "es",
|
|
6330
6421
|
getCredentials: () => {
|
|
6331
|
-
const
|
|
6422
|
+
const provider2 = fromNodeProviderChain2({
|
|
6332
6423
|
...config2.awsProfile && { profile: config2.awsProfile }
|
|
6333
6424
|
});
|
|
6334
|
-
return
|
|
6425
|
+
return provider2();
|
|
6335
6426
|
}
|
|
6336
6427
|
});
|
|
6337
6428
|
return new Client({
|
|
@@ -7252,15 +7343,19 @@ var OpenSearchRunOperations = class {
|
|
|
7252
7343
|
async getAll(options) {
|
|
7253
7344
|
const size = options?.size ?? 100;
|
|
7254
7345
|
const from = options?.from ?? 0;
|
|
7346
|
+
const body = {
|
|
7347
|
+
size,
|
|
7348
|
+
from,
|
|
7349
|
+
sort: [{ createdAt: { order: "desc" } }],
|
|
7350
|
+
query: { match_all: {} }
|
|
7351
|
+
};
|
|
7352
|
+
if (options?._source && options._source.length > 0) {
|
|
7353
|
+
body._source = options._source;
|
|
7354
|
+
}
|
|
7255
7355
|
try {
|
|
7256
7356
|
const result = await this.client.search({
|
|
7257
7357
|
index: this.index,
|
|
7258
|
-
body
|
|
7259
|
-
size,
|
|
7260
|
-
from,
|
|
7261
|
-
sort: [{ createdAt: { order: "desc" } }],
|
|
7262
|
-
query: { match_all: {} }
|
|
7263
|
-
}
|
|
7358
|
+
body
|
|
7264
7359
|
});
|
|
7265
7360
|
const items = hitsToSources(result.body.hits?.hits || []);
|
|
7266
7361
|
const total = typeof result.body.hits?.total === "object" ? result.body.hits.total.value : result.body.hits?.total ?? 0;
|
|
@@ -7964,8 +8059,12 @@ function resolveObservabilityConfig(req) {
|
|
|
7964
8059
|
if (fileConfig) {
|
|
7965
8060
|
return {
|
|
7966
8061
|
endpoint: fileConfig.endpoint,
|
|
8062
|
+
authType: fileConfig.authType,
|
|
7967
8063
|
username: fileConfig.username,
|
|
7968
8064
|
password: fileConfig.password,
|
|
8065
|
+
awsProfile: fileConfig.awsProfile,
|
|
8066
|
+
awsRegion: fileConfig.awsRegion,
|
|
8067
|
+
awsService: fileConfig.awsService,
|
|
7969
8068
|
tlsSkipVerify: fileConfig.tlsSkipVerify,
|
|
7970
8069
|
indexes: {
|
|
7971
8070
|
traces: fileConfig.indexes?.traces || DEFAULT_OTEL_INDEXES.traces,
|
|
@@ -8009,14 +8108,34 @@ function getStorageConfigFromEnv() {
|
|
|
8009
8108
|
awsService: process.env.OPENSEARCH_STORAGE_AWS_SERVICE || void 0
|
|
8010
8109
|
};
|
|
8011
8110
|
}
|
|
8111
|
+
function getObservabilityConfigFromEnv() {
|
|
8112
|
+
const endpoint = process.env.OPENSEARCH_LOGS_ENDPOINT;
|
|
8113
|
+
if (!endpoint) {
|
|
8114
|
+
return null;
|
|
8115
|
+
}
|
|
8116
|
+
return {
|
|
8117
|
+
endpoint,
|
|
8118
|
+
authType: process.env.OPENSEARCH_LOGS_AUTH_TYPE || void 0,
|
|
8119
|
+
username: process.env.OPENSEARCH_LOGS_USERNAME,
|
|
8120
|
+
password: process.env.OPENSEARCH_LOGS_PASSWORD,
|
|
8121
|
+
awsProfile: process.env.OPENSEARCH_LOGS_AWS_PROFILE,
|
|
8122
|
+
awsRegion: process.env.OPENSEARCH_LOGS_AWS_REGION,
|
|
8123
|
+
awsService: process.env.OPENSEARCH_LOGS_AWS_SERVICE || void 0,
|
|
8124
|
+
indexes: {
|
|
8125
|
+
traces: process.env.OPENSEARCH_LOGS_TRACES_INDEX || DEFAULT_OTEL_INDEXES.traces,
|
|
8126
|
+
logs: process.env.OPENSEARCH_LOGS_INDEX || DEFAULT_OTEL_INDEXES.logs,
|
|
8127
|
+
metrics: DEFAULT_OTEL_INDEXES.metrics
|
|
8128
|
+
}
|
|
8129
|
+
};
|
|
8130
|
+
}
|
|
8012
8131
|
|
|
8013
8132
|
// server/routes/traces.ts
|
|
8014
8133
|
var router4 = Router4();
|
|
8015
8134
|
router4.post("/api/traces", async (req, res) => {
|
|
8016
8135
|
try {
|
|
8017
|
-
const { traceId, runIds, startTime, endTime, size = 100, serviceName, textSearch, cursor } = req.body;
|
|
8136
|
+
const { traceId, runIds, sessionId, startTime, endTime, size = 100, serviceName, textSearch, cursor } = req.body;
|
|
8018
8137
|
const hasTimeRange = startTime || endTime;
|
|
8019
|
-
const hasIdFilter = traceId || runIds && runIds.length > 0;
|
|
8138
|
+
const hasIdFilter = traceId || runIds && runIds.length > 0 || sessionId;
|
|
8020
8139
|
if (!hasIdFilter && !hasTimeRange) {
|
|
8021
8140
|
return res.status(400).json({
|
|
8022
8141
|
error: "Either traceId, runIds, or time range is required"
|
|
@@ -8030,16 +8149,18 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8030
8149
|
}
|
|
8031
8150
|
let realSpans = [];
|
|
8032
8151
|
let warning;
|
|
8152
|
+
let warningCategory;
|
|
8153
|
+
let suggestion;
|
|
8033
8154
|
let nextCursor = null;
|
|
8034
8155
|
let hasMore = false;
|
|
8035
8156
|
const config2 = resolveObservabilityConfig(req);
|
|
8036
|
-
if (config2 && (traceId || runIds && runIds.length > 0 || startTime || endTime)) {
|
|
8157
|
+
if (config2 && (traceId || runIds && runIds.length > 0 || sessionId || startTime || endTime)) {
|
|
8037
8158
|
let client;
|
|
8038
8159
|
try {
|
|
8039
8160
|
client = createOpenSearchClient(config2);
|
|
8040
8161
|
const indexPattern = config2.indexes?.traces || DEFAULT_OTEL_INDEXES.traces;
|
|
8041
8162
|
const result = await fetchTraces(
|
|
8042
|
-
{ traceId, runIds, startTime, endTime, size, serviceName, textSearch, cursor },
|
|
8163
|
+
{ traceId, runIds, sessionId, startTime, endTime, size, serviceName, textSearch, cursor },
|
|
8043
8164
|
client,
|
|
8044
8165
|
indexPattern
|
|
8045
8166
|
);
|
|
@@ -8047,8 +8168,11 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8047
8168
|
nextCursor = result.nextCursor || null;
|
|
8048
8169
|
hasMore = result.hasMore || false;
|
|
8049
8170
|
} catch (e) {
|
|
8050
|
-
|
|
8051
|
-
|
|
8171
|
+
const classified = classifyOpenSearchError(e);
|
|
8172
|
+
console.warn(`[TracesAPI] OpenSearch query failed (${classified.category}):`, classified.message);
|
|
8173
|
+
warning = classified.message;
|
|
8174
|
+
warningCategory = classified.category;
|
|
8175
|
+
suggestion = classified.suggestion;
|
|
8052
8176
|
} finally {
|
|
8053
8177
|
if (client) {
|
|
8054
8178
|
await client.close().catch(() => {
|
|
@@ -8074,6 +8198,8 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8074
8198
|
}
|
|
8075
8199
|
}
|
|
8076
8200
|
warning = "Observability data source not configured";
|
|
8201
|
+
warningCategory = "not_configured";
|
|
8202
|
+
suggestion = "Configure OPENSEARCH_LOGS_ENDPOINT or observabilityStorage in agent-health.config.ts to connect to your traces cluster.";
|
|
8077
8203
|
}
|
|
8078
8204
|
const allSpans = [...sampleSpans, ...realSpans];
|
|
8079
8205
|
res.json({
|
|
@@ -8081,7 +8207,9 @@ router4.post("/api/traces", async (req, res) => {
|
|
|
8081
8207
|
total: allSpans.length,
|
|
8082
8208
|
nextCursor,
|
|
8083
8209
|
hasMore,
|
|
8084
|
-
warning
|
|
8210
|
+
warning,
|
|
8211
|
+
warningCategory,
|
|
8212
|
+
suggestion
|
|
8085
8213
|
});
|
|
8086
8214
|
} catch (error) {
|
|
8087
8215
|
console.error("[TracesAPI] Error:", error);
|
|
@@ -8198,33 +8326,21 @@ function computeMetricsFromSampleSpans(runId) {
|
|
|
8198
8326
|
status: "success"
|
|
8199
8327
|
};
|
|
8200
8328
|
}
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
headers: {
|
|
8217
|
-
"Content-Type": "application/json",
|
|
8218
|
-
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8219
|
-
},
|
|
8220
|
-
body: JSON.stringify(query)
|
|
8221
|
-
});
|
|
8222
|
-
if (!response.ok) {
|
|
8223
|
-
const errorText = await response.text();
|
|
8224
|
-
throw new Error(`OpenSearch query failed: ${response.status} - ${errorText}`);
|
|
8225
|
-
}
|
|
8226
|
-
const data = await response.json();
|
|
8227
|
-
const spans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8329
|
+
var METRICS_SOURCE_FIELDS = [
|
|
8330
|
+
"span.attributes.gen_ai@request@id",
|
|
8331
|
+
"span.attributes.gen_ai@usage@input_tokens",
|
|
8332
|
+
"span.attributes.gen_ai@usage@output_tokens",
|
|
8333
|
+
"span.attributes.gen_ai@request@model",
|
|
8334
|
+
"span.attributes.gen_ai@tool@name",
|
|
8335
|
+
"span.attributes.tool.name",
|
|
8336
|
+
"name",
|
|
8337
|
+
"traceId",
|
|
8338
|
+
"startTime",
|
|
8339
|
+
"endTime",
|
|
8340
|
+
"durationInNanos",
|
|
8341
|
+
"status.code"
|
|
8342
|
+
];
|
|
8343
|
+
function computeMetricsFromSpans(runId, spans) {
|
|
8228
8344
|
if (spans.length === 0) {
|
|
8229
8345
|
return {
|
|
8230
8346
|
runId,
|
|
@@ -8296,6 +8412,95 @@ async function computeMetrics(runId, osConfig) {
|
|
|
8296
8412
|
status
|
|
8297
8413
|
};
|
|
8298
8414
|
}
|
|
8415
|
+
async function computeMetrics(runId, osConfig) {
|
|
8416
|
+
const { endpoint, username, password, indexPattern = "otel-v1-apm-span-*" } = osConfig;
|
|
8417
|
+
const query = {
|
|
8418
|
+
size: 500,
|
|
8419
|
+
sort: [{ startTime: { order: "asc" } }],
|
|
8420
|
+
query: {
|
|
8421
|
+
bool: {
|
|
8422
|
+
must: [
|
|
8423
|
+
{ term: { "span.attributes.gen_ai@request@id": runId } }
|
|
8424
|
+
]
|
|
8425
|
+
}
|
|
8426
|
+
}
|
|
8427
|
+
};
|
|
8428
|
+
const response = await fetch(`${endpoint}/${indexPattern}/_search`, {
|
|
8429
|
+
method: "POST",
|
|
8430
|
+
headers: {
|
|
8431
|
+
"Content-Type": "application/json",
|
|
8432
|
+
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8433
|
+
},
|
|
8434
|
+
body: JSON.stringify(query)
|
|
8435
|
+
});
|
|
8436
|
+
if (!response.ok) {
|
|
8437
|
+
const errorText = await response.text();
|
|
8438
|
+
throw new Error(`OpenSearch query failed: ${response.status} - ${errorText}`);
|
|
8439
|
+
}
|
|
8440
|
+
const data = await response.json();
|
|
8441
|
+
const spans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8442
|
+
return computeMetricsFromSpans(runId, spans);
|
|
8443
|
+
}
|
|
8444
|
+
async function computeBatchMetrics(runIds, osConfig) {
|
|
8445
|
+
if (runIds.length === 0) return [];
|
|
8446
|
+
const { endpoint, username, password, indexPattern = "otel-v1-apm-span-*" } = osConfig;
|
|
8447
|
+
const CHUNK_SIZE = 50;
|
|
8448
|
+
const allResults = [];
|
|
8449
|
+
const chunks = [];
|
|
8450
|
+
for (let i = 0; i < runIds.length; i += CHUNK_SIZE) {
|
|
8451
|
+
chunks.push(runIds.slice(i, i + CHUNK_SIZE));
|
|
8452
|
+
}
|
|
8453
|
+
const chunkResults = await Promise.all(chunks.map(async (chunk) => {
|
|
8454
|
+
const query = {
|
|
8455
|
+
size: 1e4,
|
|
8456
|
+
sort: [{ startTime: { order: "asc" } }],
|
|
8457
|
+
_source: METRICS_SOURCE_FIELDS,
|
|
8458
|
+
query: {
|
|
8459
|
+
bool: {
|
|
8460
|
+
must: [
|
|
8461
|
+
{ terms: { "span.attributes.gen_ai@request@id": chunk } }
|
|
8462
|
+
]
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
};
|
|
8466
|
+
const response = await fetch(`${endpoint}/${indexPattern}/_search`, {
|
|
8467
|
+
method: "POST",
|
|
8468
|
+
headers: {
|
|
8469
|
+
"Content-Type": "application/json",
|
|
8470
|
+
"Authorization": `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
|
|
8471
|
+
},
|
|
8472
|
+
body: JSON.stringify(query)
|
|
8473
|
+
});
|
|
8474
|
+
if (!response.ok) {
|
|
8475
|
+
const responseBody = await response.text();
|
|
8476
|
+
console.warn(
|
|
8477
|
+
`OpenSearch metrics query failed for chunk (${chunk.length} run IDs): ${response.status} ${response.statusText}. Response body: ${responseBody}`
|
|
8478
|
+
);
|
|
8479
|
+
return chunk.map((runId) => computeMetricsFromSpans(runId, []));
|
|
8480
|
+
}
|
|
8481
|
+
const data = await response.json();
|
|
8482
|
+
const allSpans = data.hits?.hits?.map((h) => h._source) || [];
|
|
8483
|
+
const totalHits = data.hits?.total?.value ?? allSpans.length;
|
|
8484
|
+
if (totalHits > 1e4) {
|
|
8485
|
+
console.warn(
|
|
8486
|
+
`OpenSearch batch metrics query returned ${allSpans.length} of ${totalHits} spans for chunk of ${chunk.length} run IDs. Metrics may be incomplete.`
|
|
8487
|
+
);
|
|
8488
|
+
}
|
|
8489
|
+
const spansByRunId = /* @__PURE__ */ new Map();
|
|
8490
|
+
for (const rid of chunk) spansByRunId.set(rid, []);
|
|
8491
|
+
for (const span of allSpans) {
|
|
8492
|
+
const rid = span["span.attributes.gen_ai@request@id"];
|
|
8493
|
+
if (rid && spansByRunId.has(rid)) {
|
|
8494
|
+
spansByRunId.get(rid).push(span);
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
return chunk.map((runId) => computeMetricsFromSpans(runId, spansByRunId.get(runId) || []));
|
|
8498
|
+
}));
|
|
8499
|
+
for (const results of chunkResults) {
|
|
8500
|
+
allResults.push(...results);
|
|
8501
|
+
}
|
|
8502
|
+
return allResults;
|
|
8503
|
+
}
|
|
8299
8504
|
function computeAggregateMetrics(metricsArray) {
|
|
8300
8505
|
if (!metricsArray || metricsArray.length === 0) {
|
|
8301
8506
|
return {
|
|
@@ -8374,15 +8579,6 @@ router5.get("/api/metrics/:runId", async (req, res) => {
|
|
|
8374
8579
|
res.status(500).json({ error: error.message });
|
|
8375
8580
|
}
|
|
8376
8581
|
});
|
|
8377
|
-
async function processBatches(items, batchSize, processor) {
|
|
8378
|
-
const results = [];
|
|
8379
|
-
for (let i = 0; i < items.length; i += batchSize) {
|
|
8380
|
-
const batch = items.slice(i, i + batchSize);
|
|
8381
|
-
const batchResults = await Promise.all(batch.map(processor));
|
|
8382
|
-
results.push(...batchResults);
|
|
8383
|
-
}
|
|
8384
|
-
return results;
|
|
8385
|
-
}
|
|
8386
8582
|
router5.post("/api/metrics/batch", async (req, res) => {
|
|
8387
8583
|
try {
|
|
8388
8584
|
const { runIds } = req.body;
|
|
@@ -8413,16 +8609,15 @@ router5.post("/api/metrics/batch", async (req, res) => {
|
|
|
8413
8609
|
password: config2.password,
|
|
8414
8610
|
indexPattern
|
|
8415
8611
|
};
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
(runId) => computeMetrics(runId, osConfig).catch((e) => ({
|
|
8612
|
+
try {
|
|
8613
|
+
realResults = await computeBatchMetrics(realRunIds, osConfig);
|
|
8614
|
+
} catch (e) {
|
|
8615
|
+
realResults = realRunIds.map((runId) => ({
|
|
8421
8616
|
runId,
|
|
8422
8617
|
error: e.message,
|
|
8423
8618
|
status: "error"
|
|
8424
|
-
}))
|
|
8425
|
-
|
|
8619
|
+
}));
|
|
8620
|
+
}
|
|
8426
8621
|
}
|
|
8427
8622
|
}
|
|
8428
8623
|
const resultsMap = /* @__PURE__ */ new Map();
|
|
@@ -12092,11 +12287,150 @@ var OpenAICompatibleConnector = class extends BaseConnector {
|
|
|
12092
12287
|
};
|
|
12093
12288
|
var openaiCompatibleConnector = new OpenAICompatibleConnector();
|
|
12094
12289
|
|
|
12290
|
+
// services/connectors/langgraph/LangGraphConnector.ts
|
|
12291
|
+
var LangGraphConnector = class extends BaseConnector {
|
|
12292
|
+
constructor() {
|
|
12293
|
+
super(...arguments);
|
|
12294
|
+
this.type = "langgraph";
|
|
12295
|
+
this.name = "LangGraph (REST)";
|
|
12296
|
+
this.supportsStreaming = false;
|
|
12297
|
+
}
|
|
12298
|
+
buildPayload(request2) {
|
|
12299
|
+
const config2 = request2.connectorConfig || {};
|
|
12300
|
+
return {
|
|
12301
|
+
input: {
|
|
12302
|
+
messages: [
|
|
12303
|
+
{
|
|
12304
|
+
role: "user",
|
|
12305
|
+
content: request2.testCase.initialPrompt
|
|
12306
|
+
}
|
|
12307
|
+
]
|
|
12308
|
+
},
|
|
12309
|
+
config: {
|
|
12310
|
+
configurable: {
|
|
12311
|
+
...request2.modelId && { model: request2.modelId },
|
|
12312
|
+
...config2.configurable
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
};
|
|
12316
|
+
}
|
|
12317
|
+
async execute(endpoint, request2, auth, onProgress, onRawEvent) {
|
|
12318
|
+
const payload = request2.payload || this.buildPayload(request2);
|
|
12319
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12320
|
+
const config2 = request2.connectorConfig || {};
|
|
12321
|
+
const graphId = config2.graphId || "agent";
|
|
12322
|
+
const baseUrl = endpoint.replace(/\/+$/, "");
|
|
12323
|
+
const threadId = config2.threadId || request2.threadId;
|
|
12324
|
+
const invokeUrl = threadId ? `${baseUrl}/threads/${threadId}/runs/wait` : `${baseUrl}/assistants/${graphId}/invoke`;
|
|
12325
|
+
this.debug("Executing LangGraph request");
|
|
12326
|
+
this.debug("URL:", invokeUrl);
|
|
12327
|
+
const response = await fetch(invokeUrl, {
|
|
12328
|
+
method: "POST",
|
|
12329
|
+
headers: {
|
|
12330
|
+
"Content-Type": "application/json",
|
|
12331
|
+
...headers
|
|
12332
|
+
},
|
|
12333
|
+
body: JSON.stringify(payload)
|
|
12334
|
+
});
|
|
12335
|
+
if (!response.ok) {
|
|
12336
|
+
const errorText = await response.text();
|
|
12337
|
+
throw new Error(`LangGraph request failed: ${response.status} - ${errorText}`);
|
|
12338
|
+
}
|
|
12339
|
+
const data = await response.json();
|
|
12340
|
+
onRawEvent?.(data);
|
|
12341
|
+
const trajectory = this.parseResponse(data);
|
|
12342
|
+
trajectory.forEach((step) => onProgress?.(step));
|
|
12343
|
+
return {
|
|
12344
|
+
trajectory,
|
|
12345
|
+
runId: data.run_id || data.thread_id || threadId || null,
|
|
12346
|
+
rawEvents: [data],
|
|
12347
|
+
metadata: {
|
|
12348
|
+
graphId,
|
|
12349
|
+
threadId: data.thread_id || threadId
|
|
12350
|
+
}
|
|
12351
|
+
};
|
|
12352
|
+
}
|
|
12353
|
+
parseResponse(data) {
|
|
12354
|
+
const steps = [];
|
|
12355
|
+
const messages = data.output?.messages || data.values?.messages || data.messages || [];
|
|
12356
|
+
for (const msg of messages) {
|
|
12357
|
+
const role = msg.type || msg.role;
|
|
12358
|
+
const content = typeof msg.content === "string" ? msg.content : Array.isArray(msg.content) ? msg.content.map((c) => c.text || JSON.stringify(c)).join("\n") : JSON.stringify(msg.content);
|
|
12359
|
+
if (role === "ai" || role === "assistant") {
|
|
12360
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
|
|
12361
|
+
for (const toolCall of msg.tool_calls) {
|
|
12362
|
+
steps.push(this.createStep("action", `Calling ${toolCall.name}...`, {
|
|
12363
|
+
toolName: toolCall.name,
|
|
12364
|
+
toolArgs: toolCall.args
|
|
12365
|
+
}));
|
|
12366
|
+
}
|
|
12367
|
+
}
|
|
12368
|
+
if (content && !msg.tool_calls?.length) {
|
|
12369
|
+
steps.push(this.createStep("assistant", content));
|
|
12370
|
+
}
|
|
12371
|
+
} else if (role === "tool") {
|
|
12372
|
+
steps.push(this.createStep("tool_result", content, {
|
|
12373
|
+
toolName: msg.name,
|
|
12374
|
+
status: "SUCCESS"
|
|
12375
|
+
}));
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
const lastAssistant = [...steps].reverse().find((s) => s.type === "assistant");
|
|
12379
|
+
if (lastAssistant) {
|
|
12380
|
+
lastAssistant.type = "response";
|
|
12381
|
+
}
|
|
12382
|
+
if (data.intermediate_steps && Array.isArray(data.intermediate_steps)) {
|
|
12383
|
+
for (const [action, observation] of data.intermediate_steps) {
|
|
12384
|
+
if (action?.tool) {
|
|
12385
|
+
steps.push(this.createStep("action", `Calling ${action.tool}...`, {
|
|
12386
|
+
toolName: action.tool,
|
|
12387
|
+
toolArgs: action.tool_input
|
|
12388
|
+
}));
|
|
12389
|
+
}
|
|
12390
|
+
if (observation !== void 0) {
|
|
12391
|
+
const obsContent = typeof observation === "string" ? observation : JSON.stringify(observation);
|
|
12392
|
+
steps.push(this.createStep("tool_result", obsContent, {
|
|
12393
|
+
status: "SUCCESS"
|
|
12394
|
+
}));
|
|
12395
|
+
}
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12398
|
+
if (data.output && typeof data.output === "string" && steps.length === 0) {
|
|
12399
|
+
steps.push(this.createStep("response", data.output));
|
|
12400
|
+
}
|
|
12401
|
+
if (steps.length === 0 && data) {
|
|
12402
|
+
steps.push(this.createStep("response", JSON.stringify(data, null, 2)));
|
|
12403
|
+
}
|
|
12404
|
+
return steps;
|
|
12405
|
+
}
|
|
12406
|
+
async healthCheck(endpoint, auth) {
|
|
12407
|
+
try {
|
|
12408
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12409
|
+
const baseUrl = endpoint.replace(/\/+$/, "");
|
|
12410
|
+
const response = await fetch(`${baseUrl}/ok`, {
|
|
12411
|
+
method: "GET",
|
|
12412
|
+
headers
|
|
12413
|
+
});
|
|
12414
|
+
return response.ok;
|
|
12415
|
+
} catch {
|
|
12416
|
+
try {
|
|
12417
|
+
const headers = this.buildAuthHeaders(auth);
|
|
12418
|
+
const response = await fetch(endpoint, { method: "GET", headers });
|
|
12419
|
+
return response.ok;
|
|
12420
|
+
} catch {
|
|
12421
|
+
return false;
|
|
12422
|
+
}
|
|
12423
|
+
}
|
|
12424
|
+
}
|
|
12425
|
+
};
|
|
12426
|
+
var langgraphConnector = new LangGraphConnector();
|
|
12427
|
+
|
|
12095
12428
|
// services/connectors/index.ts
|
|
12096
12429
|
connectorRegistry.register(aguiStreamingConnector);
|
|
12097
12430
|
connectorRegistry.register(mockConnector);
|
|
12098
12431
|
connectorRegistry.register(restConnector);
|
|
12099
12432
|
connectorRegistry.register(openaiCompatibleConnector);
|
|
12433
|
+
connectorRegistry.register(langgraphConnector);
|
|
12100
12434
|
console.log("[Connectors] Browser-safe connectors registered:", connectorRegistry.getRegisteredTypes().join(", "));
|
|
12101
12435
|
|
|
12102
12436
|
// services/connectors/subprocess/SubprocessConnector.ts
|
|
@@ -12140,6 +12474,7 @@ Question: ${prompt}`;
|
|
|
12140
12474
|
const command = endpoint || this.config.command;
|
|
12141
12475
|
const args = this.config.args || [];
|
|
12142
12476
|
const input = request2.payload || this.buildPayload(request2);
|
|
12477
|
+
const runId = `subprocess-${Date.now()}`;
|
|
12143
12478
|
this.debug("Command:", command);
|
|
12144
12479
|
this.debug("Args:", args);
|
|
12145
12480
|
this.debug("Input mode:", this.config.inputMode);
|
|
@@ -12147,10 +12482,12 @@ Question: ${prompt}`;
|
|
|
12147
12482
|
this.debug("Timeout:", this.config.timeout);
|
|
12148
12483
|
this.debug("Input (first 500 chars):", input.substring(0, 500));
|
|
12149
12484
|
this.debug("Working dir:", this.config.workingDir || process.cwd());
|
|
12485
|
+
this.debug("Run ID:", runId);
|
|
12150
12486
|
const env = {
|
|
12151
12487
|
...process.env,
|
|
12152
12488
|
...this.buildAuthEnv(auth),
|
|
12153
|
-
...this.config.env
|
|
12489
|
+
...this.config.env,
|
|
12490
|
+
AGENT_EVAL_RUN_ID: runId
|
|
12154
12491
|
};
|
|
12155
12492
|
return new Promise((resolve2, reject) => {
|
|
12156
12493
|
const trajectory = [];
|
|
@@ -12209,6 +12546,17 @@ Question: ${prompt}`;
|
|
|
12209
12546
|
this.error(`Process exited with code ${code}`);
|
|
12210
12547
|
this.error("stderr:", stderr);
|
|
12211
12548
|
}
|
|
12549
|
+
if (this.config.outputParser === "streaming") {
|
|
12550
|
+
this.onBeforeStreamEnd(trajectory, onProgress);
|
|
12551
|
+
if (code !== 0 && trajectory.length === 0) {
|
|
12552
|
+
const errorContent = stderr.trim() ? `Error: Process exited with code ${code}. ${stderr.trim()}` : `Error: Process exited with code ${code}`;
|
|
12553
|
+
const errorStep = this.createStep("tool_result", errorContent, {
|
|
12554
|
+
status: "FAILURE" /* FAILURE */
|
|
12555
|
+
});
|
|
12556
|
+
trajectory.push(errorStep);
|
|
12557
|
+
onProgress?.(errorStep);
|
|
12558
|
+
}
|
|
12559
|
+
}
|
|
12212
12560
|
const finalTrajectory = this.config.outputParser === "streaming" ? trajectory : this.parseResponse({ stdout, stderr, exitCode: code });
|
|
12213
12561
|
if (this.config.outputParser !== "streaming") {
|
|
12214
12562
|
finalTrajectory.forEach((step) => onProgress?.(step));
|
|
@@ -12216,7 +12564,7 @@ Question: ${prompt}`;
|
|
|
12216
12564
|
this.debug("Resolving with trajectory of", finalTrajectory.length, "steps");
|
|
12217
12565
|
resolve2({
|
|
12218
12566
|
trajectory: finalTrajectory,
|
|
12219
|
-
runId
|
|
12567
|
+
runId,
|
|
12220
12568
|
rawEvents: rawOutput,
|
|
12221
12569
|
metadata: {
|
|
12222
12570
|
command,
|
|
@@ -12247,6 +12595,12 @@ Question: ${prompt}`;
|
|
|
12247
12595
|
});
|
|
12248
12596
|
this.debug("========== execute() COMPLETED ==========");
|
|
12249
12597
|
}
|
|
12598
|
+
/**
|
|
12599
|
+
* Hook called before returning the streaming trajectory on process close.
|
|
12600
|
+
* Subclasses can override to flush internal buffers.
|
|
12601
|
+
*/
|
|
12602
|
+
onBeforeStreamEnd(_trajectory, _onProgress) {
|
|
12603
|
+
}
|
|
12250
12604
|
/**
|
|
12251
12605
|
* Parse streaming output and emit steps in real-time
|
|
12252
12606
|
*/
|
|
@@ -12341,6 +12695,7 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12341
12695
|
this.name = "Claude Code CLI";
|
|
12342
12696
|
this.outputBuffer = "";
|
|
12343
12697
|
this.thinkingBuffer = "";
|
|
12698
|
+
this.textBuffer = "";
|
|
12344
12699
|
this.isInThinking = false;
|
|
12345
12700
|
}
|
|
12346
12701
|
/**
|
|
@@ -12410,11 +12765,17 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12410
12765
|
if (event.delta?.type === "thinking_delta" && event.delta.thinking) {
|
|
12411
12766
|
this.thinkingBuffer += event.delta.thinking;
|
|
12412
12767
|
} else if (event.delta?.type === "text_delta" && event.delta.text) {
|
|
12413
|
-
|
|
12768
|
+
this.textBuffer += event.delta.text;
|
|
12769
|
+
}
|
|
12770
|
+
} else if (event.type === "content_block_stop") {
|
|
12771
|
+
if (this.thinkingBuffer) {
|
|
12772
|
+
steps.push(this.createStep("thinking", this.thinkingBuffer));
|
|
12773
|
+
this.thinkingBuffer = "";
|
|
12774
|
+
}
|
|
12775
|
+
if (this.textBuffer) {
|
|
12776
|
+
steps.push(this.createStep("assistant", this.textBuffer));
|
|
12777
|
+
this.textBuffer = "";
|
|
12414
12778
|
}
|
|
12415
|
-
} else if (event.type === "content_block_stop" && this.thinkingBuffer) {
|
|
12416
|
-
steps.push(this.createStep("thinking", this.thinkingBuffer));
|
|
12417
|
-
this.thinkingBuffer = "";
|
|
12418
12779
|
} else if (event.type === "result" && event.result) {
|
|
12419
12780
|
steps.push(this.createStep(
|
|
12420
12781
|
"response",
|
|
@@ -12460,8 +12821,41 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12460
12821
|
resetState() {
|
|
12461
12822
|
this.outputBuffer = "";
|
|
12462
12823
|
this.thinkingBuffer = "";
|
|
12824
|
+
this.textBuffer = "";
|
|
12463
12825
|
this.isInThinking = false;
|
|
12464
12826
|
}
|
|
12827
|
+
/**
|
|
12828
|
+
* Flush remaining buffers when the subprocess stream ends.
|
|
12829
|
+
*/
|
|
12830
|
+
onBeforeStreamEnd(trajectory, onProgress) {
|
|
12831
|
+
if (this.outputBuffer.trim()) {
|
|
12832
|
+
try {
|
|
12833
|
+
const event = JSON.parse(this.outputBuffer.trim());
|
|
12834
|
+
const steps = this.parseJsonEvent(event);
|
|
12835
|
+
for (const step of steps) {
|
|
12836
|
+
trajectory.push(step);
|
|
12837
|
+
onProgress?.(step);
|
|
12838
|
+
}
|
|
12839
|
+
} catch {
|
|
12840
|
+
const step = this.createStep("assistant", this.outputBuffer.trim());
|
|
12841
|
+
trajectory.push(step);
|
|
12842
|
+
onProgress?.(step);
|
|
12843
|
+
}
|
|
12844
|
+
this.outputBuffer = "";
|
|
12845
|
+
}
|
|
12846
|
+
if (this.thinkingBuffer) {
|
|
12847
|
+
const step = this.createStep("thinking", this.thinkingBuffer);
|
|
12848
|
+
trajectory.push(step);
|
|
12849
|
+
onProgress?.(step);
|
|
12850
|
+
this.thinkingBuffer = "";
|
|
12851
|
+
}
|
|
12852
|
+
if (this.textBuffer) {
|
|
12853
|
+
const step = this.createStep("response", this.textBuffer);
|
|
12854
|
+
trajectory.push(step);
|
|
12855
|
+
onProgress?.(step);
|
|
12856
|
+
this.textBuffer = "";
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12465
12859
|
/**
|
|
12466
12860
|
* Build CLI args from ClaudeCodeConnectorConfig
|
|
12467
12861
|
*/
|
|
@@ -12562,9 +12956,214 @@ var ClaudeCodeConnector = class extends SubprocessConnector {
|
|
|
12562
12956
|
};
|
|
12563
12957
|
var claudeCodeConnector = new ClaudeCodeConnector();
|
|
12564
12958
|
|
|
12959
|
+
// services/connectors/strands/StrandsConnector.ts
|
|
12960
|
+
var StrandsConnector = class extends BaseConnector {
|
|
12961
|
+
constructor() {
|
|
12962
|
+
super(...arguments);
|
|
12963
|
+
this.type = "strands";
|
|
12964
|
+
this.name = "Amazon Strands";
|
|
12965
|
+
this.supportsStreaming = true;
|
|
12966
|
+
}
|
|
12967
|
+
buildPayload(request2) {
|
|
12968
|
+
const config2 = request2.connectorConfig || {};
|
|
12969
|
+
return {
|
|
12970
|
+
agentId: "",
|
|
12971
|
+
// Set from endpoint in execute()
|
|
12972
|
+
agentAliasId: config2.agentAliasId || "TSTALIASID",
|
|
12973
|
+
sessionId: config2.sessionId || `eval-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
12974
|
+
inputText: request2.testCase.initialPrompt,
|
|
12975
|
+
enableTrace: config2.enableTrace !== false
|
|
12976
|
+
};
|
|
12977
|
+
}
|
|
12978
|
+
async execute(endpoint, request2, auth, onProgress, onRawEvent) {
|
|
12979
|
+
const payload = request2.payload || this.buildPayload(request2);
|
|
12980
|
+
payload.agentId = endpoint;
|
|
12981
|
+
const config2 = request2.connectorConfig || {};
|
|
12982
|
+
const region = config2.region || auth.awsRegion || process.env.AWS_REGION || "us-east-1";
|
|
12983
|
+
this.debug("Executing Strands agent");
|
|
12984
|
+
this.debug("Agent ID:", endpoint);
|
|
12985
|
+
this.debug("Alias:", payload.agentAliasId);
|
|
12986
|
+
this.debug("Region:", region);
|
|
12987
|
+
const { BedrockAgentRuntimeClient, InvokeAgentCommand } = await import("@aws-sdk/client-bedrock-agent-runtime");
|
|
12988
|
+
const clientConfig = { region };
|
|
12989
|
+
if (auth.type === "aws-sigv4" && auth.awsAccessKeyId && auth.awsSecretAccessKey) {
|
|
12990
|
+
clientConfig.credentials = {
|
|
12991
|
+
accessKeyId: auth.awsAccessKeyId,
|
|
12992
|
+
secretAccessKey: auth.awsSecretAccessKey,
|
|
12993
|
+
...auth.awsSessionToken && { sessionToken: auth.awsSessionToken }
|
|
12994
|
+
};
|
|
12995
|
+
}
|
|
12996
|
+
const client = new BedrockAgentRuntimeClient(clientConfig);
|
|
12997
|
+
const command = new InvokeAgentCommand({
|
|
12998
|
+
agentId: payload.agentId,
|
|
12999
|
+
agentAliasId: payload.agentAliasId,
|
|
13000
|
+
sessionId: payload.sessionId,
|
|
13001
|
+
inputText: payload.inputText,
|
|
13002
|
+
enableTrace: payload.enableTrace
|
|
13003
|
+
});
|
|
13004
|
+
const response = await client.send(command);
|
|
13005
|
+
const trajectory = [];
|
|
13006
|
+
let finalOutput = "";
|
|
13007
|
+
if (response.completion) {
|
|
13008
|
+
for await (const event of response.completion) {
|
|
13009
|
+
onRawEvent?.(event);
|
|
13010
|
+
if (event.chunk?.bytes) {
|
|
13011
|
+
const text = new TextDecoder().decode(event.chunk.bytes);
|
|
13012
|
+
finalOutput += text;
|
|
13013
|
+
}
|
|
13014
|
+
if (event.trace?.trace) {
|
|
13015
|
+
const traceSteps = this.parseTraceEvent(event.trace.trace);
|
|
13016
|
+
for (const step of traceSteps) {
|
|
13017
|
+
trajectory.push(step);
|
|
13018
|
+
onProgress?.(step);
|
|
13019
|
+
}
|
|
13020
|
+
}
|
|
13021
|
+
}
|
|
13022
|
+
}
|
|
13023
|
+
const hasResponseFromTrace = trajectory.some((s) => s.type === "response");
|
|
13024
|
+
if (finalOutput && !hasResponseFromTrace) {
|
|
13025
|
+
const responseStep = this.createStep("response", finalOutput);
|
|
13026
|
+
trajectory.push(responseStep);
|
|
13027
|
+
onProgress?.(responseStep);
|
|
13028
|
+
}
|
|
13029
|
+
return {
|
|
13030
|
+
trajectory,
|
|
13031
|
+
runId: payload.sessionId,
|
|
13032
|
+
metadata: {
|
|
13033
|
+
agentId: endpoint,
|
|
13034
|
+
agentAliasId: payload.agentAliasId,
|
|
13035
|
+
sessionId: payload.sessionId,
|
|
13036
|
+
region
|
|
13037
|
+
}
|
|
13038
|
+
};
|
|
13039
|
+
}
|
|
13040
|
+
parseResponse(rawResponse) {
|
|
13041
|
+
if (rawResponse?.trace?.trace) {
|
|
13042
|
+
return this.parseTraceEvent(rawResponse.trace.trace);
|
|
13043
|
+
}
|
|
13044
|
+
if (typeof rawResponse === "string") {
|
|
13045
|
+
return [this.createStep("response", rawResponse)];
|
|
13046
|
+
}
|
|
13047
|
+
return [];
|
|
13048
|
+
}
|
|
13049
|
+
/**
|
|
13050
|
+
* Parse a Bedrock Agent trace event into TrajectorySteps
|
|
13051
|
+
*/
|
|
13052
|
+
parseTraceEvent(trace3) {
|
|
13053
|
+
const steps = [];
|
|
13054
|
+
if (trace3.preProcessingTrace) {
|
|
13055
|
+
const pre = trace3.preProcessingTrace;
|
|
13056
|
+
if (pre.modelInvocationOutput?.parsedResponse?.isValid !== void 0) {
|
|
13057
|
+
steps.push(this.createStep(
|
|
13058
|
+
"thinking",
|
|
13059
|
+
`Pre-processing: Input ${pre.modelInvocationOutput.parsedResponse.isValid ? "valid" : "invalid"}` + (pre.modelInvocationOutput.parsedResponse.rationale ? ` \u2014 ${pre.modelInvocationOutput.parsedResponse.rationale}` : "")
|
|
13060
|
+
));
|
|
13061
|
+
}
|
|
13062
|
+
}
|
|
13063
|
+
if (trace3.orchestrationTrace) {
|
|
13064
|
+
const orch = trace3.orchestrationTrace;
|
|
13065
|
+
if (orch.rationale?.text) {
|
|
13066
|
+
steps.push(this.createStep("thinking", orch.rationale.text));
|
|
13067
|
+
}
|
|
13068
|
+
if (orch.modelInvocationInput?.text) {
|
|
13069
|
+
steps.push(this.createStep("thinking", `Model input: ${this.truncate(orch.modelInvocationInput.text, 500)}`));
|
|
13070
|
+
}
|
|
13071
|
+
if (orch.invocationInput) {
|
|
13072
|
+
const inv = orch.invocationInput;
|
|
13073
|
+
if (inv.actionGroupInvocationInput) {
|
|
13074
|
+
const action = inv.actionGroupInvocationInput;
|
|
13075
|
+
steps.push(this.createStep("action", `Calling ${action.actionGroupName || "action"}::${action.apiPath || action.function || "invoke"}`, {
|
|
13076
|
+
toolName: `${action.actionGroupName || "action"}::${action.apiPath || action.function || "invoke"}`,
|
|
13077
|
+
toolArgs: action.parameters ? Object.fromEntries(
|
|
13078
|
+
action.parameters.map((p) => [p.name, p.value])
|
|
13079
|
+
) : void 0
|
|
13080
|
+
}));
|
|
13081
|
+
}
|
|
13082
|
+
if (inv.knowledgeBaseLookupInput) {
|
|
13083
|
+
steps.push(this.createStep("action", `Knowledge base lookup: ${inv.knowledgeBaseLookupInput.text}`, {
|
|
13084
|
+
toolName: "knowledge_base_lookup",
|
|
13085
|
+
toolArgs: { query: inv.knowledgeBaseLookupInput.text }
|
|
13086
|
+
}));
|
|
13087
|
+
}
|
|
13088
|
+
}
|
|
13089
|
+
if (orch.observation) {
|
|
13090
|
+
const obs = orch.observation;
|
|
13091
|
+
if (obs.actionGroupInvocationOutput?.text) {
|
|
13092
|
+
steps.push(this.createStep("tool_result", obs.actionGroupInvocationOutput.text, {
|
|
13093
|
+
status: "SUCCESS"
|
|
13094
|
+
}));
|
|
13095
|
+
}
|
|
13096
|
+
if (obs.knowledgeBaseLookupOutput?.retrievedReferences) {
|
|
13097
|
+
const refs = obs.knowledgeBaseLookupOutput.retrievedReferences;
|
|
13098
|
+
steps.push(this.createStep(
|
|
13099
|
+
"tool_result",
|
|
13100
|
+
`Retrieved ${refs.length} reference(s) from knowledge base`,
|
|
13101
|
+
{
|
|
13102
|
+
status: "SUCCESS",
|
|
13103
|
+
toolOutput: refs.map((r) => ({
|
|
13104
|
+
content: this.truncate(r.content?.text, 200),
|
|
13105
|
+
location: r.location
|
|
13106
|
+
}))
|
|
13107
|
+
}
|
|
13108
|
+
));
|
|
13109
|
+
}
|
|
13110
|
+
if (obs.finalResponse?.text) {
|
|
13111
|
+
steps.push(this.createStep("response", obs.finalResponse.text));
|
|
13112
|
+
}
|
|
13113
|
+
}
|
|
13114
|
+
}
|
|
13115
|
+
if (trace3.postProcessingTrace) {
|
|
13116
|
+
const post = trace3.postProcessingTrace;
|
|
13117
|
+
if (post.modelInvocationOutput?.parsedResponse?.text) {
|
|
13118
|
+
steps.push(this.createStep(
|
|
13119
|
+
"thinking",
|
|
13120
|
+
`Post-processing: ${this.truncate(post.modelInvocationOutput.parsedResponse.text, 300)}`
|
|
13121
|
+
));
|
|
13122
|
+
}
|
|
13123
|
+
}
|
|
13124
|
+
if (trace3.failureTrace) {
|
|
13125
|
+
steps.push(this.createStep(
|
|
13126
|
+
"response",
|
|
13127
|
+
`Agent error: ${trace3.failureTrace.failureReason || "Unknown failure"}`
|
|
13128
|
+
));
|
|
13129
|
+
}
|
|
13130
|
+
return steps;
|
|
13131
|
+
}
|
|
13132
|
+
/**
|
|
13133
|
+
* Health check: verify the agent exists via GetAgent
|
|
13134
|
+
*/
|
|
13135
|
+
async healthCheck(endpoint, auth) {
|
|
13136
|
+
try {
|
|
13137
|
+
const config2 = auth;
|
|
13138
|
+
const region = config2.awsRegion || process.env.AWS_REGION || "us-east-1";
|
|
13139
|
+
const { BedrockAgentClient, GetAgentCommand } = await import("@aws-sdk/client-bedrock-agent");
|
|
13140
|
+
const clientConfig = { region };
|
|
13141
|
+
if (auth.type === "aws-sigv4" && auth.awsAccessKeyId && auth.awsSecretAccessKey) {
|
|
13142
|
+
clientConfig.credentials = {
|
|
13143
|
+
accessKeyId: auth.awsAccessKeyId,
|
|
13144
|
+
secretAccessKey: auth.awsSecretAccessKey,
|
|
13145
|
+
...auth.awsSessionToken && { sessionToken: auth.awsSessionToken }
|
|
13146
|
+
};
|
|
13147
|
+
}
|
|
13148
|
+
const client = new BedrockAgentClient(clientConfig);
|
|
13149
|
+
const result = await client.send(new GetAgentCommand({ agentId: endpoint }));
|
|
13150
|
+
return result.agent?.agentStatus === "PREPARED";
|
|
13151
|
+
} catch (error) {
|
|
13152
|
+
this.error("Health check failed:", error);
|
|
13153
|
+
return false;
|
|
13154
|
+
}
|
|
13155
|
+
}
|
|
13156
|
+
truncate(text, maxLen) {
|
|
13157
|
+
if (!text) return "";
|
|
13158
|
+
return text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
|
|
13159
|
+
}
|
|
13160
|
+
};
|
|
13161
|
+
var strandsConnector = new StrandsConnector();
|
|
13162
|
+
|
|
12565
13163
|
// services/connectors/server.ts
|
|
12566
13164
|
connectorRegistry.register(subprocessConnector);
|
|
12567
13165
|
connectorRegistry.register(claudeCodeConnector);
|
|
13166
|
+
connectorRegistry.register(strandsConnector);
|
|
12568
13167
|
console.log("[Connectors] Server connectors registered:", connectorRegistry.getRegisteredTypes().join(", "));
|
|
12569
13168
|
|
|
12570
13169
|
// services/benchmarkRunner.ts
|
|
@@ -12670,6 +13269,9 @@ var runStorage = {
|
|
|
12670
13269
|
const params = new URLSearchParams();
|
|
12671
13270
|
if (options.size) params.append("size", options.size.toString());
|
|
12672
13271
|
if (options.from) params.append("from", options.from.toString());
|
|
13272
|
+
if (options._source && options._source.length > 0) {
|
|
13273
|
+
params.append("fields", options._source.join(","));
|
|
13274
|
+
}
|
|
12673
13275
|
const query = params.toString() ? `?${params.toString()}` : "";
|
|
12674
13276
|
return request("GET", `/runs${query}`);
|
|
12675
13277
|
},
|
|
@@ -13316,7 +13918,489 @@ var TracePollingManager = class {
|
|
|
13316
13918
|
};
|
|
13317
13919
|
var tracePollingManager = new TracePollingManager();
|
|
13318
13920
|
|
|
13921
|
+
// lib/telemetry/provider.ts
|
|
13922
|
+
import { trace } from "@opentelemetry/api";
|
|
13923
|
+
import { NodeTracerProvider, BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
13924
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
13925
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
13926
|
+
|
|
13927
|
+
// lib/telemetry/constants.ts
|
|
13928
|
+
import {
|
|
13929
|
+
ATTR_TEST_SUITE_NAME,
|
|
13930
|
+
ATTR_TEST_SUITE_RUN_STATUS,
|
|
13931
|
+
ATTR_TEST_CASE_NAME,
|
|
13932
|
+
ATTR_TEST_CASE_RESULT_STATUS,
|
|
13933
|
+
TEST_SUITE_RUN_STATUS_VALUE_SUCCESS,
|
|
13934
|
+
TEST_SUITE_RUN_STATUS_VALUE_FAILURE,
|
|
13935
|
+
TEST_SUITE_RUN_STATUS_VALUE_IN_PROGRESS,
|
|
13936
|
+
TEST_CASE_RESULT_STATUS_VALUE_PASS,
|
|
13937
|
+
TEST_CASE_RESULT_STATUS_VALUE_FAIL,
|
|
13938
|
+
ATTR_GEN_AI_OPERATION_NAME as ATTR_GEN_AI_OPERATION_NAME4,
|
|
13939
|
+
ATTR_GEN_AI_EVALUATION_NAME,
|
|
13940
|
+
ATTR_GEN_AI_EVALUATION_SCORE_VALUE,
|
|
13941
|
+
ATTR_GEN_AI_EVALUATION_SCORE_LABEL,
|
|
13942
|
+
ATTR_GEN_AI_EVALUATION_EXPLANATION,
|
|
13943
|
+
EVENT_GEN_AI_EVALUATION_RESULT
|
|
13944
|
+
} from "@opentelemetry/semantic-conventions/incubating";
|
|
13945
|
+
var ATTR_TEST_SUITE_RUN_ID = "test.suite.run.id";
|
|
13946
|
+
var ATTR_TEST_CASE_ID = "test.case.id";
|
|
13947
|
+
var ATTR_TEST_CASE_INPUT = "test.case.input";
|
|
13948
|
+
var ATTR_TEST_CASE_OUTPUT = "test.case.output";
|
|
13949
|
+
var ATTR_TEST_CASE_EXPECTED = "test.case.expected";
|
|
13950
|
+
var ATTR_AGENT_HEALTH_JUDGE_MODEL_ID = "agent_health.judge.model_id";
|
|
13951
|
+
var ATTR_AGENT_HEALTH_JUDGE_DURATION_MS = "agent_health.judge.duration_ms";
|
|
13952
|
+
var ATTR_AGENT_HEALTH_JUDGE_ATTEMPTS = "agent_health.judge.attempts";
|
|
13953
|
+
var ATTR_AGENT_HEALTH_AGENT_DURATION_MS = "agent_health.agent.duration_ms";
|
|
13954
|
+
var ATTR_AGENT_HEALTH_CONNECTOR_PROTOCOL = "agent_health.connector.protocol";
|
|
13955
|
+
var ATTR_AGENT_HEALTH_AGENT_RUN_ID = "gen_ai.request.id";
|
|
13956
|
+
var GEN_AI_OPERATION_NAME_VALUE_EVALUATION = "evaluation";
|
|
13957
|
+
var MAX_ATTRIBUTE_LENGTH = 1e4;
|
|
13958
|
+
var MAX_EXPLANATION_LENGTH = 4e3;
|
|
13959
|
+
var EVAL_TRACER_NAME = "agent-health-eval";
|
|
13960
|
+
|
|
13961
|
+
// lib/telemetry/opensearchExporter.ts
|
|
13962
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
13963
|
+
import { SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
13964
|
+
import { Client as Client2 } from "@opensearch-project/opensearch";
|
|
13965
|
+
function hrTimeToDate(hrTime) {
|
|
13966
|
+
const ms = hrTime[0] * 1e3 + hrTime[1] / 1e6;
|
|
13967
|
+
return new Date(ms).toISOString();
|
|
13968
|
+
}
|
|
13969
|
+
function hrTimeToNanos(hrTime) {
|
|
13970
|
+
return hrTime[0] * 1e9 + hrTime[1];
|
|
13971
|
+
}
|
|
13972
|
+
function durationNanos(start, end) {
|
|
13973
|
+
return hrTimeToNanos(end) - hrTimeToNanos(start);
|
|
13974
|
+
}
|
|
13975
|
+
function flattenKey(key) {
|
|
13976
|
+
return key.replace(/\./g, "@");
|
|
13977
|
+
}
|
|
13978
|
+
function spanKindToString(kind) {
|
|
13979
|
+
switch (kind) {
|
|
13980
|
+
case SpanKind.INTERNAL:
|
|
13981
|
+
return "SPAN_KIND_INTERNAL";
|
|
13982
|
+
case SpanKind.SERVER:
|
|
13983
|
+
return "SPAN_KIND_SERVER";
|
|
13984
|
+
case SpanKind.CLIENT:
|
|
13985
|
+
return "SPAN_KIND_CLIENT";
|
|
13986
|
+
case SpanKind.PRODUCER:
|
|
13987
|
+
return "SPAN_KIND_PRODUCER";
|
|
13988
|
+
case SpanKind.CONSUMER:
|
|
13989
|
+
return "SPAN_KIND_CONSUMER";
|
|
13990
|
+
default:
|
|
13991
|
+
return "SPAN_KIND_INTERNAL";
|
|
13992
|
+
}
|
|
13993
|
+
}
|
|
13994
|
+
function statusCodeToInt(code) {
|
|
13995
|
+
switch (code) {
|
|
13996
|
+
case SpanStatusCode.UNSET:
|
|
13997
|
+
return 0;
|
|
13998
|
+
case SpanStatusCode.OK:
|
|
13999
|
+
return 1;
|
|
14000
|
+
case SpanStatusCode.ERROR:
|
|
14001
|
+
return 2;
|
|
14002
|
+
default:
|
|
14003
|
+
return 0;
|
|
14004
|
+
}
|
|
14005
|
+
}
|
|
14006
|
+
function spanToDocument(span) {
|
|
14007
|
+
const doc = {
|
|
14008
|
+
traceId: span.spanContext().traceId,
|
|
14009
|
+
spanId: span.spanContext().spanId,
|
|
14010
|
+
parentSpanId: span.parentSpanContext?.spanId && span.parentSpanContext.spanId !== "0000000000000000" ? span.parentSpanContext.spanId : "",
|
|
14011
|
+
name: span.name,
|
|
14012
|
+
kind: spanKindToString(span.kind),
|
|
14013
|
+
startTime: hrTimeToDate(span.startTime),
|
|
14014
|
+
endTime: hrTimeToDate(span.endTime),
|
|
14015
|
+
durationInNanos: durationNanos(span.startTime, span.endTime),
|
|
14016
|
+
"status.code": statusCodeToInt(span.status.code),
|
|
14017
|
+
"status.message": span.status.message || "",
|
|
14018
|
+
traceState: "",
|
|
14019
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
14020
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
14021
|
+
droppedLinksCount: span.droppedLinksCount
|
|
14022
|
+
};
|
|
14023
|
+
for (const [key, value] of Object.entries(span.attributes)) {
|
|
14024
|
+
if (value !== void 0) {
|
|
14025
|
+
doc[`span.attributes.${flattenKey(key)}`] = value;
|
|
14026
|
+
}
|
|
14027
|
+
}
|
|
14028
|
+
const resource = span.resource;
|
|
14029
|
+
if (resource?.attributes) {
|
|
14030
|
+
for (const [key, value] of Object.entries(resource.attributes)) {
|
|
14031
|
+
if (value !== void 0) {
|
|
14032
|
+
doc[`resource.attributes.${flattenKey(key)}`] = value;
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
}
|
|
14036
|
+
const serviceName = resource?.attributes?.["service.name"] || "unknown";
|
|
14037
|
+
doc.serviceName = serviceName;
|
|
14038
|
+
const scope = span.instrumentationScope ?? span.instrumentationLibrary ?? {};
|
|
14039
|
+
doc["instrumentationScope.name"] = scope.name || "";
|
|
14040
|
+
if (scope.version) {
|
|
14041
|
+
doc["instrumentationScope.version"] = scope.version;
|
|
14042
|
+
}
|
|
14043
|
+
if (span.events.length > 0) {
|
|
14044
|
+
doc.events = span.events.map((event) => {
|
|
14045
|
+
const eventDoc = {
|
|
14046
|
+
name: event.name,
|
|
14047
|
+
time: hrTimeToDate(event.time),
|
|
14048
|
+
droppedAttributesCount: event.droppedAttributesCount || 0
|
|
14049
|
+
};
|
|
14050
|
+
if (event.attributes) {
|
|
14051
|
+
const attrs = {};
|
|
14052
|
+
for (const [key, value] of Object.entries(event.attributes)) {
|
|
14053
|
+
if (value !== void 0) {
|
|
14054
|
+
attrs[flattenKey(key)] = value;
|
|
14055
|
+
}
|
|
14056
|
+
}
|
|
14057
|
+
eventDoc.attributes = attrs;
|
|
14058
|
+
}
|
|
14059
|
+
return eventDoc;
|
|
14060
|
+
});
|
|
14061
|
+
} else {
|
|
14062
|
+
doc.events = [];
|
|
14063
|
+
}
|
|
14064
|
+
if (span.links.length > 0) {
|
|
14065
|
+
doc.links = span.links.map((link) => ({
|
|
14066
|
+
traceId: link.context.traceId,
|
|
14067
|
+
spanId: link.context.spanId,
|
|
14068
|
+
traceState: link.context.traceState?.serialize() || "",
|
|
14069
|
+
attributes: link.attributes || {},
|
|
14070
|
+
droppedAttributesCount: link.droppedAttributesCount || 0
|
|
14071
|
+
}));
|
|
14072
|
+
} else {
|
|
14073
|
+
doc.links = [];
|
|
14074
|
+
}
|
|
14075
|
+
const hasRealParent = span.parentSpanContext?.spanId && span.parentSpanContext.spanId !== "0000000000000000";
|
|
14076
|
+
if (!hasRealParent) {
|
|
14077
|
+
doc.traceGroup = span.name;
|
|
14078
|
+
doc.traceGroupFields = {
|
|
14079
|
+
endTime: hrTimeToDate(span.endTime),
|
|
14080
|
+
durationInNanos: durationNanos(span.startTime, span.endTime),
|
|
14081
|
+
statusCode: statusCodeToInt(span.status.code)
|
|
14082
|
+
};
|
|
14083
|
+
}
|
|
14084
|
+
return doc;
|
|
14085
|
+
}
|
|
14086
|
+
var OpenSearchSpanExporter = class {
|
|
14087
|
+
constructor(config2) {
|
|
14088
|
+
this.shutdownRequested = false;
|
|
14089
|
+
const auth = config2.username && config2.password ? `${config2.username}:${config2.password}` : void 0;
|
|
14090
|
+
this.client = new Client2({
|
|
14091
|
+
node: config2.endpoint,
|
|
14092
|
+
auth: auth ? { username: config2.username, password: config2.password } : void 0,
|
|
14093
|
+
ssl: {
|
|
14094
|
+
rejectUnauthorized: config2.tlsSkipVerify !== true
|
|
14095
|
+
}
|
|
14096
|
+
});
|
|
14097
|
+
this.indexName = config2.indexName || "otel-v1-apm-span-000001";
|
|
14098
|
+
console.log(`[Telemetry] OpenSearch exporter initialized \u2192 ${config2.endpoint} (index: ${this.indexName})`);
|
|
14099
|
+
}
|
|
14100
|
+
export(spans, resultCallback) {
|
|
14101
|
+
if (this.shutdownRequested) {
|
|
14102
|
+
resultCallback({ code: ExportResultCode.FAILED });
|
|
14103
|
+
return;
|
|
14104
|
+
}
|
|
14105
|
+
this._export(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS })).catch((err) => {
|
|
14106
|
+
console.error("[Telemetry] OpenSearch export failed:", err instanceof Error ? err.message : err);
|
|
14107
|
+
resultCallback({ code: ExportResultCode.FAILED });
|
|
14108
|
+
});
|
|
14109
|
+
}
|
|
14110
|
+
async _export(spans) {
|
|
14111
|
+
if (spans.length === 0) return;
|
|
14112
|
+
const body = [];
|
|
14113
|
+
for (const span of spans) {
|
|
14114
|
+
const doc = spanToDocument(span);
|
|
14115
|
+
const docId = `${doc.traceId}/${doc.spanId}`;
|
|
14116
|
+
body.push({ index: { _index: this.indexName, _id: docId } });
|
|
14117
|
+
body.push(doc);
|
|
14118
|
+
}
|
|
14119
|
+
const response = await this.client.bulk({ body });
|
|
14120
|
+
if (response.body.errors) {
|
|
14121
|
+
const errors = response.body.items.filter((item) => item.index?.error).map((item) => `${item.index.error.type}: ${item.index.error.reason}`);
|
|
14122
|
+
if (errors.length > 0) {
|
|
14123
|
+
console.warn(`[Telemetry] OpenSearch bulk write had ${errors.length} error(s):`, errors[0]);
|
|
14124
|
+
}
|
|
14125
|
+
} else {
|
|
14126
|
+
console.log(`[Telemetry] Exported ${spans.length} span(s) to OpenSearch`);
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
14129
|
+
async shutdown() {
|
|
14130
|
+
this.shutdownRequested = true;
|
|
14131
|
+
await this.client.close();
|
|
14132
|
+
}
|
|
14133
|
+
async forceFlush() {
|
|
14134
|
+
}
|
|
14135
|
+
};
|
|
14136
|
+
|
|
14137
|
+
// lib/telemetry/provider.ts
|
|
14138
|
+
var provider = null;
|
|
14139
|
+
var telemetryEnabled = false;
|
|
14140
|
+
function resolveEvalTelemetryConfig(userConfig) {
|
|
14141
|
+
const enabled = userConfig?.enabled ?? process.env.OTEL_EVAL_ENABLED === "true";
|
|
14142
|
+
const exporterEndpoint = userConfig?.exporterEndpoint ?? process.env.OTEL_EVAL_EXPORTER_ENDPOINT ?? "http://localhost:4318/v1/traces";
|
|
14143
|
+
let exporterHeaders = userConfig?.exporterHeaders;
|
|
14144
|
+
if (!exporterHeaders && process.env.OTEL_EVAL_EXPORTER_HEADERS) {
|
|
14145
|
+
try {
|
|
14146
|
+
exporterHeaders = JSON.parse(process.env.OTEL_EVAL_EXPORTER_HEADERS);
|
|
14147
|
+
} catch {
|
|
14148
|
+
console.warn("[Telemetry] Failed to parse OTEL_EVAL_EXPORTER_HEADERS as JSON");
|
|
14149
|
+
}
|
|
14150
|
+
}
|
|
14151
|
+
const serviceName = userConfig?.serviceName ?? process.env.OTEL_EVAL_SERVICE_NAME ?? "agent-health";
|
|
14152
|
+
return { enabled, exporterEndpoint, exporterHeaders, serviceName, opensearch: userConfig?.opensearch };
|
|
14153
|
+
}
|
|
14154
|
+
function initEvalTracerProvider(config2) {
|
|
14155
|
+
if (provider) {
|
|
14156
|
+
return;
|
|
14157
|
+
}
|
|
14158
|
+
if (!config2.enabled) {
|
|
14159
|
+
telemetryEnabled = false;
|
|
14160
|
+
console.log("[Telemetry] Evaluation telemetry disabled (OTEL_EVAL_ENABLED != true)");
|
|
14161
|
+
return;
|
|
14162
|
+
}
|
|
14163
|
+
const resource = resourceFromAttributes({
|
|
14164
|
+
"service.name": config2.serviceName ?? "agent-health",
|
|
14165
|
+
"telemetry.sdk.name": "agent-health",
|
|
14166
|
+
"telemetry.sdk.language": "nodejs"
|
|
14167
|
+
});
|
|
14168
|
+
const spanProcessors = [];
|
|
14169
|
+
const hasExplicitOtlpEndpoint = config2.exporterEndpoint !== "http://localhost:4318/v1/traces";
|
|
14170
|
+
if (hasExplicitOtlpEndpoint) {
|
|
14171
|
+
const otlpExporter = new OTLPTraceExporter({
|
|
14172
|
+
url: config2.exporterEndpoint,
|
|
14173
|
+
headers: config2.exporterHeaders
|
|
14174
|
+
});
|
|
14175
|
+
spanProcessors.push(new BatchSpanProcessor(otlpExporter));
|
|
14176
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OTLP (${config2.exporterEndpoint})`);
|
|
14177
|
+
} else if (config2.opensearch) {
|
|
14178
|
+
const osExporter = new OpenSearchSpanExporter(config2.opensearch);
|
|
14179
|
+
spanProcessors.push(new SimpleSpanProcessor(osExporter));
|
|
14180
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OpenSearch direct (${config2.opensearch.endpoint})`);
|
|
14181
|
+
} else {
|
|
14182
|
+
const otlpExporter = new OTLPTraceExporter({
|
|
14183
|
+
url: config2.exporterEndpoint,
|
|
14184
|
+
headers: config2.exporterHeaders
|
|
14185
|
+
});
|
|
14186
|
+
spanProcessors.push(new BatchSpanProcessor(otlpExporter));
|
|
14187
|
+
console.log(`[Telemetry] Evaluation telemetry enabled \u2192 OTLP (${config2.exporterEndpoint})`);
|
|
14188
|
+
}
|
|
14189
|
+
provider = new NodeTracerProvider({
|
|
14190
|
+
resource,
|
|
14191
|
+
spanProcessors
|
|
14192
|
+
});
|
|
14193
|
+
provider.register();
|
|
14194
|
+
telemetryEnabled = true;
|
|
14195
|
+
}
|
|
14196
|
+
function getEvalTracer() {
|
|
14197
|
+
return trace.getTracer(EVAL_TRACER_NAME);
|
|
14198
|
+
}
|
|
14199
|
+
function isEvalTelemetryEnabled() {
|
|
14200
|
+
return telemetryEnabled;
|
|
14201
|
+
}
|
|
14202
|
+
async function flushEvalTracer() {
|
|
14203
|
+
if (provider) {
|
|
14204
|
+
try {
|
|
14205
|
+
await provider.forceFlush();
|
|
14206
|
+
} catch (err) {
|
|
14207
|
+
console.warn("[Telemetry] forceFlush failed:", err instanceof Error ? err.message : err);
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
async function shutdownEvalTracer() {
|
|
14212
|
+
if (provider) {
|
|
14213
|
+
await provider.shutdown();
|
|
14214
|
+
provider = null;
|
|
14215
|
+
telemetryEnabled = false;
|
|
14216
|
+
console.log("[Telemetry] Evaluation telemetry shut down");
|
|
14217
|
+
}
|
|
14218
|
+
}
|
|
14219
|
+
|
|
14220
|
+
// lib/telemetry/evalSpans.ts
|
|
14221
|
+
import {
|
|
14222
|
+
context,
|
|
14223
|
+
trace as trace2,
|
|
14224
|
+
TraceFlags,
|
|
14225
|
+
SpanKind as SpanKind2,
|
|
14226
|
+
SpanStatusCode as SpanStatusCode2
|
|
14227
|
+
} from "@opentelemetry/api";
|
|
14228
|
+
function truncate(value, maxLength) {
|
|
14229
|
+
if (!value) return void 0;
|
|
14230
|
+
return value.length > maxLength ? value.slice(0, maxLength) + "..." : value;
|
|
14231
|
+
}
|
|
14232
|
+
function extractAgentResponse(report) {
|
|
14233
|
+
const responseStep = [...report.trajectory].reverse().find(
|
|
14234
|
+
(s) => s.type === "response" || s.type === "assistant"
|
|
14235
|
+
);
|
|
14236
|
+
return responseStep?.content;
|
|
14237
|
+
}
|
|
14238
|
+
function startTestSuiteRunSpan(benchmark, run) {
|
|
14239
|
+
if (!isEvalTelemetryEnabled()) return null;
|
|
14240
|
+
const tracer = getEvalTracer();
|
|
14241
|
+
const span = tracer.startSpan(`test_suite_run ${benchmark.name}`, {
|
|
14242
|
+
kind: SpanKind2.INTERNAL,
|
|
14243
|
+
attributes: {
|
|
14244
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14245
|
+
[ATTR_TEST_SUITE_RUN_ID]: run.id,
|
|
14246
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14247
|
+
}
|
|
14248
|
+
});
|
|
14249
|
+
const ctx = context.active();
|
|
14250
|
+
const spanContext = trace2.setSpan(ctx, span);
|
|
14251
|
+
console.log(`[Telemetry] Started test_suite_run span for "${benchmark.name}" (runId=${run.id})`);
|
|
14252
|
+
return { span, context: spanContext };
|
|
14253
|
+
}
|
|
14254
|
+
function startTestCaseSpan(parentCtx, testCase, benchmark, run, agentRunId, links) {
|
|
14255
|
+
if (!isEvalTelemetryEnabled()) return null;
|
|
14256
|
+
const tracer = getEvalTracer();
|
|
14257
|
+
const attributes = {
|
|
14258
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14259
|
+
[ATTR_TEST_SUITE_RUN_ID]: run.id,
|
|
14260
|
+
[ATTR_TEST_CASE_ID]: testCase.id,
|
|
14261
|
+
[ATTR_TEST_CASE_NAME]: testCase.name,
|
|
14262
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14263
|
+
};
|
|
14264
|
+
if (agentRunId) {
|
|
14265
|
+
attributes[ATTR_AGENT_HEALTH_AGENT_RUN_ID] = agentRunId;
|
|
14266
|
+
}
|
|
14267
|
+
attributes[ATTR_TEST_CASE_INPUT] = truncate(testCase.initialPrompt || "", MAX_ATTRIBUTE_LENGTH);
|
|
14268
|
+
const expected = truncate(
|
|
14269
|
+
testCase.expectedOutcomes ? JSON.stringify(testCase.expectedOutcomes) : void 0,
|
|
14270
|
+
MAX_ATTRIBUTE_LENGTH
|
|
14271
|
+
);
|
|
14272
|
+
if (expected) attributes[ATTR_TEST_CASE_EXPECTED] = expected;
|
|
14273
|
+
const span = tracer.startSpan(
|
|
14274
|
+
"test_case",
|
|
14275
|
+
{
|
|
14276
|
+
kind: SpanKind2.INTERNAL,
|
|
14277
|
+
attributes,
|
|
14278
|
+
links
|
|
14279
|
+
},
|
|
14280
|
+
parentCtx
|
|
14281
|
+
);
|
|
14282
|
+
const ctx = trace2.setSpan(parentCtx, span);
|
|
14283
|
+
console.log(`[Telemetry] Started test_case span for "${testCase.name}" (agentRunId=${agentRunId || "none"})`);
|
|
14284
|
+
return { span, context: ctx };
|
|
14285
|
+
}
|
|
14286
|
+
function addEvaluationResultEvents(span, report) {
|
|
14287
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14288
|
+
if (report.metrics?.accuracy !== void 0) {
|
|
14289
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14290
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "accuracy",
|
|
14291
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.accuracy,
|
|
14292
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_LABEL]: report.passFailStatus === "passed" ? "pass" : "fail",
|
|
14293
|
+
[ATTR_GEN_AI_EVALUATION_EXPLANATION]: truncate(report.llmJudgeReasoning, MAX_EXPLANATION_LENGTH)
|
|
14294
|
+
});
|
|
14295
|
+
}
|
|
14296
|
+
if (report.metrics?.faithfulness !== void 0) {
|
|
14297
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14298
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "faithfulness",
|
|
14299
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.faithfulness
|
|
14300
|
+
});
|
|
14301
|
+
}
|
|
14302
|
+
if (report.metrics?.latency_score !== void 0) {
|
|
14303
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14304
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "latency_score",
|
|
14305
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.latency_score
|
|
14306
|
+
});
|
|
14307
|
+
}
|
|
14308
|
+
if (report.metrics?.trajectory_alignment_score !== void 0) {
|
|
14309
|
+
span.addEvent(EVENT_GEN_AI_EVALUATION_RESULT, {
|
|
14310
|
+
[ATTR_GEN_AI_EVALUATION_NAME]: "trajectory_alignment_score",
|
|
14311
|
+
[ATTR_GEN_AI_EVALUATION_SCORE_VALUE]: report.metrics.trajectory_alignment_score
|
|
14312
|
+
});
|
|
14313
|
+
}
|
|
14314
|
+
}
|
|
14315
|
+
function finalizeTestCaseSpan(span, report, endTime) {
|
|
14316
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14317
|
+
const status = report.passFailStatus === "passed" ? TEST_CASE_RESULT_STATUS_VALUE_PASS : TEST_CASE_RESULT_STATUS_VALUE_FAIL;
|
|
14318
|
+
span.setAttribute(ATTR_TEST_CASE_RESULT_STATUS, status);
|
|
14319
|
+
const output = extractAgentResponse(report) || "";
|
|
14320
|
+
span.setAttribute(ATTR_TEST_CASE_OUTPUT, truncate(output, MAX_ATTRIBUTE_LENGTH));
|
|
14321
|
+
if (report.connectorProtocol) {
|
|
14322
|
+
span.setAttribute(ATTR_AGENT_HEALTH_CONNECTOR_PROTOCOL, report.connectorProtocol);
|
|
14323
|
+
}
|
|
14324
|
+
if (report.performanceMetrics?.agentDurationMs !== void 0) {
|
|
14325
|
+
span.setAttribute(ATTR_AGENT_HEALTH_AGENT_DURATION_MS, report.performanceMetrics.agentDurationMs);
|
|
14326
|
+
}
|
|
14327
|
+
if (report.performanceMetrics?.judgeDurationMs !== void 0) {
|
|
14328
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_DURATION_MS, report.performanceMetrics.judgeDurationMs);
|
|
14329
|
+
}
|
|
14330
|
+
if (report.performanceMetrics?.judgeAttempts !== void 0) {
|
|
14331
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_ATTEMPTS, report.performanceMetrics.judgeAttempts);
|
|
14332
|
+
}
|
|
14333
|
+
if (report.llmJudgeResponse?.modelId) {
|
|
14334
|
+
span.setAttribute(ATTR_AGENT_HEALTH_JUDGE_MODEL_ID, report.llmJudgeResponse.modelId);
|
|
14335
|
+
}
|
|
14336
|
+
if (report.status === "failed") {
|
|
14337
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: "Evaluation failed" });
|
|
14338
|
+
}
|
|
14339
|
+
span.end(endTime);
|
|
14340
|
+
console.log(`[Telemetry] Finalized test_case span (status=${status}, passFailStatus=${report.passFailStatus})`);
|
|
14341
|
+
}
|
|
14342
|
+
function finalizeTestSuiteRunSpan(span, run) {
|
|
14343
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14344
|
+
const results = Object.values(run.results);
|
|
14345
|
+
const hasFailures = results.some((r) => r.status === "failed");
|
|
14346
|
+
const status = hasFailures ? TEST_SUITE_RUN_STATUS_VALUE_FAILURE : TEST_SUITE_RUN_STATUS_VALUE_SUCCESS;
|
|
14347
|
+
span.setAttribute(ATTR_TEST_SUITE_RUN_STATUS, status);
|
|
14348
|
+
if (hasFailures) {
|
|
14349
|
+
const failCount = results.filter((r) => r.status === "failed").length;
|
|
14350
|
+
span.setStatus({ code: SpanStatusCode2.ERROR, message: `${failCount} case(s) failed` });
|
|
14351
|
+
}
|
|
14352
|
+
span.end();
|
|
14353
|
+
console.log(`[Telemetry] Finalized test_suite_run span (status=${status}), flushing...`);
|
|
14354
|
+
flushEvalTracer().catch(() => {
|
|
14355
|
+
});
|
|
14356
|
+
}
|
|
14357
|
+
function emitDeferredTestCaseSpan(testCase, report, benchmark, runId, agentRunId, startTime, endTime, agentTraceId) {
|
|
14358
|
+
if (!isEvalTelemetryEnabled()) return;
|
|
14359
|
+
const tracer = getEvalTracer();
|
|
14360
|
+
const attributes = {
|
|
14361
|
+
[ATTR_TEST_SUITE_NAME]: benchmark.name,
|
|
14362
|
+
[ATTR_TEST_SUITE_RUN_ID]: runId,
|
|
14363
|
+
[ATTR_TEST_CASE_ID]: testCase.id,
|
|
14364
|
+
[ATTR_TEST_CASE_NAME]: testCase.name,
|
|
14365
|
+
[ATTR_GEN_AI_OPERATION_NAME4]: GEN_AI_OPERATION_NAME_VALUE_EVALUATION
|
|
14366
|
+
};
|
|
14367
|
+
if (agentRunId) {
|
|
14368
|
+
attributes[ATTR_AGENT_HEALTH_AGENT_RUN_ID] = agentRunId;
|
|
14369
|
+
}
|
|
14370
|
+
attributes[ATTR_TEST_CASE_INPUT] = truncate(testCase.initialPrompt || "", MAX_ATTRIBUTE_LENGTH);
|
|
14371
|
+
const expected = truncate(
|
|
14372
|
+
testCase.expectedOutcomes ? JSON.stringify(testCase.expectedOutcomes) : void 0,
|
|
14373
|
+
MAX_ATTRIBUTE_LENGTH
|
|
14374
|
+
);
|
|
14375
|
+
if (expected) attributes[ATTR_TEST_CASE_EXPECTED] = expected;
|
|
14376
|
+
let parentCtx;
|
|
14377
|
+
if (agentTraceId) {
|
|
14378
|
+
const remoteSpanContext = {
|
|
14379
|
+
traceId: agentTraceId,
|
|
14380
|
+
// Use a valid synthetic spanId — an all-zero spanId is considered invalid
|
|
14381
|
+
// by the OTel SDK and would cause it to ignore this context entirely.
|
|
14382
|
+
// This "remote" context carries the traceId forward so the eval span
|
|
14383
|
+
// shares the same trace as the agent spans.
|
|
14384
|
+
spanId: "eeee000000000001",
|
|
14385
|
+
traceFlags: TraceFlags.SAMPLED,
|
|
14386
|
+
isRemote: true
|
|
14387
|
+
};
|
|
14388
|
+
parentCtx = trace2.setSpanContext(context.active(), remoteSpanContext);
|
|
14389
|
+
}
|
|
14390
|
+
const span = tracer.startSpan("test_case", {
|
|
14391
|
+
kind: SpanKind2.INTERNAL,
|
|
14392
|
+
startTime,
|
|
14393
|
+
attributes
|
|
14394
|
+
}, parentCtx);
|
|
14395
|
+
addEvaluationResultEvents(span, report);
|
|
14396
|
+
finalizeTestCaseSpan(span, report, endTime);
|
|
14397
|
+
console.log(`[Telemetry] Emitted deferred test_case span for "${testCase.name}" (agentRunId=${agentRunId || "none"}, traceId=${agentTraceId || "new"}), flushing...`);
|
|
14398
|
+
flushEvalTracer().catch(() => {
|
|
14399
|
+
});
|
|
14400
|
+
}
|
|
14401
|
+
|
|
13319
14402
|
// services/benchmarkRunner.ts
|
|
14403
|
+
import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
|
|
13320
14404
|
function getConfig() {
|
|
13321
14405
|
try {
|
|
13322
14406
|
return loadConfigSync();
|
|
@@ -13374,6 +14458,9 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13374
14458
|
const concurrency = run.concurrency ?? 1;
|
|
13375
14459
|
const runStartTime = Date.now();
|
|
13376
14460
|
console.log(`[BenchmarkRunner] Starting run ${run.id} with concurrency=${concurrency} for ${totalTestCases} test cases`);
|
|
14461
|
+
const suiteSpanResult = startTestSuiteRunSpan(benchmark, run);
|
|
14462
|
+
const suiteSpan = suiteSpanResult?.span;
|
|
14463
|
+
const suiteContext = suiteSpanResult?.context;
|
|
13377
14464
|
if (!run.results) {
|
|
13378
14465
|
run.results = {};
|
|
13379
14466
|
}
|
|
@@ -13424,6 +14511,7 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13424
14511
|
});
|
|
13425
14512
|
debug("BenchmarkRunner", `[${testCaseId}] Starting evaluation (${completedCount}/${totalTestCases} completed)`);
|
|
13426
14513
|
const testCaseStartTime = Date.now();
|
|
14514
|
+
let caseSpan;
|
|
13427
14515
|
run.results[testCaseId] = { reportId: "", status: "running" };
|
|
13428
14516
|
try {
|
|
13429
14517
|
const agentConfig = buildAgentConfigForRun(run);
|
|
@@ -13443,7 +14531,23 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13443
14531
|
});
|
|
13444
14532
|
updateTestCaseLastRunAt(client, testCaseId, (/* @__PURE__ */ new Date()).toISOString()).catch((err) => console.warn(`[BenchmarkRunner] Failed to update lastRunAt for ${testCaseId}:`, err.message));
|
|
13445
14533
|
if (savedReport.metricsStatus === "pending" && savedReport.runId) {
|
|
13446
|
-
startTracePollingForReport(savedReport, testCase, client);
|
|
14534
|
+
startTracePollingForReport(savedReport, testCase, client, benchmark, run);
|
|
14535
|
+
}
|
|
14536
|
+
if (suiteContext) {
|
|
14537
|
+
const caseSpanResult = startTestCaseSpan(
|
|
14538
|
+
suiteContext,
|
|
14539
|
+
testCase,
|
|
14540
|
+
benchmark,
|
|
14541
|
+
run,
|
|
14542
|
+
savedReport.runId
|
|
14543
|
+
);
|
|
14544
|
+
caseSpan = caseSpanResult?.span;
|
|
14545
|
+
if (caseSpan && savedReport.metricsStatus !== "pending") {
|
|
14546
|
+
addEvaluationResultEvents(caseSpan, savedReport);
|
|
14547
|
+
finalizeTestCaseSpan(caseSpan, savedReport);
|
|
14548
|
+
} else if (caseSpan) {
|
|
14549
|
+
caseSpan.end();
|
|
14550
|
+
}
|
|
13447
14551
|
}
|
|
13448
14552
|
run.results[testCaseId] = {
|
|
13449
14553
|
reportId: savedReport.id,
|
|
@@ -13462,6 +14566,10 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13462
14566
|
const testCaseDuration = Date.now() - testCaseStartTime;
|
|
13463
14567
|
debug("BenchmarkRunner", `[${testCaseId}] Failed in ${testCaseDuration}ms: ${errorMsg}`);
|
|
13464
14568
|
run.results[testCaseId] = { reportId: "", status: "failed", error: errorMsg };
|
|
14569
|
+
if (caseSpan) {
|
|
14570
|
+
caseSpan.setStatus({ code: SpanStatusCode3.ERROR, message: errorMsg });
|
|
14571
|
+
caseSpan.end();
|
|
14572
|
+
}
|
|
13465
14573
|
completedCount++;
|
|
13466
14574
|
if (errorMsg.includes("ThrottlingException") || errorMsg.includes("rate limit") || errorMsg.includes("429")) {
|
|
13467
14575
|
consecutiveThrottles++;
|
|
@@ -13505,6 +14613,9 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13505
14613
|
maxTestCaseDurationMs: testCaseDurations.length > 0 ? Math.max(...testCaseDurations) : 0,
|
|
13506
14614
|
minTestCaseDurationMs: testCaseDurations.length > 0 ? Math.min(...testCaseDurations) : 0
|
|
13507
14615
|
};
|
|
14616
|
+
if (suiteSpan) {
|
|
14617
|
+
finalizeTestSuiteRunSpan(suiteSpan, run);
|
|
14618
|
+
}
|
|
13508
14619
|
return run;
|
|
13509
14620
|
} catch (error) {
|
|
13510
14621
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
@@ -13513,6 +14624,10 @@ async function executeRun(benchmark, run, onProgress, options) {
|
|
|
13513
14624
|
run.results[testCaseId] = { reportId: "", status: "failed", error: `Benchmark execution failed: ${errorMsg}` };
|
|
13514
14625
|
}
|
|
13515
14626
|
});
|
|
14627
|
+
if (suiteSpan) {
|
|
14628
|
+
suiteSpan.setStatus({ code: SpanStatusCode3.ERROR, message: errorMsg });
|
|
14629
|
+
suiteSpan.end();
|
|
14630
|
+
}
|
|
13516
14631
|
throw error;
|
|
13517
14632
|
}
|
|
13518
14633
|
}
|
|
@@ -13544,6 +14659,7 @@ async function saveReportWithModule(storage, report) {
|
|
|
13544
14659
|
async function runSingleUseCase(run, testCase, storage, onStep) {
|
|
13545
14660
|
const agentConfig = buildAgentConfigForRun(run);
|
|
13546
14661
|
const bedrockModelId = getBedrockModelId(run.modelId);
|
|
14662
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
13547
14663
|
const report = await runEvaluationWithConnector(
|
|
13548
14664
|
agentConfig,
|
|
13549
14665
|
bedrockModelId,
|
|
@@ -13561,6 +14677,17 @@ async function runSingleUseCase(run, testCase, storage, onStep) {
|
|
|
13561
14677
|
if (savedReport.metricsStatus === "pending" && savedReport.runId) {
|
|
13562
14678
|
startTracePollingForReportWithModule(savedReport, testCase, storage);
|
|
13563
14679
|
}
|
|
14680
|
+
if (savedReport.metricsStatus !== "pending") {
|
|
14681
|
+
emitDeferredTestCaseSpan(
|
|
14682
|
+
testCase,
|
|
14683
|
+
savedReport,
|
|
14684
|
+
{ name: `standalone:${agentConfig.name || agentConfig.key}` },
|
|
14685
|
+
run.id,
|
|
14686
|
+
savedReport.runId,
|
|
14687
|
+
startTime,
|
|
14688
|
+
/* @__PURE__ */ new Date()
|
|
14689
|
+
);
|
|
14690
|
+
}
|
|
13564
14691
|
return savedReport.id;
|
|
13565
14692
|
}
|
|
13566
14693
|
function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
@@ -13600,6 +14727,24 @@ function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
|
13600
14727
|
llmJudgeReasoning: judgment.llmJudgeReasoning,
|
|
13601
14728
|
improvementStrategies: judgment.improvementStrategies
|
|
13602
14729
|
});
|
|
14730
|
+
const completedReport = {
|
|
14731
|
+
...report,
|
|
14732
|
+
passFailStatus: judgment.passFailStatus,
|
|
14733
|
+
metrics: judgment.metrics,
|
|
14734
|
+
llmJudgeReasoning: judgment.llmJudgeReasoning
|
|
14735
|
+
};
|
|
14736
|
+
emitDeferredTestCaseSpan(
|
|
14737
|
+
testCase,
|
|
14738
|
+
completedReport,
|
|
14739
|
+
{ name: report.experimentId ? `benchmark:${report.experimentId}` : `standalone:${report.agentKey}` },
|
|
14740
|
+
report.experimentRunId || report.id,
|
|
14741
|
+
report.runId,
|
|
14742
|
+
void 0,
|
|
14743
|
+
// startTime
|
|
14744
|
+
void 0,
|
|
14745
|
+
// endTime
|
|
14746
|
+
spans[0]?.traceId
|
|
14747
|
+
);
|
|
13603
14748
|
if (report.experimentId) {
|
|
13604
14749
|
await refreshBenchmarkRunStats(storage, report.experimentId, report.id);
|
|
13605
14750
|
}
|
|
@@ -13627,7 +14772,7 @@ function startTracePollingForReportWithModule(report, testCase, storage) {
|
|
|
13627
14772
|
}
|
|
13628
14773
|
);
|
|
13629
14774
|
}
|
|
13630
|
-
function startTracePollingForReport(report, testCase, client) {
|
|
14775
|
+
function startTracePollingForReport(report, testCase, client, benchmark, run) {
|
|
13631
14776
|
if (!report.runId) {
|
|
13632
14777
|
console.warn(`[BenchmarkRunner] No runId for report ${report.id}, cannot start trace polling`);
|
|
13633
14778
|
return;
|
|
@@ -13662,6 +14807,16 @@ function startTracePollingForReport(report, testCase, client) {
|
|
|
13662
14807
|
if (report.experimentId) {
|
|
13663
14808
|
await updateBenchmarkRunStatsForReport(client, report.experimentId, report.id);
|
|
13664
14809
|
}
|
|
14810
|
+
if (benchmark && run) {
|
|
14811
|
+
const completedReport = {
|
|
14812
|
+
...report,
|
|
14813
|
+
passFailStatus: judgment.passFailStatus,
|
|
14814
|
+
metrics: judgment.metrics,
|
|
14815
|
+
llmJudgeReasoning: judgment.llmJudgeReasoning
|
|
14816
|
+
};
|
|
14817
|
+
const agentTraceId = spans[0]?.traceId;
|
|
14818
|
+
emitDeferredTestCaseSpan(testCase, completedReport, benchmark, run.id, report.runId, void 0, void 0, agentTraceId);
|
|
14819
|
+
}
|
|
13665
14820
|
} catch (error) {
|
|
13666
14821
|
console.error(`[BenchmarkRunner] Failed to judge report ${report.id}:`, error instanceof Error ? error.message : error);
|
|
13667
14822
|
await updateRunWithClient(client, report.id, {
|
|
@@ -15097,11 +16252,13 @@ router10.get("/api/storage/runs", async (req, res) => {
|
|
|
15097
16252
|
try {
|
|
15098
16253
|
const { size = "100", from = "0", fields } = req.query;
|
|
15099
16254
|
let realData = [];
|
|
16255
|
+
const fieldList = typeof fields === "string" ? fields.split(",").map((f) => f.trim()).filter(Boolean) : void 0;
|
|
15100
16256
|
const storage = getStorageModule();
|
|
15101
16257
|
try {
|
|
15102
16258
|
const result = await storage.runs.getAll({
|
|
15103
16259
|
size: parseInt(size),
|
|
15104
|
-
from: parseInt(from)
|
|
16260
|
+
from: parseInt(from),
|
|
16261
|
+
_source: fieldList
|
|
15105
16262
|
});
|
|
15106
16263
|
realData = result.items;
|
|
15107
16264
|
} catch (e) {
|
|
@@ -19263,7 +20420,25 @@ async function initializeStorageBackend() {
|
|
|
19263
20420
|
}
|
|
19264
20421
|
async function createApp() {
|
|
19265
20422
|
await migrateYamlToJsonIfNeeded();
|
|
19266
|
-
await loadConfig();
|
|
20423
|
+
const config2 = await loadConfig();
|
|
20424
|
+
const obsConfig = getObservabilityConfigFromFile() ?? getObservabilityConfigFromEnv();
|
|
20425
|
+
let opensearchExporterConfig;
|
|
20426
|
+
if (obsConfig?.endpoint) {
|
|
20427
|
+
opensearchExporterConfig = {
|
|
20428
|
+
endpoint: obsConfig.endpoint,
|
|
20429
|
+
username: obsConfig.username,
|
|
20430
|
+
password: obsConfig.password,
|
|
20431
|
+
tlsSkipVerify: obsConfig.tlsSkipVerify,
|
|
20432
|
+
indexName: "otel-v1-apm-span"
|
|
20433
|
+
};
|
|
20434
|
+
}
|
|
20435
|
+
initEvalTracerProvider(resolveEvalTelemetryConfig({
|
|
20436
|
+
...config2.telemetry,
|
|
20437
|
+
opensearch: opensearchExporterConfig
|
|
20438
|
+
}));
|
|
20439
|
+
process.once("SIGTERM", async () => {
|
|
20440
|
+
await shutdownEvalTracer();
|
|
20441
|
+
});
|
|
19267
20442
|
await initializeStorageBackend();
|
|
19268
20443
|
const app = express2();
|
|
19269
20444
|
setupMiddleware(app);
|