Skip to content

Commit 02f1d3d

Browse files
authored
feat: Add support for tracking errors. (#715)
1 parent ce66e1c commit 02f1d3d

File tree

3 files changed

+131
-14
lines changed

3 files changed

+131
-14
lines changed

packages/sdk/server-ai/__tests__/LDAIConfigTrackerImpl.test.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,39 @@ it('tracks OpenAI usage', async () => {
134134
);
135135
});
136136

137+
it('tracks error when OpenAI metrics function throws', async () => {
138+
const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext);
139+
jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000);
140+
141+
const error = new Error('OpenAI API error');
142+
await expect(
143+
tracker.trackOpenAIMetrics(async () => {
144+
throw error;
145+
}),
146+
).rejects.toThrow(error);
147+
148+
expect(mockTrack).toHaveBeenCalledWith(
149+
'$ld:ai:duration:total',
150+
testContext,
151+
{ configKey, variationKey },
152+
1000,
153+
);
154+
155+
expect(mockTrack).toHaveBeenCalledWith(
156+
'$ld:ai:generation',
157+
testContext,
158+
{ configKey, variationKey },
159+
1,
160+
);
161+
162+
expect(mockTrack).toHaveBeenCalledWith(
163+
'$ld:ai:generation:error',
164+
testContext,
165+
{ configKey, variationKey },
166+
1,
167+
);
168+
});
169+
137170
it('tracks Bedrock conversation with successful response', () => {
138171
const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext);
139172

@@ -196,11 +229,22 @@ it('tracks Bedrock conversation with error response', () => {
196229
$metadata: { httpStatusCode: 400 },
197230
};
198231

199-
// TODO: We may want a track failure.
200-
201232
tracker.trackBedrockConverseMetrics(response);
202233

203-
expect(mockTrack).not.toHaveBeenCalled();
234+
expect(mockTrack).toHaveBeenCalledTimes(2);
235+
expect(mockTrack).toHaveBeenCalledWith(
236+
'$ld:ai:generation',
237+
testContext,
238+
{ configKey, variationKey },
239+
1,
240+
);
241+
242+
expect(mockTrack).toHaveBeenCalledWith(
243+
'$ld:ai:generation:error',
244+
testContext,
245+
{ configKey, variationKey },
246+
1,
247+
);
204248
});
205249

206250
it('tracks tokens', () => {
@@ -304,3 +348,41 @@ it('summarizes tracked metrics', () => {
304348
success: true,
305349
});
306350
});
351+
352+
it('tracks duration when async function throws', async () => {
353+
const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext);
354+
jest.spyOn(global.Date, 'now').mockReturnValueOnce(1000).mockReturnValueOnce(2000);
355+
356+
const error = new Error('test error');
357+
await expect(
358+
tracker.trackDurationOf(async () => {
359+
throw error;
360+
}),
361+
).rejects.toThrow(error);
362+
363+
expect(mockTrack).toHaveBeenCalledWith(
364+
'$ld:ai:duration:total',
365+
testContext,
366+
{ configKey, variationKey },
367+
1000,
368+
);
369+
});
370+
371+
it('tracks error', () => {
372+
const tracker = new LDAIConfigTrackerImpl(mockLdClient, configKey, variationKey, testContext);
373+
tracker.trackError();
374+
375+
expect(mockTrack).toHaveBeenCalledWith(
376+
'$ld:ai:generation',
377+
testContext,
378+
{ configKey, variationKey },
379+
1,
380+
);
381+
382+
expect(mockTrack).toHaveBeenCalledWith(
383+
'$ld:ai:generation:error',
384+
testContext,
385+
{ configKey, variationKey },
386+
1,
387+
);
388+
});

