diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 98c7376aaf..d0227c1dbe 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -33,18 +33,18 @@ - + - + - - + + @@ -61,9 +61,9 @@ - - - + + + @@ -71,11 +71,11 @@ - + - + diff --git a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Program.cs b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Program.cs index aa18fdd286..426b40f1f5 100644 --- a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Program.cs @@ -5,7 +5,6 @@ using Microsoft.Agents.AI; using Microsoft.Extensions.AI; using OpenAI; -using OpenAI.Responses; var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new InvalidOperationException("OPENAI_API_KEY is not set."); var model = Environment.GetEnvironmentVariable("OPENAI_MODEL") ?? "gpt-5"; @@ -15,14 +14,10 @@ .AsIChatClient().AsBuilder() .ConfigureOptions(o => { - o.RawRepresentationFactory = _ => new CreateResponseOptions() + o.Reasoning = new() { - ReasoningOptions = new() - { - ReasoningEffortLevel = ResponseReasoningEffortLevel.Medium, - // Verbosity requires OpenAI verified Organization - ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed - } + Effort = ReasoningEffort.Medium, + Output = ReasoningOutput.Full, }; }).Build(); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs index 8f38378626..a266aed278 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs @@ -21,7 +21,7 @@ ChatMessage message = new(ChatRole.User, [ new TextContent("What do you see in this image?"), - new DataContent(File.ReadAllBytes("assets/walkway.jpg"), "image/jpeg") + await DataContent.LoadFromAsync("assets/walkway.jpg"), ]); AgentSession session = await agent.CreateSessionAsync(); diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs index 18a5ba3b72..8433ff3b6f 100644 --- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs @@ -283,8 +283,13 @@ public static async Task CreateAIAgentAsync( TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) } }; - // Attempt to capture breaking glass options from the raw representation factory that match the agent definition. - if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions) + // Map reasoning options from the abstraction-level ChatOptions.Reasoning, + // falling back to extracting from the raw representation factory for breaking glass scenarios. + if (options.ChatOptions?.Reasoning is { } reasoning) + { + agentDefinition.ReasoningOptions = ToResponseReasoningOptions(reasoning); + } + else if (options.ChatOptions?.RawRepresentationFactory?.Invoke(new NoOpChatClient()) is CreateResponseOptions respCreationOptions) { agentDefinition.ReasoningOptions = respCreationOptions.ReasoningOptions; } @@ -770,6 +775,36 @@ private static string ThrowIfInvalidAgentName(string? name) } return name; } + + private static ResponseReasoningOptions? ToResponseReasoningOptions(ReasoningOptions reasoning) + { + ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch + { + ReasoningEffort.Low => ResponseReasoningEffortLevel.Low, + ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium, + ReasoningEffort.High => ResponseReasoningEffortLevel.High, + ReasoningEffort.ExtraHigh => ResponseReasoningEffortLevel.High, + _ => null, + }; + + ResponseReasoningSummaryVerbosity? summary = reasoning.Output switch + { + ReasoningOutput.Summary => ResponseReasoningSummaryVerbosity.Concise, + ReasoningOutput.Full => ResponseReasoningSummaryVerbosity.Detailed, + _ => null, + }; + + if (effortLevel is null && summary is null) + { + return null; + } + + return new ResponseReasoningOptions + { + ReasoningEffortLevel = effortLevel, + ReasoningSummaryVerbosity = summary, + }; + } } [JsonSerializable(typeof(JsonElement))] diff --git a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs index 06c7f24ee2..1812350120 100644 --- a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs @@ -217,16 +217,15 @@ protected override async IAsyncEnumerable RunCoreStreamingA } }); - List tempFiles = []; + string? tempDir = null; try { // Build prompt from text content string prompt = string.Join("\n", messages.Select(m => m.Text)); // Handle DataContent as attachments - List? attachments = await ProcessDataContentAttachmentsAsync( + (List? attachments, tempDir) = await ProcessDataContentAttachmentsAsync( messages, - tempFiles, cancellationToken).ConfigureAwait(false); // Send the message with attachments @@ -245,7 +244,7 @@ protected override async IAsyncEnumerable RunCoreStreamingA } finally { - CleanupTempFiles(tempFiles); + CleanupTempDir(tempDir); } } finally @@ -410,45 +409,23 @@ private AgentResponseUpdate ConvertToAgentResponseUpdate(SessionEvent sessionEve return new SessionConfig { Tools = mappedTools, SystemMessage = systemMessage }; } - private static readonly Dictionary s_mediaTypeExtensions = new(StringComparer.OrdinalIgnoreCase) - { - ["image/png"] = ".png", - ["image/jpeg"] = ".jpg", - ["image/jpg"] = ".jpg", - ["image/gif"] = ".gif", - ["image/webp"] = ".webp", - ["image/svg+xml"] = ".svg", - ["text/plain"] = ".txt", - ["text/html"] = ".html", - ["text/markdown"] = ".md", - ["application/json"] = ".json", - ["application/xml"] = ".xml", - ["application/pdf"] = ".pdf" - }; - - private static string GetExtensionForMediaType(string? mediaType) - { - return mediaType is not null && s_mediaTypeExtensions.TryGetValue(mediaType, out string? extension) ? extension : ".dat"; - } - - private static async Task?> ProcessDataContentAttachmentsAsync( + private static async Task<(List? Attachments, string? TempDir)> ProcessDataContentAttachmentsAsync( IEnumerable messages, - List tempFiles, CancellationToken cancellationToken) { List? attachments = null; + string? tempDir = null; foreach (ChatMessage message in messages) { foreach (AIContent content in message.Contents) { if (content is DataContent dataContent) { - // Write DataContent to a temp file - string tempFilePath = Path.Combine(Path.GetTempPath(), $"agentframework_copilot_data_{Guid.NewGuid()}{GetExtensionForMediaType(dataContent.MediaType)}"); - await File.WriteAllBytesAsync(tempFilePath, dataContent.Data.ToArray(), cancellationToken).ConfigureAwait(false); - tempFiles.Add(tempFilePath); + tempDir ??= Directory.CreateDirectory( + Path.Combine(Path.GetTempPath(), $"af_copilot_{Guid.NewGuid():N}")).FullName; + + string tempFilePath = await dataContent.SaveToAsync(tempDir, cancellationToken).ConfigureAwait(false); - // Create attachment attachments ??= []; attachments.Add(new UserMessageDataAttachmentsItem { @@ -460,19 +437,16 @@ private static string GetExtensionForMediaType(string? mediaType) } } - return attachments; + return (attachments, tempDir); } - private static void CleanupTempFiles(List tempFiles) + private static void CleanupTempDir(string? tempDir) { - foreach (string tempFile in tempFiles) + if (tempDir is not null) { try { - if (File.Exists(tempFile)) - { - File.Delete(tempFile); - } + Directory.Delete(tempDir, recursive: true); } catch { diff --git a/dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs b/dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs index 380ea5eaeb..7649635aa7 100644 --- a/dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs +++ b/dotnet/src/Shared/Workflows/Execution/WorkflowRunner.cs @@ -304,7 +304,7 @@ private async IAsyncEnumerable ProcessInputMessageAsync(ChatMessage ChatMessage? responseMessage = requestItem switch { - FunctionCallContent functionCall => await InvokeFunctionAsync(functionCall).ConfigureAwait(false), + FunctionCallContent functionCall when !functionCall.InformationalOnly => await InvokeFunctionAsync(functionCall).ConfigureAwait(false), FunctionApprovalRequestContent functionApprovalRequest => ApproveFunction(functionApprovalRequest), McpServerToolApprovalRequestContent mcpApprovalRequest => ApproveMCP(mcpApprovalRequest), _ => HandleUnknown(requestItem), diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs index 695f8a4825..d54a0644a4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/FunctionInvocationDelegatingAgentTests.cs @@ -524,8 +524,15 @@ public async Task RunAsync_MiddlewareThrowsPreInvocation_ExceptionSurfacesAsync( { // Arrange var testFunction = AIFunctionFactory.Create(() => "Function result", "TestFunction", "A test function"); - var functionCall = new FunctionCallContent("call_123", "TestFunction", new Dictionary()); - var mockChatClient = CreateMockChatClientWithFunctionCalls(functionCall); + var mockChatClient = new Mock(); + + mockChatClient.Setup(c => c.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(() => new ChatResponse([ + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("call_123", "TestFunction", new Dictionary())]) + ])); var innerAgent = new ChatClientAgent(mockChatClient.Object); var messages = new List { new(ChatRole.User, "Test message") };