Add more detailed telemetry for AdvancedPaste (#43498)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Added telemetry to capture the following:

- PasteAI: endpoint, model name, and processing time
- PasteAI Errors
- Advanced AI: endpoint, model name, and token usage
- Advanced AI Errors

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
leileizhang
2025-11-13 10:09:50 +08:00
committed by GitHub
parent a51b2647d9
commit 1c646ecb2a
7 changed files with 93 additions and 9 deletions

View File

@@ -147,6 +147,18 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent</td> <td>Microsoft.PowerToys.AdvancedPasteSemanticKernelFormatEvent</td>
<td>Triggered when Advanced Paste leverages the Semantic Kernel.</td> <td>Triggered when Advanced Paste leverages the Semantic Kernel.</td>
</tr> </tr>
<tr>
<td>Microsoft.PowerToys.AdvancedPasteSemanticKernelErrorEvent</td>
<td>Occurs when the Semantic Kernel workflow encounters an error.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.AdvancedPasteEndpointUsageEvent</td>
<td>Logs the AI provider, model, and processing duration for each endpoint call.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.AdvancedPasteCustomActionErrorEvent</td>
<td>Records provider, model, and status details when a custom action fails.</td>
</tr>
</table> </table>
### Always on Top ### Always on Top

View File

@@ -18,6 +18,7 @@ namespace AdvancedPaste.Helpers
PromptTokens = semanticKernelFormatEvent.PromptTokens; PromptTokens = semanticKernelFormatEvent.PromptTokens;
CompletionTokens = semanticKernelFormatEvent.CompletionTokens; CompletionTokens = semanticKernelFormatEvent.CompletionTokens;
ModelName = semanticKernelFormatEvent.ModelName; ModelName = semanticKernelFormatEvent.ModelName;
ProviderType = semanticKernelFormatEvent.ProviderType;
ActionChain = semanticKernelFormatEvent.ActionChain; ActionChain = semanticKernelFormatEvent.ActionChain;
} }
@@ -38,6 +39,8 @@ namespace AdvancedPaste.Helpers
public string ModelName { get; set; } public string ModelName { get; set; }
public string ProviderType { get; set; }
public string ActionChain { get; set; } public string ActionChain { get; set; }
public string ToJsonString() => JsonSerializer.Serialize(this, SourceGenerationContext.Default.AIServiceFormatEvent); public string ToJsonString() => JsonSerializer.Serialize(this, SourceGenerationContext.Default.AIServiceFormatEvent);

View File