packages/sdk/server-ai/src/LDAIConfigTrackerImpl.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@ export class LDAIConfigTrackerImpl implements LDAIConfigTracker {
3030

3131
async trackDurationOf<TRes>(func: () => Promise<TRes>): Promise<TRes> {
3232
const startTime = Date.now();
33-
const result = await func();
34-
const endTime = Date.now();
35-
const duration = endTime - startTime; // duration in milliseconds
36-
this.trackDuration(duration);
37-
return result;
33+
try {
34+
// Be sure to await here so that we can track the duration of the function and also handle errors.
35+
const result = await func();
36+
return result;
37+
} finally {
38+
const endTime = Date.now();
39+
const duration = endTime - startTime; // duration in milliseconds
40+
this.trackDuration(duration);
41+
}
3842
}
3943

4044
trackFeedback(feedback: { kind: LDFeedbackKind }): void {
@@ -49,6 +53,13 @@ export class LDAIConfigTrackerImpl implements LDAIConfigTracker {
4953
trackSuccess(): void {
5054
this._trackedMetrics.success = true;
5155
this._ldClient.track('$ld:ai:generation', this._context, this._getTrackData(), 1);
56+
this._ldClient.track('$ld:ai:generation:success', this._context, this._getTrackData(), 1);
57+
}
58+
59+
trackError(): void {
60+
this._trackedMetrics.success = false;
61+
this._ldClient.track('$ld:ai:generation', this._context, this._getTrackData(), 1);
62+
this._ldClient.track('$ld:ai:generation:error', this._context, this._getTrackData(), 1);
5263
}
5364

5465
async trackOpenAIMetrics<
@@ -60,12 +71,17 @@ export class LDAIConfigTrackerImpl implements LDAIConfigTracker {
6071
};
6172
},
6273
>(func: () => Promise<TRes>): Promise<TRes> {
63-
const result = await this.trackDurationOf(func);
64-
this.trackSuccess();
65-
if (result.usage) {
66-
this.trackTokens(createOpenAiUsage(result.usage));
74+
try {
75+
const result = await this.trackDurationOf(func);
76+
this.trackSuccess();
77+
if (result.usage) {
78+
this.trackTokens(createOpenAiUsage(result.usage));
79+
}
80+
return result;
81+
} catch (err) {
82+
this.trackError();
83+
throw err;
6784
}
68-
return result;
6985
}
7086

7187
trackBedrockConverseMetrics<
@@ -82,7 +98,7 @@ export class LDAIConfigTrackerImpl implements LDAIConfigTracker {
8298
if (res.$metadata?.httpStatusCode === 200) {
8399
this.trackSuccess();
84100
} else if (res.$metadata?.httpStatusCode && res.$metadata.httpStatusCode >= 400) {
85-
// Potentially add error tracking in the future.
101+
this.trackError();
86102
}
87103
if (res.metrics && res.metrics.latencyMs) {
88104
this.trackDuration(res.metrics.latencyMs);

packages/sdk/server-ai/src/api/config/LDAIConfigTracker.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ export interface LDAIConfigTracker {
5050
*/
5151
trackSuccess(): void;
5252

53+
/**
54+
* An error was encountered during generation.
55+
*/
56+
trackError(): void;
57+
5358
/**
5459
* Track sentiment about the generation.
5560
*
@@ -59,6 +64,12 @@ export interface LDAIConfigTracker {
5964

6065
/**
6166
* Track the duration of execution of the provided function.
67+
*
68+
* If the provided function throws, then this method will also throw.
69+
* In the case the provided function throws, this function will still record the duration.
70+
*
71+
* This function does not automatically record an error when the function throws.
72+
*
6273
* @param func The function to track the duration of.
6374
* @returns The result of the function.
6475
*/
@@ -67,6 +78,12 @@ export interface LDAIConfigTracker {
6778
/**
6879
* Track an OpenAI operation.
6980
*
81+
* This function will track the duration of the operation, the token usage, and the success or error status.
82+
*
83+
* If the provided function throws, then this method will also throw.
84+
* In the case the provided function throws, this function will record the duration and an error.
85+
* A failed operation will not have any token usage data.
86+
*
7087
* @param func Function which executes the operation.
7188
* @returns The result of the operation.
7289
*/
@@ -85,6 +102,8 @@ export interface LDAIConfigTracker {
85102
/**
86103
* Track an operation which uses Bedrock.
87104
*
105+
* This function will track the duration of the operation, the token usage, and the success or error status.
106+
*
88107
* @param res The result of the Bedrock operation.
89108
* @returns The input operation.
90109
*/

0 commit comments

Comments
 (0)