Skip to content

Commit 3b7082a

Browse files
authored
telemetry(amazonq): /dev error classification aws#6772
## Problem Today, telemetry emitted from the feature development is classified as error, fault, or LLM-based failure across various parts of the code base. This makes it difficult to know all the error types that exist and how to classify them appropriately when emitting metrics. ## Solution The solution here allows different exceptions to raise from different parts of the code base and uses inheritance to model each error as a client error, service error, or LLM-based error. When emitting telemetry, we now only need to check whether the error received extends from one of these 3 to know how to classify the telemetry.
1 parent 86f87a5 commit 3b7082a

File tree

12 files changed

+153
-112
lines changed

12 files changed

+153
-112
lines changed

packages/amazonq/test/unit/amazonqFeatureDev/util/files.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import {
1111
maxRepoSizeBytes,
1212
} from 'aws-core-vscode/amazonqFeatureDev'
1313
import { assertTelemetry, getWorkspaceFolder, TestFolder } from 'aws-core-vscode/test'
14-
import { fs, AmazonqCreateUpload, ZipStream } from 'aws-core-vscode/shared'
14+
import { fs, AmazonqCreateUpload, ZipStream, ContentLengthError } from 'aws-core-vscode/shared'
1515
import { MetricName, Span } from 'aws-core-vscode/telemetry'
1616
import sinon from 'sinon'
1717
import { CodeWhispererSettings } from 'aws-core-vscode/codewhisperer'
18-
import { ContentLengthError, CurrentWsFolders } from 'aws-core-vscode/amazonq'
18+
import { CurrentWsFolders } from 'aws-core-vscode/amazonq'
1919
import path from 'path'
2020

