Skip to content

Commit 68880af

Browse files
committed
Update STE UserTriggerDecision and UserModification events
1. add multiple fields in UserTriggerDecision event and UserModification event, 2. Update the latency calculation to reflect the latest definition 3. added some initial a/b testing changes for new auto-trigger UX
1 parent 7e4f6e3 commit 68880af

File tree

10 files changed

+142
-108
lines changed

10 files changed

+142
-108
lines changed

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/credentials/CodeWhispererClientAdaptor.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
5656
import software.aws.toolkits.telemetry.CodewhispererCompletionType
5757
import software.aws.toolkits.telemetry.CodewhispererSuggestionState
5858
import java.time.Instant
59-
import java.util.concurrent.TimeUnit
6059
import kotlin.reflect.KProperty0
6160
import kotlin.reflect.jvm.isAccessible
6261

@@ -114,7 +113,8 @@ interface CodeWhispererClientAdaptor : Disposable {
114113
requestId: String,
115114
language: CodeWhispererProgrammingLanguage,
116115
customizationArn: String,
117-
modificationPercentage: Double,
116+
acceptedCharacterCount: Int,
117+
unmodifiedAcceptedTokenCount: Int,
118118
): SendTelemetryEventResponse
119119

120120
fun sendCodeScanTelemetry(
@@ -305,13 +305,14 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
305305
val programmingLanguage = fileContext.programmingLanguage
306306
var e2eLatency = requestContext.latencyContext.getCodeWhispererEndToEndLatency()
307307

308-
// When we send a userTriggerDecision of Empty or Discard, we set the time users see the first
309-
// suggestion to be now.
310-
if (e2eLatency < 0) {
311-
e2eLatency = TimeUnit.NANOSECONDS.toMillis(
312-
System.nanoTime() - requestContext.latencyContext.codewhispererEndToEndStart
313-
).toDouble()
308+
// When we send a userTriggerDecision for neither Accept nor Reject, service side should not use this value
309+
// and client side will set this value to 0.0.
310+
if (suggestionState != CodewhispererSuggestionState.Accept &&
311+
suggestionState != CodewhispererSuggestionState.Reject
312+
) {
313+
e2eLatency = 0.0
314314
}
315+
315316
return bearerClient().sendTelemetryEvent { requestBuilder ->
316317
requestBuilder.telemetryEvent { telemetryEventBuilder ->
317318
telemetryEventBuilder.userTriggerDecisionEvent {
@@ -321,6 +322,9 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
321322
it.sessionId(responseContext.sessionId)
322323
it.recommendationLatencyMilliseconds(e2eLatency)
323324
it.triggerToResponseLatencyMilliseconds(requestContext.latencyContext.paginationFirstCompletionTime)
325+
it.perceivedLatencyMilliseconds(
326+
requestContext.latencyContext.getPerceivedLatency(requestContext.triggerTypeInfo.triggerType)
327+
)
324328
it.suggestionState(suggestionState.toCodeWhispererSdkType())
325329
it.timestamp(Instant.now())
326330
it.suggestionReferenceCount(suggestionReferenceCount)
@@ -360,7 +364,8 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
360364
requestId: String,
361365
language: CodeWhispererProgrammingLanguage,
362366
customizationArn: String,
363-
modificationPercentage: Double,
367+
acceptedCharacterCount: Int,
368+
unmodifiedAcceptedTokenCount: Int,
364369
): SendTelemetryEventResponse = bearerClient().sendTelemetryEvent { requestBuilder ->
365370
requestBuilder.telemetryEvent { telemetryEventBuilder ->
366371
telemetryEventBuilder.userModificationEvent {
@@ -370,8 +375,11 @@ open class CodeWhispererClientAdaptorImpl(override val project: Project) : CodeW
370375
languageBuilder.languageName(language.toCodeWhispererRuntimeLanguage().languageId)
371376
}
372377
it.customizationArn(customizationArn)
373-
it.modificationPercentage(modificationPercentage)
378+
// deprecated field, service side should not use this % anymore
379+
it.modificationPercentage(0.0)
374380
it.timestamp(Instant.now())
381+
it.acceptedCharacterCount(acceptedCharacterCount)
382+
it.unmodifiedAcceptedCharacterCount(unmodifiedAcceptedTokenCount)
375383
}
376384
}
377385
requestBuilder.optOutPreference(getTelemetryOptOutPreference())

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.GenerateComple
1313
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnection
1414
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.PayloadContext
1515
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
16+
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutoTriggerService
1617
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType
1718
import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
1819
import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
@@ -198,6 +199,18 @@ data class LatencyContext(
198199
fun getCodeWhispererPreprocessingLatency() = TimeUnit.NANOSECONDS.toMillis(
199200
codewhispererPreprocessingEnd - codewhispererPreprocessingStart
200201
).toDouble()
202+
203+
// For auto-trigger it's from the time when last char typed
204+
// for manual-trigger it's from the time when last trigger action happened(alt + c)
205+
fun getPerceivedLatency(triggerType: CodewhispererTriggerType) =
206+
if (triggerType == CodewhispererTriggerType.OnDemand) {
207+
getCodeWhispererEndToEndLatency()
208+
} else {
209+
(
210+
TimeUnit.NANOSECONDS.toMillis(codewhispererEndToEndEnd) -
211+
CodeWhispererAutoTriggerService.getInstance().timeAtLastCharTyped.toEpochMilli()
212+
).toDouble()
213+
}
201214
}
202215

203216
data class TryExampleRowContext(

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererAutoTriggerService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
4242

4343
private var lastInvocationTime: Instant? = null
4444
private var lastInvocationLineNum: Int? = null
45+
var timeAtLastCharTyped: Instant = Instant.now()
4546

4647
init {
4748
scheduleReset()
@@ -54,6 +55,7 @@ class CodeWhispererAutoTriggerService : CodeWhispererAutoTriggerHandler, Disposa
5455
// a util wrapper
5556
fun tryInvokeAutoTrigger(editor: Editor, triggerType: CodeWhispererAutomatedTriggerType): Job? {
5657
// only needed for Classifier group, thus calculate it lazily
58+
timeAtLastCharTyped = Instant.now()
5759
val classifierResult: ClassifierResult by lazy { shouldTriggerClassifier(editor, triggerType.telemetryType) }
5860
val language = runReadAction {
5961
FileDocumentManager.getInstance().getFile(editor.document)?.programmingLanguage()

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererFeatureConfigService.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ class CodeWhispererFeatureConfigService {
3232
featureConfigs[it.feature()] = FeatureContext(it.feature(), it.variation(), it.value())
3333
}
3434

35+
// Only apply new auto-trigger UX to BID users
36+
val isNewAutoTriggerUX = getIsNewAutoTriggerUX()
37+
if (isNewAutoTriggerUX) {
38+
calculateIfIamIdentityCenterConnection(project) {
39+
featureConfigs.remove(NEW_AUTO_TRIGGER_UX)
40+
}
41+
}
42+
3543
val customizationArnOverride = featureConfigs[CUSTOMIZATION_ARN_OVERRIDE_NAME]?.value?.stringValue()
3644
if (customizationArnOverride != null) {
3745
// Double check if server-side wrongly returns a customizationArn to BID users
@@ -84,20 +92,24 @@ class CodeWhispererFeatureConfigService {
8492

8593
fun getCustomizationArnOverride(): String = getFeatureValueForKey(CUSTOMIZATION_ARN_OVERRIDE_NAME).stringValue()
8694

95+
fun getIsNewAutoTriggerUX(): Boolean = getFeatureValueForKey(NEW_AUTO_TRIGGER_UX).boolValue()
96+
8797
// Get the feature value for the given key.
88-
// In case of a misconfiguration, it will return a default feature value of Boolean true.
98+
// In case of a misconfiguration, it will return a default feature value of Boolean false.
8999
private fun getFeatureValueForKey(name: String): FeatureValue =
90100
featureConfigs[name]?.value ?: FEATURE_DEFINITIONS[name]?.value
91-
?: FeatureValue.builder().boolValue(true).build()
101+
?: FeatureValue.builder().boolValue(false).build()
92102

93103
companion object {
94104
fun getInstance(): CodeWhispererFeatureConfigService = service()
95105
private const val TEST_FEATURE_NAME = "testFeature"
96106
private const val DATA_COLLECTION_FEATURE = "IDEProjectContextDataCollection"
97107
const val CUSTOMIZATION_ARN_OVERRIDE_NAME = "customizationArnOverride"
108+
private const val NEW_AUTO_TRIGGER_UX = "newAutoTriggerUX"
98109
private val LOG = getLogger<CodeWhispererFeatureConfigService>()
99110

100111
// TODO: add real feature later
112+
// Also serve as default values in case server-side config isn't there yet
101113
internal val FEATURE_DEFINITIONS = mapOf(
102114
TEST_FEATURE_NAME to FeatureContext(
103115
TEST_FEATURE_NAME,
@@ -109,7 +121,12 @@ class CodeWhispererFeatureConfigService {
109121
CUSTOMIZATION_ARN_OVERRIDE_NAME,
110122
"customizationARN",
111123
FeatureValue.builder().stringValue("").build()
112-
)
124+
),
125+
NEW_AUTO_TRIGGER_UX to FeatureContext(
126+
NEW_AUTO_TRIGGER_UX,
127+
"CONTROL",
128+
FeatureValue.builder().boolValue(false).build()
129+
),
113130
)
114131
}
115132
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ class CodeWhispererService(private val cs: CoroutineScope) : Disposable {
244244
val sessionId = response.sdkHttpResponse().headers().getOrDefault(KET_SESSION_ID, listOf(requestId))[0]
245245
if (requestCount == 1) {
246246
requestContext.latencyContext.codewhispererPostprocessingStart = System.nanoTime()
247-
requestContext.latencyContext.paginationFirstCompletionTime = latency
247+
requestContext.latencyContext.paginationFirstCompletionTime =
248+
(endTime - requestContext.latencyContext.codewhispererEndToEndStart).toDouble()
248249
requestContext.latencyContext.firstRequestId = requestId
249250
CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
250251
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererCodeCoverageTracker.kt

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe
3131
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
3232
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.TOTAL_SECONDS_IN_MINUTE
3333
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCodeWhispererStartUrl
34+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getUnmodifiedAcceptedCharsCount
3435
import software.aws.toolkits.jetbrains.services.codewhisperer.util.runIfIdcConnectionOrTelemetryEnabled
3536
import software.aws.toolkits.telemetry.CodewhispererTelemetry
3637
import java.time.Duration
@@ -40,6 +41,7 @@ import java.util.concurrent.atomic.AtomicInteger
4041
import kotlin.math.roundToLong
4142

4243
// TODO: reset code coverage calculator on logging out connection?
44+
// TODO: rename "Tokens" to "Characters", and many more renames in this file
4345
abstract class CodeWhispererCodeCoverageTracker(
4446
private val project: Project,
4547
private val timeWindowInSec: Long,
@@ -146,19 +148,6 @@ abstract class CodeWhispererCodeCoverageTracker(
146148
rangeMarker.range?.let { myRange -> rangeMarker.document.getText(myRange) }
147149
}
148150

149-
// With edit distance, complicate usermodification can be considered as simple edit(add, delete, replace),
150-
// and thus the unmodified part of recommendation length can be deducted/approximated
151-
// ex. (modified > original): originalRecom: foo -> modifiedRecom: fobarbarbaro, distance = 9, delta = 12 - 9 = 3
152-
// ex. (modified == original): originalRecom: helloworld -> modifiedRecom: HelloWorld, distance = 2, delta = 10 - 2 = 8
153-
// ex. (modified < original): originalRecom: CodeWhisperer -> modifiedRecom: CODE, distance = 12, delta = 13 - 12 = 1
154-
internal fun getAcceptedTokensDelta(originalRecommendation: String, modifiedRecommendation: String): Int {
155-
val editDistance = getEditDistance(modifiedRecommendation, originalRecommendation).toInt()
156-
return maxOf(originalRecommendation.length, modifiedRecommendation.length) - editDistance
157-
}
158-
159-
protected open fun getEditDistance(modifiedString: String, originalString: String): Double =
160-
levenshteinChecker.distance(modifiedString, originalString)
161-
162151
private fun flush() {
163152
try {
164153
if (isTelemetryEnabled()) emitCodeWhispererCodeContribution()
@@ -174,12 +163,12 @@ abstract class CodeWhispererCodeCoverageTracker(
174163
}
175164
}
176165

177-
private fun incrementAcceptedTokens(document: Document, delta: Int) {
166+
private fun incrementUnmodifiedAcceptedCharsCount(document: Document, delta: Int) {
178167
val tokens = fileToTokens.getOrPut(document) { CodeCoverageTokens() }
179168
tokens.acceptedTokens.addAndGet(delta)
180169
}
181170

182-
private fun incrementRawAcceptedTokens(document: Document, delta: Int) {
171+
private fun incrementAcceptedCharsCount(document: Document, delta: Int) {
183172
val tokens = fileToTokens.getOrPut(document) { CodeCoverageTokens() }
184173
tokens.rawAcceptedTokens.addAndGet(delta)
185174
}
@@ -217,10 +206,10 @@ abstract class CodeWhispererCodeCoverageTracker(
217206
}
218207
return@forEach
219208
}
220-
val delta = getAcceptedTokensDelta(originalRecommendation, modifiedRecommendation)
209+
val unmodifiedRecommendationLength = getUnmodifiedAcceptedCharsCount(originalRecommendation, modifiedRecommendation)
221210
runReadAction {
222-
incrementRawAcceptedTokens(rangeMarker.document, originalRecommendation.length)
223-
incrementAcceptedTokens(rangeMarker.document, delta)
211+
incrementAcceptedCharsCount(rangeMarker.document, originalRecommendation.length)
212+
incrementUnmodifiedAcceptedCharsCount(rangeMarker.document, unmodifiedRecommendationLength)
224213
}
225214
}
226215
val customizationArn: String? = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)?.arn
@@ -277,7 +266,7 @@ abstract class CodeWhispererCodeCoverageTracker(
277266

278267
companion object {
279268
@JvmStatic
280-
protected val levenshteinChecker = Levenshtein()
269+
val levenshteinChecker = Levenshtein()
281270
private const val REMAINING_RECOMMENDATION = "remainingRecommendation"
282271
private val KEY_REMAINING_RECOMMENDATION = Key<String>(REMAINING_RECOMMENDATION)
283272
private val LOG = getLogger<CodeWhispererCodeCoverageTracker>()

0 commit comments

Comments
 (0)