mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 19:57:57 +01:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user