observability-toolkit 1.8.4 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backends/local-jsonl-boolean-search.test.js +8 -8
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
- package/dist/backends/local-jsonl-cache.test.d.ts +2 -0
- package/dist/backends/local-jsonl-cache.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-cache.test.js +295 -0
- package/dist/backends/local-jsonl-cache.test.js.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts +2 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js +180 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js.map +1 -0
- package/dist/backends/local-jsonl-export.test.d.ts +2 -0
- package/dist/backends/local-jsonl-export.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-export.test.js +704 -0
- package/dist/backends/local-jsonl-export.test.js.map +1 -0
- package/dist/backends/local-jsonl-index.test.d.ts +2 -0
- package/dist/backends/local-jsonl-index.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-index.test.js +554 -0
- package/dist/backends/local-jsonl-index.test.js.map +1 -0
- package/dist/backends/local-jsonl-logs.test.js +52 -43
- package/dist/backends/local-jsonl-logs.test.js.map +1 -1
- package/dist/backends/local-jsonl-metrics.test.d.ts +2 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.js +876 -0
- package/dist/backends/local-jsonl-metrics.test.js.map +1 -0
- package/dist/backends/local-jsonl-traces.test.js +89 -83
- package/dist/backends/local-jsonl-traces.test.js.map +1 -1
- package/dist/backends/local-jsonl.d.ts +9 -0
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +348 -227
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts +6 -0
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js +548 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts +6 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.js +389 -0
- package/dist/backends/signoz-api-rate-limiter.test.js.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts +6 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.js +216 -0
- package/dist/backends/signoz-api-ssrf.test.js.map +1 -0
- package/dist/backends/signoz-api-test-helpers.d.ts +80 -0
- package/dist/backends/signoz-api-test-helpers.d.ts.map +1 -0
- package/dist/backends/signoz-api-test-helpers.js +79 -0
- package/dist/backends/signoz-api-test-helpers.js.map +1 -0
- package/dist/backends/signoz-api.d.ts +16 -0
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +71 -9
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.d.ts +9 -0
- package/dist/backends/signoz-api.test.d.ts.map +1 -1
- package/dist/backends/signoz-api.test.js +14 -1027
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/cache.d.ts +47 -1
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +40 -3
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +83 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +125 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/circuit-breaker.test.d.ts +2 -0
- package/dist/lib/circuit-breaker.test.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.test.js +263 -0
- package/dist/lib/circuit-breaker.test.js.map +1 -0
- package/dist/lib/constants-symlink.test.d.ts +12 -0
- package/dist/lib/constants-symlink.test.d.ts.map +1 -0
- package/dist/lib/constants-symlink.test.js +357 -0
- package/dist/lib/constants-symlink.test.js.map +1 -0
- package/dist/lib/edge-cases.test.js +17 -17
- package/dist/lib/edge-cases.test.js.map +1 -1
- package/dist/lib/error-sanitizer.d.ts.map +1 -1
- package/dist/lib/error-sanitizer.js +29 -3
- package/dist/lib/error-sanitizer.js.map +1 -1
- package/dist/lib/error-sanitizer.test.js +159 -0
- package/dist/lib/error-sanitizer.test.js.map +1 -1
- package/dist/lib/error-types.d.ts +54 -0
- package/dist/lib/error-types.d.ts.map +1 -0
- package/dist/lib/error-types.js +154 -0
- package/dist/lib/error-types.js.map +1 -0
- package/dist/lib/error-types.test.d.ts +2 -0
- package/dist/lib/error-types.test.d.ts.map +1 -0
- package/dist/lib/error-types.test.js +196 -0
- package/dist/lib/error-types.test.js.map +1 -0
- package/dist/lib/indexer.test.js +27 -27
- package/dist/lib/indexer.test.js.map +1 -1
- package/dist/lib/input-validator.d.ts +12 -0
- package/dist/lib/input-validator.d.ts.map +1 -1
- package/dist/lib/input-validator.fuzz.test.d.ts +12 -0
- package/dist/lib/input-validator.fuzz.test.d.ts.map +1 -0
- package/dist/lib/input-validator.fuzz.test.js +290 -0
- package/dist/lib/input-validator.fuzz.test.js.map +1 -0
- package/dist/lib/input-validator.js +57 -3
- package/dist/lib/input-validator.js.map +1 -1
- package/dist/lib/input-validator.test.js +129 -1
- package/dist/lib/input-validator.test.js.map +1 -1
- package/dist/lib/logger.d.ts +46 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/logger.test.js +122 -0
- package/dist/lib/logger.test.js.map +1 -0
- package/dist/lib/server-utils.d.ts +8 -0
- package/dist/lib/server-utils.d.ts.map +1 -1
- package/dist/lib/server-utils.js +34 -2
- package/dist/lib/server-utils.js.map +1 -1
- package/dist/lib/shared-schemas.d.ts +22 -0
- package/dist/lib/shared-schemas.d.ts.map +1 -1
- package/dist/lib/shared-schemas.js +22 -0
- package/dist/lib/shared-schemas.js.map +1 -1
- package/dist/lib/toon-encoder.d.ts +7 -2
- package/dist/lib/toon-encoder.d.ts.map +1 -1
- package/dist/lib/toon-encoder.js +21 -6
- package/dist/lib/toon-encoder.js.map +1 -1
- package/dist/lib/toon-encoder.test.d.ts +5 -0
- package/dist/lib/toon-encoder.test.d.ts.map +1 -0
- package/dist/lib/toon-encoder.test.js +85 -0
- package/dist/lib/toon-encoder.test.js.map +1 -0
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +30 -0
- package/dist/server.test.js.map +1 -1
- package/dist/test-helpers/env-utils.d.ts +22 -0
- package/dist/test-helpers/env-utils.d.ts.map +1 -1
- package/dist/test-helpers/env-utils.js +38 -0
- package/dist/test-helpers/env-utils.js.map +1 -1
- package/dist/test-helpers/fuzz-generators.d.ts +58 -0
- package/dist/test-helpers/fuzz-generators.d.ts.map +1 -0
- package/dist/test-helpers/fuzz-generators.js +216 -0
- package/dist/test-helpers/fuzz-generators.js.map +1 -0
- package/dist/test-helpers/index.d.ts +1 -0
- package/dist/test-helpers/index.d.ts.map +1 -1
- package/dist/test-helpers/index.js +2 -0
- package/dist/test-helpers/index.js.map +1 -1
- package/dist/test-helpers/memfs-utils.d.ts +181 -0
- package/dist/test-helpers/memfs-utils.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.js +292 -0
- package/dist/test-helpers/memfs-utils.js.map +1 -0
- package/dist/test-helpers/memfs-utils.test.d.ts +5 -0
- package/dist/test-helpers/memfs-utils.test.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.test.js +338 -0
- package/dist/test-helpers/memfs-utils.test.js.map +1 -0
- package/dist/test-helpers/race-condition-helpers.d.ts +85 -0
- package/dist/test-helpers/race-condition-helpers.d.ts.map +1 -0
- package/dist/test-helpers/race-condition-helpers.js +279 -0
- package/dist/test-helpers/race-condition-helpers.js.map +1 -0
- package/dist/test-helpers/test-data-builders.d.ts +40 -3
- package/dist/test-helpers/test-data-builders.d.ts.map +1 -1
- package/dist/test-helpers/test-data-builders.js +54 -5
- package/dist/test-helpers/test-data-builders.js.map +1 -1
- package/dist/test-helpers/tool-validators.d.ts.map +1 -1
- package/dist/test-helpers/tool-validators.js +16 -1
- package/dist/test-helpers/tool-validators.js.map +1 -1
- package/dist/tools/query-evaluations.d.ts.map +1 -1
- package/dist/tools/query-evaluations.js +16 -2
- package/dist/tools/query-evaluations.js.map +1 -1
- package/dist/tools/query-evaluations.test.js +53 -46
- package/dist/tools/query-evaluations.test.js.map +1 -1
- package/dist/tools/query-llm-events.test.js +6 -3
- package/dist/tools/query-llm-events.test.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test';
|
|
2
|
+
import * as assert from 'node:assert';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { LocalJsonlBackend } from './local-jsonl.js';
|
|
6
|
+
import { buildAndWriteIndex, getIndexPath } from '../lib/indexer.js';
|
|
7
|
+
import { getSharedTempDir, clearTempDir, removeSharedTempDir, writeJsonlFileAsync, getTestDate } from '../test-helpers/file-utils.js';
|
|
8
|
+
import { createMockSpan, createMockLog, createMockMetric, createMockEvaluation, createMockEvaluations, resetBuilderCounters, TEST_TIMESTAMP, TEST_TIMESTAMP_ISO, } from '../test-helpers/test-data-builders.js';
|
|
9
|
+
describe('OTLP Export', () => {
|
|
10
|
+
let tempDir;
|
|
11
|
+
let backend;
|
|
12
|
+
before(() => {
|
|
13
|
+
tempDir = getSharedTempDir('OTLPExport');
|
|
14
|
+
});
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
clearTempDir(tempDir);
|
|
17
|
+
backend = new LocalJsonlBackend(tempDir);
|
|
18
|
+
});
|
|
19
|
+
after(() => {
|
|
20
|
+
removeSharedTempDir('OTLPExport');
|
|
21
|
+
});
|
|
22
|
+
describe('exportTracesOTLP', () => {
|
|
23
|
+
it('should export traces in OTLP JSON format', async () => {
|
|
24
|
+
const today = getTestDate();
|
|
25
|
+
resetBuilderCounters();
|
|
26
|
+
const mockSpans = [
|
|
27
|
+
createMockSpan({
|
|
28
|
+
traceId: 'trace1',
|
|
29
|
+
spanId: 'span1',
|
|
30
|
+
name: 'root-span',
|
|
31
|
+
kind: 1, // SERVER
|
|
32
|
+
startTime: [...TEST_TIMESTAMP],
|
|
33
|
+
endTime: [1700000001, 500000000],
|
|
34
|
+
status: { code: 1, message: 'OK' },
|
|
35
|
+
resource: { serviceName: 'test-service', serviceVersion: '1.0.0' },
|
|
36
|
+
attributes: { 'http.method': 'GET', 'http.status_code': 200 },
|
|
37
|
+
}),
|
|
38
|
+
createMockSpan({
|
|
39
|
+
traceId: 'trace1',
|
|
40
|
+
spanId: 'span2',
|
|
41
|
+
name: 'db-query',
|
|
42
|
+
kind: 2, // CLIENT
|
|
43
|
+
startTime: [1700000000, 100000000],
|
|
44
|
+
endTime: [1700000000, 500000000],
|
|
45
|
+
status: { code: 1 },
|
|
46
|
+
resource: { serviceName: 'test-service' },
|
|
47
|
+
attributes: { 'db.system': 'postgresql' },
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
await writeJsonlFileAsync(path.join(tempDir, `traces-${today}.jsonl`), mockSpans);
|
|
51
|
+
const otlp = await backend.exportTracesOTLP({});
|
|
52
|
+
assert.ok(otlp.resourceSpans);
|
|
53
|
+
assert.strictEqual(otlp.resourceSpans.length, 1);
|
|
54
|
+
const resourceSpan = otlp.resourceSpans[0];
|
|
55
|
+
assert.ok(resourceSpan.resource);
|
|
56
|
+
assert.ok(resourceSpan.resource.attributes.some((a) => a.key === 'service.name' && a.value.stringValue === 'test-service'));
|
|
57
|
+
assert.ok(resourceSpan.scopeSpans);
|
|
58
|
+
assert.strictEqual(resourceSpan.scopeSpans.length, 1);
|
|
59
|
+
const scopeSpan = resourceSpan.scopeSpans[0];
|
|
60
|
+
assert.strictEqual(scopeSpan.spans.length, 2);
|
|
61
|
+
const rootSpan = scopeSpan.spans.find((s) => s.name === 'root-span');
|
|
62
|
+
assert.ok(rootSpan);
|
|
63
|
+
assert.strictEqual(rootSpan.traceId, 'trace1');
|
|
64
|
+
assert.strictEqual(rootSpan.spanId, 'span1');
|
|
65
|
+
assert.strictEqual(rootSpan.kind, 1); // SERVER
|
|
66
|
+
assert.strictEqual(rootSpan.status?.code, 1);
|
|
67
|
+
});
|
|
68
|
+
it('should group spans by service name in OTLP export', async () => {
|
|
69
|
+
const today = getTestDate();
|
|
70
|
+
resetBuilderCounters();
|
|
71
|
+
const mockSpans = [
|
|
72
|
+
createMockSpan({
|
|
73
|
+
traceId: 'trace1',
|
|
74
|
+
spanId: 'span1',
|
|
75
|
+
name: 'service-a-op',
|
|
76
|
+
resource: { serviceName: 'service-a' },
|
|
77
|
+
}),
|
|
78
|
+
createMockSpan({
|
|
79
|
+
traceId: 'trace1',
|
|
80
|
+
spanId: 'span2',
|
|
81
|
+
name: 'service-b-op',
|
|
82
|
+
resource: { serviceName: 'service-b' },
|
|
83
|
+
}),
|
|
84
|
+
];
|
|
85
|
+
await writeJsonlFileAsync(path.join(tempDir, `traces-${today}.jsonl`), mockSpans);
|
|
86
|
+
const otlp = await backend.exportTracesOTLP({});
|
|
87
|
+
assert.strictEqual(otlp.resourceSpans.length, 2);
|
|
88
|
+
const serviceNames = otlp.resourceSpans.map((rs) => rs.resource.attributes.find((a) => a.key === 'service.name')?.value.stringValue);
|
|
89
|
+
assert.ok(serviceNames.includes('service-a'));
|
|
90
|
+
assert.ok(serviceNames.includes('service-b'));
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe('exportLogsOTLP', () => {
|
|
94
|
+
it('should export logs in OTLP JSON format', async () => {
|
|
95
|
+
const today = getTestDate();
|
|
96
|
+
resetBuilderCounters();
|
|
97
|
+
const mockLogs = [
|
|
98
|
+
createMockLog({
|
|
99
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
100
|
+
severityText: 'ERROR',
|
|
101
|
+
body: 'Database connection failed',
|
|
102
|
+
traceId: 'trace1',
|
|
103
|
+
spanId: 'span1',
|
|
104
|
+
attributes: { 'error.type': 'ConnectionError' },
|
|
105
|
+
}),
|
|
106
|
+
createMockLog({
|
|
107
|
+
timestamp: '2024-01-15T10:00:01.000Z',
|
|
108
|
+
severityText: 'INFO',
|
|
109
|
+
body: 'Request processed',
|
|
110
|
+
}),
|
|
111
|
+
];
|
|
112
|
+
// Add resource separately as it's not part of MockLog interface
|
|
113
|
+
const mockLogsWithResource = mockLogs.map(log => ({
|
|
114
|
+
...log,
|
|
115
|
+
resource: { serviceName: 'test-service' },
|
|
116
|
+
}));
|
|
117
|
+
await writeJsonlFileAsync(path.join(tempDir, `logs-${today}.jsonl`), mockLogsWithResource);
|
|
118
|
+
const otlp = await backend.exportLogsOTLP({});
|
|
119
|
+
assert.ok(otlp.resourceLogs);
|
|
120
|
+
assert.strictEqual(otlp.resourceLogs.length, 1);
|
|
121
|
+
const resourceLog = otlp.resourceLogs[0];
|
|
122
|
+
assert.ok(resourceLog.resource);
|
|
123
|
+
assert.ok(resourceLog.resource.attributes.some((a) => a.key === 'service.name' && a.value.stringValue === 'test-service'));
|
|
124
|
+
assert.ok(resourceLog.scopeLogs);
|
|
125
|
+
const scopeLog = resourceLog.scopeLogs[0];
|
|
126
|
+
assert.strictEqual(scopeLog.logRecords.length, 2);
|
|
127
|
+
const errorLog = scopeLog.logRecords.find((l) => l.severityText === 'ERROR');
|
|
128
|
+
assert.ok(errorLog);
|
|
129
|
+
assert.ok(errorLog.body?.stringValue?.includes('Database connection failed'));
|
|
130
|
+
assert.strictEqual(errorLog.traceId, 'trace1');
|
|
131
|
+
assert.strictEqual(errorLog.spanId, 'span1');
|
|
132
|
+
assert.strictEqual(errorLog.severityNumber, 17); // ERROR severity number
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('exportMetricsOTLP', () => {
|
|
136
|
+
it('should export gauge metrics in OTLP JSON format', async () => {
|
|
137
|
+
const today = getTestDate();
|
|
138
|
+
resetBuilderCounters();
|
|
139
|
+
const mockMetrics = [
|
|
140
|
+
createMockMetric({
|
|
141
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
142
|
+
name: 'cpu.utilization',
|
|
143
|
+
value: 75.5,
|
|
144
|
+
type: 'gauge',
|
|
145
|
+
unit: 'percent',
|
|
146
|
+
resource: { serviceName: 'test-service' },
|
|
147
|
+
attributes: { 'host.name': 'server1' },
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
150
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
151
|
+
const otlp = await backend.exportMetricsOTLP({});
|
|
152
|
+
assert.ok(otlp.resourceMetrics);
|
|
153
|
+
assert.strictEqual(otlp.resourceMetrics.length, 1);
|
|
154
|
+
const resourceMetric = otlp.resourceMetrics[0];
|
|
155
|
+
assert.ok(resourceMetric.scopeMetrics);
|
|
156
|
+
const metric = resourceMetric.scopeMetrics[0].metrics[0];
|
|
157
|
+
assert.strictEqual(metric.name, 'cpu.utilization');
|
|
158
|
+
assert.strictEqual(metric.unit, 'percent');
|
|
159
|
+
assert.ok(metric.gauge);
|
|
160
|
+
assert.strictEqual(metric.gauge.dataPoints.length, 1);
|
|
161
|
+
assert.strictEqual(metric.gauge.dataPoints[0].asDouble, 75.5);
|
|
162
|
+
});
|
|
163
|
+
it('should export counter metrics with aggregation temporality', async () => {
|
|
164
|
+
const today = getTestDate();
|
|
165
|
+
resetBuilderCounters();
|
|
166
|
+
const mockMetrics = [
|
|
167
|
+
{
|
|
168
|
+
...createMockMetric({
|
|
169
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
170
|
+
name: 'http.requests',
|
|
171
|
+
value: 100,
|
|
172
|
+
type: 'counter',
|
|
173
|
+
unit: 'requests',
|
|
174
|
+
}),
|
|
175
|
+
aggregationTemporality: 2, // CUMULATIVE
|
|
176
|
+
resource: { serviceName: 'test-service' },
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
180
|
+
const otlp = await backend.exportMetricsOTLP({});
|
|
181
|
+
const metric = otlp.resourceMetrics[0].scopeMetrics[0].metrics[0];
|
|
182
|
+
assert.ok(metric.sum);
|
|
183
|
+
assert.strictEqual(metric.sum.aggregationTemporality, 2); // CUMULATIVE
|
|
184
|
+
assert.strictEqual(metric.sum.isMonotonic, true);
|
|
185
|
+
});
|
|
186
|
+
it('should export histogram metrics in OTLP JSON format', async () => {
|
|
187
|
+
const today = getTestDate();
|
|
188
|
+
resetBuilderCounters();
|
|
189
|
+
const mockMetrics = [
|
|
190
|
+
{
|
|
191
|
+
...createMockMetric({
|
|
192
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
193
|
+
name: 'http.request.duration',
|
|
194
|
+
value: 250,
|
|
195
|
+
type: 'histogram',
|
|
196
|
+
unit: 'ms',
|
|
197
|
+
histogram: {
|
|
198
|
+
buckets: [
|
|
199
|
+
{ le: 10, count: 5 },
|
|
200
|
+
{ le: 50, count: 15 },
|
|
201
|
+
{ le: 100, count: 25 },
|
|
202
|
+
{ le: 500, count: 45 },
|
|
203
|
+
{ le: Infinity, count: 50 },
|
|
204
|
+
],
|
|
205
|
+
sum: 12500,
|
|
206
|
+
count: 50,
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
aggregationTemporality: 2,
|
|
210
|
+
resource: { serviceName: 'test-service' },
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
214
|
+
const otlp = await backend.exportMetricsOTLP({});
|
|
215
|
+
const metric = otlp.resourceMetrics[0].scopeMetrics[0].metrics[0];
|
|
216
|
+
assert.strictEqual(metric.name, 'http.request.duration');
|
|
217
|
+
assert.ok(metric.histogram);
|
|
218
|
+
assert.strictEqual(metric.histogram.aggregationTemporality, 2);
|
|
219
|
+
const dataPoint = metric.histogram.dataPoints[0];
|
|
220
|
+
assert.strictEqual(dataPoint.count, '50');
|
|
221
|
+
assert.strictEqual(dataPoint.sum, 12500);
|
|
222
|
+
assert.strictEqual(dataPoint.bucketCounts.length, 5);
|
|
223
|
+
});
|
|
224
|
+
it('should include exemplars in OTLP metric export', async () => {
|
|
225
|
+
const today = getTestDate();
|
|
226
|
+
resetBuilderCounters();
|
|
227
|
+
const mockMetrics = [
|
|
228
|
+
createMockMetric({
|
|
229
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
230
|
+
name: 'http.latency',
|
|
231
|
+
value: 150,
|
|
232
|
+
type: 'gauge',
|
|
233
|
+
unit: 'ms',
|
|
234
|
+
resource: { serviceName: 'test-service' },
|
|
235
|
+
exemplars: [
|
|
236
|
+
{
|
|
237
|
+
timestamp: '2024-01-15T10:00:00.000Z',
|
|
238
|
+
value: 150,
|
|
239
|
+
traceId: 'trace123',
|
|
240
|
+
spanId: 'span456',
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
}),
|
|
244
|
+
];
|
|
245
|
+
await writeJsonlFileAsync(path.join(tempDir, `metrics-${today}.jsonl`), mockMetrics);
|
|
246
|
+
const otlp = await backend.exportMetricsOTLP({});
|
|
247
|
+
const dataPoint = otlp.resourceMetrics[0].scopeMetrics[0].metrics[0].gauge?.dataPoints[0];
|
|
248
|
+
assert.ok(dataPoint?.exemplars);
|
|
249
|
+
assert.strictEqual(dataPoint.exemplars.length, 1);
|
|
250
|
+
assert.strictEqual(dataPoint.exemplars[0].traceId, 'trace123');
|
|
251
|
+
assert.strictEqual(dataPoint.exemplars[0].spanId, 'span456');
|
|
252
|
+
assert.strictEqual(dataPoint.exemplars[0].asDouble, 150);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('queryEvaluations', () => {
|
|
256
|
+
it('should read and normalize evaluations from JSONL files', async () => {
|
|
257
|
+
const today = getTestDate();
|
|
258
|
+
resetBuilderCounters();
|
|
259
|
+
const mockEvaluations = [
|
|
260
|
+
createMockEvaluation({
|
|
261
|
+
attributes: {
|
|
262
|
+
'gen_ai.evaluation.name': 'Relevance',
|
|
263
|
+
'gen_ai.evaluation.score.value': 0.92,
|
|
264
|
+
'gen_ai.evaluation.score.label': 'relevant',
|
|
265
|
+
'gen_ai.evaluation.explanation': 'Response addresses the query',
|
|
266
|
+
},
|
|
267
|
+
}),
|
|
268
|
+
];
|
|
269
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
270
|
+
const results = await backend.queryEvaluations({});
|
|
271
|
+
assert.strictEqual(results.length, 1);
|
|
272
|
+
assert.strictEqual(results[0].evaluationName, 'Relevance');
|
|
273
|
+
assert.strictEqual(results[0].scoreValue, 0.92);
|
|
274
|
+
assert.strictEqual(results[0].scoreLabel, 'relevant');
|
|
275
|
+
assert.strictEqual(results[0].explanation, 'Response addresses the query');
|
|
276
|
+
});
|
|
277
|
+
it('should filter evaluations by evaluationName substring', async () => {
|
|
278
|
+
const today = getTestDate();
|
|
279
|
+
resetBuilderCounters();
|
|
280
|
+
const mockEvaluations = [
|
|
281
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance' } }),
|
|
282
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Faithfulness' } }),
|
|
283
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'RelevanceScore' } }),
|
|
284
|
+
];
|
|
285
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
286
|
+
const results = await backend.queryEvaluations({ evaluationName: 'Relevance' });
|
|
287
|
+
assert.strictEqual(results.length, 2);
|
|
288
|
+
assert.ok(results.every(e => e.evaluationName.includes('Relevance')));
|
|
289
|
+
});
|
|
290
|
+
it('should filter evaluations by scoreMin threshold', async () => {
|
|
291
|
+
const today = getTestDate();
|
|
292
|
+
resetBuilderCounters();
|
|
293
|
+
const mockEvaluations = [
|
|
294
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.3 } }),
|
|
295
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.7 } }),
|
|
296
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.9 } }),
|
|
297
|
+
];
|
|
298
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
299
|
+
const results = await backend.queryEvaluations({ scoreMin: 0.5 });
|
|
300
|
+
assert.strictEqual(results.length, 2);
|
|
301
|
+
assert.ok(results.every(e => e.scoreValue >= 0.5));
|
|
302
|
+
});
|
|
303
|
+
it('should filter evaluations by scoreMax threshold', async () => {
|
|
304
|
+
const today = getTestDate();
|
|
305
|
+
resetBuilderCounters();
|
|
306
|
+
const mockEvaluations = [
|
|
307
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.3 } }),
|
|
308
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.7 } }),
|
|
309
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.9 } }),
|
|
310
|
+
];
|
|
311
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
312
|
+
const results = await backend.queryEvaluations({ scoreMax: 0.5 });
|
|
313
|
+
assert.strictEqual(results.length, 1);
|
|
314
|
+
assert.strictEqual(results[0].scoreValue, 0.3);
|
|
315
|
+
});
|
|
316
|
+
it('should filter evaluations by score range', async () => {
|
|
317
|
+
const today = getTestDate();
|
|
318
|
+
resetBuilderCounters();
|
|
319
|
+
const mockEvaluations = [
|
|
320
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.3 } }),
|
|
321
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.5 } }),
|
|
322
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.7 } }),
|
|
323
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.9 } }),
|
|
324
|
+
];
|
|
325
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
326
|
+
const results = await backend.queryEvaluations({ scoreMin: 0.4, scoreMax: 0.8 });
|
|
327
|
+
assert.strictEqual(results.length, 2);
|
|
328
|
+
assert.ok(results.some(e => e.scoreValue === 0.5));
|
|
329
|
+
assert.ok(results.some(e => e.scoreValue === 0.7));
|
|
330
|
+
});
|
|
331
|
+
it('should pass evaluations without scoreValue through score range filters (P1-1)', async () => {
|
|
332
|
+
const today = getTestDate();
|
|
333
|
+
resetBuilderCounters();
|
|
334
|
+
const mockEvaluations = [
|
|
335
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'ToolCorrectness', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
336
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.score.value': 0.8 } }),
|
|
337
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.score.value': 0.3 } }),
|
|
338
|
+
];
|
|
339
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
340
|
+
const results = await backend.queryEvaluations({ scoreMin: 0.5 });
|
|
341
|
+
// Should include ToolCorrectness (no scoreValue) AND Relevance with 0.8
|
|
342
|
+
assert.strictEqual(results.length, 2);
|
|
343
|
+
assert.ok(results.some(e => e.evaluationName === 'ToolCorrectness'));
|
|
344
|
+
assert.ok(results.some(e => e.scoreValue === 0.8));
|
|
345
|
+
});
|
|
346
|
+
it('should filter evaluations by scoreLabel exact match', async () => {
|
|
347
|
+
const today = getTestDate();
|
|
348
|
+
resetBuilderCounters();
|
|
349
|
+
const mockEvaluations = [
|
|
350
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
351
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': 'fail' } }),
|
|
352
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
353
|
+
];
|
|
354
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
355
|
+
const results = await backend.queryEvaluations({ scoreLabel: 'pass' });
|
|
356
|
+
assert.strictEqual(results.length, 2);
|
|
357
|
+
assert.ok(results.every(e => e.scoreLabel === 'pass'));
|
|
358
|
+
});
|
|
359
|
+
it('should filter evaluations by responseId', async () => {
|
|
360
|
+
const today = getTestDate();
|
|
361
|
+
resetBuilderCounters();
|
|
362
|
+
const mockEvaluations = [
|
|
363
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.response.id': 'resp-123' } }),
|
|
364
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.response.id': 'resp-456' } }),
|
|
365
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.response.id': 'resp-123' } }),
|
|
366
|
+
];
|
|
367
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
368
|
+
const results = await backend.queryEvaluations({ responseId: 'resp-123' });
|
|
369
|
+
assert.strictEqual(results.length, 2);
|
|
370
|
+
assert.ok(results.every(e => e.responseId === 'resp-123'));
|
|
371
|
+
});
|
|
372
|
+
it('should filter evaluations by traceId', async () => {
|
|
373
|
+
const today = getTestDate();
|
|
374
|
+
resetBuilderCounters();
|
|
375
|
+
const mockEvaluations = [
|
|
376
|
+
createMockEvaluation({ traceId: 'trace-abc', attributes: { 'gen_ai.evaluation.name': 'Test' } }),
|
|
377
|
+
createMockEvaluation({ traceId: 'trace-xyz', attributes: { 'gen_ai.evaluation.name': 'Test' } }),
|
|
378
|
+
createMockEvaluation({ traceId: 'trace-abc', attributes: { 'gen_ai.evaluation.name': 'Test' } }),
|
|
379
|
+
];
|
|
380
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
381
|
+
const results = await backend.queryEvaluations({ traceId: 'trace-abc' });
|
|
382
|
+
assert.strictEqual(results.length, 2);
|
|
383
|
+
assert.ok(results.every(e => e.traceId === 'trace-abc'));
|
|
384
|
+
});
|
|
385
|
+
it('should filter evaluations by sessionId', async () => {
|
|
386
|
+
const today = getTestDate();
|
|
387
|
+
resetBuilderCounters();
|
|
388
|
+
const mockEvaluations = [
|
|
389
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'session.id': 'sess-111' } }),
|
|
390
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'session.id': 'sess-222' } }),
|
|
391
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'session.id': 'sess-111' } }),
|
|
392
|
+
];
|
|
393
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
394
|
+
const results = await backend.queryEvaluations({ sessionId: 'sess-111' });
|
|
395
|
+
assert.strictEqual(results.length, 2);
|
|
396
|
+
assert.ok(results.every(e => e.sessionId === 'sess-111'));
|
|
397
|
+
});
|
|
398
|
+
it('should skip evaluations without required evaluationName', async () => {
|
|
399
|
+
const today = getTestDate();
|
|
400
|
+
resetBuilderCounters();
|
|
401
|
+
const mockEvaluations = [
|
|
402
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Valid' } }),
|
|
403
|
+
{ timestamp: TEST_TIMESTAMP_ISO, attributes: { 'gen_ai.evaluation.score.value': 0.5 } }, // Missing name - inline for invalid case
|
|
404
|
+
{ timestamp: TEST_TIMESTAMP_ISO, attributes: {} }, // Empty - inline for invalid case
|
|
405
|
+
];
|
|
406
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
407
|
+
const results = await backend.queryEvaluations({});
|
|
408
|
+
assert.strictEqual(results.length, 1);
|
|
409
|
+
assert.strictEqual(results[0].evaluationName, 'Valid');
|
|
410
|
+
});
|
|
411
|
+
it('should reject NaN and Infinity in scoreValue (P0-2)', async () => {
|
|
412
|
+
const today = getTestDate();
|
|
413
|
+
// Write raw JSONL with invalid numbers (simulating corrupted data)
|
|
414
|
+
const rawContent = [
|
|
415
|
+
JSON.stringify({ timestamp: '2026-01-29T10:00:00Z', attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.5 } }),
|
|
416
|
+
'{"timestamp":"2026-01-29T10:01:00Z","attributes":{"gen_ai.evaluation.name":"NaNTest","gen_ai.evaluation.score.value":"NaN"}}',
|
|
417
|
+
JSON.stringify({ timestamp: '2026-01-29T10:02:00Z', attributes: { 'gen_ai.evaluation.name': 'Valid', 'gen_ai.evaluation.score.value': 0.8 } }),
|
|
418
|
+
].join('\n');
|
|
419
|
+
fs.writeFileSync(path.join(tempDir, `evaluations-${today}.jsonl`), rawContent, 'utf-8');
|
|
420
|
+
const results = await backend.queryEvaluations({});
|
|
421
|
+
// Should have 3 evaluations (NaNTest has scoreValue as string "NaN" so it becomes undefined, but evaluation itself is valid)
|
|
422
|
+
assert.strictEqual(results.length, 3);
|
|
423
|
+
// The NaNTest should have undefined scoreValue (string "NaN" is not a number type)
|
|
424
|
+
const nanTest = results.find(e => e.evaluationName === 'NaNTest');
|
|
425
|
+
assert.strictEqual(nanTest?.scoreValue, undefined);
|
|
426
|
+
});
|
|
427
|
+
it('should reject empty strings in scoreLabel (P0-2)', async () => {
|
|
428
|
+
const today = getTestDate();
|
|
429
|
+
resetBuilderCounters();
|
|
430
|
+
const mockEvaluations = [
|
|
431
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
432
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': '' } }),
|
|
433
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.label': ' ' } }),
|
|
434
|
+
];
|
|
435
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
436
|
+
const results = await backend.queryEvaluations({});
|
|
437
|
+
assert.strictEqual(results.length, 3);
|
|
438
|
+
// Only first should have scoreLabel
|
|
439
|
+
assert.strictEqual(results[0].scoreLabel, 'pass');
|
|
440
|
+
assert.strictEqual(results[1].scoreLabel, undefined);
|
|
441
|
+
assert.strictEqual(results[2].scoreLabel, undefined);
|
|
442
|
+
});
|
|
443
|
+
it('should cache query results', async () => {
|
|
444
|
+
const today = getTestDate();
|
|
445
|
+
resetBuilderCounters();
|
|
446
|
+
const mockEvaluations = [
|
|
447
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test' } }),
|
|
448
|
+
];
|
|
449
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
450
|
+
// First query
|
|
451
|
+
const results1 = await backend.queryEvaluations({ evaluationName: 'Test' });
|
|
452
|
+
const stats1 = backend.getCacheStats();
|
|
453
|
+
// Second query (same params)
|
|
454
|
+
const results2 = await backend.queryEvaluations({ evaluationName: 'Test' });
|
|
455
|
+
const stats2 = backend.getCacheStats();
|
|
456
|
+
assert.deepStrictEqual(results1, results2);
|
|
457
|
+
assert.strictEqual(stats2.evaluations.hits, stats1.evaluations.hits + 1);
|
|
458
|
+
});
|
|
459
|
+
it('should apply limit and offset', async () => {
|
|
460
|
+
const today = getTestDate();
|
|
461
|
+
resetBuilderCounters();
|
|
462
|
+
const mockEvaluations = createMockEvaluations(10, (i) => ({
|
|
463
|
+
timestamp: `2026-01-29T10:${String(i).padStart(2, '0')}:00Z`,
|
|
464
|
+
attributes: { 'gen_ai.evaluation.name': `Eval${i}` },
|
|
465
|
+
}));
|
|
466
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
467
|
+
const results = await backend.queryEvaluations({ limit: 3, offset: 2 });
|
|
468
|
+
assert.strictEqual(results.length, 3);
|
|
469
|
+
assert.strictEqual(results[0].evaluationName, 'Eval2');
|
|
470
|
+
assert.strictEqual(results[1].evaluationName, 'Eval3');
|
|
471
|
+
assert.strictEqual(results[2].evaluationName, 'Eval4');
|
|
472
|
+
});
|
|
473
|
+
it('should return empty array when no files found', async () => {
|
|
474
|
+
const results = await backend.queryEvaluations({});
|
|
475
|
+
assert.deepStrictEqual(results, []);
|
|
476
|
+
});
|
|
477
|
+
it('should use index when available', async () => {
|
|
478
|
+
const today = getTestDate();
|
|
479
|
+
resetBuilderCounters();
|
|
480
|
+
const mockEvaluations = [
|
|
481
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
482
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Faithfulness', 'gen_ai.evaluation.score.label': 'fail' } }),
|
|
483
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.score.label': 'pass' } }),
|
|
484
|
+
];
|
|
485
|
+
const filePath = path.join(tempDir, `evaluations-${today}.jsonl`);
|
|
486
|
+
await writeJsonlFileAsync(filePath, mockEvaluations);
|
|
487
|
+
// Build index
|
|
488
|
+
await buildAndWriteIndex(filePath, 'evaluations');
|
|
489
|
+
// Verify index exists
|
|
490
|
+
const idxPath = getIndexPath(filePath);
|
|
491
|
+
assert.ok(fs.existsSync(idxPath));
|
|
492
|
+
// Query should use index
|
|
493
|
+
const results = await backend.queryEvaluations({ evaluationName: 'Relevance' });
|
|
494
|
+
assert.strictEqual(results.length, 2);
|
|
495
|
+
assert.ok(results.every(e => e.evaluationName === 'Relevance'));
|
|
496
|
+
});
|
|
497
|
+
it('should filter by date range', async () => {
|
|
498
|
+
const today = getTestDate();
|
|
499
|
+
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
500
|
+
resetBuilderCounters();
|
|
501
|
+
// Write files for both days
|
|
502
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), [
|
|
503
|
+
createMockEvaluation({ timestamp: `${today}T10:00:00Z`, attributes: { 'gen_ai.evaluation.name': 'Today' } }),
|
|
504
|
+
]);
|
|
505
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${yesterday}.jsonl`), [
|
|
506
|
+
createMockEvaluation({ timestamp: `${yesterday}T10:00:00Z`, attributes: { 'gen_ai.evaluation.name': 'Yesterday' } }),
|
|
507
|
+
]);
|
|
508
|
+
// Query only today
|
|
509
|
+
const results = await backend.queryEvaluations({ startDate: today, endDate: today });
|
|
510
|
+
assert.strictEqual(results.length, 1);
|
|
511
|
+
assert.strictEqual(results[0].evaluationName, 'Today');
|
|
512
|
+
});
|
|
513
|
+
// Phase 3: evaluator field tests
|
|
514
|
+
it('should read and normalize evaluator and evaluatorType fields', async () => {
|
|
515
|
+
const today = getTestDate();
|
|
516
|
+
resetBuilderCounters();
|
|
517
|
+
const mockEvaluations = [
|
|
518
|
+
createMockEvaluation({
|
|
519
|
+
attributes: {
|
|
520
|
+
'gen_ai.evaluation.name': 'Relevance',
|
|
521
|
+
'gen_ai.evaluation.score.value': 0.92,
|
|
522
|
+
'gen_ai.evaluation.evaluator': 'gpt-4-as-judge',
|
|
523
|
+
'gen_ai.evaluation.evaluator.type': 'llm',
|
|
524
|
+
},
|
|
525
|
+
}),
|
|
526
|
+
];
|
|
527
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
528
|
+
const results = await backend.queryEvaluations({});
|
|
529
|
+
assert.strictEqual(results.length, 1);
|
|
530
|
+
assert.strictEqual(results[0].evaluator, 'gpt-4-as-judge');
|
|
531
|
+
assert.strictEqual(results[0].evaluatorType, 'llm');
|
|
532
|
+
});
|
|
533
|
+
it('should filter evaluations by evaluator exact match', async () => {
|
|
534
|
+
const today = getTestDate();
|
|
535
|
+
resetBuilderCounters();
|
|
536
|
+
const mockEvaluations = [
|
|
537
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'gpt-4-as-judge' } }),
|
|
538
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'human-reviewer' } }),
|
|
539
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'gpt-4-as-judge' } }),
|
|
540
|
+
];
|
|
541
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
542
|
+
const results = await backend.queryEvaluations({ evaluator: 'gpt-4-as-judge' });
|
|
543
|
+
assert.strictEqual(results.length, 2);
|
|
544
|
+
assert.ok(results.every(e => e.evaluator === 'gpt-4-as-judge'));
|
|
545
|
+
});
|
|
546
|
+
it('should filter evaluations by evaluatorType exact match', async () => {
|
|
547
|
+
const today = getTestDate();
|
|
548
|
+
resetBuilderCounters();
|
|
549
|
+
const mockEvaluations = [
|
|
550
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator.type': 'llm' } }),
|
|
551
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator.type': 'human' } }),
|
|
552
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator.type': 'llm' } }),
|
|
553
|
+
];
|
|
554
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
555
|
+
const results = await backend.queryEvaluations({ evaluatorType: 'llm' });
|
|
556
|
+
assert.strictEqual(results.length, 2);
|
|
557
|
+
assert.ok(results.every(e => e.evaluatorType === 'llm'));
|
|
558
|
+
});
|
|
559
|
+
it('should handle all valid evaluatorType values', async () => {
|
|
560
|
+
const today = getTestDate();
|
|
561
|
+
resetBuilderCounters();
|
|
562
|
+
const mockEvaluations = [
|
|
563
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test1', 'gen_ai.evaluation.evaluator.type': 'llm' } }),
|
|
564
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test2', 'gen_ai.evaluation.evaluator.type': 'human' } }),
|
|
565
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test3', 'gen_ai.evaluation.evaluator.type': 'rule' } }),
|
|
566
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test4', 'gen_ai.evaluation.evaluator.type': 'classifier' } }),
|
|
567
|
+
];
|
|
568
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
569
|
+
const results = await backend.queryEvaluations({});
|
|
570
|
+
assert.strictEqual(results.length, 4);
|
|
571
|
+
// Results may not be in insertion order - check that all types are present
|
|
572
|
+
const types = results.map(r => r.evaluatorType).sort();
|
|
573
|
+
assert.deepStrictEqual(types, ['classifier', 'human', 'llm', 'rule']);
|
|
574
|
+
});
|
|
575
|
+
it('should reject invalid evaluatorType values', async () => {
|
|
576
|
+
const today = getTestDate();
|
|
577
|
+
resetBuilderCounters();
|
|
578
|
+
const mockEvaluations = [
|
|
579
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator.type': 'invalid' } }),
|
|
580
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator.type': 'LLM' } }), // Case matters
|
|
581
|
+
];
|
|
582
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
583
|
+
const results = await backend.queryEvaluations({});
|
|
584
|
+
assert.strictEqual(results.length, 2);
|
|
585
|
+
// Invalid types should be undefined
|
|
586
|
+
assert.strictEqual(results[0].evaluatorType, undefined);
|
|
587
|
+
assert.strictEqual(results[1].evaluatorType, 'llm'); // Normalized to lowercase
|
|
588
|
+
});
|
|
589
|
+
it('should use index when filtering by evaluator', async () => {
|
|
590
|
+
const today = getTestDate();
|
|
591
|
+
resetBuilderCounters();
|
|
592
|
+
const filePath = path.join(tempDir, `evaluations-${today}.jsonl`);
|
|
593
|
+
const mockEvaluations = [
|
|
594
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.evaluator': 'gpt-4-as-judge' } }),
|
|
595
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Faithfulness', 'gen_ai.evaluation.evaluator': 'human-reviewer' } }),
|
|
596
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Relevance', 'gen_ai.evaluation.evaluator': 'gpt-4-as-judge' } }),
|
|
597
|
+
];
|
|
598
|
+
await writeJsonlFileAsync(filePath, mockEvaluations);
|
|
599
|
+
// Build index
|
|
600
|
+
await buildAndWriteIndex(filePath, 'evaluations');
|
|
601
|
+
// Verify index exists
|
|
602
|
+
const idxPath = getIndexPath(filePath);
|
|
603
|
+
assert.ok(fs.existsSync(idxPath));
|
|
604
|
+
// Query should use index
|
|
605
|
+
const results = await backend.queryEvaluations({ evaluator: 'gpt-4-as-judge' });
|
|
606
|
+
assert.strictEqual(results.length, 2);
|
|
607
|
+
assert.ok(results.every(e => e.evaluator === 'gpt-4-as-judge'));
|
|
608
|
+
});
|
|
609
|
+
it('should handle empty evaluator string', async () => {
|
|
610
|
+
const today = getTestDate();
|
|
611
|
+
resetBuilderCounters();
|
|
612
|
+
const mockEvaluations = [
|
|
613
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': '' } }),
|
|
614
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': ' ' } }),
|
|
615
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'valid' } }),
|
|
616
|
+
];
|
|
617
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
618
|
+
const results = await backend.queryEvaluations({});
|
|
619
|
+
assert.strictEqual(results.length, 3);
|
|
620
|
+
// Empty and whitespace-only should be undefined
|
|
621
|
+
assert.strictEqual(results[0].evaluator, undefined);
|
|
622
|
+
assert.strictEqual(results[1].evaluator, undefined);
|
|
623
|
+
assert.strictEqual(results[2].evaluator, 'valid');
|
|
624
|
+
});
|
|
625
|
+
it('should filter by both evaluator and evaluatorType', async () => {
|
|
626
|
+
const today = getTestDate();
|
|
627
|
+
resetBuilderCounters();
|
|
628
|
+
const mockEvaluations = [
|
|
629
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'gpt-4', 'gen_ai.evaluation.evaluator.type': 'llm' } }),
|
|
630
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'gpt-4', 'gen_ai.evaluation.evaluator.type': 'classifier' } }),
|
|
631
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.evaluator': 'claude', 'gen_ai.evaluation.evaluator.type': 'llm' } }),
|
|
632
|
+
];
|
|
633
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
634
|
+
const results = await backend.queryEvaluations({ evaluator: 'gpt-4', evaluatorType: 'llm' });
|
|
635
|
+
assert.strictEqual(results.length, 1);
|
|
636
|
+
assert.strictEqual(results[0].evaluator, 'gpt-4');
|
|
637
|
+
assert.strictEqual(results[0].evaluatorType, 'llm');
|
|
638
|
+
});
|
|
639
|
+
// Phase 1: scoreUnit field tests
|
|
640
|
+
it('should read and normalize scoreUnit field', async () => {
|
|
641
|
+
const today = getTestDate();
|
|
642
|
+
resetBuilderCounters();
|
|
643
|
+
const mockEvaluations = [
|
|
644
|
+
createMockEvaluation({
|
|
645
|
+
attributes: {
|
|
646
|
+
'gen_ai.evaluation.name': 'Relevance',
|
|
647
|
+
'gen_ai.evaluation.score.value': 85,
|
|
648
|
+
'gen_ai.evaluation.score.unit': 'percentage',
|
|
649
|
+
},
|
|
650
|
+
}),
|
|
651
|
+
];
|
|
652
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
653
|
+
const results = await backend.queryEvaluations({});
|
|
654
|
+
assert.strictEqual(results.length, 1);
|
|
655
|
+
assert.strictEqual(results[0].scoreValue, 85);
|
|
656
|
+
assert.strictEqual(results[0].scoreUnit, 'percentage');
|
|
657
|
+
});
|
|
658
|
+
it('should handle various scoreUnit values', async () => {
|
|
659
|
+
const today = getTestDate();
|
|
660
|
+
resetBuilderCounters();
|
|
661
|
+
const mockEvaluations = [
|
|
662
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test1', 'gen_ai.evaluation.score.value': 85, 'gen_ai.evaluation.score.unit': 'percentage' } }),
|
|
663
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test2', 'gen_ai.evaluation.score.value': 0.85, 'gen_ai.evaluation.score.unit': 'ratio_0_1' } }),
|
|
664
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test3', 'gen_ai.evaluation.score.value': 4, 'gen_ai.evaluation.score.unit': 'stars_1_5' } }),
|
|
665
|
+
];
|
|
666
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
667
|
+
const results = await backend.queryEvaluations({});
|
|
668
|
+
assert.strictEqual(results.length, 3);
|
|
669
|
+
// Results may not be in insertion order
|
|
670
|
+
const percentage = results.find(r => r.evaluationName === 'Test1');
|
|
671
|
+
const ratio = results.find(r => r.evaluationName === 'Test2');
|
|
672
|
+
const stars = results.find(r => r.evaluationName === 'Test3');
|
|
673
|
+
assert.strictEqual(percentage?.scoreUnit, 'percentage');
|
|
674
|
+
assert.strictEqual(ratio?.scoreUnit, 'ratio_0_1');
|
|
675
|
+
assert.strictEqual(stars?.scoreUnit, 'stars_1_5');
|
|
676
|
+
});
|
|
677
|
+
it('should handle missing scoreUnit', async () => {
|
|
678
|
+
const today = getTestDate();
|
|
679
|
+
resetBuilderCounters();
|
|
680
|
+
const mockEvaluations = [
|
|
681
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.85 } }),
|
|
682
|
+
];
|
|
683
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
684
|
+
const results = await backend.queryEvaluations({});
|
|
685
|
+
assert.strictEqual(results.length, 1);
|
|
686
|
+
assert.strictEqual(results[0].scoreValue, 0.85);
|
|
687
|
+
assert.strictEqual(results[0].scoreUnit, undefined);
|
|
688
|
+
});
|
|
689
|
+
it('should handle empty scoreUnit string', async () => {
|
|
690
|
+
const today = getTestDate();
|
|
691
|
+
resetBuilderCounters();
|
|
692
|
+
const mockEvaluations = [
|
|
693
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.85, 'gen_ai.evaluation.score.unit': '' } }),
|
|
694
|
+
createMockEvaluation({ attributes: { 'gen_ai.evaluation.name': 'Test', 'gen_ai.evaluation.score.value': 0.90, 'gen_ai.evaluation.score.unit': ' ' } }),
|
|
695
|
+
];
|
|
696
|
+
await writeJsonlFileAsync(path.join(tempDir, `evaluations-${today}.jsonl`), mockEvaluations);
|
|
697
|
+
const results = await backend.queryEvaluations({});
|
|
698
|
+
assert.strictEqual(results.length, 2);
|
|
699
|
+
// Empty and whitespace-only should be undefined
|
|
700
|
+
assert.ok(results.every(r => r.scoreUnit === undefined));
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
//# sourceMappingURL=local-jsonl-export.test.js.map
|