google-logging-utils-internal 1.1.3
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/package/LICENSE +203 -0
- package/package/README.md +83 -0
- package/package/lib/auth.d.mts +33 -0
- package/package/lib/auth.d.ts +33 -0
- package/package/lib/auth.js +70 -0
- package/package/lib/auth.js.map +1 -0
- package/package/lib/auth.mjs +45 -0
- package/package/lib/auth.mjs.map +1 -0
- package/package/lib/gcpLogger.d.mts +25 -0
- package/package/lib/gcpLogger.d.ts +25 -0
- package/package/lib/gcpLogger.js +118 -0
- package/package/lib/gcpLogger.js.map +1 -0
- package/package/lib/gcpLogger.mjs +82 -0
- package/package/lib/gcpLogger.mjs.map +1 -0
- package/package/lib/gcpOpenTelemetry.d.mts +59 -0
- package/package/lib/gcpOpenTelemetry.d.ts +59 -0
- package/package/lib/gcpOpenTelemetry.js +374 -0
- package/package/lib/gcpOpenTelemetry.js.map +1 -0
- package/package/lib/gcpOpenTelemetry.mjs +364 -0
- package/package/lib/gcpOpenTelemetry.mjs.map +1 -0
- package/package/lib/index.d.mts +36 -0
- package/package/lib/index.d.ts +36 -0
- package/package/lib/index.js +56 -0
- package/package/lib/index.js.map +1 -0
- package/package/lib/index.mjs +29 -0
- package/package/lib/index.mjs.map +1 -0
- package/package/lib/metrics.d.mts +65 -0
- package/package/lib/metrics.d.ts +65 -0
- package/package/lib/metrics.js +91 -0
- package/package/lib/metrics.js.map +1 -0
- package/package/lib/metrics.mjs +65 -0
- package/package/lib/metrics.mjs.map +1 -0
- package/package/lib/model-armor.d.mts +59 -0
- package/package/lib/model-armor.d.ts +59 -0
- package/package/lib/model-armor.js +205 -0
- package/package/lib/model-armor.js.map +1 -0
- package/package/lib/model-armor.mjs +181 -0
- package/package/lib/model-armor.mjs.map +1 -0
- package/package/lib/telemetry/action.d.mts +27 -0
- package/package/lib/telemetry/action.d.ts +27 -0
- package/package/lib/telemetry/action.js +92 -0
- package/package/lib/telemetry/action.js.map +1 -0
- package/package/lib/telemetry/action.mjs +73 -0
- package/package/lib/telemetry/action.mjs.map +1 -0
- package/package/lib/telemetry/defaults.d.mts +30 -0
- package/package/lib/telemetry/defaults.d.ts +30 -0
- package/package/lib/telemetry/defaults.js +70 -0
- package/package/lib/telemetry/defaults.js.map +1 -0
- package/package/lib/telemetry/defaults.mjs +46 -0
- package/package/lib/telemetry/defaults.mjs.map +1 -0
- package/package/lib/telemetry/engagement.d.mts +35 -0
- package/package/lib/telemetry/engagement.d.ts +35 -0
- package/package/lib/telemetry/engagement.js +106 -0
- package/package/lib/telemetry/engagement.js.map +1 -0
- package/package/lib/telemetry/engagement.mjs +85 -0
- package/package/lib/telemetry/engagement.mjs.map +1 -0
- package/package/lib/telemetry/feature.d.mts +35 -0
- package/package/lib/telemetry/feature.d.ts +35 -0
- package/package/lib/telemetry/feature.js +142 -0
- package/package/lib/telemetry/feature.js.map +1 -0
- package/package/lib/telemetry/feature.mjs +127 -0
- package/package/lib/telemetry/feature.mjs.map +1 -0
- package/package/lib/telemetry/generate.d.mts +53 -0
- package/package/lib/telemetry/generate.d.ts +53 -0
- package/package/lib/telemetry/generate.js +326 -0
- package/package/lib/telemetry/generate.js.map +1 -0
- package/package/lib/telemetry/generate.mjs +314 -0
- package/package/lib/telemetry/generate.mjs.map +1 -0
- package/package/lib/telemetry/path.d.mts +32 -0
- package/package/lib/telemetry/path.d.ts +32 -0
- package/package/lib/telemetry/path.js +91 -0
- package/package/lib/telemetry/path.js.map +1 -0
- package/package/lib/telemetry/path.mjs +78 -0
- package/package/lib/telemetry/path.mjs.map +1 -0
- package/package/lib/types.d.mts +121 -0
- package/package/lib/types.d.ts +121 -0
- package/package/lib/types.js +17 -0
- package/package/lib/types.js.map +1 -0
- package/package/lib/types.mjs +1 -0
- package/package/lib/types.mjs.map +1 -0
- package/package/lib/utils.d.mts +57 -0
- package/package/lib/utils.d.ts +57 -0
- package/package/lib/utils.js +143 -0
- package/package/lib/utils.js.map +1 -0
- package/package/lib/utils.mjs +104 -0
- package/package/lib/utils.mjs.map +1 -0
- package/package/package.json +90 -0
- package/package/src/auth.ts +89 -0
- package/package/src/gcpLogger.ts +124 -0
- package/package/src/gcpOpenTelemetry.ts +485 -0
- package/package/src/index.ts +59 -0
- package/package/src/metrics.ts +122 -0
- package/package/src/model-armor.ts +317 -0
- package/package/src/telemetry/action.ts +106 -0
- package/package/src/telemetry/defaults.ts +72 -0
- package/package/src/telemetry/engagement.ts +120 -0
- package/package/src/telemetry/feature.ts +170 -0
- package/package/src/telemetry/generate.ts +454 -0
- package/package/src/telemetry/path.ts +111 -0
- package/package/src/types.ts +133 -0
- package/package/src/utils.ts +175 -0
- package/package/tests/logs_no_input_output_test.ts +267 -0
- package/package/tests/logs_session_test.ts +219 -0
- package/package/tests/logs_test.ts +633 -0
- package/package/tests/metrics_test.ts +792 -0
- package/package/tests/model_armor_test.ts +336 -0
- package/package/tests/traces_test.ts +380 -0
- package/package/typedoc.json +3 -0
- package/package.json +10 -0
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2024 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
afterAll,
|
|
19
|
+
beforeAll,
|
|
20
|
+
beforeEach,
|
|
21
|
+
describe,
|
|
22
|
+
it,
|
|
23
|
+
jest,
|
|
24
|
+
} from '@jest/globals';
|
|
25
|
+
import type {
|
|
26
|
+
DataPoint,
|
|
27
|
+
Histogram,
|
|
28
|
+
HistogramMetricData,
|
|
29
|
+
ScopeMetrics,
|
|
30
|
+
SumMetricData,
|
|
31
|
+
} from '@opentelemetry/sdk-metrics';
|
|
32
|
+
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
33
|
+
import * as assert from 'assert';
|
|
34
|
+
import { genkit, z, type GenerateResponseData, type Genkit } from 'genkit';
|
|
35
|
+
import { SPAN_TYPE_ATTR, appendSpan } from 'genkit/tracing';
|
|
36
|
+
import {
|
|
37
|
+
GcpOpenTelemetry,
|
|
38
|
+
__forceFlushSpansForTesting,
|
|
39
|
+
__getMetricExporterForTesting,
|
|
40
|
+
__getSpanExporterForTesting,
|
|
41
|
+
enableGoogleCloudTelemetry,
|
|
42
|
+
} from '../src/index.js';
|
|
43
|
+
|
|
44
|
+
jest.mock('../src/auth.js', () => {
|
|
45
|
+
const original = jest.requireActual('../src/auth.js');
|
|
46
|
+
return {
|
|
47
|
+
...(original || {}),
|
|
48
|
+
resolveCurrentPrincipal: jest.fn().mockImplementation(() => {
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
projectId: 'test',
|
|
51
|
+
serviceAccountEmail: 'test@test.com',
|
|
52
|
+
});
|
|
53
|
+
}),
|
|
54
|
+
credentialsFromEnvironment: jest.fn().mockImplementation(() => {
|
|
55
|
+
return Promise.resolve({
|
|
56
|
+
projectId: 'test',
|
|
57
|
+
credentials: {
|
|
58
|
+
client_email: 'test@genkit.com',
|
|
59
|
+
private_key: '-----BEGIN PRIVATE KEY-----',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('GoogleCloudMetrics', () => {
|
|
67
|
+
let ai: Genkit;
|
|
68
|
+
|
|
69
|
+
beforeAll(async () => {
|
|
70
|
+
process.env.GCLOUD_PROJECT = 'test';
|
|
71
|
+
process.env.GENKIT_ENV = 'dev';
|
|
72
|
+
await enableGoogleCloudTelemetry({
|
|
73
|
+
projectId: 'test',
|
|
74
|
+
forceDevExport: false,
|
|
75
|
+
metricExportIntervalMillis: 100,
|
|
76
|
+
metricExportTimeoutMillis: 100,
|
|
77
|
+
});
|
|
78
|
+
ai = genkit({});
|
|
79
|
+
});
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
__getMetricExporterForTesting().reset();
|
|
82
|
+
__getSpanExporterForTesting().reset();
|
|
83
|
+
});
|
|
84
|
+
afterAll(async () => {
|
|
85
|
+
await ai.stopServers();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('writes feature metrics for a successful flow', async () => {
|
|
89
|
+
const testFlow = createFlow(ai, 'testFlow');
|
|
90
|
+
|
|
91
|
+
await testFlow();
|
|
92
|
+
await testFlow();
|
|
93
|
+
|
|
94
|
+
await getExportedSpans();
|
|
95
|
+
|
|
96
|
+
const requestCounter = await getCounterMetric('genkit/feature/requests');
|
|
97
|
+
const latencyHistogram = await getHistogramMetric('genkit/feature/latency');
|
|
98
|
+
assert.equal(requestCounter.value, 2);
|
|
99
|
+
assert.equal(requestCounter.attributes.name, 'testFlow');
|
|
100
|
+
assert.equal(requestCounter.attributes.source, 'ts');
|
|
101
|
+
assert.equal(requestCounter.attributes.status, 'success');
|
|
102
|
+
assert.ok(requestCounter.attributes.sourceVersion);
|
|
103
|
+
assert.equal(latencyHistogram.value.count, 2);
|
|
104
|
+
assert.equal(latencyHistogram.attributes.name, 'testFlow');
|
|
105
|
+
assert.equal(latencyHistogram.attributes.source, 'ts');
|
|
106
|
+
assert.equal(latencyHistogram.attributes.status, 'success');
|
|
107
|
+
assert.ok(latencyHistogram.attributes.sourceVersion);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('writes feature metrics for a failing flow', async () => {
|
|
111
|
+
const testFlow = createFlow(ai, 'testFlow', async () => {
|
|
112
|
+
return explode();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
assert.rejects(async () => {
|
|
116
|
+
await testFlow();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await getExportedSpans();
|
|
120
|
+
|
|
121
|
+
const requestCounter = await getCounterMetric('genkit/feature/requests');
|
|
122
|
+
assert.equal(requestCounter.value, 1);
|
|
123
|
+
assert.equal(requestCounter.attributes.name, 'testFlow');
|
|
124
|
+
assert.equal(requestCounter.attributes.source, 'ts');
|
|
125
|
+
assert.equal(requestCounter.attributes.error, 'TypeError');
|
|
126
|
+
assert.equal(requestCounter.attributes.status, 'failure');
|
|
127
|
+
}, 10000); //timeout
|
|
128
|
+
|
|
129
|
+
it('writes feature metrics for an action', async () => {
|
|
130
|
+
const testAction = createAction(ai, 'featureAction');
|
|
131
|
+
|
|
132
|
+
await testAction(null);
|
|
133
|
+
await testAction(null);
|
|
134
|
+
|
|
135
|
+
await getExportedSpans();
|
|
136
|
+
|
|
137
|
+
const requestCounter = await getCounterMetric('genkit/feature/requests');
|
|
138
|
+
const latencyHistogram = await getHistogramMetric('genkit/feature/latency');
|
|
139
|
+
assert.equal(requestCounter.value, 2);
|
|
140
|
+
assert.equal(requestCounter.attributes.name, 'featureAction');
|
|
141
|
+
assert.equal(requestCounter.attributes.source, 'ts');
|
|
142
|
+
assert.equal(requestCounter.attributes.status, 'success');
|
|
143
|
+
assert.ok(requestCounter.attributes.sourceVersion);
|
|
144
|
+
assert.equal(latencyHistogram.value.count, 2);
|
|
145
|
+
assert.equal(latencyHistogram.attributes.name, 'featureAction');
|
|
146
|
+
assert.equal(latencyHistogram.attributes.source, 'ts');
|
|
147
|
+
assert.equal(latencyHistogram.attributes.status, 'success');
|
|
148
|
+
assert.ok(latencyHistogram.attributes.sourceVersion);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('writes feature metrics for generate', async () => {
|
|
152
|
+
const testModel = createTestModel(ai, 'helloModel');
|
|
153
|
+
await ai.generate({ model: testModel, prompt: 'Hi' });
|
|
154
|
+
await ai.generate({ model: testModel, prompt: 'Yo' });
|
|
155
|
+
|
|
156
|
+
await getExportedSpans();
|
|
157
|
+
|
|
158
|
+
const requestCounter = await getCounterMetric('genkit/feature/requests');
|
|
159
|
+
const latencyHistogram = await getHistogramMetric('genkit/feature/latency');
|
|
160
|
+
assert.equal(requestCounter.value, 2);
|
|
161
|
+
assert.equal(requestCounter.attributes.name, 'generate');
|
|
162
|
+
assert.equal(requestCounter.attributes.source, 'ts');
|
|
163
|
+
assert.equal(requestCounter.attributes.status, 'success');
|
|
164
|
+
assert.ok(requestCounter.attributes.sourceVersion);
|
|
165
|
+
assert.equal(latencyHistogram.value.count, 2);
|
|
166
|
+
assert.equal(latencyHistogram.attributes.name, 'generate');
|
|
167
|
+
assert.equal(latencyHistogram.attributes.source, 'ts');
|
|
168
|
+
assert.equal(latencyHistogram.attributes.status, 'success');
|
|
169
|
+
assert.ok(latencyHistogram.attributes.sourceVersion);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('writes generate metrics for a successful model action', async () => {
|
|
173
|
+
const testModel = createTestModel(ai, 'testModel');
|
|
174
|
+
|
|
175
|
+
await ai.generate({
|
|
176
|
+
model: testModel,
|
|
177
|
+
prompt: 'test prompt',
|
|
178
|
+
config: {
|
|
179
|
+
maxOutputTokens: 7,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await getExportedSpans();
|
|
184
|
+
|
|
185
|
+
const requestCounter = await getCounterMetric(
|
|
186
|
+
'genkit/ai/generate/requests'
|
|
187
|
+
);
|
|
188
|
+
const inputTokenCounter = await getCounterMetric(
|
|
189
|
+
'genkit/ai/generate/input/tokens'
|
|
190
|
+
);
|
|
191
|
+
const outputTokenCounter = await getCounterMetric(
|
|
192
|
+
'genkit/ai/generate/output/tokens'
|
|
193
|
+
);
|
|
194
|
+
const thoughtTokenCounter = await getCounterMetric(
|
|
195
|
+
'genkit/ai/generate/thinking/tokens'
|
|
196
|
+
);
|
|
197
|
+
const inputCharacterCounter = await getCounterMetric(
|
|
198
|
+
'genkit/ai/generate/input/characters'
|
|
199
|
+
);
|
|
200
|
+
const outputCharacterCounter = await getCounterMetric(
|
|
201
|
+
'genkit/ai/generate/output/characters'
|
|
202
|
+
);
|
|
203
|
+
const inputImageCounter = await getCounterMetric(
|
|
204
|
+
'genkit/ai/generate/input/images'
|
|
205
|
+
);
|
|
206
|
+
const outputImageCounter = await getCounterMetric(
|
|
207
|
+
'genkit/ai/generate/output/images'
|
|
208
|
+
);
|
|
209
|
+
const latencyHistogram = await getHistogramMetric(
|
|
210
|
+
'genkit/ai/generate/latency'
|
|
211
|
+
);
|
|
212
|
+
assert.equal(requestCounter.value, 1);
|
|
213
|
+
assert.equal(inputTokenCounter.value, 10);
|
|
214
|
+
assert.equal(outputTokenCounter.value, 14);
|
|
215
|
+
assert.equal(thoughtTokenCounter.value, 5);
|
|
216
|
+
assert.equal(inputCharacterCounter.value, 8);
|
|
217
|
+
assert.equal(outputCharacterCounter.value, 16);
|
|
218
|
+
assert.equal(inputImageCounter.value, 1);
|
|
219
|
+
assert.equal(outputImageCounter.value, 3);
|
|
220
|
+
assert.equal(latencyHistogram.value.count, 1);
|
|
221
|
+
for (const metric of [
|
|
222
|
+
requestCounter,
|
|
223
|
+
inputTokenCounter,
|
|
224
|
+
outputTokenCounter,
|
|
225
|
+
thoughtTokenCounter,
|
|
226
|
+
inputCharacterCounter,
|
|
227
|
+
outputCharacterCounter,
|
|
228
|
+
inputImageCounter,
|
|
229
|
+
outputImageCounter,
|
|
230
|
+
latencyHistogram,
|
|
231
|
+
]) {
|
|
232
|
+
assert.equal(metric.attributes.modelName, 'testModel');
|
|
233
|
+
assert.equal(metric.attributes.source, 'ts');
|
|
234
|
+
assert.equal(metric.attributes.status, 'success');
|
|
235
|
+
assert.equal(metric.attributes.featureName, 'generate');
|
|
236
|
+
assert.ok(metric.attributes.sourceVersion);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('writes generate metrics for a failing model action', async () => {
|
|
241
|
+
const testModel = createModel(ai, 'failingTestModel', async () => {
|
|
242
|
+
return explode();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
assert.rejects(async () => {
|
|
246
|
+
return ai.generate({
|
|
247
|
+
model: testModel,
|
|
248
|
+
prompt: 'test prompt',
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await getExportedSpans();
|
|
253
|
+
|
|
254
|
+
const requestCounter = await getCounterMetric(
|
|
255
|
+
'genkit/ai/generate/requests'
|
|
256
|
+
);
|
|
257
|
+
assert.equal(requestCounter.value, 1);
|
|
258
|
+
assert.equal(requestCounter.attributes.modelName, 'failingTestModel');
|
|
259
|
+
assert.equal(requestCounter.attributes.source, 'ts');
|
|
260
|
+
assert.equal(requestCounter.attributes.status, 'failure');
|
|
261
|
+
assert.equal(requestCounter.attributes.error, 'TypeError');
|
|
262
|
+
assert.ok(requestCounter.attributes.sourceVersion);
|
|
263
|
+
}, 10000); //timeout
|
|
264
|
+
|
|
265
|
+
it('writes feature label to generate metrics when running inside a flow', async () => {
|
|
266
|
+
const testModel = createModel(ai, 'testModel', async () => {
|
|
267
|
+
return {
|
|
268
|
+
message: {
|
|
269
|
+
role: 'user',
|
|
270
|
+
content: [
|
|
271
|
+
{
|
|
272
|
+
text: 'response',
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
finishReason: 'stop',
|
|
277
|
+
usage: {
|
|
278
|
+
inputTokens: 10,
|
|
279
|
+
outputTokens: 14,
|
|
280
|
+
thoughtsTokens: 5,
|
|
281
|
+
inputCharacters: 8,
|
|
282
|
+
outputCharacters: 16,
|
|
283
|
+
inputImages: 1,
|
|
284
|
+
outputImages: 3,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
289
|
+
return await ai.generate({
|
|
290
|
+
model: testModel,
|
|
291
|
+
prompt: 'test prompt',
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await flow();
|
|
296
|
+
|
|
297
|
+
await getExportedSpans();
|
|
298
|
+
|
|
299
|
+
const metrics = [
|
|
300
|
+
await getCounterMetric('genkit/ai/generate/requests'),
|
|
301
|
+
await getCounterMetric('genkit/ai/generate/input/tokens'),
|
|
302
|
+
await getCounterMetric('genkit/ai/generate/output/tokens'),
|
|
303
|
+
await getCounterMetric('genkit/ai/generate/thinking/tokens'),
|
|
304
|
+
await getCounterMetric('genkit/ai/generate/input/characters'),
|
|
305
|
+
await getCounterMetric('genkit/ai/generate/output/characters'),
|
|
306
|
+
await getCounterMetric('genkit/ai/generate/input/images'),
|
|
307
|
+
await getCounterMetric('genkit/ai/generate/output/images'),
|
|
308
|
+
await getHistogramMetric('genkit/ai/generate/latency'),
|
|
309
|
+
];
|
|
310
|
+
for (const metric of metrics) {
|
|
311
|
+
assert.equal(metric.attributes.featureName, 'testFlow');
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('path metrics', () => {
|
|
316
|
+
it('writes no path metrics for a successful flow', async () => {
|
|
317
|
+
const flow = createFlow(ai, 'pathTestFlow', async () => {
|
|
318
|
+
await ai.run('step1', async () => {
|
|
319
|
+
return await ai.run('substep_a', async () => {
|
|
320
|
+
return await ai.run('substep_b', async () => 'res1');
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
await ai.run('step2', async () => 'res2');
|
|
324
|
+
return;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await flow();
|
|
328
|
+
|
|
329
|
+
await getExportedSpans();
|
|
330
|
+
|
|
331
|
+
await assert.rejects(async () => {
|
|
332
|
+
await getCounterDataPoints('genkit/feature/path/requests');
|
|
333
|
+
});
|
|
334
|
+
await assert.rejects(async () => {
|
|
335
|
+
await getHistogramDataPoints('genkit/feature/path/latency');
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('writes path metrics for a failing flow with exception in root', async () => {
|
|
340
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
341
|
+
await ai.run('sub-action', async () => {
|
|
342
|
+
return 'done';
|
|
343
|
+
});
|
|
344
|
+
return Promise.reject(new Error('failed'));
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
assert.rejects(async () => {
|
|
348
|
+
await flow();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
await getExportedSpans();
|
|
352
|
+
|
|
353
|
+
const reqPoints = await getCounterDataPoints(
|
|
354
|
+
'genkit/feature/path/requests'
|
|
355
|
+
);
|
|
356
|
+
const reqStatuses = reqPoints.map((p) => [
|
|
357
|
+
p.attributes.path,
|
|
358
|
+
p.attributes.status,
|
|
359
|
+
]);
|
|
360
|
+
assert.deepEqual(reqStatuses, [['/{testFlow,t:flow}', 'failure']]);
|
|
361
|
+
const latencyPoints = await getHistogramDataPoints(
|
|
362
|
+
'genkit/feature/path/latency'
|
|
363
|
+
);
|
|
364
|
+
const latencyStatuses = latencyPoints.map((p) => [
|
|
365
|
+
p.attributes.path,
|
|
366
|
+
p.attributes.status,
|
|
367
|
+
]);
|
|
368
|
+
assert.deepEqual(latencyStatuses, [['/{testFlow,t:flow}', 'failure']]);
|
|
369
|
+
}, 10000); //timeout
|
|
370
|
+
|
|
371
|
+
it('writes path metrics for a failing flow with exception in subaction', async () => {
|
|
372
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
373
|
+
await ai.run('sub-action-1', async () => {
|
|
374
|
+
await ai.run('sub-action-2', async () => {
|
|
375
|
+
return Promise.reject(new Error('failed'));
|
|
376
|
+
});
|
|
377
|
+
return 'done';
|
|
378
|
+
});
|
|
379
|
+
return 'done';
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
assert.rejects(async () => {
|
|
383
|
+
await flow();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
await getExportedSpans();
|
|
387
|
+
|
|
388
|
+
const reqPoints = await getCounterDataPoints(
|
|
389
|
+
'genkit/feature/path/requests'
|
|
390
|
+
);
|
|
391
|
+
const reqStatuses = reqPoints.map((p) => [
|
|
392
|
+
p.attributes.path,
|
|
393
|
+
p.attributes.status,
|
|
394
|
+
]);
|
|
395
|
+
assert.deepEqual(reqStatuses, [
|
|
396
|
+
[
|
|
397
|
+
'/{testFlow,t:flow}/{sub-action-1,t:flowStep}/{sub-action-2,t:flowStep}',
|
|
398
|
+
'failure',
|
|
399
|
+
],
|
|
400
|
+
]);
|
|
401
|
+
const latencyPoints = await getHistogramDataPoints(
|
|
402
|
+
'genkit/feature/path/latency'
|
|
403
|
+
);
|
|
404
|
+
const latencyStatuses = latencyPoints.map((p) => [
|
|
405
|
+
p.attributes.path,
|
|
406
|
+
p.attributes.status,
|
|
407
|
+
]);
|
|
408
|
+
assert.deepEqual(latencyStatuses, [
|
|
409
|
+
[
|
|
410
|
+
'/{testFlow,t:flow}/{sub-action-1,t:flowStep}/{sub-action-2,t:flowStep}',
|
|
411
|
+
'failure',
|
|
412
|
+
],
|
|
413
|
+
]);
|
|
414
|
+
}, 10000); //timeout
|
|
415
|
+
|
|
416
|
+
it('writes path metrics for a flow with exception in action', async () => {
|
|
417
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
418
|
+
await ai.run('sub-action-1', async () => {
|
|
419
|
+
await ai.run('sub-action-2', async () => {
|
|
420
|
+
return 'done';
|
|
421
|
+
});
|
|
422
|
+
return Promise.reject(new Error('failed'));
|
|
423
|
+
});
|
|
424
|
+
return 'done';
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
assert.rejects(async () => {
|
|
428
|
+
await flow();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
await getExportedSpans();
|
|
432
|
+
|
|
433
|
+
const reqPoints = await getCounterDataPoints(
|
|
434
|
+
'genkit/feature/path/requests'
|
|
435
|
+
);
|
|
436
|
+
const reqStatuses = reqPoints.map((p) => [
|
|
437
|
+
p.attributes.path,
|
|
438
|
+
p.attributes.status,
|
|
439
|
+
]);
|
|
440
|
+
assert.deepEqual(reqStatuses, [
|
|
441
|
+
['/{testFlow,t:flow}/{sub-action-1,t:flowStep}', 'failure'],
|
|
442
|
+
]);
|
|
443
|
+
const latencyPoints = await getHistogramDataPoints(
|
|
444
|
+
'genkit/feature/path/latency'
|
|
445
|
+
);
|
|
446
|
+
const latencyStatuses = latencyPoints.map((p) => [
|
|
447
|
+
p.attributes.path,
|
|
448
|
+
p.attributes.status,
|
|
449
|
+
]);
|
|
450
|
+
assert.deepEqual(latencyStatuses, [
|
|
451
|
+
['/{testFlow,t:flow}/{sub-action-1,t:flowStep}', 'failure'],
|
|
452
|
+
]);
|
|
453
|
+
}, 10000); //timeout
|
|
454
|
+
|
|
455
|
+
it('writes path metrics for a flow with an exception in a serial action', async () => {
|
|
456
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
457
|
+
await ai.run('sub-action-1', async () => {
|
|
458
|
+
return 'done';
|
|
459
|
+
});
|
|
460
|
+
await ai.run('sub-action-2', async () => {
|
|
461
|
+
return Promise.reject(new Error('failed'));
|
|
462
|
+
});
|
|
463
|
+
return 'done';
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
assert.rejects(async () => {
|
|
467
|
+
await flow();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
await getExportedSpans();
|
|
471
|
+
|
|
472
|
+
const reqPoints = await getCounterDataPoints(
|
|
473
|
+
'genkit/feature/path/requests'
|
|
474
|
+
);
|
|
475
|
+
const reqStatuses = reqPoints.map((p) => [
|
|
476
|
+
p.attributes.path,
|
|
477
|
+
p.attributes.status,
|
|
478
|
+
]);
|
|
479
|
+
assert.deepEqual(reqStatuses, [
|
|
480
|
+
['/{testFlow,t:flow}/{sub-action-2,t:flowStep}', 'failure'],
|
|
481
|
+
]);
|
|
482
|
+
const latencyPoints = await getHistogramDataPoints(
|
|
483
|
+
'genkit/feature/path/latency'
|
|
484
|
+
);
|
|
485
|
+
const latencyStatuses = latencyPoints.map((p) => [
|
|
486
|
+
p.attributes.path,
|
|
487
|
+
p.attributes.status,
|
|
488
|
+
]);
|
|
489
|
+
assert.deepEqual(latencyStatuses, [
|
|
490
|
+
['/{testFlow,t:flow}/{sub-action-2,t:flowStep}', 'failure'],
|
|
491
|
+
]);
|
|
492
|
+
}, 10000); //timeout
|
|
493
|
+
|
|
494
|
+
it('writes path metrics for a flow multiple failing actions', async () => {
|
|
495
|
+
const flow = createFlow(ai, 'testFlow', async () => {
|
|
496
|
+
await Promise.all([
|
|
497
|
+
ai.run('sub1', async () => {
|
|
498
|
+
return explode();
|
|
499
|
+
}),
|
|
500
|
+
ai.run('sub2', async () => {
|
|
501
|
+
return explode();
|
|
502
|
+
}),
|
|
503
|
+
]);
|
|
504
|
+
return 'not failing';
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
assert.rejects(async () => {
|
|
508
|
+
await flow();
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
await getExportedSpans();
|
|
512
|
+
|
|
513
|
+
const reqPoints = await getCounterDataPoints(
|
|
514
|
+
'genkit/feature/path/requests'
|
|
515
|
+
);
|
|
516
|
+
const reqStatuses = reqPoints.map((p) => [
|
|
517
|
+
p.attributes.path,
|
|
518
|
+
p.attributes.status,
|
|
519
|
+
]);
|
|
520
|
+
assert.deepEqual(reqStatuses, [
|
|
521
|
+
['/{testFlow,t:flow}/{sub1,t:flowStep}', 'failure'],
|
|
522
|
+
['/{testFlow,t:flow}/{sub2,t:flowStep}', 'failure'],
|
|
523
|
+
]);
|
|
524
|
+
const latencyPoints = await getHistogramDataPoints(
|
|
525
|
+
'genkit/feature/path/latency'
|
|
526
|
+
);
|
|
527
|
+
const latencyStatuses = latencyPoints.map((p) => [
|
|
528
|
+
p.attributes.path,
|
|
529
|
+
p.attributes.status,
|
|
530
|
+
]);
|
|
531
|
+
assert.deepEqual(latencyStatuses, [
|
|
532
|
+
['/{testFlow,t:flow}/{sub1,t:flowStep}', 'failure'],
|
|
533
|
+
['/{testFlow,t:flow}/{sub2,t:flowStep}', 'failure'],
|
|
534
|
+
]);
|
|
535
|
+
}, 10000); //timeout
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it('writes user feedback metrics', async () => {
|
|
539
|
+
await appendSpan(
|
|
540
|
+
'trace1',
|
|
541
|
+
'parent1',
|
|
542
|
+
{
|
|
543
|
+
name: 'user-feedback',
|
|
544
|
+
path: '/{flowName}',
|
|
545
|
+
metadata: {
|
|
546
|
+
subtype: 'userFeedback',
|
|
547
|
+
feedbackValue: 'negative',
|
|
548
|
+
textFeedback: 'terrible',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
{ [SPAN_TYPE_ATTR]: 'userEngagement' }
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
await getExportedSpans();
|
|
555
|
+
const dataPoints = await getCounterDataPoints('genkit/engagement/feedback');
|
|
556
|
+
|
|
557
|
+
const points = dataPoints.map((p) => [
|
|
558
|
+
p.attributes.name,
|
|
559
|
+
p.attributes.value,
|
|
560
|
+
p.attributes.hasText,
|
|
561
|
+
p.attributes.source,
|
|
562
|
+
]);
|
|
563
|
+
assert.deepEqual(points, [['flowName', 'negative', true, 'ts']]);
|
|
564
|
+
assert.ok(dataPoints[0].attributes.sourceVersion);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('writes user acceptance metrics', async () => {
|
|
568
|
+
await appendSpan(
|
|
569
|
+
'trace1',
|
|
570
|
+
'parent1',
|
|
571
|
+
{
|
|
572
|
+
name: 'user-acceptance',
|
|
573
|
+
path: '/{flowName}',
|
|
574
|
+
metadata: { subtype: 'userAcceptance', acceptanceValue: 'rejected' },
|
|
575
|
+
},
|
|
576
|
+
{ [SPAN_TYPE_ATTR]: 'userEngagement' }
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
await getExportedSpans();
|
|
580
|
+
const dataPoints = await getCounterDataPoints(
|
|
581
|
+
'genkit/engagement/acceptance'
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const points = dataPoints.map((p) => [
|
|
585
|
+
p.attributes.name,
|
|
586
|
+
p.attributes.value,
|
|
587
|
+
p.attributes.source,
|
|
588
|
+
]);
|
|
589
|
+
assert.deepEqual(points, [['flowName', 'rejected', 'ts']]);
|
|
590
|
+
assert.ok(dataPoints[0].attributes.sourceVersion);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe('Configuration', () => {
|
|
594
|
+
it('should export only traces', async () => {
|
|
595
|
+
const telemetry = new GcpOpenTelemetry({
|
|
596
|
+
export: true,
|
|
597
|
+
disableMetrics: true,
|
|
598
|
+
disableTraces: false,
|
|
599
|
+
} as any);
|
|
600
|
+
assert.equal(telemetry['shouldExportTraces'](), true);
|
|
601
|
+
assert.equal(telemetry['shouldExportMetrics'](), false);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it('should export only metrics', async () => {
|
|
605
|
+
const telemetry = new GcpOpenTelemetry({
|
|
606
|
+
export: true,
|
|
607
|
+
disableTraces: true,
|
|
608
|
+
disableMetrics: false,
|
|
609
|
+
} as any);
|
|
610
|
+
assert.equal(telemetry['shouldExportTraces'](), false);
|
|
611
|
+
assert.equal(telemetry['shouldExportMetrics'](), true);
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
/** Polls the in memory metric exporter until the genkit scope is found. */
|
|
616
|
+
async function getGenkitMetrics(
|
|
617
|
+
name = 'genkit',
|
|
618
|
+
maxAttempts = 100
|
|
619
|
+
): Promise<ScopeMetrics | undefined> {
|
|
620
|
+
var attempts = 0;
|
|
621
|
+
while (attempts++ < maxAttempts) {
|
|
622
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
623
|
+
const found = __getMetricExporterForTesting()
|
|
624
|
+
.getMetrics()
|
|
625
|
+
.find((e) => e.scopeMetrics.map((sm) => sm.scope.name).includes(name));
|
|
626
|
+
if (found) {
|
|
627
|
+
return found.scopeMetrics.find((e) => e.scope.name === name);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
assert.fail(`Waiting for metric ${name} but it has not been written.`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Polls the in memory metric exporter until the genkit scope is found. */
|
|
634
|
+
async function getExportedSpans(maxAttempts = 200): Promise<ReadableSpan[]> {
|
|
635
|
+
__forceFlushSpansForTesting();
|
|
636
|
+
var attempts = 0;
|
|
637
|
+
while (attempts++ < maxAttempts) {
|
|
638
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
639
|
+
const found = __getSpanExporterForTesting().getFinishedSpans();
|
|
640
|
+
if (found.length > 0) {
|
|
641
|
+
return found;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
assert.fail(`Timed out while waiting for spans to be exported.`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/** Finds all datapoints for a counter metric with the given name in the in memory exporter */
|
|
648
|
+
async function getCounterDataPoints(
|
|
649
|
+
metricName: string
|
|
650
|
+
): Promise<Array<DataPoint<number>>> {
|
|
651
|
+
const genkitMetrics = await getGenkitMetrics();
|
|
652
|
+
if (genkitMetrics) {
|
|
653
|
+
const counterMetric: SumMetricData = genkitMetrics.metrics.find(
|
|
654
|
+
(e) =>
|
|
655
|
+
e.descriptor.name === metricName && e.descriptor.type === 'COUNTER'
|
|
656
|
+
) as SumMetricData;
|
|
657
|
+
if (counterMetric) {
|
|
658
|
+
return counterMetric.dataPoints;
|
|
659
|
+
}
|
|
660
|
+
assert.fail(
|
|
661
|
+
`No counter metric named ${metricName} was found. Only found: ${genkitMetrics.metrics.map((e) => e.descriptor.name)}`
|
|
662
|
+
);
|
|
663
|
+
} else {
|
|
664
|
+
assert.fail(`No genkit metrics found.`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Finds a counter metric with the given name in the in memory exporter */
|
|
669
|
+
async function getCounterMetric(
|
|
670
|
+
metricName: string
|
|
671
|
+
): Promise<DataPoint<number>> {
|
|
672
|
+
const counter = await getCounterDataPoints(metricName).then((points) =>
|
|
673
|
+
points.at(-1)
|
|
674
|
+
);
|
|
675
|
+
if (counter === undefined) {
|
|
676
|
+
assert.fail(`Counter not found`);
|
|
677
|
+
} else {
|
|
678
|
+
return counter;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Finds all datapoints for a histogram metric with the given name in the in
|
|
684
|
+
* memory exporter.
|
|
685
|
+
*/
|
|
686
|
+
async function getHistogramDataPoints(
|
|
687
|
+
metricName: string
|
|
688
|
+
): Promise<Array<DataPoint<Histogram>>> {
|
|
689
|
+
const genkitMetrics = await getGenkitMetrics();
|
|
690
|
+
if (genkitMetrics === undefined) {
|
|
691
|
+
assert.fail('Genkit metrics not found');
|
|
692
|
+
} else {
|
|
693
|
+
const histogramMetric = genkitMetrics.metrics.find(
|
|
694
|
+
(e) =>
|
|
695
|
+
e.descriptor.name === metricName && e.descriptor.type === 'HISTOGRAM'
|
|
696
|
+
) as HistogramMetricData;
|
|
697
|
+
if (histogramMetric === undefined) {
|
|
698
|
+
assert.fail(
|
|
699
|
+
`No histogram metric named ${metricName} was found. Only found: ${genkitMetrics.metrics.map((e) => e.descriptor.name)}`
|
|
700
|
+
);
|
|
701
|
+
} else {
|
|
702
|
+
return histogramMetric.dataPoints;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Finds a histogram metric with the given name in the in memory exporter */
|
|
708
|
+
async function getHistogramMetric(
|
|
709
|
+
metricName: string
|
|
710
|
+
): Promise<DataPoint<Histogram>> {
|
|
711
|
+
const metric = await getHistogramDataPoints(metricName).then((points) =>
|
|
712
|
+
points.at(-1)
|
|
713
|
+
);
|
|
714
|
+
if (metric === undefined) {
|
|
715
|
+
assert.fail('Metric not found');
|
|
716
|
+
} else {
|
|
717
|
+
return metric;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** Helper to create a flow with no inputs or outputs */
|
|
722
|
+
function createFlow(
|
|
723
|
+
ai: Genkit,
|
|
724
|
+
name: string,
|
|
725
|
+
fn: () => Promise<any> = async () => {}
|
|
726
|
+
) {
|
|
727
|
+
return ai.defineFlow(
|
|
728
|
+
{
|
|
729
|
+
name,
|
|
730
|
+
inputSchema: z.void(),
|
|
731
|
+
outputSchema: z.any(),
|
|
732
|
+
},
|
|
733
|
+
fn
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/** Helper to create an action with no inputs or outputs */
|
|
738
|
+
function createAction(
|
|
739
|
+
ai: Genkit,
|
|
740
|
+
name: string,
|
|
741
|
+
fn: () => Promise<void> = async () => {}
|
|
742
|
+
) {
|
|
743
|
+
return ai.defineTool(
|
|
744
|
+
{
|
|
745
|
+
name,
|
|
746
|
+
description: "I don't do much...",
|
|
747
|
+
},
|
|
748
|
+
fn
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/** Helper to create a model that returns the value produced by the given
|
|
753
|
+
* response function. */
|
|
754
|
+
function createModel(
|
|
755
|
+
ai: Genkit,
|
|
756
|
+
name: string,
|
|
757
|
+
respFn: () => Promise<GenerateResponseData>
|
|
758
|
+
) {
|
|
759
|
+
return ai.defineModel({ name }, (req) => respFn());
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function createTestModel(ai: Genkit, name: string) {
|
|
763
|
+
return createModel(ai, name, async () => {
|
|
764
|
+
return {
|
|
765
|
+
message: {
|
|
766
|
+
role: 'model',
|
|
767
|
+
content: [
|
|
768
|
+
{
|
|
769
|
+
text: 'Oh hello',
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
finishReason: 'stop',
|
|
774
|
+
usage: {
|
|
775
|
+
inputTokens: 10,
|
|
776
|
+
outputTokens: 14,
|
|
777
|
+
inputCharacters: 8,
|
|
778
|
+
outputCharacters: 16,
|
|
779
|
+
inputImages: 1,
|
|
780
|
+
outputImages: 3,
|
|
781
|
+
thoughtsTokens: 5,
|
|
782
|
+
},
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
function explode() {
|
|
789
|
+
const nothing: { missing?: any } = { missing: 1 };
|
|
790
|
+
delete nothing.missing;
|
|
791
|
+
return nothing.missing.explode;
|
|
792
|
+
}
|