@@ -83,32 +83,39 @@ namespace AdvancedPaste.Services.CustomActions
SystemPrompt = systemPrompt, SystemPrompt = systemPrompt,
}; };
var operationStart = DateTime.UtcNow;
var providerContent = await provider.ProcessPasteAsync( var providerContent = await provider.ProcessPasteAsync(
request, request,
cancellationToken, cancellationToken,
progress); progress);
var durationMs = (int)Math.Round((DateTime.UtcNow - operationStart).TotalMilliseconds);
var usage = request.Usage; var usage = request.Usage;
var content = providerContent ?? string.Empty; var content = providerContent ?? string.Empty;
// Log endpoint usage // Log endpoint usage (custom action pipeline is not the advanced SK flow)
var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType); var endpointEvent = new AdvancedPasteEndpointUsageEvent(providerConfig.ProviderType, providerConfig.Model ?? string.Empty, isAdvanced: false, durationMs: durationMs);
PowerToysTelemetry.Log.WriteEvent(endpointEvent); PowerToysTelemetry.Log.WriteEvent(endpointEvent);
Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}"); Logger.LogDebug($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} complete; ModelName={providerConfig.Model ?? string.Empty}, PromptTokens={usage.PromptTokens}, CompletionTokens={usage.CompletionTokens}, DurationMs={durationMs}");
return new CustomActionTransformResult(content, usage); return new CustomActionTransformResult(content, usage);
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogError($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} failed", ex); Logger.LogError($"{nameof(CustomActionTransformService)}.{nameof(TransformAsync)} failed", ex);
var statusCode = ExtractStatusCode(ex);
var modelName = providerConfig.Model ?? string.Empty;
AdvancedPasteCustomActionErrorEvent errorEvent = new(providerConfig.ProviderType, modelName, statusCode, ex is PasteActionModeratedException ? PasteActionModeratedException.ErrorDescription : ex.Message);
PowerToysTelemetry.Log.WriteEvent(errorEvent);
if (ex is PasteActionException or OperationCanceledException) if (ex is PasteActionException or OperationCanceledException)
{ {
throw; throw;
} }
var statusCode = ExtractStatusCode(ex);
var failureMessage = providerConfig.ProviderType switch var failureMessage = providerConfig.ProviderType switch
{ {
AIServiceType.OpenAI or AIServiceType.AzureOpenAI => ErrorHelpers.TranslateErrorText(statusCode), AIServiceType.OpenAI or AIServiceType.AzureOpenAI => ErrorHelpers.TranslateErrorText(statusCode),

View File

@@ -186,12 +186,20 @@ public abstract class KernelServiceBase(
private void LogResult(bool cacheUsed, bool isSavedQuery, IEnumerable<ActionChainItem> actionChain, AIServiceUsage usage) private void LogResult(bool cacheUsed, bool isSavedQuery, IEnumerable<ActionChainItem> actionChain, AIServiceUsage usage)
{ {
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(cacheUsed, isSavedQuery, usage.PromptTokens, usage.CompletionTokens, AdvancedAIModelName, AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain)); var runtimeConfig = GetRuntimeConfiguration();
AdvancedPasteSemanticKernelFormatEvent telemetryEvent = new(
cacheUsed,
isSavedQuery,
usage.PromptTokens,
usage.CompletionTokens,
AdvancedAIModelName,
runtimeConfig.ServiceType.ToString(),
AdvancedPasteSemanticKernelFormatEvent.FormatActionChain(actionChain));
PowerToysTelemetry.Log.WriteEvent(telemetryEvent); PowerToysTelemetry.Log.WriteEvent(telemetryEvent);
// Log endpoint usage // Log endpoint usage
var runtimeConfig = GetRuntimeConfiguration(); var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType, AdvancedAIModelName, isAdvanced: true);
var endpointEvent = new AdvancedPasteEndpointUsageEvent(runtimeConfig.ServiceType);
PowerToysTelemetry.Log.WriteEvent(endpointEvent); PowerToysTelemetry.Log.WriteEvent(endpointEvent);
var logEvent = new AIServiceFormatEvent(telemetryEvent); var logEvent = new AIServiceFormatEvent(telemetryEvent);

View File

@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace AdvancedPaste.Telemetry;
[EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public sealed class AdvancedPasteCustomActionErrorEvent : EventBase, IEvent
{
public AdvancedPasteCustomActionErrorEvent(AIServiceType providerType, string modelName, int statusCode, string error)
{
ProviderType = providerType.ToString();
ModelName = modelName;
StatusCode = statusCode;
Error = error;
}
public string ProviderType { get; set; }
public string ModelName { get; set; }
public int StatusCode { get; set; }
public string Error { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}

View File

@@ -19,9 +19,27 @@ public class AdvancedPasteEndpointUsageEvent : EventBase, IEvent
/// </summary> /// </summary>
public string ProviderType { get; set; } public string ProviderType { get; set; }
public AdvancedPasteEndpointUsageEvent(AIServiceType providerType) /// <summary>
/// Gets or sets the configured model name.
/// </summary>
public string ModelName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the advanced AI pipeline was used.
/// </summary>
public bool IsAdvanced { get; set; }
/// <summary>
/// Gets or sets the total duration in milliseconds, or -1 if unavailable.
/// </summary>
public int DurationMs { get; set; }
public AdvancedPasteEndpointUsageEvent(AIServiceType providerType, string modelName, bool isAdvanced, int durationMs = -1)
{ {
ProviderType = providerType.ToString(); ProviderType = providerType.ToString();
ModelName = modelName;
IsAdvanced = isAdvanced;
DurationMs = durationMs;
} }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;

View File

@@ -14,7 +14,7 @@ namespace AdvancedPaste.Telemetry;
[EventData] [EventData]
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string actionChain) : EventBase, IEvent public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSavedQuery, int promptTokens, int completionTokens, string modelName, string providerType, string actionChain) : EventBase, IEvent
{ {
public static string FormatActionChain(IEnumerable<ActionChainItem> actionChain) => FormatActionChain(actionChain.Select(item => item.Format)); public static string FormatActionChain(IEnumerable<ActionChainItem> actionChain) => FormatActionChain(actionChain.Select(item => item.Format));
@@ -30,6 +30,8 @@ public class AdvancedPasteSemanticKernelFormatEvent(bool cacheUsed, bool isSaved
public string ModelName { get; set; } = modelName; public string ModelName { get; set; } = modelName;
public string ProviderType { get; set; } = providerType;
/// <summary> /// <summary>
/// Gets or sets a comma-separated list of paste formats used - in the same order they were executed. /// Gets or sets a comma-separated list of paste formats used - in the same order they were executed.
/// Conceptually an array but formatted this way to work around https://github.com/dotnet/runtime/issues/10428 /// Conceptually an array but formatted this way to work around https://github.com/dotnet/runtime/issues/10428