mirror of
https://github.com/baz-scm/awesome-reviewers.git
synced 2025-08-20 18:58:52 +03:00
194 lines
52 KiB
JSON
194 lines
52 KiB
JSON
[
|
||
{
|
||
"discussion_id": "2167978316",
|
||
"pr_number": 50898,
|
||
"pr_file": "sdk/ai/Azure.AI.Agents.Persistent/src/Custom/PersistentAgentsChatClient.cs",
|
||
"created_at": "2025-06-26T02:34:06+00:00",
|
||
"commented_code": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\n#nullable enable\n#pragma warning disable AZC0004, AZC0007, AZC0015\n\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing System.Text;\nusing System.Text.Json;\nusing System.Text.Json.Nodes;\nusing System.Text.Json.Serialization;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.AI;\n\nnamespace Azure.AI.Agents.Persistent\n{\n /// <summary>Represents an <see cref=\"IChatClient\"/> for an Azure.AI.Agents.Persistent <see cref=\"PersistentAgentsClient\"/>.</summary>\n public partial class PersistentAgentsChatClient : IChatClient\n {\n /// <summary>The name of the chat client provider.</summary>\n private const string ProviderName = \"azure\";\n\n /// <summary>The underlying <see cref=\"PersistentAgentsClient\" />.</summary>\n private readonly PersistentAgentsClient? _client;\n\n /// <summary>Metadata for the client.</summary>\n private readonly ChatClientMetadata? _metadata;\n\n /// <summary>The ID of the agent to use.</summary>\n private readonly string? _agentId;\n\n /// <summary>The thread ID to use if none is supplied in <see cref=\"ChatOptions.ConversationId\"/>.</summary>\n private readonly string? _defaultThreadId;\n\n /// <summary>List of tools associated with the agent.</summary>\n private IReadOnlyList<ToolDefinition>? _agentTools;\n\n /// <summary>Initializes a new instance of the <see cref=\"PersistentAgentsChatClient\"/> class for the specified <see cref=\"PersistentAgentsClient\"/>.</summary>\n public PersistentAgentsChatClient(PersistentAgentsClient client, string agentId, string? defaultThreadId = null)\n {\n Argument.AssertNotNull(client, nameof(client));\n Argument.AssertNotNullOrWhiteSpace(agentId, nameof(agentId));\n\n _client = client;\n _agentId = agentId;\n _defaultThreadId = defaultThreadId;\n\n _metadata = new(ProviderName);\n }\n\n protected PersistentAgentsChatClient() { }\n\n /// <inheritdoc />\n public virtual object? GetService(Type serviceType, object? serviceKey = null) =>\n serviceType is null ? throw new ArgumentNullException(nameof(serviceType)) :\n serviceKey is not null ? null :\n serviceType == typeof(ChatClientMetadata) ? _metadata :\n serviceType == typeof(PersistentAgentsClient) ? _client :\n serviceType.IsInstanceOfType(this) ? this :\n null;\n\n /// <inheritdoc />\n public virtual Task<ChatResponse> GetResponseAsync(\n IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) =>\n GetStreamingResponseAsync(messages, options, cancellationToken).ToChatResponseAsync(cancellationToken);\n\n /// <inheritdoc />\n public virtual async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(\n IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)\n {\n Argument.AssertNotNull(messages, nameof(messages));\n\n // Extract necessary state from messages and options.\n (ThreadAndRunOptions runOptions, List<FunctionResultContent>? toolResults) =\n await CreateRunOptionsAsync(messages, options, cancellationToken).ConfigureAwait(false);\n\n // Get the thread ID.\n string? threadId = options?.ConversationId ?? _defaultThreadId;\n if (threadId is null && toolResults is not null)\n {\n throw new ArgumentException(\"No thread ID was provided, but chat messages includes tool results.\", nameof(messages));\n }\n\n // Get any active run ID for this thread.\n ThreadRun? threadRun = null;\n if (threadId is not null)\n {\n await foreach (ThreadRun? run in _client!.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))\n {\n if (run.Status != RunStatus.Completed && run.Status != RunStatus.Cancelled && run.Status != RunStatus.Failed && run.Status != RunStatus.Expired)\n {\n threadRun = run;\n break;\n }\n }\n }\n\n // Submit the request.\n IAsyncEnumerable<StreamingUpdate> updates;\n if (threadRun is not null &&\n ConvertFunctionResultsToToolOutput(toolResults, out List<ToolOutput>? toolOutputs) is { } toolRunId &&\n toolRunId == threadRun.Id)\n {\n // There's an active run and we have tool results to submit, so submit the results and continue streaming.\n // This is going to ignore any additional messages in the run options, as we are only submitting tool outputs,\n // but there doesn't appear to be a way to submit additional messages, and having such additional messages is rare.\n updates = _client!.Runs.SubmitToolOutputsToStreamAsync(threadRun, toolOutputs, cancellationToken);\n }\n else\n {\n if (threadId is null)\n {\n // No thread ID was provided, so create a new thread.\n PersistentAgentThread thread = await _client!.Threads.CreateThreadAsync(runOptions.ThreadOptions.Messages, runOptions.ToolResources, runOptions.Metadata, cancellationToken).ConfigureAwait(false);\n runOptions.ThreadOptions.Messages.Clear();\n threadId = thread.Id;\n }\n else if (threadRun is not null)\n {\n // There was an active run; we need to cancel it before starting a new run.\n await _client!.Runs.CancelRunAsync(threadId, threadRun.Id, cancellationToken).ConfigureAwait(false);\n threadRun = null;\n }\n\n // Now create a new run and stream the results.\n updates = _client!.Runs.CreateRunStreamingAsync(\n threadId: threadId,\n agentId: _agentId,\n overrideModelName: runOptions?.OverrideModelName,\n overrideInstructions: runOptions?.OverrideInstructions,\n additionalInstructions: null,\n additionalMessages: runOptions?.ThreadOptions.Messages,\n overrideTools: runOptions?.OverrideTools,\n temperature: runOptions?.Temperature,\n topP: runOptions?.TopP,\n maxPromptTokens: runOptions?.MaxPromptTokens,\n maxCompletionTokens: runOptions?.MaxCompletionTokens,\n truncationStrategy: runOptions?.TruncationStrategy,\n toolChoice: runOptions?.ToolChoice,\n responseFormat: runOptions?.ResponseFormat,\n parallelToolCalls: runOptions?.ParallelToolCalls,\n metadata: runOptions?.Metadata,\n cancellationToken);\n }\n\n // Process each update.\n string? responseId = null;\n await foreach (StreamingUpdate? update in updates.ConfigureAwait(false))\n {\n switch (update)\n {\n case ThreadUpdate tu:\n threadId ??= tu.Value.Id;\n goto default;\n\n case RunUpdate ru:\n threadId ??= ru.Value.ThreadId;\n responseId ??= ru.Value.Id;\n\n ChatResponseUpdate ruUpdate = new()\n {\n AuthorName = ru.Value.AssistantId,\n ConversationId = threadId,\n CreatedAt = ru.Value.CreatedAt,\n MessageId = responseId,\n ModelId = ru.Value.Model,\n RawRepresentation = ru,\n ResponseId = responseId,\n Role = ChatRole.Assistant,\n };\n\n if (ru.Value.Usage is { } usage)\n {\n ruUpdate.Contents.Add(new UsageContent(new()\n {\n InputTokenCount = usage.PromptTokens,\n OutputTokenCount = usage.CompletionTokens,\n TotalTokenCount = usage.TotalTokens,\n }));\n }\n\n if (ru is RequiredActionUpdate rau && rau.ToolCallId is string toolCallId && rau.FunctionName is string functionName)\n {\n ruUpdate.Contents.Add(\n new FunctionCallContent(\n JsonSerializer.Serialize([ru.Value.Id, toolCallId], AgentsChatClientJsonContext.Default.StringArray),\n functionName,\n JsonSerializer.Deserialize(rau.FunctionArguments, AgentsChatClientJsonContext.Default.IDictionaryStringObject)!));\n }\n\n yield return ruUpdate;\n break;\n\n case MessageContentUpdate mcu:\n yield return new(mcu.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant, mcu.Text)\n {\n ConversationId = threadId,\n MessageId = responseId,\n RawRepresentation = mcu,\n ResponseId = responseId,\n };\n break;\n\n default:\n yield return new ChatResponseUpdate\n {\n ConversationId = threadId,\n MessageId = responseId,\n RawRepresentation = update,\n ResponseId = responseId,\n Role = ChatRole.Assistant,\n };\n break;\n }\n }\n }\n\n /// <inheritdoc />\n public void Dispose() { }\n\n /// <summary>\n /// Creates the <see cref=\"ThreadAndRunOptions\"/> to use for the request and extracts any function result contents\n /// that need to be submitted as tool results.\n /// </summary>\n private async ValueTask<(ThreadAndRunOptions RunOptions, List<FunctionResultContent>? ToolResults)> CreateRunOptionsAsync(\n IEnumerable<ChatMessage> messages, ChatOptions? options, CancellationToken cancellationToken)\n {\n // Create the options instance to populate, either a fresh or using one the caller provides.\n ThreadAndRunOptions runOptions =\n options?.RawRepresentationFactory?.Invoke(this) as ThreadAndRunOptions ??\n new();\n\n // Populate the run options from the ChatOptions, if provided.\n if (options is not null)\n {\n runOptions.MaxCompletionTokens ??= options.MaxOutputTokens;\n runOptions.OverrideModelName ??= options.ModelId;\n runOptions.TopP ??= options.TopP;\n runOptions.Temperature ??= options.Temperature;\n runOptions.ParallelToolCalls ??= options.AllowMultipleToolCalls;\n // Ignored: options.TopK, options.FrequencyPenalty, options.Seed, options.StopSequences\n\n if (options.Tools is { Count: > 0 } tools)\n {\n List<ToolDefinition> toolDefinitions = [];\n\n // If the caller has provided any tool overrides, we'll assume they don't want to use the agent's tools.\n // But if they haven't, the only way we can provide our tools is via an override, whereas we'd really like to\n // just add them. To handle that, we'll get all of the agent's tools and add them to the override list\n // along with our tools.\n if (runOptions.OverrideTools is null || !runOptions.OverrideTools.Any())\n {\n if (_agentTools is null)\n {\n PersistentAgent agent = await _client!.Administration.GetAgentAsync(_agentId, cancellationToken).ConfigureAwait(false);\n _agentTools = agent.Tools;\n }\n\n toolDefinitions.AddRange(_agentTools);\n }",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2167978316",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 50898,
|
||
"pr_file": "sdk/ai/Azure.AI.Agents.Persistent/src/Custom/PersistentAgentsChatClient.cs",
|
||
"discussion_id": "2167978316",
|
||
"commented_code": "@@ -0,0 +1,451 @@\n+// Copyright (c) Microsoft Corporation. All rights reserved.\n+// Licensed under the MIT License.\n+\n+#nullable enable\n+#pragma warning disable AZC0004, AZC0007, AZC0015\n+\n+using System;\n+using System.Collections.Generic;\n+using System.Linq;\n+using System.Runtime.CompilerServices;\n+using System.Text;\n+using System.Text.Json;\n+using System.Text.Json.Nodes;\n+using System.Text.Json.Serialization;\n+using System.Threading;\n+using System.Threading.Tasks;\n+using Microsoft.Extensions.AI;\n+\n+namespace Azure.AI.Agents.Persistent\n+{\n+ /// <summary>Represents an <see cref=\"IChatClient\"/> for an Azure.AI.Agents.Persistent <see cref=\"PersistentAgentsClient\"/>.</summary>\n+ public partial class PersistentAgentsChatClient : IChatClient\n+ {\n+ /// <summary>The name of the chat client provider.</summary>\n+ private const string ProviderName = \"azure\";\n+\n+ /// <summary>The underlying <see cref=\"PersistentAgentsClient\" />.</summary>\n+ private readonly PersistentAgentsClient? _client;\n+\n+ /// <summary>Metadata for the client.</summary>\n+ private readonly ChatClientMetadata? _metadata;\n+\n+ /// <summary>The ID of the agent to use.</summary>\n+ private readonly string? _agentId;\n+\n+ /// <summary>The thread ID to use if none is supplied in <see cref=\"ChatOptions.ConversationId\"/>.</summary>\n+ private readonly string? _defaultThreadId;\n+\n+ /// <summary>List of tools associated with the agent.</summary>\n+ private IReadOnlyList<ToolDefinition>? _agentTools;\n+\n+ /// <summary>Initializes a new instance of the <see cref=\"PersistentAgentsChatClient\"/> class for the specified <see cref=\"PersistentAgentsClient\"/>.</summary>\n+ public PersistentAgentsChatClient(PersistentAgentsClient client, string agentId, string? defaultThreadId = null)\n+ {\n+ Argument.AssertNotNull(client, nameof(client));\n+ Argument.AssertNotNullOrWhiteSpace(agentId, nameof(agentId));\n+\n+ _client = client;\n+ _agentId = agentId;\n+ _defaultThreadId = defaultThreadId;\n+\n+ _metadata = new(ProviderName);\n+ }\n+\n+ protected PersistentAgentsChatClient() { }\n+\n+ /// <inheritdoc />\n+ public virtual object? GetService(Type serviceType, object? serviceKey = null) =>\n+ serviceType is null ? throw new ArgumentNullException(nameof(serviceType)) :\n+ serviceKey is not null ? null :\n+ serviceType == typeof(ChatClientMetadata) ? _metadata :\n+ serviceType == typeof(PersistentAgentsClient) ? _client :\n+ serviceType.IsInstanceOfType(this) ? this :\n+ null;\n+\n+ /// <inheritdoc />\n+ public virtual Task<ChatResponse> GetResponseAsync(\n+ IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default) =>\n+ GetStreamingResponseAsync(messages, options, cancellationToken).ToChatResponseAsync(cancellationToken);\n+\n+ /// <inheritdoc />\n+ public virtual async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(\n+ IEnumerable<ChatMessage> messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)\n+ {\n+ Argument.AssertNotNull(messages, nameof(messages));\n+\n+ // Extract necessary state from messages and options.\n+ (ThreadAndRunOptions runOptions, List<FunctionResultContent>? toolResults) =\n+ await CreateRunOptionsAsync(messages, options, cancellationToken).ConfigureAwait(false);\n+\n+ // Get the thread ID.\n+ string? threadId = options?.ConversationId ?? _defaultThreadId;\n+ if (threadId is null && toolResults is not null)\n+ {\n+ throw new ArgumentException(\"No thread ID was provided, but chat messages includes tool results.\", nameof(messages));\n+ }\n+\n+ // Get any active run ID for this thread.\n+ ThreadRun? threadRun = null;\n+ if (threadId is not null)\n+ {\n+ await foreach (ThreadRun? run in _client!.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))\n+ {\n+ if (run.Status != RunStatus.Completed && run.Status != RunStatus.Cancelled && run.Status != RunStatus.Failed && run.Status != RunStatus.Expired)\n+ {\n+ threadRun = run;\n+ break;\n+ }\n+ }\n+ }\n+\n+ // Submit the request.\n+ IAsyncEnumerable<StreamingUpdate> updates;\n+ if (threadRun is not null &&\n+ ConvertFunctionResultsToToolOutput(toolResults, out List<ToolOutput>? toolOutputs) is { } toolRunId &&\n+ toolRunId == threadRun.Id)\n+ {\n+ // There's an active run and we have tool results to submit, so submit the results and continue streaming.\n+ // This is going to ignore any additional messages in the run options, as we are only submitting tool outputs,\n+ // but there doesn't appear to be a way to submit additional messages, and having such additional messages is rare.\n+ updates = _client!.Runs.SubmitToolOutputsToStreamAsync(threadRun, toolOutputs, cancellationToken);\n+ }\n+ else\n+ {\n+ if (threadId is null)\n+ {\n+ // No thread ID was provided, so create a new thread.\n+ PersistentAgentThread thread = await _client!.Threads.CreateThreadAsync(runOptions.ThreadOptions.Messages, runOptions.ToolResources, runOptions.Metadata, cancellationToken).ConfigureAwait(false);\n+ runOptions.ThreadOptions.Messages.Clear();\n+ threadId = thread.Id;\n+ }\n+ else if (threadRun is not null)\n+ {\n+ // There was an active run; we need to cancel it before starting a new run.\n+ await _client!.Runs.CancelRunAsync(threadId, threadRun.Id, cancellationToken).ConfigureAwait(false);\n+ threadRun = null;\n+ }\n+\n+ // Now create a new run and stream the results.\n+ updates = _client!.Runs.CreateRunStreamingAsync(\n+ threadId: threadId,\n+ agentId: _agentId,\n+ overrideModelName: runOptions?.OverrideModelName,\n+ overrideInstructions: runOptions?.OverrideInstructions,\n+ additionalInstructions: null,\n+ additionalMessages: runOptions?.ThreadOptions.Messages,\n+ overrideTools: runOptions?.OverrideTools,\n+ temperature: runOptions?.Temperature,\n+ topP: runOptions?.TopP,\n+ maxPromptTokens: runOptions?.MaxPromptTokens,\n+ maxCompletionTokens: runOptions?.MaxCompletionTokens,\n+ truncationStrategy: runOptions?.TruncationStrategy,\n+ toolChoice: runOptions?.ToolChoice,\n+ responseFormat: runOptions?.ResponseFormat,\n+ parallelToolCalls: runOptions?.ParallelToolCalls,\n+ metadata: runOptions?.Metadata,\n+ cancellationToken);\n+ }\n+\n+ // Process each update.\n+ string? responseId = null;\n+ await foreach (StreamingUpdate? update in updates.ConfigureAwait(false))\n+ {\n+ switch (update)\n+ {\n+ case ThreadUpdate tu:\n+ threadId ??= tu.Value.Id;\n+ goto default;\n+\n+ case RunUpdate ru:\n+ threadId ??= ru.Value.ThreadId;\n+ responseId ??= ru.Value.Id;\n+\n+ ChatResponseUpdate ruUpdate = new()\n+ {\n+ AuthorName = ru.Value.AssistantId,\n+ ConversationId = threadId,\n+ CreatedAt = ru.Value.CreatedAt,\n+ MessageId = responseId,\n+ ModelId = ru.Value.Model,\n+ RawRepresentation = ru,\n+ ResponseId = responseId,\n+ Role = ChatRole.Assistant,\n+ };\n+\n+ if (ru.Value.Usage is { } usage)\n+ {\n+ ruUpdate.Contents.Add(new UsageContent(new()\n+ {\n+ InputTokenCount = usage.PromptTokens,\n+ OutputTokenCount = usage.CompletionTokens,\n+ TotalTokenCount = usage.TotalTokens,\n+ }));\n+ }\n+\n+ if (ru is RequiredActionUpdate rau && rau.ToolCallId is string toolCallId && rau.FunctionName is string functionName)\n+ {\n+ ruUpdate.Contents.Add(\n+ new FunctionCallContent(\n+ JsonSerializer.Serialize([ru.Value.Id, toolCallId], AgentsChatClientJsonContext.Default.StringArray),\n+ functionName,\n+ JsonSerializer.Deserialize(rau.FunctionArguments, AgentsChatClientJsonContext.Default.IDictionaryStringObject)!));\n+ }\n+\n+ yield return ruUpdate;\n+ break;\n+\n+ case MessageContentUpdate mcu:\n+ yield return new(mcu.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant, mcu.Text)\n+ {\n+ ConversationId = threadId,\n+ MessageId = responseId,\n+ RawRepresentation = mcu,\n+ ResponseId = responseId,\n+ };\n+ break;\n+\n+ default:\n+ yield return new ChatResponseUpdate\n+ {\n+ ConversationId = threadId,\n+ MessageId = responseId,\n+ RawRepresentation = update,\n+ ResponseId = responseId,\n+ Role = ChatRole.Assistant,\n+ };\n+ break;\n+ }\n+ }\n+ }\n+\n+ /// <inheritdoc />\n+ public void Dispose() { }\n+\n+ /// <summary>\n+ /// Creates the <see cref=\"ThreadAndRunOptions\"/> to use for the request and extracts any function result contents\n+ /// that need to be submitted as tool results.\n+ /// </summary>\n+ private async ValueTask<(ThreadAndRunOptions RunOptions, List<FunctionResultContent>? ToolResults)> CreateRunOptionsAsync(\n+ IEnumerable<ChatMessage> messages, ChatOptions? options, CancellationToken cancellationToken)\n+ {\n+ // Create the options instance to populate, either a fresh or using one the caller provides.\n+ ThreadAndRunOptions runOptions =\n+ options?.RawRepresentationFactory?.Invoke(this) as ThreadAndRunOptions ??\n+ new();\n+\n+ // Populate the run options from the ChatOptions, if provided.\n+ if (options is not null)\n+ {\n+ runOptions.MaxCompletionTokens ??= options.MaxOutputTokens;\n+ runOptions.OverrideModelName ??= options.ModelId;\n+ runOptions.TopP ??= options.TopP;\n+ runOptions.Temperature ??= options.Temperature;\n+ runOptions.ParallelToolCalls ??= options.AllowMultipleToolCalls;\n+ // Ignored: options.TopK, options.FrequencyPenalty, options.Seed, options.StopSequences\n+\n+ if (options.Tools is { Count: > 0 } tools)\n+ {\n+ List<ToolDefinition> toolDefinitions = [];\n+\n+ // If the caller has provided any tool overrides, we'll assume they don't want to use the agent's tools.\n+ // But if they haven't, the only way we can provide our tools is via an override, whereas we'd really like to\n+ // just add them. To handle that, we'll get all of the agent's tools and add them to the override list\n+ // along with our tools.\n+ if (runOptions.OverrideTools is null || !runOptions.OverrideTools.Any())\n+ {\n+ if (_agentTools is null)\n+ {\n+ PersistentAgent agent = await _client!.Administration.GetAgentAsync(_agentId, cancellationToken).ConfigureAwait(false);\n+ _agentTools = agent.Tools;\n+ }\n+\n+ toolDefinitions.AddRange(_agentTools);\n+ }",
|
||
"comment_created_at": "2025-06-26T02:34:06+00:00",
|
||
"comment_author": "dmytrostruk",
|
||
"comment_body": "This logic is required, because currently there is no way (at least I didn't find it) how to add \"additional\" tools on top of already existing ones rather than override tools per request. If there will be a possibility to add \"additional\" tools, then this logic can be removed. \r\n\r\nWith this logic, `_agentTools` are stored on the class level to avoid pulling agent tools every time when user sends a request. So, there is a small possibility that agent tools can be updated on the server side, but previous tool collection will be cached in the class instance. So, it depends on how often agent tools change and if it happens rarely, this logic should be a good trade-off between performance and consistency.",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"discussion_id": "2180633548",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"created_at": "2025-07-02T17:42:50+00:00",
|
||
"commented_code": "return Task.CompletedTask;\n }\n }\n\n private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2180633548",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"discussion_id": "2180633548",
|
||
"commented_code": "@@ -346,5 +360,35 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation)\n return Task.CompletedTask;\n }\n }\n+\n+ private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"comment_created_at": "2025-07-02T17:42:50+00:00",
|
||
"comment_author": "KrzysztofCwalina",
|
||
"comment_body": "Why do we need this type? What's the advantage over using ModelReaderWriter.Write (inside the Cereate method above) to create BinaryData and then returning the existing BinaryContent.Create(binaryData)?",
|
||
"pr_file_module": null
|
||
},
|
||
{
|
||
"comment_id": "2180644575",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"discussion_id": "2180633548",
|
||
"commented_code": "@@ -346,5 +360,35 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation)\n return Task.CompletedTask;\n }\n }\n+\n+ private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"comment_created_at": "2025-07-02T17:49:51+00:00",
|
||
"comment_author": "JoshLove-msft",
|
||
"comment_body": "Creating a BinaryData will result in allocations. Using BinaryContent.Create(IPersistableModel) will use shared buffer to avoid allocations - https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/src/Message/BinaryContent.cs#L224",
|
||
"pr_file_module": null
|
||
},
|
||
{
|
||
"comment_id": "2180645941",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"discussion_id": "2180633548",
|
||
"commented_code": "@@ -346,5 +360,35 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation)\n return Task.CompletedTask;\n }\n }\n+\n+ private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"comment_created_at": "2025-07-02T17:50:43+00:00",
|
||
"comment_author": "JoshLove-msft",
|
||
"comment_body": "https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/src/ModelReaderWriter/UnsafeBufferSequence.cs#L14",
|
||
"pr_file_module": null
|
||
},
|
||
{
|
||
"comment_id": "2180656922",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"discussion_id": "2180633548",
|
||
"commented_code": "@@ -346,5 +360,35 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation)\n return Task.CompletedTask;\n }\n }\n+\n+ private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"comment_created_at": "2025-07-02T17:57:30+00:00",
|
||
"comment_author": "Copilot",
|
||
"comment_body": "The PersistableModelRequestContent<T> type wraps BinaryContent.Create<T>() specifically to leverage its streaming optimizations.\n\n**Alternative approach (ModelReaderWriter.Write + BinaryData):**\n- ModelReaderWriter.Write() fully serializes the model to memory first (BinaryData)\n- Then RequestContent.Create(binaryData) streams from that memory\n\n**Current approach (BinaryContent wrapper):**\n- BinaryContent.Create<T>() for IJsonModel types uses ModelWriter with UnsafeBufferSequence \n- Streams directly via SequenceReader.CopyTo() without full serialization to memory first\n- Only falls back to memory serialization for non-IJsonModel types\n\nThe wrapper type is necessary because BinaryContent and RequestContent are separate hierarchies, but BinaryContent has the optimized streaming implementation we want to reuse.",
|
||
"pr_file_module": null
|
||
},
|
||
{
|
||
"comment_id": "2180695432",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 51021,
|
||
"pr_file": "sdk/core/Azure.Core/src/RequestContent.cs",
|
||
"discussion_id": "2180633548",
|
||
"commented_code": "@@ -346,5 +360,35 @@ public override Task WriteToAsync(Stream stream, CancellationToken cancellation)\n return Task.CompletedTask;\n }\n }\n+\n+ private sealed class PersistableModelRequestContent<T> : RequestContent where T : IPersistableModel<T>",
|
||
"comment_created_at": "2025-07-02T18:16:48+00:00",
|
||
"comment_author": "m-nash",
|
||
"comment_body": "@KrzysztofCwalina in the json format case (or where wire = json) we will use the UnsafeBufferSequence internally which is about 30% faster for speed and allocates about 80% less on average due to the use of shared buffers. For the non json cases like xml there is no difference between what you suggest and what is implemented.",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"discussion_id": "2093706419",
|
||
"pr_number": 50129,
|
||
"pr_file": "sdk/resourcemanager/Azure.ResourceManager/src/ArmClientOptions.cs",
|
||
"created_at": "2025-05-16T21:43:20+00:00",
|
||
"commented_code": "var assembly = Assembly.GetExecutingAssembly();\n using (Stream stream = assembly.GetManifestResourceStream(profile.GetManifestName()))\n {\n var allProfile = BinaryData.FromStream(stream).ToObjectFromJson<Dictionary<string, Dictionary<string, object>>>();\n var span = BinaryData.FromStream(stream).ToMemory().Span;\n var allProfile = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, JsonElement>>>(span, ArmClientOptionsJsonContext.Default.DictionaryStringDictionaryStringJsonElement);",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2093706419",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 50129,
|
||
"pr_file": "sdk/resourcemanager/Azure.ResourceManager/src/ArmClientOptions.cs",
|
||
"discussion_id": "2093706419",
|
||
"commented_code": "@@ -47,12 +48,13 @@ public void SetApiVersionsFromProfile(AzureStackProfile profile)\n var assembly = Assembly.GetExecutingAssembly();\n using (Stream stream = assembly.GetManifestResourceStream(profile.GetManifestName()))\n {\n- var allProfile = BinaryData.FromStream(stream).ToObjectFromJson<Dictionary<string, Dictionary<string, object>>>();\n+ var span = BinaryData.FromStream(stream).ToMemory().Span;\n+ var allProfile = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, JsonElement>>>(span, ArmClientOptionsJsonContext.Default.DictionaryStringDictionaryStringJsonElement);",
|
||
"comment_created_at": "2025-05-16T21:43:20+00:00",
|
||
"comment_author": "Copilot",
|
||
"comment_body": "Consider evaluating the memory impact of loading the entire stream into a Span, especially if the configuration file could be large. If applicable, explore streaming deserialization to mitigate potential memory overhead.\n```suggestion\n var allProfile = JsonSerializer.DeserializeAsync<Dictionary<string, Dictionary<string, JsonElement>>>(stream, ArmClientOptionsJsonContext.Default.DictionaryStringDictionaryStringJsonElement).Result;\n```",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"discussion_id": "2170421287",
|
||
"pr_number": 50926,
|
||
"pr_file": "sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyAttributes.cs",
|
||
"created_at": "2025-06-27T01:11:30+00:00",
|
||
"commented_code": "if (Attestation != null)\n {\n json.WriteStartObject(s_keyAttestationPropertyNameBytes);\n if (Attestation.CertificatePemFile != null)\n if (!Attestation.CertificatePemFile.IsEmpty)\n {\n json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile));\n json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile.ToArray()));\n }\n if (Attestation.PrivateKeyAttestation != null)\n if (!Attestation.PrivateKeyAttestation.IsEmpty)\n {\n json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation));\n json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation.ToArray()));\n }\n if (Attestation.PublicKeyAttestation != null)\n if (!Attestation.PublicKeyAttestation.IsEmpty)\n {\n json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation));\n json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation.ToArray()));",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2170421287",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 50926,
|
||
"pr_file": "sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyAttributes.cs",
|
||
"discussion_id": "2170421287",
|
||
"commented_code": "@@ -143,17 +143,17 @@ internal void WriteProperties(Utf8JsonWriter json)\n if (Attestation != null)\n {\n json.WriteStartObject(s_keyAttestationPropertyNameBytes);\n- if (Attestation.CertificatePemFile != null)\n+ if (!Attestation.CertificatePemFile.IsEmpty)\n {\n- json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile));\n+ json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile.ToArray()));\n }\n- if (Attestation.PrivateKeyAttestation != null)\n+ if (!Attestation.PrivateKeyAttestation.IsEmpty)\n {\n- json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation));\n+ json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation.ToArray()));\n }\n- if (Attestation.PublicKeyAttestation != null)\n+ if (!Attestation.PublicKeyAttestation.IsEmpty)\n {\n- json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation));\n+ json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation.ToArray()));",
|
||
"comment_created_at": "2025-06-27T01:11:30+00:00",
|
||
"comment_author": "Copilot",
|
||
"comment_body": "Avoid allocating a new array with ToArray(): use the ReadOnlyMemory's Span overload, e.g. `Convert.ToBase64String(Attestation.CertificatePemFile.Span)` to improve performance.",
|
||
"pr_file_module": null
|
||
},
|
||
{
|
||
"comment_id": "2170432767",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 50926,
|
||
"pr_file": "sdk/keyvault/Azure.Security.KeyVault.Keys/src/KeyAttributes.cs",
|
||
"discussion_id": "2170421287",
|
||
"commented_code": "@@ -143,17 +143,17 @@ internal void WriteProperties(Utf8JsonWriter json)\n if (Attestation != null)\n {\n json.WriteStartObject(s_keyAttestationPropertyNameBytes);\n- if (Attestation.CertificatePemFile != null)\n+ if (!Attestation.CertificatePemFile.IsEmpty)\n {\n- json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile));\n+ json.WriteString(\"certificatePemFile\", Convert.ToBase64String(Attestation.CertificatePemFile.ToArray()));\n }\n- if (Attestation.PrivateKeyAttestation != null)\n+ if (!Attestation.PrivateKeyAttestation.IsEmpty)\n {\n- json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation));\n+ json.WriteString(\"privateKeyAttestation\", Convert.ToBase64String(Attestation.PrivateKeyAttestation.ToArray()));\n }\n- if (Attestation.PublicKeyAttestation != null)\n+ if (!Attestation.PublicKeyAttestation.IsEmpty)\n {\n- json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation));\n+ json.WriteString(\"publicKeyAttestation\", Convert.ToBase64String(Attestation.PublicKeyAttestation.ToArray()));",
|
||
"comment_created_at": "2025-06-27T01:19:16+00:00",
|
||
"comment_author": "JonathanCrd",
|
||
"comment_body": "No, `Convert.ToBase64String` is expecting an array.",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"discussion_id": "2114870690",
|
||
"pr_number": 50279,
|
||
"pr_file": "sdk/core/Azure.Core/src/ResponseError.Serialization.cs",
|
||
"created_at": "2025-05-29T23:05:01+00:00",
|
||
"commented_code": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System;\nusing System.ClientModel.Primitives;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing Azure.Core;\n\nnamespace Azure\n{\n /// <summary>\n /// Represents an error returned by an Azure Service.\n /// </summary>\n [JsonConverter(typeof(Converter))]\n [TypeReferenceType(true, [nameof(Target), nameof(Details)])]\n public sealed partial class ResponseError : IJsonModel<ResponseError>\n {\n // This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator\n internal class Converter : JsonConverter<ResponseError?>\n {\n public override ResponseError? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n {\n using var document = JsonDocument.ParseValue(ref reader);\n var element = document.RootElement;\n return ReadFromJson(element);\n }\n\n public override void Write(Utf8JsonWriter writer, ResponseError? value, JsonSerializerOptions options)\n {\n throw new NotImplementedException();\n }\n }\n\n /// <summary>\n /// Writes the <see cref=\"ResponseError\"/> to the provided <see cref=\"Utf8JsonWriter\"/>.\n /// </summary>\n public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)\n {\n writer.WriteStartObject();\n\n if (Code != null)\n {\n writer.WritePropertyName(\"code\");\n writer.WriteStringValue(Code);\n }\n\n if (Message != null)\n {\n writer.WritePropertyName(\"message\");\n writer.WriteStringValue(Message);\n }\n\n if (Target != null)\n {\n writer.WritePropertyName(\"target\");\n writer.WriteStringValue(Target);\n }\n\n if (InnerError != null)\n {\n writer.WritePropertyName(\"innererror\");\n InnerError.Write(writer, ModelReaderWriterOptions.Json);\n }\n\n if (Details.Count > 0)\n {\n writer.WritePropertyName(\"details\");\n writer.WriteStartArray();\n\n foreach (var detail in Details)\n {\n if (detail == null)\n {\n writer.WriteNullValue();\n }\n else\n {\n detail.Write(writer, options);\n }\n }\n\n writer.WriteEndArray();\n }\n\n writer.WriteEndObject();\n }\n\n /// <inheritdoc />\n public ResponseError Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)\n {\n var format = options.Format == \"W\" ? GetFormatFromOptions(options) : options.Format;\n if (format != \"J\")\n {\n throw new FormatException($\"The model {nameof(ResponseError)} does not support '{format}' format.\");\n }\n\n using var document = JsonDocument.ParseValue(ref reader);\n var element = document.RootElement;\n return ReadFromJson(element) ?? new ResponseError();\n }\n\n /// <summary>\n /// Writes the <see cref=\"ResponseError\"/> to the provided <see cref=\"Utf8JsonWriter\"/>.\n /// </summary>\n public BinaryData Write(ModelReaderWriterOptions options)\n {\n var format = options.Format == \"W\" ? GetFormatFromOptions(options) : options.Format;\n if (format != \"J\")\n {\n throw new FormatException($\"The model {nameof(ResponseError)} does not support '{format}' format.\");\n }\n\n using var stream = new System.IO.MemoryStream();",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2114870690",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 50279,
|
||
"pr_file": "sdk/core/Azure.Core/src/ResponseError.Serialization.cs",
|
||
"discussion_id": "2114870690",
|
||
"commented_code": "@@ -0,0 +1,192 @@\n+// Copyright (c) Microsoft Corporation. All rights reserved.\n+// Licensed under the MIT License.\n+\n+using System;\n+using System.ClientModel.Primitives;\n+using System.Collections.Generic;\n+using System.Globalization;\n+using System.Text;\n+using System.Text.Json;\n+using System.Text.Json.Serialization;\n+using Azure.Core;\n+\n+namespace Azure\n+{\n+ /// <summary>\n+ /// Represents an error returned by an Azure Service.\n+ /// </summary>\n+ [JsonConverter(typeof(Converter))]\n+ [TypeReferenceType(true, [nameof(Target), nameof(Details)])]\n+ public sealed partial class ResponseError : IJsonModel<ResponseError>\n+ {\n+ // This class needs to be internal rather than private so that it can be used by the System.Text.Json source generator\n+ internal class Converter : JsonConverter<ResponseError?>\n+ {\n+ public override ResponseError? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n+ {\n+ using var document = JsonDocument.ParseValue(ref reader);\n+ var element = document.RootElement;\n+ return ReadFromJson(element);\n+ }\n+\n+ public override void Write(Utf8JsonWriter writer, ResponseError? value, JsonSerializerOptions options)\n+ {\n+ throw new NotImplementedException();\n+ }\n+ }\n+\n+ /// <summary>\n+ /// Writes the <see cref=\"ResponseError\"/> to the provided <see cref=\"Utf8JsonWriter\"/>.\n+ /// </summary>\n+ public void Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)\n+ {\n+ writer.WriteStartObject();\n+\n+ if (Code != null)\n+ {\n+ writer.WritePropertyName(\"code\");\n+ writer.WriteStringValue(Code);\n+ }\n+\n+ if (Message != null)\n+ {\n+ writer.WritePropertyName(\"message\");\n+ writer.WriteStringValue(Message);\n+ }\n+\n+ if (Target != null)\n+ {\n+ writer.WritePropertyName(\"target\");\n+ writer.WriteStringValue(Target);\n+ }\n+\n+ if (InnerError != null)\n+ {\n+ writer.WritePropertyName(\"innererror\");\n+ InnerError.Write(writer, ModelReaderWriterOptions.Json);\n+ }\n+\n+ if (Details.Count > 0)\n+ {\n+ writer.WritePropertyName(\"details\");\n+ writer.WriteStartArray();\n+\n+ foreach (var detail in Details)\n+ {\n+ if (detail == null)\n+ {\n+ writer.WriteNullValue();\n+ }\n+ else\n+ {\n+ detail.Write(writer, options);\n+ }\n+ }\n+\n+ writer.WriteEndArray();\n+ }\n+\n+ writer.WriteEndObject();\n+ }\n+\n+ /// <inheritdoc />\n+ public ResponseError Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)\n+ {\n+ var format = options.Format == \"W\" ? GetFormatFromOptions(options) : options.Format;\n+ if (format != \"J\")\n+ {\n+ throw new FormatException($\"The model {nameof(ResponseError)} does not support '{format}' format.\");\n+ }\n+\n+ using var document = JsonDocument.ParseValue(ref reader);\n+ var element = document.RootElement;\n+ return ReadFromJson(element) ?? new ResponseError();\n+ }\n+\n+ /// <summary>\n+ /// Writes the <see cref=\"ResponseError\"/> to the provided <see cref=\"Utf8JsonWriter\"/>.\n+ /// </summary>\n+ public BinaryData Write(ModelReaderWriterOptions options)\n+ {\n+ var format = options.Format == \"W\" ? GetFormatFromOptions(options) : options.Format;\n+ if (format != \"J\")\n+ {\n+ throw new FormatException($\"The model {nameof(ResponseError)} does not support '{format}' format.\");\n+ }\n+\n+ using var stream = new System.IO.MemoryStream();",
|
||
"comment_created_at": "2025-05-29T23:05:01+00:00",
|
||
"comment_author": "m-nash",
|
||
"comment_body": "Should call into ModelReaderWriter here it uses a faster buffer than memory stream. You can use RehydrationToken as a guide https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/src/RehydrationToken.Serialization.cs#L136-L147",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"discussion_id": "2037553900",
|
||
"pr_number": 49324,
|
||
"pr_file": "sdk/cloudmachine/Azure.Projects.AI/src/Agents/ChatRunner.cs",
|
||
"created_at": "2025-04-10T14:24:39+00:00",
|
||
"commented_code": "}\n else\n {\n ChatCompletion completion = _chat.CompleteChat(conversation);\n ChatCompletionOptions options = new();\n options.Temperature = 0.0f;",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"discussion_comments": [
|
||
{
|
||
"comment_id": "2037553900",
|
||
"repo_full_name": "Azure/azure-sdk-for-net",
|
||
"pr_number": 49324,
|
||
"pr_file": "sdk/cloudmachine/Azure.Projects.AI/src/Agents/ChatRunner.cs",
|
||
"discussion_id": "2037553900",
|
||
"commented_code": "@@ -113,7 +113,9 @@ protected virtual ChatCompletion OnComplete(List<ChatMessage> conversation, stri\n }\n else\n {\n- ChatCompletion completion = _chat.CompleteChat(conversation);\n+ ChatCompletionOptions options = new();\n+ options.Temperature = 0.0f;",
|
||
"comment_created_at": "2025-04-10T14:24:39+00:00",
|
||
"comment_author": "christothes",
|
||
"comment_body": "Can we initialize the options once and reuse it here each time?",
|
||
"pr_file_module": null
|
||
}
|
||
]
|
||
}
|
||
] |