2121
const testDevfilePrepareRepo = async (devfileEnabled: boolean) => {

packages/core/src/amazonq/errors.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
* When thrown from common components, individual agents can catch and transform this error
1010
* to provide their own customized error messages.
1111
*/
12-
import { ToolkitError } from '../shared/errors'
12+
import { ErrorInformation, ToolkitError } from '../shared/errors'
1313

14-
export class ContentLengthError extends ToolkitError {
15-
constructor(message: string) {
16-
super(message, { code: 'ContentLengthError' })
14+
/**
15+
* Errors extending this class are considered "LLM failures" in service metrics.
16+
*/
17+
export class LlmError extends ToolkitError {
18+
constructor(message: string, info: ErrorInformation = {}) {
19+
super(message, info)
1720
}
1821
}

packages/core/src/amazonq/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export { ExtensionMessage } from '../amazonq/webview/ui/commands'
4444
export { CodeReference } from '../codewhispererChat/view/connector/connector'
4545
export { extractAuthFollowUp } from './util/authUtils'
4646
export { Messenger } from './commons/connector/baseMessenger'
47-
export { ContentLengthError } from './errors'
4847
import { FeatureContext } from '../shared/featureConfig'
4948

5049
/**

packages/core/src/amazonq/util/files.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { PrepareRepoFailedError } from '../../amazonqFeatureDev/errors'
1616
import { getLogger } from '../../shared/logger/logger'
1717
import { maxFileSizeBytes } from '../../amazonqFeatureDev/limits'
1818
import { CurrentWsFolders, DeletedFileInfo, NewFileInfo, NewFileZipContents } from '../../amazonqDoc/types'
19-
import { hasCode, ToolkitError } from '../../shared/errors'
19+
import { ContentLengthError, hasCode, ToolkitError } from '../../shared/errors'
2020
import { AmazonqCreateUpload, Span, telemetry as amznTelemetry, telemetry } from '../../shared/telemetry/telemetry'
2121
import { maxRepoSizeBytes } from '../../amazonqFeatureDev/constants'
2222
import { isCodeFile } from '../../shared/filetypes'
@@ -28,7 +28,6 @@ import { ZipStream } from '../../shared/utilities/zipStream'
2828
import { isPresent } from '../../shared/utilities/collectionUtils'
2929
import { AuthUtil } from '../../codewhisperer/util/authUtil'
3030
import { TelemetryHelper } from '../util/telemetryHelper'
31-
import { ContentLengthError } from '../errors'
3231

3332
export const SvgFileExtension = '.svg'
3433

packages/core/src/amazonqDoc/session/session.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import fs from '../../shared/fs/fs'
3030
import globals from '../../shared/extensionGlobals'
3131
import { extensionVersion } from '../../shared/vscode/env'
3232
import { getLogger } from '../../shared/logger/logger'
33+
import { ContentLengthError as CommonContentLengthError } from '../../shared/errors'
3334
import { ContentLengthError } from '../errors'
34-
import { ContentLengthError as CommonAmazonQContentLengthError } from '../../amazonq/errors'
3535

3636
export class Session {
3737
private _state?: SessionState | Omit<SessionState, 'uploadId'>
@@ -152,7 +152,7 @@ export class Session {
152152

153153
return resp.interaction
154154
} catch (e) {
155-
if (e instanceof CommonAmazonQContentLengthError) {
155+
if (e instanceof CommonContentLengthError) {
156156
getLogger().debug(`Content length validation failed: ${e.message}`)
157157
throw new ContentLengthError()
158158
}

packages/core/src/amazonqFeatureDev/client/featureDev.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import { featureName, startTaskAssistLimitReachedMessage } from '../constants'
1414
import { CodeReference } from '../../amazonq/webview/ui/connector'
1515
import {
1616
ApiError,
17+
ApiServiceError,
1718
CodeIterationLimitError,
1819
ContentLengthError,
20+
FeatureDevServiceError,
1921
MonthlyConversationLimitError,
2022
UnknownApiError,
2123
} from '../errors'
22-
import { ToolkitError, isAwsError } from '../../shared/errors'
24+
import { isAwsError } from '../../shared/errors'
2325
import { getCodewhispererConfig } from '../../codewhisperer/client/codewhisperer'
2426
import { createCodeWhispererChatStreamingClient } from '../../shared/clients/codewhispererChatClient'
2527
import { getClientId, getOptOutPreference, getOperatingSystem } from '../../shared/telemetry/util'
@@ -93,7 +95,7 @@ export class FeatureDevClient implements FeatureClient {
9395
) {
9496
throw new MonthlyConversationLimitError(e.message)
9597
}
96-
throw new ApiError(e.message, 'CreateConversation', e.code, e.statusCode ?? 400)
98+
throw ApiError.of(e.message, 'CreateConversation', e.code, e.statusCode ?? 500)
9799
}
98100

99101
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateConversation')
@@ -136,7 +138,7 @@ export class FeatureDevClient implements FeatureClient {
136138
if (e.code === 'ValidationException' && e.message.includes('Invalid contentLength')) {
137139
throw new ContentLengthError()
138140
}
139-
throw new ApiError(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 400)
141+
throw ApiError.of(e.message, 'CreateUploadUrl', e.code, e.statusCode ?? 500)
140142
}
141143

142144
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'CreateUploadUrl')
@@ -198,8 +200,10 @@ export class FeatureDevClient implements FeatureClient {
198200
) {
199201
throw new CodeIterationLimitError()
200202
}
203+
throw ApiError.of(e.message, 'StartTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
201204
}
202-
throw new ToolkitError((e as Error).message, { code: 'StartCodeGenerationFailed' })
205+
206+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'StartTaskAssistCodeGeneration')
203207
}
204208
}
205209

@@ -220,7 +224,12 @@ export class FeatureDevClient implements FeatureClient {
220224
(e as any).requestId
221225
}`
222226
)
223-
throw new ToolkitError((e as Error).message, { code: 'GetCodeGenerationFailed' })
227+
228+
if (isAwsError(e)) {
229+
throw ApiError.of(e.message, 'GetTaskAssistCodeGeneration', e.code, e.statusCode ?? 500)
230+
}
231+
232+
throw new UnknownApiError(e instanceof Error ? e.message : 'Unknown error', 'GetTaskAssistCodeGeneration')
224233
}
225234
}
226235

@@ -235,7 +244,12 @@ export class FeatureDevClient implements FeatureClient {
235244
const archiveResponse = await streamingClient.exportResultArchive(params)
236245
const buffer: number[] = []
237246
if (archiveResponse.body === undefined) {
238-
throw new ToolkitError('Empty response from CodeWhisperer Streaming service.')
247+
throw new ApiServiceError(
248+
'Empty response from CodeWhisperer Streaming service.',
249+
'ExportResultArchive',
250+
'EmptyResponse',
251+
500
252+
)
239253
}
240254
for await (const chunk of archiveResponse.body) {
241255
if (chunk.internalServerException !== undefined) {
@@ -274,7 +288,12 @@ export class FeatureDevClient implements FeatureClient {
274288
(e as any).requestId
275289
}`
276290
)
277-
throw new ToolkitError((e as Error).message, { code: 'ExportResultArchiveFailed' })
291+
292+
if (isAwsError(e)) {
293+
throw ApiError.of(e.message, 'ExportResultArchive', e.code, e.statusCode ?? 500)
294+
}
295+
296+
throw new FeatureDevServiceError(e instanceof Error ? e.message : 'Unknown error', 'ExportResultArchive')
278297
}
279298
}
280299

packages/core/src/amazonqFeatureDev/controllers/chat/controller.ts

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
createUserFacingErrorMessage,
1616
denyListedErrors,
1717
FeatureDevServiceError,
18-
isAPIClientError,
18+
getMetricResult,
1919
MonthlyConversationLimitError,
2020
NoChangeRequiredException,
2121
PrepareRepoFailedError,
@@ -550,34 +550,7 @@ export class FeatureDevController {
550550
this.messenger.sendUpdatePlaceholder(tabID, i18n('AWS.amazonq.featureDev.pillText.selectOption'))
551551
} catch (err: any) {
552552
getLogger().error(`${featureName}: Error during code generation: ${err}`)
553-
554-
let result: string
555-
switch (err.constructor.name) {
556-
case FeatureDevServiceError.name:
557-
if (err.code === 'EmptyPatchException') {
558-
result = MetricDataResult.LlmFailure
559-
} else if (err.code === 'GuardrailsException' || err.code === 'ThrottlingException') {
560-
result = MetricDataResult.Error
561-
} else {
562-
result = MetricDataResult.Fault
563-
}
564-
break
565-
case MonthlyConversationLimitError.name:
566-
case CodeIterationLimitError.name:
567-
case PromptRefusalException.name:
568-
case NoChangeRequiredException.name:
569-
result = MetricDataResult.Error
570-
break
571-
default:
572-
if (isAPIClientError(err)) {
573-
result = MetricDataResult.Error
574-
} else {
575-
result = MetricDataResult.Fault
576-
}
577-
break
578-
}
579-
580-
await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, result)
553+
await session.sendMetricDataTelemetry(MetricDataOperationName.EndCodeGeneration, getMetricResult(err))
581554
throw err
582555
} finally {
583556
// Finish processing the event

0 commit comments

Comments
 (0)