From 6a88c7b1a130ea649c6a87c2b184a64214c01d36 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:56:42 +0000 Subject: [PATCH 1/3] .NET: [BREAKING] Change SerializeSession to be Async (#3879) * Change SerializeSession to be Async * Update Changelog --- .../Agent_With_CustomImplementation/Program.cs | 4 ++-- .../Program.cs | 2 +- .../AgentWithMemory_Step03_CustomMemory/Program.cs | 2 +- .../Agent_Step06_PersistedConversations/Program.cs | 2 +- .../Program.cs | 2 +- .../Program.cs | 6 +++--- .../Agent_Step20_AdditionalAIContext/Program.cs | 2 +- .../Program.cs | 2 +- dotnet/samples/M365Agent/AFAgentApplication.cs | 2 +- dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs | 4 ++-- .../src/Microsoft.Agents.AI.Abstractions/AIAgent.cs | 12 +++++++----- .../Microsoft.Agents.AI.Abstractions/AgentSession.cs | 2 +- .../DelegatingAIAgent.cs | 4 ++-- .../CopilotStudioAgent.cs | 4 ++-- .../src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md | 1 + .../DurableAIAgent.cs | 5 +++-- .../DurableAIAgentProxy.cs | 4 ++-- .../GitHubCopilotAgent.cs | 4 ++-- .../Local/InMemoryAgentSessionStore.cs | 5 ++--- .../src/Microsoft.Agents.AI.Purview/PurviewAgent.cs | 4 ++-- .../Specialized/AIAgentHostExecutor.cs | 2 +- .../WorkflowHostAgent.cs | 4 ++-- .../ChatClient/ChatClientAgent.cs | 4 ++-- .../AGUIChatClientTests.cs | 2 +- .../AIAgentTests.cs | 4 ++-- .../AgentRunContextTests.cs | 2 +- .../AggregatorPromptAgentFactoryTests.cs | 2 +- .../BasicStreamingTests.cs | 6 +++--- .../ForwardedPropertiesTests.cs | 4 ++-- .../SharedStateTests.cs | 4 ++-- .../AGUIEndpointRouteBuilderExtensionsTests.cs | 8 ++++---- .../TestAgent.cs | 2 +- .../AgentExtensionsTests.cs | 2 +- .../Microsoft.Agents.AI.UnitTests/TestAIAgent.cs | 2 +- .../AgentWorkflowBuilderTests.cs | 2 +- .../InProcessExecutionTests.cs | 2 +- .../RepresentationTests.cs | 2 +- .../RoleCheckAgent.cs | 2 +- .../Sample/06_GroupChat_Workflow.cs | 2 +- .../TestEchoAgent.cs | 4 ++-- .../TestReplayAgent.cs | 2 +- .../TestRequestAgent.cs | 2 +- .../WorkflowHostSmokeTests.cs | 2 +- 43 files changed, 72 insertions(+), 69 deletions(-) diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs index dd294040c4..1c9c9a3964 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs @@ -31,14 +31,14 @@ namespace SampleApp protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new CustomAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not CustomAgentSession typedSession) { throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session)); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs index 4a0dbe0839..a70ec030aa 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs @@ -55,7 +55,7 @@ await Task.Delay(TimeSpan.FromSeconds(2)); Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session)); Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n"); -JsonElement serializedSession = agent.SerializeSession(session); +JsonElement serializedSession = await agent.SerializeSessionAsync(session); AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession); Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession)); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs index edd9248ff9..f4b4a69bc6 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs @@ -47,7 +47,7 @@ Console.WriteLine(await agent.RunAsync("My name is Ruaidhrí", session)); Console.WriteLine(await agent.RunAsync("I am 20 years old", session)); // We can serialize the session. The serialized state will include the state of the memory component. -JsonElement sesionElement = agent.SerializeSession(session); +JsonElement sesionElement = await agent.SerializeSessionAsync(session); Console.WriteLine("\n>> Use deserialized session with previously created memories\n"); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs index 8acbff2690..9b4e3ea6ef 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs @@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync(); Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); // Serialize the session state to a JsonElement, so it can be stored for later use. -JsonElement serializedSession = agent.SerializeSession(session); +JsonElement serializedSession = await agent.SerializeSessionAsync(session); // Save the serialized session to a temporary file (for demonstration purposes). string tempFilePath = Path.GetTempFileName(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs index 33176d8fdf..4072cff27b 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs @@ -49,7 +49,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session // Serialize the session state, so it can be stored for later use. // Since the chat history is stored in the vector store, the serialized session // only contains the guid that the messages are stored under in the vector store. -JsonElement serializedSession = agent.SerializeSession(session); +JsonElement serializedSession = await agent.SerializeSessionAsync(session); Console.WriteLine("\n--- Serialized session ---\n"); Console.WriteLine(JsonSerializer.Serialize(serializedSession, new JsonSerializerOptions { WriteIndented = true })); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs index 2104ba536b..bb1603d367 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs @@ -40,7 +40,7 @@ AgentResponse response = await agent.RunAsync("Write a very long novel about a t // Poll for background responses until complete. while (response.ContinuationToken is not null) { - PersistAgentState(agent, session, response.ContinuationToken); + await PersistAgentState(agent, session, response.ContinuationToken); await Task.Delay(TimeSpan.FromSeconds(10)); @@ -52,9 +52,9 @@ while (response.ContinuationToken is not null) Console.WriteLine(response.Text); -void PersistAgentState(AIAgent agent, AgentSession? session, ResponseContinuationToken? continuationToken) +async Task PersistAgentState(AIAgent agent, AgentSession? session, ResponseContinuationToken? continuationToken) { - stateStore["session"] = agent.SerializeSession(session!); + stateStore["session"] = await agent.SerializeSessionAsync(session!); stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken))); } diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs index 055172fa82..7540bd213c 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs @@ -65,7 +65,7 @@ Console.WriteLine(await agent.RunAsync("I need to make a dentist appointment for Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", session) + "\n"); // We can serialize the session, and it will contain both the chat history and the data that each AI context provider serialized. -JsonElement serializedSession = agent.SerializeSession(session); +JsonElement serializedSession = await agent.SerializeSessionAsync(session); // Let's print it to console to show the contents. Console.WriteLine(JsonSerializer.Serialize(serializedSession, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n"); // The serialized session can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation. diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs index 7e839bce95..e6738d8637 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs @@ -25,7 +25,7 @@ AgentSession session = await agent.CreateSessionAsync(); Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); // Serialize the session state to a JsonElement, so it can be stored for later use. -JsonElement serializedSession = agent.SerializeSession(session); +JsonElement serializedSession = await agent.SerializeSessionAsync(session); // Save the serialized session to a temporary file (for demonstration purposes). string tempFilePath = Path.GetTempFileName(); diff --git a/dotnet/samples/M365Agent/AFAgentApplication.cs b/dotnet/samples/M365Agent/AFAgentApplication.cs index 6ebfa81897..7e58819a65 100644 --- a/dotnet/samples/M365Agent/AFAgentApplication.cs +++ b/dotnet/samples/M365Agent/AFAgentApplication.cs @@ -80,7 +80,7 @@ internal sealed class AFAgentApplication : AgentApplication } // Serialize and save the updated conversation history back to turn state. - JsonElement sessionElementEnd = this._agent.SerializeSession(agentSession, JsonUtilities.DefaultOptions); + JsonElement sessionElementEnd = await this._agent.SerializeSessionAsync(agentSession, JsonUtilities.DefaultOptions, cancellationToken); turnState.SetValue("conversation.chatHistory", sessionElementEnd); // End the streaming response diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs index 533b50c8fe..aea99b4e3d 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs @@ -66,7 +66,7 @@ public sealed class A2AAgent : AIAgent => new(new A2AAgentSession() { ContextId = contextId }); /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { _ = Throw.IfNull(session); @@ -75,7 +75,7 @@ public sealed class A2AAgent : AIAgent throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs index 881b398658..6258937cd2 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs @@ -157,7 +157,8 @@ public abstract class AIAgent /// /// The to serialize. /// Optional settings to customize the serialization process. - /// A containing the serialized session state. + /// The to monitor for cancellation requests. The default is . + /// A value task that represents the asynchronous operation. The task result contains a with the serialized session state. /// is . /// The type of is not supported by this agent. /// @@ -165,19 +166,20 @@ public abstract class AIAgent /// allowing conversations to resume across application restarts or be migrated between /// different agent instances. Use to restore the session. /// - public JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) - => this.SerializeSessionCore(session, jsonSerializerOptions); + public ValueTask SerializeSessionAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => this.SerializeSessionCoreAsync(session, jsonSerializerOptions, cancellationToken); /// /// Core implementation of session serialization logic. /// /// The to serialize. /// Optional settings to customize the serialization process. - /// A containing the serialized session state. + /// The to monitor for cancellation requests. The default is . + /// A value task that represents the asynchronous operation. The task result contains a with the serialized session state. /// /// This is the primary session serialization method that implementations must override. /// - protected abstract JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null); + protected abstract ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default); /// /// Deserializes an agent session from its JSON serialized representation. diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs index 3efce9be17..4c62eccc99 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs @@ -36,7 +36,7 @@ namespace Microsoft.Agents.AI; /// /// To support conversations that may need to survive application restarts or separate service requests, an can be serialized /// and deserialized, so that it can be saved in a persistent store. -/// The provides the method to serialize the session to a +/// The provides the method to serialize the session to a /// and the method /// can be used to deserialize the session. /// diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs index b20ba43dd1..94a2c531cf 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs @@ -77,8 +77,8 @@ public abstract class DelegatingAIAgent : AIAgent protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => this.InnerAgent.CreateSessionAsync(cancellationToken); /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) - => this.InnerAgent.SerializeSession(session, jsonSerializerOptions); + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => this.InnerAgent.SerializeSessionAsync(session, jsonSerializerOptions, cancellationToken); /// protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs index 48642139a9..c631f90f17 100644 --- a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs @@ -54,7 +54,7 @@ public class CopilotStudioAgent : AIAgent => new(new CopilotStudioAgentSession() { ConversationId = conversationId }); /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { Throw.IfNull(session); @@ -63,7 +63,7 @@ public class CopilotStudioAgent : AIAgent throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md b/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md index c34a8fe95d..d8260fcb84 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md @@ -11,6 +11,7 @@ - Moved AgentSession.Serialize to AIAgent.SerializeSession ([#3650](https://github.com/microsoft/agent-framework/pull/3650)) - Renamed serializedSession parameter to serializedState on DeserializeSessionAsync for consistency ([#3681](https://github.com/microsoft/agent-framework/pull/3681)) - Introduce Core method pattern for Session management methods on AIAgent ([#3699](https://github.com/microsoft/agent-framework/pull/3699)) +- Changed AIAgent.SerializeSession to AIAgent.SerializeSessionAsync ([#3879](https://github.com/microsoft/agent-framework/pull/3879)) ## v1.0.0-preview.251204.1 diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs index 547e999449..c790222e50 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs @@ -45,8 +45,9 @@ public sealed class DurableAIAgent : AIAgent /// /// The session to serialize. /// Optional JSON serializer options. + /// The cancellation token. /// A containing the serialized session state. - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is null) { @@ -58,7 +59,7 @@ public sealed class DurableAIAgent : AIAgent throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return durableSession.Serialize(jsonSerializerOptions); + return new(durableSession.Serialize(jsonSerializerOptions)); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs index 618b43916c..f0f7a4ffd4 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs @@ -11,7 +11,7 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient) public override string? Name { get; } = name; - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is null) { @@ -23,7 +23,7 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient) throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return durableSession.Serialize(jsonSerializerOptions); + return new(durableSession.Serialize(jsonSerializerOptions)); } protected override ValueTask DeserializeSessionCoreAsync( diff --git a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs index 1812350120..ed1f1a32ac 100644 --- a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs @@ -98,7 +98,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable => new(new GitHubCopilotAgentSession() { SessionId = sessionId }); /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { _ = Throw.IfNull(session); @@ -107,7 +107,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs index 26a07ce573..9999527505 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs @@ -30,11 +30,10 @@ public sealed class InMemoryAgentSessionStore : AgentSessionStore private readonly ConcurrentDictionary _threads = new(); /// - public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default) + public override async ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default) { var key = GetKey(conversationId, agent.Id); - this._threads[key] = agent.SerializeSession(session); - return default; + this._threads[key] = await agent.SerializeSessionAsync(session, cancellationToken: cancellationToken).ConfigureAwait(false); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs index 7ea854e5ec..cbb286216b 100644 --- a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs @@ -30,9 +30,9 @@ internal class PurviewAgent : AIAgent, IDisposable } /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { - return this._innerAgent.SerializeSession(session, jsonSerializerOptions); + return this._innerAgent.SerializeSessionAsync(session, jsonSerializerOptions, cancellationToken); } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs index 97c493d045..6ec9c4dccb 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs @@ -101,7 +101,7 @@ internal sealed class AIAgentHostExecutor : ChatProtocolExecutor protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) { - JsonElement? sessionState = this._session is not null ? this._agent.SerializeSession(this._session) : null; + JsonElement? sessionState = this._session is not null ? await this._agent.SerializeSessionAsync(this._session, cancellationToken: cancellationToken).ConfigureAwait(false) : null; AIAgentHostState state = new(sessionState, this._currentTurnEmitEvents); Task coreStateTask = context.QueueStateUpdateAsync(AIAgentHostStateKey, state, cancellationToken: cancellationToken).AsTask(); Task userInputRequestsTask = this._userInputHandler?.OnCheckpointingAsync(UserInputRequestStateKey, context, cancellationToken).AsTask() ?? Task.CompletedTask; diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs index 66f71a219a..c08ba5c3f4 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs @@ -68,7 +68,7 @@ internal sealed class WorkflowHostAgent : AIAgent protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new WorkflowSession(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, this._includeWorkflowOutputsInResponse)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { _ = Throw.IfNull(session); @@ -77,7 +77,7 @@ internal sealed class WorkflowHostAgent : AIAgent throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return workflowSession.Serialize(jsonSerializerOptions); + return new(workflowSession.Serialize(jsonSerializerOptions)); } protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index 37e673f710..dc462cf501 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -385,7 +385,7 @@ public sealed partial class ChatClientAgent : AIAgent } /// - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { _ = Throw.IfNull(session); @@ -394,7 +394,7 @@ public sealed partial class ChatClientAgent : AIAgent throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } /// diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs index 42c64dfeec..ede2c07d37 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs @@ -251,7 +251,7 @@ public sealed class AGUIAgentTests var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options); AIAgent agent = chatClient.AsAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []); AgentSession originalSession = await agent.CreateSessionAsync(); - JsonElement serialized = agent.SerializeSession(originalSession); + JsonElement serialized = await agent.SerializeSessionAsync(originalSession); // Act AgentSession deserialized = await agent.DeserializeSessionAsync(serialized); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index e964805b3f..2f2f9175d4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -576,7 +576,7 @@ public class AIAgentTests protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) @@ -617,7 +617,7 @@ public class AIAgentTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunContextTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunContextTests.cs index 693f1ea0a4..017e5fc3b2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunContextTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunContextTests.cs @@ -211,7 +211,7 @@ public sealed class AgentRunContextTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs index ac0db2068d..f53788baf8 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs @@ -71,7 +71,7 @@ public sealed class AggregatorPromptAgentFactoryTests throw new NotImplementedException(); } - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs index a6e7aab212..03dfe63d99 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs @@ -286,7 +286,7 @@ internal sealed class FakeChatClientAgent : AIAgent protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override async Task RunCoreAsync( @@ -353,14 +353,14 @@ internal sealed class FakeMultiMessageAgent : AIAgent protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not FakeInMemoryAgentSession fakeSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return fakeSession.Serialize(jsonSerializerOptions); + return new(fakeSession.Serialize(jsonSerializerOptions)); } protected override async Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs index afd3db44b3..67108676ac 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs @@ -340,14 +340,14 @@ internal sealed class FakeForwardedPropsAgent : AIAgent protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not FakeInMemoryAgentSession fakeSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return fakeSession.Serialize(jsonSerializerOptions); + return new(fakeSession.Serialize(jsonSerializerOptions)); } private sealed class FakeInMemoryAgentSession : InMemoryAgentSession diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs index b78ddd0e11..9ff3dde1a4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs @@ -423,14 +423,14 @@ internal sealed class FakeStateAgent : AIAgent protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not FakeInMemoryAgentSession fakeSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return fakeSession.Serialize(jsonSerializerOptions); + return new(fakeSession.Serialize(jsonSerializerOptions)); } private sealed class FakeInMemoryAgentSession : InMemoryAgentSession diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs index fecb9d421b..16efbd0e6e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs @@ -431,14 +431,14 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new TestInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not TestInMemoryAgentSession testSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return testSession.Serialize(jsonSerializerOptions); + return new(testSession.Serialize(jsonSerializerOptions)); } protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) @@ -534,14 +534,14 @@ public sealed class AGUIEndpointRouteBuilderExtensionsTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new TestInMemoryAgentSession(serializedState, jsonSerializerOptions)); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not TestInMemoryAgentSession testSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return testSession.Serialize(jsonSerializerOptions); + return new(testSession.Serialize(jsonSerializerOptions)); } protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs index 10502c7edd..14b7248fde 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs @@ -13,7 +13,7 @@ internal sealed class TestAgent(string name, string description) : AIAgent protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new DummyAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override ValueTask DeserializeSessionCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs index 8ac1ab50da..19ba8c11e6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs @@ -385,7 +385,7 @@ public class AgentExtensionsTests protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs index 0c91a75f13..7a9b3ec305 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs @@ -24,7 +24,7 @@ internal sealed class TestAIAgent : AIAgent public override string? Description => this.DescriptionFunc?.Invoke() ?? base.Description; - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs index a21eda21c4..3dfe605f2e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs @@ -141,7 +141,7 @@ public class AgentWorkflowBuilderTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DoubleEchoAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs index f12ffc6988..dc51338aa3 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs @@ -149,7 +149,7 @@ public class InProcessExecutionTests protected override ValueTask DeserializeSessionCoreAsync(System.Text.Json.JsonElement serializedState, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new SimpleTestAgentSession()); - protected override System.Text.Json.JsonElement SerializeSessionCore(AgentSession session, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override Task RunCoreAsync( diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs index 5d38353fde..391b6a3371 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs @@ -30,7 +30,7 @@ public class RepresentationTests protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs index 48fc432eeb..dde1d1feed 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs @@ -19,7 +19,7 @@ internal sealed class RoleCheckAgent(bool allowOtherAssistantRoles, string? id = protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new RoleCheckAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => new(new RoleCheckAgentSession()); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs index e4c905f814..afade362a1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs @@ -66,7 +66,7 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new HelloAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override async Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs index 088c862efa..9d5eca42bf 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs @@ -21,14 +21,14 @@ internal class TestEchoAgent(string? id = null, string? name = null, string? pre return serializedState.Deserialize(jsonSerializerOptions) ?? await this.CreateSessionAsync(cancellationToken); } - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { if (session is not EchoAgentSession typedSession) { throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); } - return typedSession.Serialize(jsonSerializerOptions); + return new(typedSession.Serialize(jsonSerializerOptions)); } protected override ValueTask CreateSessionCoreAsync(CancellationToken cancellationToken = default) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs index 032ba9001c..2dd33a67e4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs @@ -51,7 +51,7 @@ public class TestReplayAgent(List? messages = null, string? id = nu protected override ValueTask DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new ReplayAgentSession()); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; public static TestReplayAgent FromStrings(params string[] messages) => diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs index 63cd8dd6f0..65a49add96 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs @@ -45,7 +45,7 @@ internal sealed class TestRequestAgent(TestAgentRequestType requestType, int unp _ => throw new NotSupportedException(), }); - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs index ac6485131d..5a041699d1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs @@ -51,7 +51,7 @@ public class WorkflowHostSmokeTests return new(new Session()); } - protected override JsonElement SerializeSessionCore(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + protected override ValueTask SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => default; protected override async Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) From 6000b737e9326ddf487a40de4e9742b26a2c784c Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:27:54 +0000 Subject: [PATCH 2/3] use DefaultAzureCredential instead of AzureCliCredential (#3860) --- .../samples/A2AClientServer/A2AServer/HostAgentFactory.cs | 5 ++++- .../AGUIDojoServer/ChatClientAgentFactory.cs | 3 +++ dotnet/samples/AGUIClientServer/AGUIServer/Program.cs | 3 +++ dotnet/samples/AGUIWebChat/Server/Program.cs | 3 +++ .../Agents/AzureFunctions/01_SingleAgent/Program.cs | 5 ++++- .../02_AgentOrchestration_Chaining/Program.cs | 5 ++++- .../03_AgentOrchestration_Concurrency/Program.cs | 5 ++++- .../04_AgentOrchestration_Conditionals/Program.cs | 5 ++++- .../AzureFunctions/05_AgentOrchestration_HITL/Program.cs | 5 ++++- .../Agents/AzureFunctions/06_LongRunningTools/Program.cs | 5 ++++- .../Agents/AzureFunctions/07_AgentAsMcpTool/Program.cs | 5 ++++- .../Agents/AzureFunctions/08_ReliableStreaming/Program.cs | 5 ++++- .../Durable/Agents/ConsoleApps/01_SingleAgent/Program.cs | 5 ++++- .../ConsoleApps/02_AgentOrchestration_Chaining/Program.cs | 5 ++++- .../03_AgentOrchestration_Concurrency/Program.cs | 5 ++++- .../04_AgentOrchestration_Conditionals/Program.cs | 5 ++++- .../ConsoleApps/05_AgentOrchestration_HITL/Program.cs | 5 ++++- .../Agents/ConsoleApps/06_LongRunningTools/Program.cs | 5 ++++- .../Agents/ConsoleApps/07_ReliableStreaming/Program.cs | 5 ++++- .../GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs | 5 ++++- .../AGUI/Step01_GettingStarted/Server/Program.cs | 3 +++ .../AGUI/Step02_BackendTools/Server/Program.cs | 3 +++ .../AGUI/Step03_FrontendTools/Server/Program.cs | 3 +++ .../AGUI/Step04_HumanInLoop/Server/Program.cs | 3 +++ .../AGUI/Step05_StateManagement/Server/Program.cs | 3 +++ .../samples/GettingStarted/AgentOpenTelemetry/Program.cs | 5 ++++- .../AgentProviders/Agent_With_Anthropic/Program.cs | 5 ++++- .../Agent_With_AzureAIAgentsPersistent/Program.cs | 5 ++++- .../AgentProviders/Agent_With_AzureAIProject/Program.cs | 5 ++++- .../AgentProviders/Agent_With_AzureFoundryModel/Program.cs | 5 ++++- .../Agent_With_AzureOpenAIChatCompletion/Program.cs | 5 ++++- .../Agent_With_AzureOpenAIResponses/Program.cs | 5 ++++- .../AgentWithMemory_Step01_ChatHistoryMemory/Program.cs | 7 +++++-- .../AgentWithMemory_Step02_MemoryUsingMem0/Program.cs | 5 ++++- .../AgentWithMemory_Step03_CustomMemory/Program.cs | 5 ++++- .../AgentWithRAG_Step01_BasicTextRAG/Program.cs | 5 ++++- .../AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs | 5 ++++- .../AgentWithRAG_Step03_CustomRAGDataSource/Program.cs | 5 ++++- .../AgentWithRAG_Step04_FoundryServiceRAG/Program.cs | 5 ++++- .../GettingStarted/Agents/Agent_Step01_Running/Program.cs | 5 ++++- .../Agents/Agent_Step02_MultiturnConversation/Program.cs | 5 ++++- .../Agents/Agent_Step03_UsingFunctionTools/Program.cs | 5 ++++- .../Program.cs | 5 ++++- .../Agents/Agent_Step05_StructuredOutput/Program.cs | 5 ++++- .../Agents/Agent_Step06_PersistedConversations/Program.cs | 5 ++++- .../Agent_Step07_3rdPartyChatHistoryStorage/Program.cs | 5 ++++- .../Agents/Agent_Step08_Observability/Program.cs | 5 ++++- .../Agents/Agent_Step09_DependencyInjection/Program.cs | 5 ++++- .../Agents/Agent_Step10_AsMcpTool/Program.cs | 5 ++++- .../Agents/Agent_Step11_UsingImages/Program.cs | 5 ++++- .../Agents/Agent_Step12_AsFunctionTool/Program.cs | 7 +++++-- .../Program.cs | 5 ++++- .../Agents/Agent_Step14_Middleware/Program.cs | 5 ++++- .../Agents/Agent_Step14_Middleware/README.md | 2 +- .../GettingStarted/Agents/Agent_Step15_Plugins/Program.cs | 5 ++++- .../Agents/Agent_Step16_ChatReduction/Program.cs | 5 ++++- .../Agents/Agent_Step17_BackgroundResponses/Program.cs | 5 ++++- .../Agents/Agent_Step18_DeepResearch/Program.cs | 5 ++++- .../Agents/Agent_Step19_Declarative/Program.cs | 5 ++++- .../Agents/Agent_Step20_AdditionalAIContext/Program.cs | 5 ++++- .../GettingStarted/DeclarativeAgents/ChatClient/Program.cs | 5 ++++- .../DevUI/DevUI_Step01_BasicUsage/Program.cs | 5 ++++- .../FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs | 5 ++++- .../FoundryAgents_Step01.2_Running/Program.cs | 5 ++++- .../FoundryAgents_Step02_MultiturnConversation/Program.cs | 5 ++++- .../FoundryAgents_Step03_UsingFunctionTools/Program.cs | 5 ++++- .../Program.cs | 5 ++++- .../FoundryAgents_Step05_StructuredOutput/Program.cs | 5 ++++- .../FoundryAgents_Step06_PersistedConversations/Program.cs | 5 ++++- .../FoundryAgents_Step07_Observability/Program.cs | 5 ++++- .../FoundryAgents_Step08_DependencyInjection/Program.cs | 5 ++++- .../FoundryAgents_Step09_UsingMcpClientAsTools/Program.cs | 5 ++++- .../FoundryAgents_Step10_UsingImages/Program.cs | 5 ++++- .../FoundryAgents_Step11_AsFunctionTool/Program.cs | 5 ++++- .../FoundryAgents_Step12_Middleware/Program.cs | 5 ++++- .../FoundryAgents_Step12_Middleware/README.md | 2 +- .../FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs | 5 ++++- .../FoundryAgents_Step14_CodeInterpreter/Program.cs | 5 ++++- .../FoundryAgents_Step15_ComputerUse/Program.cs | 5 ++++- .../FoundryAgents/FoundryAgents_Step27_LocalMCP/Program.cs | 5 ++++- .../ModelContextProtocol/Agent_MCP_Server/Program.cs | 5 ++++- .../ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs | 5 ++++- .../FoundryAgent_Hosted_MCP/Program.cs | 5 ++++- .../ResponseAgent_Hosted_MCP/Program.cs | 7 +++++-- .../Workflows/Agents/CustomAgentExecutors/Program.cs | 5 ++++- .../Workflows/Agents/FoundryAgent/Program.cs | 5 ++++- .../Workflows/Agents/GroupChatToolApproval/Program.cs | 5 ++++- .../Workflows/Agents/WorkflowAsAnAgent/Program.cs | 5 ++++- .../Workflows/Concurrent/Concurrent/Program.cs | 5 ++++- .../Workflows/ConditionalEdges/01_EdgeCondition/Program.cs | 5 ++++- .../Workflows/ConditionalEdges/02_SwitchCase/Program.cs | 5 ++++- .../ConditionalEdges/03_MultiSelection/Program.cs | 5 ++++- .../Workflows/Declarative/CustomerSupport/Program.cs | 5 ++++- .../Workflows/Declarative/DeepResearch/Program.cs | 5 ++++- .../Workflows/Declarative/ExecuteCode/Program.cs | 5 ++++- .../Workflows/Declarative/ExecuteWorkflow/Program.cs | 5 ++++- .../Workflows/Declarative/FunctionTools/Program.cs | 5 ++++- .../Workflows/Declarative/HostedWorkflow/Program.cs | 5 ++++- .../Workflows/Declarative/InputArguments/Program.cs | 5 ++++- .../Workflows/Declarative/Marketing/Program.cs | 5 ++++- .../Workflows/Declarative/StudentTeacher/Program.cs | 5 ++++- .../Workflows/Declarative/ToolApproval/Program.cs | 5 ++++- .../Workflows/Observability/WorkflowAsAnAgent/Program.cs | 5 ++++- .../_Foundational/03_AgentsInWorkflows/Program.cs | 5 ++++- .../_Foundational/04_AgentWorkflowPatterns/Program.cs | 5 ++++- .../07_MixedWorkflowAgentsAndExecutors/Program.cs | 5 ++++- .../_Foundational/08_WriterCriticWorkflow/Program.cs | 5 ++++- dotnet/samples/HostedAgents/AgentWithHostedMCP/Program.cs | 3 +++ .../samples/HostedAgents/AgentWithTextSearchRag/Program.cs | 3 +++ dotnet/samples/HostedAgents/AgentsInWorkflows/Program.cs | 3 +++ dotnet/samples/M365Agent/Program.cs | 5 ++++- dotnet/samples/M365Agent/README.md | 2 +- dotnet/samples/Purview/AgentWithPurview/Program.cs | 5 ++++- 113 files changed, 435 insertions(+), 105 deletions(-) diff --git a/dotnet/samples/A2AClientServer/A2AServer/HostAgentFactory.cs b/dotnet/samples/A2AClientServer/A2AServer/HostAgentFactory.cs index 8af2b01daf..79c3060d90 100644 --- a/dotnet/samples/A2AClientServer/A2AServer/HostAgentFactory.cs +++ b/dotnet/samples/A2AClientServer/A2AServer/HostAgentFactory.cs @@ -14,7 +14,10 @@ internal static class HostAgentFactory { internal static async Task<(AIAgent, AgentCard)> CreateFoundryHostAgentAsync(string agentType, string model, string endpoint, string assistantId, IList? tools = null) { - var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential()); PersistentAgent persistentAgent = await persistentAgentsClient.Administration.GetAgentAsync(assistantId); AIAgent agent = await persistentAgentsClient diff --git a/dotnet/samples/AGUIClientServer/AGUIDojoServer/ChatClientAgentFactory.cs b/dotnet/samples/AGUIClientServer/AGUIDojoServer/ChatClientAgentFactory.cs index d14755db3f..cfb07d2850 100644 --- a/dotnet/samples/AGUIClientServer/AGUIDojoServer/ChatClientAgentFactory.cs +++ b/dotnet/samples/AGUIClientServer/AGUIDojoServer/ChatClientAgentFactory.cs @@ -24,6 +24,9 @@ internal static class ChatClientAgentFactory string endpoint = configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); s_deploymentName = configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set."); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. s_azureOpenAIClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()); diff --git a/dotnet/samples/AGUIClientServer/AGUIServer/Program.cs b/dotnet/samples/AGUIClientServer/AGUIServer/Program.cs index 418f72ad43..d2c17a5541 100644 --- a/dotnet/samples/AGUIClientServer/AGUIServer/Program.cs +++ b/dotnet/samples/AGUIClientServer/AGUIServer/Program.cs @@ -19,6 +19,9 @@ string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new In string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set."); // Create the AI agent with tools +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. var agent = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/AGUIWebChat/Server/Program.cs b/dotnet/samples/AGUIWebChat/Server/Program.cs index eb5b259016..0b474bb7f4 100644 --- a/dotnet/samples/AGUIWebChat/Server/Program.cs +++ b/dotnet/samples/AGUIWebChat/Server/Program.cs @@ -19,6 +19,9 @@ string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new In string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set."); // Create the AI agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient azureOpenAIClient = new( new Uri(endpoint), new DefaultAzureCredential()); diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/01_SingleAgent/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/01_SingleAgent/Program.cs index e629f3ee2c..cc000bd815 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/01_SingleAgent/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/01_SingleAgent/Program.cs @@ -19,9 +19,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Set up an AI agent following the standard Microsoft Agent Framework pattern. const string JokerName = "Joker"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs index 7ab6a23477..13d0852915 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/02_AgentOrchestration_Chaining/Program.cs @@ -19,9 +19,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Single agent used by the orchestration to demonstrate sequential calls on the same session. const string WriterName = "WriterAgent"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs index 621e093c67..5cf8aec044 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs @@ -19,9 +19,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Two agents used by the orchestration to demonstrate concurrent execution. const string PhysicistName = "PhysicistAgent"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs index b6638edf04..803b15f487 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs @@ -19,9 +19,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Two agents used by the orchestration to demonstrate conditional logic. const string SpamDetectionName = "SpamDetectionAgent"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/05_AgentOrchestration_HITL/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/05_AgentOrchestration_HITL/Program.cs index 284a6af3ba..eecd6de570 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/05_AgentOrchestration_HITL/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/05_AgentOrchestration_HITL/Program.cs @@ -19,9 +19,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Single agent used by the orchestration to demonstrate human-in-the-loop workflow. const string WriterName = "WriterAgent"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/06_LongRunningTools/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/06_LongRunningTools/Program.cs index 149e020614..a06d2d8e63 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/06_LongRunningTools/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/06_LongRunningTools/Program.cs @@ -23,9 +23,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Agent used by the orchestration to write content. const string WriterAgentName = "Writer"; diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/07_AgentAsMcpTool/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/07_AgentAsMcpTool/Program.cs index 3625eaa9eb..a0767f8860 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/07_AgentAsMcpTool/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/07_AgentAsMcpTool/Program.cs @@ -25,9 +25,12 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYM // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Define three AI agents we are going to use in this application. AIAgent agent1 = client.GetChatClient(deploymentName).AsAIAgent("You are good at telling jokes.", "Joker"); diff --git a/dotnet/samples/Durable/Agents/AzureFunctions/08_ReliableStreaming/Program.cs b/dotnet/samples/Durable/Agents/AzureFunctions/08_ReliableStreaming/Program.cs index dd90af2287..3850f967dc 100644 --- a/dotnet/samples/Durable/Agents/AzureFunctions/08_ReliableStreaming/Program.cs +++ b/dotnet/samples/Durable/Agents/AzureFunctions/08_ReliableStreaming/Program.cs @@ -40,9 +40,12 @@ int redisStreamTtlMinutes = int.TryParse( // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Travel Planner agent instructions - designed to produce longer responses for demonstrating streaming. const string TravelPlannerName = "TravelPlanner"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/01_SingleAgent/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/01_SingleAgent/Program.cs index 188d29ea46..7b54cf7c0a 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/01_SingleAgent/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/01_SingleAgent/Program.cs @@ -25,9 +25,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Set up an AI agent following the standard Microsoft Agent Framework pattern. const string JokerName = "Joker"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/02_AgentOrchestration_Chaining/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/02_AgentOrchestration_Chaining/Program.cs index 91b9d2da67..77af66252c 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/02_AgentOrchestration_Chaining/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/02_AgentOrchestration_Chaining/Program.cs @@ -29,9 +29,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Single agent used by the orchestration to demonstrate sequential calls on the same session. const string WriterName = "WriterAgent"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/03_AgentOrchestration_Concurrency/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/03_AgentOrchestration_Concurrency/Program.cs index 2093cd01f1..fd7e601f94 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/03_AgentOrchestration_Concurrency/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/03_AgentOrchestration_Concurrency/Program.cs @@ -29,9 +29,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Two agents used by the orchestration to demonstrate concurrent execution. const string PhysicistName = "PhysicistAgent"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/04_AgentOrchestration_Conditionals/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/04_AgentOrchestration_Conditionals/Program.cs index e4062779f6..dfef999613 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/04_AgentOrchestration_Conditionals/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/04_AgentOrchestration_Conditionals/Program.cs @@ -28,9 +28,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Spam detection agent const string SpamDetectionAgentName = "SpamDetectionAgent"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/05_AgentOrchestration_HITL/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/05_AgentOrchestration_HITL/Program.cs index c114ee6b48..ec98d55b5a 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/05_AgentOrchestration_HITL/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/05_AgentOrchestration_HITL/Program.cs @@ -29,9 +29,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Single agent used by the orchestration to demonstrate human-in-the-loop workflow. const string WriterName = "WriterAgent"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/06_LongRunningTools/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/06_LongRunningTools/Program.cs index 8a593020c3..6a8fe08b8d 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/06_LongRunningTools/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/06_LongRunningTools/Program.cs @@ -30,9 +30,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Agent used by the orchestration to write content. const string WriterAgentName = "Writer"; diff --git a/dotnet/samples/Durable/Agents/ConsoleApps/07_ReliableStreaming/Program.cs b/dotnet/samples/Durable/Agents/ConsoleApps/07_ReliableStreaming/Program.cs index 516ee889d8..9efe28a937 100644 --- a/dotnet/samples/Durable/Agents/ConsoleApps/07_ReliableStreaming/Program.cs +++ b/dotnet/samples/Durable/Agents/ConsoleApps/07_ReliableStreaming/Program.cs @@ -38,9 +38,12 @@ string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SC // Use Azure Key Credential if provided, otherwise use Azure CLI Credential. string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY"); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) ? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey)) - : new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()); + : new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()); // Travel Planner agent instructions - designed to produce longer responses for demonstrating streaming. const string TravelPlannerName = "TravelPlanner"; diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs index d1384d2c21..cbb3799274 100644 --- a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/Program.cs @@ -26,9 +26,12 @@ AgentCard agentCard = await agentCardResolver.GetAgentCardAsync(); AIAgent a2aAgent = agentCard.AsAIAgent(); // Create the main agent, and provide the a2a agent skills as a function tools. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent( instructions: "You are a helpful assistant that helps people with travel planning.", diff --git a/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Program.cs index fb3cbe401e..936d9430fb 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Program.cs @@ -19,6 +19,9 @@ string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set."); // Create the AI agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Server/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Server/Program.cs index 73ece031fc..5b55829b45 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Server/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step02_BackendTools/Server/Program.cs @@ -74,6 +74,9 @@ AITool[] tools = ]; // Create the AI agent with tools +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Program.cs index fb3cbe401e..936d9430fb 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Program.cs @@ -19,6 +19,9 @@ string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set."); // Create the AI agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Program.cs index 023b3327ba..b90f59a1d0 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Program.cs @@ -52,6 +52,9 @@ AITool[] tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(Approv #pragma warning restore MEAI001 // Create base agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient openAIChatClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/Program.cs b/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/Program.cs index a6bd6f5ef6..46637e376b 100644 --- a/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/Program.cs +++ b/dotnet/samples/GettingStarted/AGUI/Step05_StateManagement/Server/Program.cs @@ -29,6 +29,9 @@ string deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] var jsonOptions = app.Services.GetRequiredService>().Value; // Create base agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs b/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs index b818de2e44..69d71e7b88 100644 --- a/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs +++ b/dotnet/samples/GettingStarted/AgentOpenTelemetry/Program.cs @@ -110,7 +110,10 @@ static async Task GetWeatherAsync([Description("The location to get the return $"The weather in {location} is cloudy with a high of 15°C."; } -using var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +using var instrumentedChatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient() // Converts a native OpenAI SDK ChatClient into a Microsoft.Extensions.AI.IChatClient .AsBuilder() diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs index b281274051..a099d5aad3 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs @@ -17,11 +17,14 @@ string? apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"); const string JokerInstructions = "You are good at telling jokes."; const string JokerName = "JokerAgent"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. using AnthropicClient client = (resource is null) ? new AnthropicClient() { ApiKey = apiKey ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is required when no ANTHROPIC_RESOURCE is provided") } // If no resource is provided, use Anthropic public API : (apiKey is not null) ? new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(apiKey, resource)) // If an apiKey is provided, use Foundry with ApiKey authentication - : new AnthropicFoundryClient(new AnthropicFoundryIdentityTokenCredentials(new AzureCliCredential(), resource, ["https://ai.azure.com/.default"])); // Otherwise, use Foundry with Azure TokenCredential authentication + : new AnthropicFoundryClient(new AnthropicFoundryIdentityTokenCredentials(new DefaultAzureCredential(), resource, ["https://ai.azure.com/.default"])); // Otherwise, use Foundry with Azure TokenCredential authentication AIAgent agent = client.AsAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName); diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs index 07b160e880..20da6b3720 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Program.cs @@ -13,7 +13,10 @@ const string JokerName = "Joker"; const string JokerInstructions = "You are good at telling jokes."; // Get a client to create/retrieve server side agents with. -var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential()); // You can create a server side persistent agent with the Azure.AI.Agents.Persistent SDK. var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync( diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs index 4ac6f40022..ced1665951 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs @@ -13,7 +13,10 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_D const string JokerName = "JokerAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -var aiProjectClient = new AIProjectClient(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) var agentVersionCreationOptions = new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." }); diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Program.cs index d22fc627ff..752b6d0ec1 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Program.cs @@ -19,8 +19,11 @@ var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_MODEL_DEPLOYMENT") var clientOptions = new OpenAIClientOptions() { Endpoint = new Uri(endpoint) }; // Create the OpenAI client with either an API key or Azure CLI credential. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. OpenAIClient client = string.IsNullOrWhiteSpace(apiKey) - ? new OpenAIClient(new BearerTokenPolicy(new AzureCliCredential(), "https://ai.azure.com/.default"), clientOptions) + ? new OpenAIClient(new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"), clientOptions) : new OpenAIClient(new ApiKeyCredential(apiKey), clientOptions); AIAgent agent = client diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Program.cs index ea647c2d4f..1f83f6fbef 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Program.cs @@ -10,9 +10,12 @@ using OpenAI.Chat; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker"); diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Program.cs index 31a24b6585..5dfcafbb6d 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Program.cs @@ -10,9 +10,12 @@ using OpenAI.Responses; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker"); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs index ec55abf3a4..4e2065e0eb 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs @@ -20,7 +20,10 @@ var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_E // Replace this with a vector store implementation of your choice that can persist the chat history long term. VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions() { - EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetEmbeddingClient(embeddingDeploymentName) .AsIEmbeddingGenerator() }); @@ -28,7 +31,7 @@ VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions // Create the agent and add the ChatHistoryMemoryProvider to store chat messages in the vector store. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions { diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs index a70ec030aa..a81c496b5e 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs @@ -24,9 +24,12 @@ using HttpClient mem0HttpClient = new(); mem0HttpClient.BaseAddress = new Uri(mem0ServiceUri); mem0HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", mem0ApiKey); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions() { diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs index f4b4a69bc6..4a736674fc 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs @@ -18,9 +18,12 @@ using SampleApp; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName); // Create the agent and provide a factory to add our custom memory component to diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs index ca798aa333..516585f7dc 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs @@ -18,9 +18,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient azureOpenAIClient = new( new Uri(endpoint), - new AzureCliCredential()); + new DefaultAzureCredential()); // Create an In-Memory vector store that uses the Azure OpenAI embedding model to generate embeddings. VectorStore vectorStore = new InMemoryVectorStore(new() diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs index 3648ccc898..4120f2d604 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs @@ -19,9 +19,12 @@ var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_E var afOverviewUrl = "https://github.com/MicrosoftDocs/semantic-kernel-docs/blob/main/agent-framework/overview/agent-framework-overview.md"; var afMigrationUrl = "https://raw.githubusercontent.com/MicrosoftDocs/semantic-kernel-docs/refs/heads/main/agent-framework/migration-guide/from-semantic-kernel/index.md"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AzureOpenAIClient azureOpenAIClient = new( new Uri(endpoint), - new AzureCliCredential()); + new DefaultAzureCredential()); // Create a Qdrant vector store that uses the Azure OpenAI embedding model to generate embeddings. QdrantClient client = new("localhost"); diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs index bcc823de46..06da840df4 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs @@ -22,9 +22,12 @@ TextSearchProviderOptions textSearchOptions = new() RecentMessageMemoryLimit = 6, }; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions { diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs index cfb40f4029..4234be6c5a 100644 --- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/Program.cs @@ -15,9 +15,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOIN var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Create an AI Project client and get an OpenAI client that works with the foundry service. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIProjectClient aiProjectClient = new( new Uri(endpoint), - new AzureCliCredential()); + new DefaultAzureCredential()); OpenAIClient openAIClient = aiProjectClient.GetProjectOpenAIClient(); // Upload the file that contains the data to be used for RAG to the Foundry service. diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Program.cs index 3ce20975b9..e461f9ba75 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Program.cs @@ -10,9 +10,12 @@ using OpenAI.Chat; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker"); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs index 6a60de132d..5d49e806ed 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Program.cs @@ -10,9 +10,12 @@ using OpenAI.Chat; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker"); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Program.cs index 87cc021fb3..da0b638562 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Program.cs @@ -18,9 +18,12 @@ static string GetWeather([Description("The location to get the weather for.")] s => $"The weather in {location} is cloudy with a high of 15°C."; // Create the chat client and agent, and provide the function tool to the agent. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs index 12c4af9d56..5bdfc9421c 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Program.cs @@ -23,9 +23,12 @@ static string GetWeather([Description("The location to get the weather for.")] s // Create the chat client and agent. // Note that we are wrapping the function tool with ApprovalRequiredAIFunction to require user approval before invoking it. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are a helpful assistant", tools: [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))]); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs index 38762ebfd1..851b3340d5 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs @@ -15,9 +15,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Create chat client to be used by chat client agents. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. ChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName); // Create the ChatClientAgent with the specified name and instructions. diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs index 9b4e3ea6ef..e22c377929 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs @@ -12,9 +12,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Create the agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker"); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs index 4072cff27b..0eaf3d8bc5 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyChatHistoryStorage/Program.cs @@ -25,9 +25,12 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT VectorStore vectorStore = new InMemoryVectorStore(); // Create the agent +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions { diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs index 6a969d7512..20a0c252a2 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Program.cs @@ -27,7 +27,10 @@ if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) using var tracerProvider = tracerProviderBuilder.Build(); // Create the agent, and enable OpenTelemetry instrumentation. -AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are good at telling jokes.", name: "Joker") .AsBuilder() diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs index 1eb0c16742..218ab1a10e 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs @@ -21,9 +21,12 @@ HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Services.AddSingleton(new ChatClientAgentOptions() { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." } }); // Add a chat client to the service collection. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient()); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Program.cs index 16bc3cd51e..3ecad341b0 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Program.cs @@ -12,7 +12,10 @@ using ModelContextProtocol.Server; var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; -var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential()); // Create a server side persistent agent var agentMetadata = await persistentAgentsClient.Administration.CreateAgentAsync( diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs index 9e5985b8c0..984a9e3b5c 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Program.cs @@ -11,7 +11,10 @@ using ChatMessage = Microsoft.Extensions.AI.ChatMessage; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o"; -var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent( name: "VisionAgent", diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Program.cs index 765174072f..aca1a95ce4 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Program.cs @@ -17,9 +17,12 @@ static string GetWeather([Description("The location to get the weather for.")] s => $"The weather in {location} is cloudy with a high of 15°C."; // Create the chat client and agent, and provide the function tool to the agent. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent weatherAgent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent( instructions: "You answer questions about the weather.", @@ -30,7 +33,7 @@ AIAgent weatherAgent = new AzureOpenAIClient( // Create the main agent, and provide the weather agent as a function tool. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You are a helpful assistant who responds in French.", tools: [weatherAgent.AsAIFunction()]); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs index bb1603d367..5d9c70a5fd 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs @@ -19,9 +19,12 @@ var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT var stateStore = new Dictionary(); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsAIAgent( name: "SpaceNovelWriter", diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs index e795b87366..d98689f895 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs @@ -17,7 +17,10 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o"; // Get a client to create/retrieve server side agents with -var azureOpenAIClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var azureOpenAIClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName); [Description("Get the weather for a given location.")] diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/README.md b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/README.md index bacf33f828..d9433d6230 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/README.md +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/README.md @@ -7,7 +7,7 @@ This sample demonstrates how to add middleware to intercept: ## What This Sample Shows -1. Azure OpenAI integration via `AzureOpenAIClient` and `AzureCliCredential` +1. Azure OpenAI integration via `AzureOpenAIClient` and `DefaultAzureCredential` 2. Chat client middleware using `ChatClientBuilder.Use(...)` 3. Agent run middleware (PII redaction and wording guardrails) 4. Function invocation middleware (logging and overriding a tool result) diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs index 54f977352b..2e9b405183 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Program.cs @@ -27,9 +27,12 @@ services.AddSingleton(); // The plugin depends on WeatherProvider a IServiceProvider serviceProvider = services.BuildServiceProvider(); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent( instructions: "You are a helpful assistant that helps people find information.", diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs index f9a5a1fc01..77abb7898a 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs @@ -16,9 +16,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Construct the agent, and provide a factory to create an in-memory chat message store with a reducer that keeps only the last 2 non-system messages. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions { diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs index 1fa436b156..62db550556 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Program.cs @@ -10,9 +10,12 @@ using OpenAI.Responses; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsAIAgent(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs index d7ebc9fca4..c14b9e5b55 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Program.cs @@ -16,7 +16,10 @@ PersistentAgentsAdministrationClientOptions persistentAgentsClientOptions = new( persistentAgentsClientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(20); // Get a client to create/retrieve server side agents with. -PersistentAgentsClient persistentAgentsClient = new(endpoint, new AzureCliCredential(), persistentAgentsClientOptions); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +PersistentAgentsClient persistentAgentsClient = new(endpoint, new DefaultAzureCredential(), persistentAgentsClientOptions); // Define and configure the Deep Research tool. DeepResearchToolDefinition deepResearchTool = new(new DeepResearchDetails( diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs index 1fc985b3bb..215833c795 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs @@ -11,9 +11,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Create the chat client +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. IChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs index 7540bd213c..b04cf836bb 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs @@ -34,9 +34,12 @@ Func> loadNextThreeCalendarEvents = async () => }; // Create an agent with an AI context provider attached that aggregates two other providers: +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(new ChatClientAgentOptions() { diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs index bed16f496a..270acfb946 100644 --- a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs +++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs @@ -12,9 +12,12 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; // Create the chat client +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. IChatClient chatClient = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); diff --git a/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/Program.cs b/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/Program.cs index 7fded8c55b..d35c1385cc 100644 --- a/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/Program.cs +++ b/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/Program.cs @@ -46,7 +46,10 @@ internal static class Program var endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = builder.Configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs index 4f370b410e..72450b1ae4 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs @@ -13,7 +13,10 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJEC const string JokerName = "JokerAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." }); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/Program.cs index 6da363905e..79ded7c557 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/Program.cs @@ -14,7 +14,10 @@ const string JokerInstructions = "You are good at telling jokes."; const string JokerName = "JokerAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions }); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs index 1e1e7d72a8..d74180db14 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/Program.cs @@ -14,7 +14,10 @@ const string JokerInstructions = "You are good at telling jokes."; const string JokerName = "JokerAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions }); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs index 9393b71b9a..43ff40aa04 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/Program.cs @@ -20,7 +20,10 @@ const string AssistantInstructions = "You are a helpful assistant that can get w const string AssistantName = "WeatherAssistant"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent with function tools. AITool tool = AIFunctionFactory.Create(GetWeather); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs index 1f98e485f9..f7a847aeb4 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/Program.cs @@ -23,7 +23,10 @@ const string AssistantInstructions = "You are a helpful assistant that can get w const string AssistantName = "WeatherAssistant"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); ApprovalRequiredAIFunction approvalTool = new(AIFunctionFactory.Create(GetWeather, name: nameof(GetWeather))); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs index d252b82b1e..fc26d09ea7 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs @@ -19,7 +19,10 @@ const string AssistantInstructions = "You are a helpful assistant that extracts const string AssistantName = "StructuredOutputAssistant"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Create ChatClientAgent directly ChatClientAgent agent = await aiProjectClient.CreateAIAgentAsync( diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs index e6738d8637..2d163b0fb1 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs @@ -14,7 +14,10 @@ const string JokerInstructions = "You are good at telling jokes."; const string JokerName = "JokerAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: JokerInstructions); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs index ac0dc23012..35bac61639 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/Program.cs @@ -29,7 +29,10 @@ if (!string.IsNullOrWhiteSpace(applicationInsightsConnectionString)) using var tracerProvider = tracerProviderBuilder.Build(); // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) AIAgent agent = (await aiProjectClient.CreateAIAgentAsync(name: JokerName, model: deploymentName, instructions: JokerInstructions)) diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs index 4c53d4ab9c..16708c4e99 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/Program.cs @@ -15,7 +15,10 @@ string deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJEC const string JokerInstructions = "You are good at telling jokes."; const string JokerName = "JokerAgent"; -AIProjectClient aIProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aIProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Create a new agent if one doesn't exist already. ChatClientAgent agent; diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/Program.cs index cfa4b39534..e33f754912 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/Program.cs @@ -25,7 +25,10 @@ await using var mcpClient = await McpClient.CreateAsync(new StdioClientTransport IList mcpTools = await mcpClient.ListToolsAsync(); string agentName = "AgentWithMCP"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); Console.WriteLine($"Creating the agent '{agentName}' ..."); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs index a266aed278..732ca25dde 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/Program.cs @@ -14,7 +14,10 @@ const string VisionInstructions = "You are a helpful agent that can analyze imag const string VisionName = "VisionAgent"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent you want to create. (Prompt Agent in this case) AIAgent agent = await aiProjectClient.CreateAIAgentAsync(name: VisionName, model: deploymentName, instructions: VisionInstructions); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs index ccf0a0f012..7bbe478b4c 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/Program.cs @@ -21,7 +21,10 @@ static string GetWeather([Description("The location to get the weather for.")] s => $"The weather in {location} is cloudy with a high of 15°C."; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Create the weather agent with function tools. AITool weatherTool = AIFunctionFactory.Create(GetWeather); diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs index 71f70f04b8..c91cc6a6f1 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/Program.cs @@ -20,7 +20,10 @@ const string AssistantInstructions = "You are an AI assistant that helps people const string AssistantName = "InformationAssistant"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); [Description("Get the weather for a given location.")] static string GetWeather([Description("The location to get the weather for.")] string location) diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md index 04192a2cc6..eaaafe7ab1 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md @@ -4,7 +4,7 @@ This sample demonstrates how to add middleware to intercept agent runs and funct ## What This Sample Shows -1. Azure Foundry Agents integration via `AIProjectClient` and `AzureCliCredential` +1. Azure Foundry Agents integration via `AIProjectClient` and `DefaultAzureCredential` 2. Agent run middleware (logging and monitoring) 3. Function invocation middleware (logging and overriding tool results) 4. Per-request agent run middleware diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs index b761c120db..1f18dc21db 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/Program.cs @@ -30,7 +30,10 @@ services.AddSingleton(); // The plugin depends on WeatherProvider a IServiceProvider serviceProvider = services.BuildServiceProvider(); // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Define the agent with plugin tools // Define the agent you want to create. (Prompt Agent in this case) diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/Program.cs index 858c678528..77bfa6f8d7 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/Program.cs @@ -19,7 +19,10 @@ const string AgentNameMEAI = "CoderAgent-MEAI"; const string AgentNameNative = "CoderAgent-NATIVE"; // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Option 1 - Using HostedCodeInterpreterTool + AgentOptions (MEAI + AgentFramework) // Create the server side agent version diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs index e3294be059..8bdda9bdcd 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/Program.cs @@ -18,8 +18,11 @@ internal sealed class Program string endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set."); string deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "computer-use-preview"; + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. - AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); + AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); const string AgentInstructions = @" You are a computer automation assistant. diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step27_LocalMCP/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step27_LocalMCP/Program.cs index 6b1d22c14b..a9c4d4b7ed 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step27_LocalMCP/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step27_LocalMCP/Program.cs @@ -35,7 +35,10 @@ Console.WriteLine($"MCP tools available: {string.Join(", ", mcpTools.Select(t => List wrappedTools = mcpTools.Select(tool => (AITool)new LoggingMcpTool(tool)).ToList(); // Get a client to create/retrieve/delete server side agents with Azure Foundry Agents. -AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +AIProjectClient aiProjectClient = new(new Uri(endpoint), new DefaultAzureCredential()); // Create the agent with the locally-resolved MCP tools. AIAgent agent = await aiProjectClient.CreateAIAgentAsync( diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs index 568830bb04..d773332fdd 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs @@ -23,9 +23,12 @@ await using var mcpClient = await McpClient.CreateAsync(new StdioClientTransport // Retrieve the list of tools available on the GitHub server var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You answer questions related to GitHub repositories only.", tools: [.. mcpTools.Cast()]); diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs index 1a08945680..564ede4477 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs @@ -46,9 +46,12 @@ await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory // Retrieve the list of tools available on the GitHub server var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsAIAgent(instructions: "You answer questions related to the weather.", tools: [.. mcpTools]); diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs index 27677073c6..f6397ce182 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs @@ -13,7 +13,10 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOIN var model = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4.1-mini"; // Get a client to create/retrieve server side agents with. -var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential()); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential()); // **** MCP Tool with Auto Approval **** // ************************************* diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs index 79f7ff1302..194952e68a 100644 --- a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs +++ b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/Program.cs @@ -27,9 +27,12 @@ var mcpTool = new HostedMcpServerTool( }; // Create an agent based on Azure OpenAI Responses as the backend. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsAIAgent( instructions: "You answer questions by searching the Microsoft Learn content only.", @@ -56,7 +59,7 @@ var mcpToolWithApproval = new HostedMcpServerTool( // Create an agent based on Azure OpenAI Responses as the backend. AIAgent agentWithRequiredApproval = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsAIAgent( instructions: "You answer questions by searching the Microsoft Learn content only.", diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs index 02fbdf3ecc..1017111082 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs @@ -34,7 +34,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create the executors var sloganWriter = new SloganWriterExecutor("SloganWriter", chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/Program.cs index 35809685ea..48a41b73d6 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/Program.cs @@ -24,7 +24,10 @@ public static class Program var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var persistentAgentsClient = new PersistentAgentsClient(endpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var persistentAgentsClient = new PersistentAgentsClient(endpoint, new DefaultAzureCredential()); // Create agents AIAgent frenchAgent = await GetTranslationAgentAsync("French", persistentAgentsClient, deploymentName); diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs index 267c6d7ce5..0508ad80a8 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/GroupChatToolApproval/Program.cs @@ -45,8 +45,11 @@ public static class Program var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. // 1. Create AI client - IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) + IChatClient client = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs index 48436b7b8f..ff402e2e66 100644 --- a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/Program.cs @@ -32,7 +32,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create the workflow and turn it into an agent var workflow = WorkflowFactory.BuildWorkflow(chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Program.cs index e5373554c3..bed7d3fe6d 100644 --- a/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Program.cs @@ -34,7 +34,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create the executors ChatClientAgent physicist = new( diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs index 0f762ea40d..a04f081ef2 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs @@ -37,7 +37,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create agents AIAgent spamDetectionAgent = GetSpamDetectionAgent(chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs index ccda3fa19e..4919bcea09 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs @@ -38,7 +38,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create agents AIAgent spamDetectionAgent = GetSpamDetectionAgent(chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs index 49faff39da..32a727f8b7 100644 --- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs @@ -40,7 +40,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create agents AIAgent emailAnalysisAgent = GetEmailAnalysisAgent(chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/Program.cs index f18b8b4658..0547c37b72 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/Program.cs @@ -61,7 +61,10 @@ internal sealed class Program private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration, TicketingPlugin plugin) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "SelfServiceAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs index 7aaa61b398..ae410a0371 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/Program.cs @@ -47,7 +47,10 @@ internal sealed class Program private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "ResearchAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/Program.cs index bfc738c336..0566a5ff55 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/Program.cs @@ -47,9 +47,12 @@ internal sealed class Program private Workflow CreateWorkflow() { + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. // Use DeclarativeWorkflowBuilder to build a workflow based on a YAML file. DeclarativeWorkflowOptions options = - new(new AzureAgentProvider(new Uri(this.FoundryEndpoint), new AzureCliCredential())) + new(new AzureAgentProvider(new Uri(this.FoundryEndpoint), new DefaultAzureCredential())) { Configuration = this.Configuration }; diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/Program.cs index d1f0980ec7..36eb9af2d1 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/Program.cs @@ -71,8 +71,11 @@ internal sealed class Program /// private Workflow CreateWorkflow() { + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. // Create the agent provider that will service agent requests within the workflow. - AzureAgentProvider agentProvider = new(new Uri(this.FoundryEndpoint), new AzureCliCredential()) + AzureAgentProvider agentProvider = new(new Uri(this.FoundryEndpoint), new DefaultAzureCredential()) { // Functions included here will be auto-executed by the framework. Functions = this.Functions diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs index bc092a7600..a5000635e4 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/Program.cs @@ -56,7 +56,10 @@ internal sealed class Program private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration, AIFunction[] functions) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "MenuAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs index b6011a1a71..2cf24a137b 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/Program.cs @@ -34,8 +34,11 @@ internal sealed class Program IConfiguration configuration = Application.InitializeConfig(); Uri foundryEndpoint = new(configuration.GetValue(Application.Settings.FoundryEndpoint)); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. // Create the agent service client - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); // Ensure sample agents exist in Foundry. await CreateAgentsAsync(aiProjectClient, configuration); diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/Program.cs index 9aab54b4cf..523865cc62 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/Program.cs @@ -47,7 +47,10 @@ internal sealed class Program private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "LocationTriageAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Program.cs index 229658310d..43ff497930 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Program.cs @@ -46,7 +46,10 @@ internal sealed class Program private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "AnalystAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs index 7422e29f63..80afd61911 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/Program.cs @@ -46,7 +46,10 @@ internal sealed class Program private static async Task CreateAgentsAsync(Uri foundryEndpoint, IConfiguration configuration) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "StudentAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs b/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs index 3ccfc46d88..691c3d2243 100644 --- a/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/Program.cs @@ -47,7 +47,10 @@ internal sealed class Program private static async Task CreateAgentAsync(Uri foundryEndpoint, IConfiguration configuration) { - AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential()); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + AIProjectClient aiProjectClient = new(foundryEndpoint, new DefaultAzureCredential()); await aiProjectClient.CreateAgentAsync( agentName: "DocumentSearchAgent", diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs index 7d7d4d69fd..c61e690adb 100644 --- a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/Program.cs @@ -73,7 +73,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()) + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient() .AsBuilder() diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/Program.cs index 4e61b5def6..126401250c 100644 --- a/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/Program.cs @@ -30,7 +30,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create agents AIAgent frenchAgent = GetTranslationAgent("French", chatClient); diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs index 225f11b59a..0df9d34913 100644 --- a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs @@ -25,7 +25,10 @@ public static class Program // Set up the Azure OpenAI client. var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var client = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var client = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); Console.Write("Choose workflow type ('sequential', 'concurrent', 'handoffs', 'groupchat'): "); switch (Console.ReadLine()) diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/Program.cs index 096471811c..5269b5b974 100644 --- a/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/Program.cs @@ -43,7 +43,10 @@ public static class Program // Set up the Azure OpenAI client var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + var chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create executors for text processing UserInputExecutor userInput = new(); diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs index 5654b23baf..38bb80dddc 100644 --- a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs +++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs @@ -50,7 +50,10 @@ public static class Program // Set up the Azure OpenAI client string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; - IChatClient chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient(); + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. + IChatClient chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()).GetChatClient(deploymentName).AsIChatClient(); // Create executors for content creation and review WriterExecutor writer = new(chatClient); diff --git a/dotnet/samples/HostedAgents/AgentWithHostedMCP/Program.cs b/dotnet/samples/HostedAgents/AgentWithHostedMCP/Program.cs index 4dffdf92bd..827b161052 100644 --- a/dotnet/samples/HostedAgents/AgentWithHostedMCP/Program.cs +++ b/dotnet/samples/HostedAgents/AgentWithHostedMCP/Program.cs @@ -22,6 +22,9 @@ AITool mcpTool = new HostedMcpServerTool(serverName: "microsoft_learn", serverAd }; // Create an agent with the MCP tool using Azure OpenAI Responses. +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs index b197ffeefc..ae94a52f67 100644 --- a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs +++ b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs @@ -21,6 +21,9 @@ TextSearchProviderOptions textSearchOptions = new() RecentMessageMemoryLimit = 6, }; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. AIAgent agent = new AzureOpenAIClient( new Uri(endpoint), new DefaultAzureCredential()) diff --git a/dotnet/samples/HostedAgents/AgentsInWorkflows/Program.cs b/dotnet/samples/HostedAgents/AgentsInWorkflows/Program.cs index b1d8a922fd..bd37a8311f 100644 --- a/dotnet/samples/HostedAgents/AgentsInWorkflows/Program.cs +++ b/dotnet/samples/HostedAgents/AgentsInWorkflows/Program.cs @@ -15,6 +15,9 @@ using Microsoft.Extensions.AI; var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. IChatClient chatClient = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); diff --git a/dotnet/samples/M365Agent/Program.cs b/dotnet/samples/M365Agent/Program.cs index 834ac2180a..6e4bc0c0b4 100644 --- a/dotnet/samples/M365Agent/Program.cs +++ b/dotnet/samples/M365Agent/Program.cs @@ -36,9 +36,12 @@ if (builder.Configuration.GetSection("AIServices").GetValue("UseAzureOpenA var deploymentName = builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("DeploymentName")!; var endpoint = builder.Configuration.GetSection("AIServices:AzureOpenAI").GetValue("Endpoint")!; + // WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. + // In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid + // latency issues, unintended credential probing, and potential security risks from fallback mechanisms. chatClient = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetChatClient(deploymentName) .AsIChatClient(); } diff --git a/dotnet/samples/M365Agent/README.md b/dotnet/samples/M365Agent/README.md index e61fa438f5..08e1c3d6c2 100644 --- a/dotnet/samples/M365Agent/README.md +++ b/dotnet/samples/M365Agent/README.md @@ -21,7 +21,7 @@ This Agent Sample is intended to introduce you the basics of integrating Agent F "AzureOpenAI": { "DeploymentName": "", // This is the Deployment (as opposed to model) Name of the Azure OpenAI model "Endpoint": "", // This is the Endpoint of the Azure OpenAI resource - "ApiKey": "" // This is the API Key of the Azure OpenAI resource. Optional, uses AzureCliCredential if not provided + "ApiKey": "" // This is the API Key of the Azure OpenAI resource. Optional, uses DefaultAzureCredential if not provided }, "OpenAI": { "ModelId": "", // This is the Model ID of the OpenAI model diff --git a/dotnet/samples/Purview/AgentWithPurview/Program.cs b/dotnet/samples/Purview/AgentWithPurview/Program.cs index a4b27c47cd..fc0974c5bd 100644 --- a/dotnet/samples/Purview/AgentWithPurview/Program.cs +++ b/dotnet/samples/Purview/AgentWithPurview/Program.cs @@ -24,9 +24,12 @@ TokenCredential browserCredential = new InteractiveBrowserCredential( ClientId = purviewClientAppId }); +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. using IChatClient client = new AzureOpenAIClient( new Uri(endpoint), - new AzureCliCredential()) + new DefaultAzureCredential()) .GetResponsesClient(deploymentName) .AsIChatClient() .AsBuilder() From 8ed50009c6e8fab52a5e17f1d0f726075ce5030c Mon Sep 17 00:00:00 2001 From: Eduard van Valkenburg Date: Thu, 12 Feb 2026 14:49:42 +0100 Subject: [PATCH 3/3] Python: Centralize tool result parsing in FunctionTool.invoke() (#3854) * Centralize tool result parsing in FunctionTool.invoke() - Add parse_result static method to FunctionTool that converts raw function return values to strings at invocation time - Add result_parser parameter to FunctionTool and @tool decorator for custom parsing - Remove prepare_function_call_results from all 9 consumer files and from the public API - Update MCPTool to parse MCP types directly to strings via _parse_tool_result_from_mcp and _parse_prompt_result_from_mcp - Change MCPTool parse_tool_results/parse_prompt_results type from Literal[True] | Callable | None to Callable | None - Remove ReturnT type parameter from FunctionTool (now single generic ArgsT since invoke() always returns str) - Update all subclass signatures and docstrings Fixes #1147 * Fix test_mcp_tool_call_tool_with_meta_integration for string results The test was still accessing result[0].additional_properties but invoke() now returns a string, not a list of Content objects. * Fix SIM108 lint: use binary operator for output assignment * Fix bedrock: use FunctionTool.parse_result instead of str() fallback str(result) turns None into literal 'None' and dicts into Python reprs with single quotes, breaking JSON parsing. Use the shared parse_result which handles None as '' and serializes via json.dumps. * updated lock * updates from feedback --- .../ag-ui/agent_framework_ag_ui/_client.py | 2 +- .../_message_adapters.py | 4 +- .../ag-ui/agent_framework_ag_ui/_run.py | 3 +- .../ag-ui/agent_framework_ag_ui/_utils.py | 6 +- .../agents/ui_generator_agent.py | 8 +- .../tests/ag_ui/test_message_adapters.py | 28 +- .../agent_framework_anthropic/_chat_client.py | 3 +- .../agent_framework_azure_ai/_chat_client.py | 3 +- .../tests/test_azure_ai_agent_client.py | 26 +- .../agent_framework_bedrock/_chat_client.py | 3 +- .../bedrock/tests/test_bedrock_settings.py | 2 +- .../claude/agent_framework_claude/_agent.py | 2 +- .../packages/core/agent_framework/_agents.py | 4 +- python/packages/core/agent_framework/_mcp.py | 259 +++++++++++------- .../core/agent_framework/_middleware.py | 2 +- .../packages/core/agent_framework/_tools.py | 132 ++++++--- .../packages/core/agent_framework/_types.py | 31 --- .../core/agent_framework/observability.py | 6 +- .../openai/_assistants_client.py | 10 +- .../agent_framework/openai/_chat_client.py | 5 +- .../openai/_responses_client.py | 3 +- python/packages/core/tests/core/test_mcp.py | 204 ++++---------- .../core/tests/core/test_middleware.py | 28 +- .../core/test_middleware_context_result.py | 12 +- python/packages/core/tests/core/test_tools.py | 10 +- python/packages/core/tests/core/test_types.py | 61 +++-- .../tests/openai/test_openai_chat_client.py | 27 +- .../agent_framework_github_copilot/_agent.py | 2 +- .../agent_framework_lab_tau2/_tau2_utils.py | 2 +- .../_handoff.py | 4 +- python/uv.lock | 46 ++-- 31 files changed, 486 insertions(+), 452 deletions(-) diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_client.py b/python/packages/ag-ui/agent_framework_ag_ui/_client.py index 1df1ba84e2..f1dba1b078 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_client.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_client.py @@ -267,7 +267,7 @@ class AGUIChatClient( if any(getattr(tool, "name", None) == tool_name for tool in additional_tools): return - placeholder: FunctionTool[Any, Any] = FunctionTool( + placeholder: FunctionTool[Any] = FunctionTool( name=tool_name, description="Server-managed tool placeholder (AG-UI)", func=None, diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py b/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py index 709d8f4887..efdb3a0f53 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_message_adapters.py @@ -11,7 +11,6 @@ from typing import Any, cast from agent_framework import ( Content, Message, - prepare_function_call_results, ) from ._utils import ( @@ -697,8 +696,7 @@ def agent_framework_messages_to_agui(messages: list[Message] | list[dict[str, An elif content.type == "function_result": # Tool result content - extract call_id and result tool_result_call_id = content.call_id - # Serialize result to string using core utility - content_text = prepare_function_call_results(content.result) + content_text = content.result if content.result is not None else "" agui_msg: dict[str, Any] = { "id": msg.message_id if msg.message_id else generate_event_id(), # Always include id diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_run.py b/python/packages/ag-ui/agent_framework_ag_ui/_run.py index 853127e630..c376120a5a 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_run.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_run.py @@ -31,7 +31,6 @@ from agent_framework import ( Content, Message, SupportsAgentRun, - prepare_function_call_results, ) from agent_framework._middleware import FunctionMiddlewarePipeline from agent_framework._tools import ( @@ -360,7 +359,7 @@ def _emit_tool_result( events.append(ToolCallEndEvent(tool_call_id=content.call_id)) flow.tool_calls_ended.add(content.call_id) # Track ended tool calls - result_content = prepare_function_call_results(content.result) + result_content = content.result if content.result is not None else "" message_id = generate_event_id() events.append( ToolCallResultEvent( diff --git a/python/packages/ag-ui/agent_framework_ag_ui/_utils.py b/python/packages/ag-ui/agent_framework_ag_ui/_utils.py index fd63202a47..abbfb88562 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui/_utils.py +++ b/python/packages/ag-ui/agent_framework_ag_ui/_utils.py @@ -162,7 +162,7 @@ def make_json_safe(obj: Any) -> Any: # noqa: ANN401 def convert_agui_tools_to_agent_framework( agui_tools: list[dict[str, Any]] | None, -) -> list[FunctionTool[Any, Any]] | None: +) -> list[FunctionTool[Any]] | None: """Convert AG-UI tool definitions to Agent Framework FunctionTool declarations. Creates declaration-only FunctionTool instances (no executable implementation). @@ -181,13 +181,13 @@ def convert_agui_tools_to_agent_framework( if not agui_tools: return None - result: list[FunctionTool[Any, Any]] = [] + result: list[FunctionTool[Any]] = [] for tool_def in agui_tools: # Create declaration-only FunctionTool (func=None means no implementation) # When func=None, the declaration_only property returns True, # which tells the function invocation mixin to return the function call # without executing it (so it can be sent back to the client) - func: FunctionTool[Any, Any] = FunctionTool( + func: FunctionTool[Any] = FunctionTool( name=tool_def.get("name", ""), description=tool_def.get("description", ""), func=None, # CRITICAL: Makes declaration_only=True diff --git a/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/ui_generator_agent.py b/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/ui_generator_agent.py index 961f276603..7f5a4b0f2c 100644 --- a/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/ui_generator_agent.py +++ b/python/packages/ag-ui/agent_framework_ag_ui_examples/agents/ui_generator_agent.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: from agent_framework import ChatOptions # Declaration-only tools (func=None) - actual rendering happens on the client side -generate_haiku = FunctionTool[Any, str]( +generate_haiku = FunctionTool[Any]( name="generate_haiku", description="""Generate a haiku with image and gradient background (FRONTEND_RENDER). @@ -71,7 +71,7 @@ generate_haiku = FunctionTool[Any, str]( }, ) -create_chart = FunctionTool[Any, str]( +create_chart = FunctionTool[Any]( name="create_chart", description="""Create an interactive chart (FRONTEND_RENDER). @@ -99,7 +99,7 @@ create_chart = FunctionTool[Any, str]( }, ) -display_timeline = FunctionTool[Any, str]( +display_timeline = FunctionTool[Any]( name="display_timeline", description="""Display an interactive timeline (FRONTEND_RENDER). @@ -127,7 +127,7 @@ display_timeline = FunctionTool[Any, str]( }, ) -show_comparison_table = FunctionTool[Any, str]( +show_comparison_table = FunctionTool[Any]( name="show_comparison_table", description="""Show a comparison table (FRONTEND_RENDER). diff --git a/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py b/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py index 61cd9f1d06..4a715bed15 100644 --- a/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py +++ b/python/packages/ag-ui/tests/ag_ui/test_message_adapters.py @@ -543,7 +543,7 @@ def test_agent_framework_to_agui_function_result_dict(): """Test converting FunctionResultContent with dict result to AG-UI.""" msg = Message( role="tool", - contents=[Content.from_function_result(call_id="call-123", result={"key": "value", "count": 42})], + contents=[Content.from_function_result(call_id="call-123", result='{"key": "value", "count": 42}')], message_id="msg-789", ) @@ -568,8 +568,8 @@ def test_agent_framework_to_agui_function_result_none(): assert len(messages) == 1 agui_msg = messages[0] - # None serializes as JSON null - assert agui_msg["content"] == "null" + # None result maps to empty string (FunctionTool.invoke returns "" for None) + assert agui_msg["content"] == "" def test_agent_framework_to_agui_function_result_string(): @@ -591,7 +591,7 @@ def test_agent_framework_to_agui_function_result_empty_list(): """Test converting FunctionResultContent with empty list result to AG-UI.""" msg = Message( role="tool", - contents=[Content.from_function_result(call_id="call-123", result=[])], + contents=[Content.from_function_result(call_id="call-123", result="[]")], message_id="msg-789", ) @@ -604,16 +604,10 @@ def test_agent_framework_to_agui_function_result_empty_list(): def test_agent_framework_to_agui_function_result_single_text_content(): - """Test converting FunctionResultContent with single TextContent-like item.""" - from dataclasses import dataclass - - @dataclass - class MockTextContent: - text: str - + """Test converting FunctionResultContent with single TextContent-like item (pre-parsed).""" msg = Message( role="tool", - contents=[Content.from_function_result(call_id="call-123", result=[MockTextContent("Hello from MCP!")])], + contents=[Content.from_function_result(call_id="call-123", result='["Hello from MCP!"]')], message_id="msg-789", ) @@ -626,19 +620,13 @@ def test_agent_framework_to_agui_function_result_single_text_content(): def test_agent_framework_to_agui_function_result_multiple_text_contents(): - """Test converting FunctionResultContent with multiple TextContent-like items.""" - from dataclasses import dataclass - - @dataclass - class MockTextContent: - text: str - + """Test converting FunctionResultContent with multiple TextContent-like items (pre-parsed).""" msg = Message( role="tool", contents=[ Content.from_function_result( call_id="call-123", - result=[MockTextContent("First result"), MockTextContent("Second result")], + result='["First result", "Second result"]', ) ], message_id="msg-789", diff --git a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py index bf9992d7ff..d3ea19dfa0 100644 --- a/python/packages/anthropic/agent_framework_anthropic/_chat_client.py +++ b/python/packages/anthropic/agent_framework_anthropic/_chat_client.py @@ -25,7 +25,6 @@ from agent_framework import ( TextSpanRegion, UsageDetails, get_logger, - prepare_function_call_results, ) from agent_framework._settings import SecretString, load_settings from agent_framework._types import _get_data_bytes_as_str # type: ignore @@ -653,7 +652,7 @@ class AnthropicClient( a_content.append({ "type": "tool_result", "tool_use_id": content.call_id, - "content": prepare_function_call_results(content.result), + "content": content.result if content.result is not None else "", "is_error": content.exception is not None, }) case "text_reasoning": diff --git a/python/packages/azure-ai/agent_framework_azure_ai/_chat_client.py b/python/packages/azure-ai/agent_framework_azure_ai/_chat_client.py index ffb39e6c25..a898117a92 100644 --- a/python/packages/azure-ai/agent_framework_azure_ai/_chat_client.py +++ b/python/packages/azure-ai/agent_framework_azure_ai/_chat_client.py @@ -33,7 +33,6 @@ from agent_framework import ( TextSpanRegion, UsageDetails, get_logger, - prepare_function_call_results, ) from agent_framework._settings import load_settings from agent_framework.exceptions import ServiceInitializationError, ServiceInvalidRequestError, ServiceResponseException @@ -1390,7 +1389,7 @@ class AzureAIAgentClient( if tool_outputs is None: tool_outputs = [] tool_outputs.append( - ToolOutput(tool_call_id=call_id, output=prepare_function_call_results(content.result)) + ToolOutput(tool_call_id=call_id, output=content.result if content.result is not None else "") ) elif content.type == "function_approval_response": if tool_approvals is None: diff --git a/python/packages/azure-ai/tests/test_azure_ai_agent_client.py b/python/packages/azure-ai/tests/test_azure_ai_agent_client.py index b9df6e5042..80c99f6e89 100644 --- a/python/packages/azure-ai/tests/test_azure_ai_agent_client.py +++ b/python/packages/azure-ai/tests/test_azure_ai_agent_client.py @@ -1024,9 +1024,10 @@ async def test_azure_ai_chat_client_convert_required_action_serde_model_results( client = create_test_azure_ai_chat_client(mock_agents_client, agent_id="test-agent") - # Test with BaseModel result + # Test with BaseModel result (pre-parsed as it would be from FunctionTool.invoke) mock_result = MockResult(name="test", value=42) - function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=mock_result) + expected_json = mock_result.to_json() + function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=expected_json) run_id, tool_outputs, tool_approvals = client._prepare_tool_outputs_for_azure_ai([function_result]) # type: ignore @@ -1035,8 +1036,7 @@ async def test_azure_ai_chat_client_convert_required_action_serde_model_results( assert tool_outputs is not None assert len(tool_outputs) == 1 assert tool_outputs[0].tool_call_id == "call_456" - # Should use model_dump_json for BaseModel - expected_json = mock_result.to_json() + # Should use pre-parsed result string directly assert tool_outputs[0].output == expected_json @@ -1051,10 +1051,14 @@ async def test_azure_ai_chat_client_convert_required_action_multiple_results( client = create_test_azure_ai_chat_client(mock_agents_client, agent_id="test-agent") - # Test with multiple results - mix of BaseModel and regular objects + # Test with multiple results - pre-parsed as FunctionTool.invoke would produce mock_basemodel = MockResult(data="model_data") results_list = [mock_basemodel, {"key": "value"}, "string_result"] - function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=results_list) + # FunctionTool.parse_result would serialize this to a JSON string + from agent_framework import FunctionTool + + pre_parsed = FunctionTool.parse_result(results_list) + function_result = Content.from_function_result(call_id='["run_123", "call_456"]', result=pre_parsed) run_id, tool_outputs, tool_approvals = client._prepare_tool_outputs_for_azure_ai([function_result]) # type: ignore @@ -1063,14 +1067,8 @@ async def test_azure_ai_chat_client_convert_required_action_multiple_results( assert len(tool_outputs) == 1 assert tool_outputs[0].tool_call_id == "call_456" - # Should JSON dump the entire results array since len > 1 - expected_results = [ - mock_basemodel.to_dict(), - {"key": "value"}, - "string_result", - ] - expected_output = json.dumps(expected_results) - assert tool_outputs[0].output == expected_output + # Result is pre-parsed string (already JSON) + assert tool_outputs[0].output == pre_parsed async def test_azure_ai_chat_client_convert_required_action_approval_response( diff --git a/python/packages/bedrock/agent_framework_bedrock/_chat_client.py b/python/packages/bedrock/agent_framework_bedrock/_chat_client.py index ba8573718e..3520d7b1a1 100644 --- a/python/packages/bedrock/agent_framework_bedrock/_chat_client.py +++ b/python/packages/bedrock/agent_framework_bedrock/_chat_client.py @@ -27,7 +27,6 @@ from agent_framework import ( ResponseStream, UsageDetails, get_logger, - prepare_function_call_results, validate_tool_mode, ) from agent_framework._settings import SecretString, load_settings @@ -528,7 +527,7 @@ class BedrockChatClient( return None def _convert_tool_result_to_blocks(self, result: Any) -> list[dict[str, Any]]: - prepared_result = prepare_function_call_results(result) + prepared_result = result if isinstance(result, str) else FunctionTool.parse_result(result) try: parsed_result = json.loads(prepared_result) except json.JSONDecodeError: diff --git a/python/packages/bedrock/tests/test_bedrock_settings.py b/python/packages/bedrock/tests/test_bedrock_settings.py index 8be9ca95e4..016ed8ff05 100644 --- a/python/packages/bedrock/tests/test_bedrock_settings.py +++ b/python/packages/bedrock/tests/test_bedrock_settings.py @@ -68,7 +68,7 @@ def test_build_request_serializes_tool_history() -> None: ), Message( role="tool", - contents=[Content.from_function_result(call_id="call-1", result={"answer": "72F"})], + contents=[Content.from_function_result(call_id="call-1", result='{"answer": "72F"}')], ), ] diff --git a/python/packages/claude/agent_framework_claude/_agent.py b/python/packages/claude/agent_framework_claude/_agent.py index 50c5b06d0f..3e900b8e27 100644 --- a/python/packages/claude/agent_framework_claude/_agent.py +++ b/python/packages/claude/agent_framework_claude/_agent.py @@ -483,7 +483,7 @@ class ClaudeAgent(BaseAgent, Generic[OptionsT]): return create_sdk_mcp_server(name=TOOLS_MCP_SERVER_NAME, tools=sdk_tools), tool_names - def _function_tool_to_sdk_mcp_tool(self, func_tool: FunctionTool[Any, Any]) -> SdkMcpTool[Any]: + def _function_tool_to_sdk_mcp_tool(self, func_tool: FunctionTool[Any]) -> SdkMcpTool[Any]: """Convert a FunctionTool to an SDK MCP tool. Args: diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index b7d8a739d5..f43abb9fa7 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -437,7 +437,7 @@ class BaseAgent(SerializationMixin): stream_callback: Callable[[AgentResponseUpdate], None] | Callable[[AgentResponseUpdate], Awaitable[None]] | None = None, - ) -> FunctionTool[BaseModel, str]: + ) -> FunctionTool[BaseModel]: """Create a FunctionTool that wraps this agent. Keyword Args: @@ -511,7 +511,7 @@ class BaseAgent(SerializationMixin): # Create final text from accumulated updates return AgentResponse.from_updates(response_updates).text - agent_tool: FunctionTool[BaseModel, str] = FunctionTool( + agent_tool: FunctionTool[BaseModel] = FunctionTool( name=tool_name, description=tool_description, func=agent_wrapper, diff --git a/python/packages/core/agent_framework/_mcp.py b/python/packages/core/agent_framework/_mcp.py index a56e7f14db..64ff60fa7f 100644 --- a/python/packages/core/agent_framework/_mcp.py +++ b/python/packages/core/agent_framework/_mcp.py @@ -81,6 +81,51 @@ __all__ = [ ] +def _parse_prompt_result_from_mcp( + mcp_type: types.GetPromptResult, +) -> str: + """Parse an MCP GetPromptResult directly into a string representation. + + Converts each message in the prompt result to its string form and combines them. + + Args: + mcp_type: The MCP GetPromptResult object to convert. + + Returns: + A string representation of the prompt result. + """ + import json + + parts: list[str] = [] + for message in mcp_type.messages: + content = message.content + if isinstance(content, types.TextContent): + parts.append(content.text) + elif isinstance(content, (types.ImageContent, types.AudioContent)): + parts.append(json.dumps({ + "type": "image" if isinstance(content, types.ImageContent) else "audio", + "data": content.data, + "mimeType": content.mimeType, + }, default=str)) + elif isinstance(content, types.EmbeddedResource): + match content.resource: + case types.TextResourceContents(): + parts.append(content.resource.text) + case types.BlobResourceContents(): + parts.append(json.dumps({ + "type": "blob", + "data": content.resource.blob, + "mimeType": content.resource.mimeType, + }, default=str)) + else: + parts.append(str(content)) + if not parts: + return "" + if len(parts) == 1: + return parts[0] + return json.dumps(parts, default=str) + + def _parse_message_from_mcp( mcp_type: types.PromptMessage | types.SamplingMessage, ) -> Message: @@ -92,54 +137,56 @@ def _parse_message_from_mcp( ) -def _parse_contents_from_mcp_tool_result( +def _parse_tool_result_from_mcp( mcp_type: types.CallToolResult, -) -> list[Content]: - """Parse an MCP CallToolResult into Agent Framework content types. +) -> str: + """Parse an MCP CallToolResult directly into a string representation. - This function extracts the complete _meta field from CallToolResult objects - and merges all metadata into the additional_properties field of converted - content items. - - Note: The _meta field from CallToolResult is applied to ALL content items - in the result, as the Agent Framework's content model doesn't have a - result-level metadata container. This ensures metadata is preserved but - means it will be duplicated across multiple content items if present. + Converts each content item in the MCP result to its string form and combines them. + This skips the intermediate Content object step for tool results. Args: mcp_type: The MCP CallToolResult object to convert. Returns: - A list of Agent Framework content items with metadata merged into - additional_properties. + A string representation of the tool result — either plain text or serialized JSON. """ - meta_data = mcp_type.meta + import json - # Prepare merged metadata once if present - merged_meta_props = None - if meta_data: - merged_meta_props = {} - if hasattr(meta_data, "__dict__"): - merged_meta_props.update(meta_data.__dict__) - elif isinstance(meta_data, dict): - merged_meta_props.update(meta_data) - else: - merged_meta_props["_meta"] = meta_data - - # Convert each content item and merge metadata - result_contents = [] + parts: list[str] = [] for item in mcp_type.content: - contents = _parse_content_from_mcp(item) - - if merged_meta_props: - for content in contents: - existing_props = getattr(content, "additional_properties", None) or {} - # Merge with content-specific properties, letting content-specific props override - final_props = merged_meta_props.copy() - final_props.update(existing_props) - content.additional_properties = final_props - result_contents.extend(contents) - return result_contents + match item: + case types.TextContent(): + parts.append(item.text) + case types.ImageContent() | types.AudioContent(): + parts.append(json.dumps({ + "type": "image" if isinstance(item, types.ImageContent) else "audio", + "data": item.data, + "mimeType": item.mimeType, + }, default=str)) + case types.ResourceLink(): + parts.append(json.dumps({ + "type": "resource_link", + "uri": str(item.uri), + "mimeType": item.mimeType, + }, default=str)) + case types.EmbeddedResource(): + match item.resource: + case types.TextResourceContents(): + parts.append(item.resource.text) + case types.BlobResourceContents(): + parts.append(json.dumps({ + "type": "blob", + "data": item.resource.blob, + "mimeType": item.resource.mimeType, + }, default=str)) + case _: + parts.append(str(item)) + if not parts: + return "" + if len(parts) == 1: + return parts[0] + return json.dumps(parts, default=str) def _parse_content_from_mcp( @@ -344,9 +391,9 @@ class MCPTool: approval_mode: (Literal["always_require", "never_require"] | MCPSpecificApproval | None) = None, allowed_tools: Collection[str] | None = None, load_tools: bool = True, - parse_tool_results: Literal[True] | Callable[[types.CallToolResult], Any] | None = True, + parse_tool_results: Callable[[types.CallToolResult], str] | None = None, load_prompts: bool = True, - parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True, + parse_prompt_results: Callable[[types.GetPromptResult], str] | None = None, session: ClientSession | None = None, request_timeout: int | None = None, client: SupportsChatGetResponse | None = None, @@ -357,6 +404,30 @@ class MCPTool: Note: Do not use this method, use one of the subclasses: MCPStreamableHTTPTool, MCPWebsocketTool or MCPStdioTool. + + Args: + name: The name of the MCP tool. + description: A description of the MCP tool. + approval_mode: Whether approval is required to run tools. + allowed_tools: A collection of tool names to allow. + load_tools: Whether to load tools from the MCP server. + parse_tool_results: An optional callable with signature + ``Callable[[types.CallToolResult], str]`` that overrides the default result + parsing. When ``None`` (the default), the built-in parser converts MCP types + directly to a string. If you need per-function result parsing, access the + ``.functions`` list after connecting and set ``result_parser`` on individual + ``FunctionTool`` instances. + load_prompts: Whether to load prompts from the MCP server. + parse_prompt_results: An optional callable with signature + ``Callable[[types.GetPromptResult], str]`` that overrides the default prompt + result parsing. When ``None`` (the default), the built-in parser converts + MCP prompt results to a string. If you need per-function result parsing, + access the ``.functions`` list after connecting and set ``result_parser`` on + individual ``FunctionTool`` instances. + session: An existing MCP client session to use. + request_timeout: Timeout in seconds for MCP requests. + client: A chat client for sampling callbacks. + additional_properties: Additional properties for the tool. """ self.name = name self.description = description or "" @@ -371,7 +442,7 @@ class MCPTool: self.session = session self.request_timeout = request_timeout self.client = client - self._functions: list[FunctionTool[Any, Any]] = [] + self._functions: list[FunctionTool[Any]] = [] self.is_connected: bool = False self._tools_loaded: bool = False self._prompts_loaded: bool = False @@ -380,7 +451,7 @@ class MCPTool: return f"MCPTool(name={self.name}, description={self.description})" @property - def functions(self) -> list[FunctionTool[Any, Any]]: + def functions(self) -> list[FunctionTool[Any]]: """Get the list of functions that are allowed.""" if not self.allowed_tools: return self._functions @@ -648,7 +719,7 @@ class MCPTool: input_model = _get_input_model_from_mcp_prompt(prompt) approval_mode = self._determine_approval_mode(local_name) - func: FunctionTool[BaseModel, list[Message] | Any | types.GetPromptResult] = FunctionTool( + func: FunctionTool[BaseModel] = FunctionTool( func=partial(self.get_prompt, prompt.name), name=local_name, description=prompt.description or "", @@ -692,7 +763,7 @@ class MCPTool: input_model = _get_input_model_from_mcp_tool(tool) approval_mode = self._determine_approval_mode(local_name) # Create FunctionTools out of each tool - func: FunctionTool[BaseModel, list[Content] | Any | types.CallToolResult] = FunctionTool( + func: FunctionTool[BaseModel] = FunctionTool( func=partial(self.call_tool, tool.name), name=local_name, description=tool.description or "", @@ -746,7 +817,7 @@ class MCPTool: inner_exception=ex, ) from ex - async def call_tool(self, tool_name: str, **kwargs: Any) -> list[Content] | Any | types.CallToolResult: + async def call_tool(self, tool_name: str, **kwargs: Any) -> str: """Call a tool with the given arguments. Args: @@ -756,7 +827,7 @@ class MCPTool: kwargs: Arguments to pass to the tool. Returns: - A list of content items returned by the tool. + A string representation of the tool result — either plain text or serialized JSON. Raises: ToolExecutionException: If the MCP server is not connected, tools are not loaded, @@ -779,17 +850,13 @@ class MCPTool: not in {"chat_options", "tools", "tool_choice", "thread", "conversation_id", "options", "response_format"} } + parser = self.parse_tool_results or _parse_tool_result_from_mcp + # Try the operation, reconnecting once if the connection is closed for attempt in range(2): try: result = await self.session.call_tool(tool_name, arguments=filtered_kwargs) # type: ignore - if self.parse_tool_results is None: - return result - if self.parse_tool_results is True: - return _parse_contents_from_mcp_tool_result(result) - if callable(self.parse_tool_results): - return self.parse_tool_results(result) - return result + return parser(result) except ClosedResourceError as cl_ex: if attempt == 0: # First attempt failed, try reconnecting @@ -815,7 +882,7 @@ class MCPTool: raise ToolExecutionException(f"Failed to call tool '{tool_name}'.", inner_exception=ex) from ex raise ToolExecutionException(f"Failed to call tool '{tool_name}' after retries.") - async def get_prompt(self, prompt_name: str, **kwargs: Any) -> list[Message] | Any | types.GetPromptResult: + async def get_prompt(self, prompt_name: str, **kwargs: Any) -> str: """Call a prompt with the given arguments. Args: @@ -825,7 +892,7 @@ class MCPTool: kwargs: Arguments to pass to the prompt. Returns: - A list of chat messages returned by the prompt. + A string representation of the prompt result — either plain text or serialized JSON. Raises: ToolExecutionException: If the MCP server is not connected, prompts are not loaded, @@ -836,17 +903,13 @@ class MCPTool: "Prompts are not loaded for this server, please set load_prompts=True in the constructor." ) + parser = self.parse_prompt_results or _parse_prompt_result_from_mcp + # Try the operation, reconnecting once if the connection is closed for attempt in range(2): try: prompt_result = await self.session.get_prompt(prompt_name, arguments=kwargs) # type: ignore - if self.parse_prompt_results is None: - return prompt_result - if self.parse_prompt_results is True: - return [_parse_message_from_mcp(message) for message in prompt_result.messages] - if callable(self.parse_prompt_results): - return self.parse_prompt_results(prompt_result) - return prompt_result + return parser(prompt_result) except ClosedResourceError as cl_ex: if attempt == 0: # First attempt failed, try reconnecting @@ -945,9 +1008,9 @@ class MCPStdioTool(MCPTool): command: str, *, load_tools: bool = True, - parse_tool_results: Literal[True] | Callable[[types.CallToolResult], Any] | None = True, + parse_tool_results: Callable[[types.CallToolResult], str] | None = None, load_prompts: bool = True, - parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True, + parse_prompt_results: Callable[[types.GetPromptResult], str] | None = None, request_timeout: int | None = None, session: ClientSession | None = None, description: str | None = None, @@ -973,15 +1036,19 @@ class MCPStdioTool(MCPTool): Keyword Args: load_tools: Whether to load tools from the MCP server. - parse_tool_results: How to parse tool results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP tool result. + parse_tool_results: An optional callable with signature + ``Callable[[types.CallToolResult], str]`` that overrides the default result + parsing. When ``None`` (the default), the built-in parser converts MCP types + directly to a string. If you need per-function result parsing, access the + ``.functions`` list after connecting and set ``result_parser`` on individual + ``FunctionTool`` instances. load_prompts: Whether to load prompts from the MCP server. - parse_prompt_results: How to parse prompt results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP prompt result. + parse_prompt_results: An optional callable with signature + ``Callable[[types.GetPromptResult], str]`` that overrides the default prompt + result parsing. When ``None`` (the default), the built-in parser converts + MCP prompt results to a string. If you need per-function result parsing, + access the ``.functions`` list after connecting and set ``result_parser`` on + individual ``FunctionTool`` instances. request_timeout: The default timeout in seconds for all requests. session: The session to use for the MCP connection. description: The description of the tool. @@ -1066,9 +1133,9 @@ class MCPStreamableHTTPTool(MCPTool): url: str, *, load_tools: bool = True, - parse_tool_results: Literal[True] | Callable[[types.CallToolResult], Any] | None = True, + parse_tool_results: Callable[[types.CallToolResult], str] | None = None, load_prompts: bool = True, - parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True, + parse_prompt_results: Callable[[types.GetPromptResult], str] | None = None, request_timeout: int | None = None, session: ClientSession | None = None, description: str | None = None, @@ -1094,15 +1161,19 @@ class MCPStreamableHTTPTool(MCPTool): Keyword Args: load_tools: Whether to load tools from the MCP server. - parse_tool_results: How to parse tool results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP tool result. + parse_tool_results: An optional callable with signature + ``Callable[[types.CallToolResult], str]`` that overrides the default result + parsing. When ``None`` (the default), the built-in parser converts MCP types + directly to a string. If you need per-function result parsing, access the + ``.functions`` list after connecting and set ``result_parser`` on individual + ``FunctionTool`` instances. load_prompts: Whether to load prompts from the MCP server. - parse_prompt_results: How to parse prompt results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP prompt result. + parse_prompt_results: An optional callable with signature + ``Callable[[types.GetPromptResult], str]`` that overrides the default prompt + result parsing. When ``None`` (the default), the built-in parser converts + MCP prompt results to a string. If you need per-function result parsing, + access the ``.functions`` list after connecting and set ``result_parser`` on + individual ``FunctionTool`` instances. request_timeout: The default timeout in seconds for all requests. session: The session to use for the MCP connection. description: The description of the tool. @@ -1181,9 +1252,9 @@ class MCPWebsocketTool(MCPTool): url: str, *, load_tools: bool = True, - parse_tool_results: Literal[True] | Callable[[types.CallToolResult], Any] | None = True, + parse_tool_results: Callable[[types.CallToolResult], str] | None = None, load_prompts: bool = True, - parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True, + parse_prompt_results: Callable[[types.GetPromptResult], str] | None = None, request_timeout: int | None = None, session: ClientSession | None = None, description: str | None = None, @@ -1207,15 +1278,19 @@ class MCPWebsocketTool(MCPTool): Keyword Args: load_tools: Whether to load tools from the MCP server. - parse_tool_results: How to parse tool results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP tool result. + parse_tool_results: An optional callable with signature + ``Callable[[types.CallToolResult], str]`` that overrides the default result + parsing. When ``None`` (the default), the built-in parser converts MCP types + directly to a string. If you need per-function result parsing, access the + ``.functions`` list after connecting and set ``result_parser`` on individual + ``FunctionTool`` instances. load_prompts: Whether to load prompts from the MCP server. - parse_prompt_results: How to parse prompt results from the MCP server. - Set to True, to use the default parser that converts to Agent Framework types. - Set to a callable to use a custom parser function. - Set to None to return the raw MCP prompt result. + parse_prompt_results: An optional callable with signature + ``Callable[[types.GetPromptResult], str]`` that overrides the default prompt + result parsing. When ``None`` (the default), the built-in parser converts + MCP prompt results to a string. If you need per-function result parsing, + access the ``.functions`` list after connecting and set ``result_parser`` on + individual ``FunctionTool`` instances. request_timeout: The default timeout in seconds for all requests. session: The session to use for the MCP connection. description: The description of the tool. diff --git a/python/packages/core/agent_framework/_middleware.py b/python/packages/core/agent_framework/_middleware.py index e595be76e3..d096e96c2a 100644 --- a/python/packages/core/agent_framework/_middleware.py +++ b/python/packages/core/agent_framework/_middleware.py @@ -234,7 +234,7 @@ class FunctionInvocationContext: def __init__( self, - function: FunctionTool[Any, Any], + function: FunctionTool[Any], arguments: BaseModel, metadata: Mapping[str, Any] | None = None, result: Any = None, diff --git a/python/packages/core/agent_framework/_tools.py b/python/packages/core/agent_framework/_tools.py index b838551f81..6362433892 100644 --- a/python/packages/core/agent_framework/_tools.py +++ b/python/packages/core/agent_framework/_tools.py @@ -90,7 +90,6 @@ ChatClientT = TypeVar("ChatClientT", bound="SupportsChatGetResponse[Any]") # region Helpers ArgsT = TypeVar("ArgsT", bound=BaseModel, default=BaseModel) -ReturnT = TypeVar("ReturnT", default=Any) def _parse_inputs( @@ -188,7 +187,7 @@ class EmptyInputModel(BaseModel): """An empty input model for functions with no parameters.""" -class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): +class FunctionTool(SerializationMixin, Generic[ArgsT]): """A tool that wraps a Python function to make it callable by AI models. This class wraps a Python function to make it callable by AI models with automatic @@ -252,8 +251,9 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): max_invocations: int | None = None, max_invocation_exceptions: int | None = None, additional_properties: dict[str, Any] | None = None, - func: Callable[..., Awaitable[ReturnT] | ReturnT] | None = None, + func: Callable[..., Any] | None = None, input_model: type[ArgsT] | Mapping[str, Any] | None = None, + result_parser: Callable[[Any], str] | None = None, **kwargs: Any, ) -> None: """Initialize the FunctionTool. @@ -281,6 +281,12 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): parameters, explicitly provide ``input_model`` (either a Pydantic ``BaseModel`` or a JSON schema dictionary) so the model can reason about the expected arguments. + result_parser: An optional callable with signature ``Callable[[Any], str]`` that + overrides the default result parsing behavior. When provided, this callable + is used to convert the raw function return value to a string instead of the + built-in :meth:`parse_result` logic. Depending on your function, it may be + easiest to just do the serialization directly in the function body rather + than providing a custom ``result_parser``. **kwargs: Additional keyword arguments. """ # Core attributes (formerly from BaseTool) @@ -306,6 +312,7 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): self.invocation_exception_count = 0 self._invocation_duration_histogram = _default_histogram() self.type: Literal["function_tool"] = "function_tool" + self.result_parser = result_parser self._forward_runtime_kwargs: bool = False if self.func: sig = inspect.signature(self.func) @@ -328,7 +335,7 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): return True return self.func is None - def __get__(self, obj: Any, objtype: type | None = None) -> FunctionTool[ArgsT, ReturnT]: + def __get__(self, obj: Any, objtype: type | None = None) -> FunctionTool[ArgsT]: """Implement the descriptor protocol to support bound methods. When a FunctionTool is accessed as an attribute of a class instance, @@ -371,7 +378,7 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): return cast(type[ArgsT], _create_model_from_json_schema(self.name, input_model)) raise TypeError("input_model must be a Pydantic BaseModel subclass or a JSON schema dict.") - def __call__(self, *args: Any, **kwargs: Any) -> ReturnT | Awaitable[ReturnT]: + def __call__(self, *args: Any, **kwargs: Any) -> Any: """Call the wrapped function with the provided arguments.""" if self.declaration_only: raise ToolException(f"Function '{self.name}' is declaration only and cannot be invoked.") @@ -402,15 +409,19 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): *, arguments: ArgsT | None = None, **kwargs: Any, - ) -> ReturnT: + ) -> str: """Run the AI function with the provided arguments as a Pydantic model. + The raw return value of the wrapped function is automatically parsed into a ``str`` + (either plain text or serialized JSON) using :meth:`parse_result` or the custom + ``result_parser`` if one was provided. + Keyword Args: arguments: A Pydantic model instance containing the arguments for the function. kwargs: Keyword arguments to pass to the function, will not be used if ``arguments`` is provided. Returns: - The result of the function execution. + The parsed result as a string — either plain text or serialized JSON. Raises: TypeError: If arguments is not an instance of the expected input model. @@ -420,6 +431,8 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): global OBSERVABILITY_SETTINGS from .observability import OBSERVABILITY_SETTINGS + parser = self.result_parser or FunctionTool.parse_result + original_kwargs = dict(kwargs) tool_call_id = original_kwargs.pop("tool_call_id", None) if arguments is not None: @@ -435,9 +448,14 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): logger.debug(f"Function arguments: {kwargs}") res = self.__call__(**kwargs) result = await res if inspect.isawaitable(res) else res + try: + parsed = parser(result) + except Exception: + logger.warning(f"Function {self.name}: result parser failed, falling back to str().") + parsed = str(result) logger.info(f"Function {self.name} succeeded.") - logger.debug(f"Function result: {result or 'None'}") - return result # type: ignore[reportReturnType] + logger.debug(f"Function result: {parsed or 'None'}") + return parsed attributes = get_function_span_attributes(self, tool_call_id=tool_call_id) if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined] @@ -481,19 +499,16 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): logger.error(f"Function failed. Error: {exception}") raise else: + try: + parsed = parser(result) + except Exception: + logger.warning(f"Function {self.name}: result parser failed, falling back to str().") + parsed = str(result) logger.info(f"Function {self.name} succeeded.") if OBSERVABILITY_SETTINGS.SENSITIVE_DATA_ENABLED: # type: ignore[name-defined] - from ._types import prepare_function_call_results - - try: - json_result = prepare_function_call_results(result) - except (TypeError, OverflowError): - span.set_attribute(OtelAttr.TOOL_RESULT, "") - logger.debug("Function result: ") - else: - span.set_attribute(OtelAttr.TOOL_RESULT, json_result) - logger.debug(f"Function result: {json_result}") - return result # type: ignore[reportReturnType] + span.set_attribute(OtelAttr.TOOL_RESULT, parsed) + logger.debug(f"Function result: {parsed}") + return parsed finally: duration = (end_time_stamp or perf_counter()) - start_time_stamp span.set_attribute(OtelAttr.MEASUREMENT_FUNCTION_INVOCATION_DURATION, duration) @@ -511,6 +526,49 @@ class FunctionTool(SerializationMixin, Generic[ArgsT, ReturnT]): self._cached_parameters = self.input_model.model_json_schema() return self._cached_parameters + @staticmethod + def _make_dumpable(value: Any) -> Any: + """Recursively convert a value to a JSON-dumpable form.""" + from ._types import Content + + if isinstance(value, list): + return [FunctionTool._make_dumpable(item) for item in value] + if isinstance(value, dict): + return {k: FunctionTool._make_dumpable(v) for k, v in value.items()} + if isinstance(value, Content): + return value.to_dict(exclude={"raw_representation", "additional_properties"}) + if isinstance(value, BaseModel): + return value.model_dump() + if hasattr(value, "to_dict"): + return value.to_dict() + if hasattr(value, "text") and isinstance(value.text, str): + return value.text + return value + + @staticmethod + def parse_result(result: Any) -> str: + """Convert a raw function return value to a string representation. + + The return value is always a ``str`` — either plain text or serialized JSON. + This is called automatically by :meth:`invoke` before returning the result, + ensuring that the result stored in ``Content.from_function_result`` is + already in a form that can be passed directly to LLM APIs. + + Args: + result: The raw return value from the wrapped function. + + Returns: + A string representation of the result, either plain text or serialized JSON. + """ + if result is None: + return "" + if isinstance(result, str): + return result + dumpable = FunctionTool._make_dumpable(result) + if isinstance(dumpable, str): + return dumpable + return json.dumps(dumpable, default=str) + def to_json_schema_spec(self) -> dict[str, Any]: """Convert a FunctionTool to the JSON Schema function specification format. @@ -874,7 +932,7 @@ def _create_model_from_json_schema(tool_name: str, schema_json: Mapping[str, Any @overload def tool( - func: Callable[..., ReturnT | Awaitable[ReturnT]], + func: Callable[..., Any], *, name: str | None = None, description: str | None = None, @@ -883,7 +941,8 @@ def tool( max_invocations: int | None = None, max_invocation_exceptions: int | None = None, additional_properties: dict[str, Any] | None = None, -) -> FunctionTool[Any, ReturnT]: ... + result_parser: Callable[[Any], str] | None = None, +) -> FunctionTool[Any]: ... @overload @@ -897,11 +956,12 @@ def tool( max_invocations: int | None = None, max_invocation_exceptions: int | None = None, additional_properties: dict[str, Any] | None = None, -) -> Callable[[Callable[..., ReturnT | Awaitable[ReturnT]]], FunctionTool[Any, ReturnT]]: ... + result_parser: Callable[[Any], str] | None = None, +) -> Callable[[Callable[..., Any]], FunctionTool[Any]]: ... def tool( - func: Callable[..., ReturnT | Awaitable[ReturnT]] | None = None, + func: Callable[..., Any] | None = None, *, name: str | None = None, description: str | None = None, @@ -910,7 +970,8 @@ def tool( max_invocations: int | None = None, max_invocation_exceptions: int | None = None, additional_properties: dict[str, Any] | None = None, -) -> FunctionTool[Any, ReturnT] | Callable[[Callable[..., ReturnT | Awaitable[ReturnT]]], FunctionTool[Any, ReturnT]]: + result_parser: Callable[[Any], str] | None = None, +) -> FunctionTool[Any] | Callable[[Callable[..., Any]], FunctionTool[Any]]: """Decorate a function to turn it into a FunctionTool that can be passed to models and executed automatically. This decorator creates a Pydantic model from the function's signature, @@ -950,6 +1011,12 @@ def tool( max_invocation_exceptions: The maximum number of exceptions allowed during invocations. If None, there is no limit, should be at least 1. additional_properties: Additional properties to set on the function. + result_parser: An optional callable with signature ``Callable[[Any], str]`` that + overrides the default result parsing. When provided, this callable converts the + raw function return value to a string instead of using the built-in + :meth:`FunctionTool.parse_result`. Depending on your function, it may be + easiest to just do the serialization directly in the function body rather + than providing a custom ``result_parser``. Note: When approval_mode is set to "always_require", the function will not be executed @@ -1028,12 +1095,12 @@ def tool( """ - def decorator(func: Callable[..., ReturnT | Awaitable[ReturnT]]) -> FunctionTool[Any, ReturnT]: + def decorator(func: Callable[..., Any]) -> FunctionTool[Any]: @wraps(func) - def wrapper(f: Callable[..., ReturnT | Awaitable[ReturnT]]) -> FunctionTool[Any, ReturnT]: + def wrapper(f: Callable[..., Any]) -> FunctionTool[Any]: tool_name: str = name or getattr(f, "__name__", "unknown_function") # type: ignore[assignment] tool_desc: str = description or (f.__doc__ or "") - return FunctionTool[Any, ReturnT]( + return FunctionTool[Any]( name=tool_name, description=tool_desc, approval_mode=approval_mode, @@ -1042,6 +1109,7 @@ def tool( additional_properties=additional_properties or {}, func=f, input_model=schema, + result_parser=result_parser, ) return wrapper(func) @@ -1125,7 +1193,7 @@ async def _auto_invoke_function( custom_args: dict[str, Any] | None = None, *, config: FunctionInvocationConfiguration, - tool_map: dict[str, FunctionTool[BaseModel, Any]], + tool_map: dict[str, FunctionTool[BaseModel]], sequence_index: int | None = None, request_index: int | None = None, middleware_pipeline: FunctionMiddlewarePipeline | None = None, # Optional MiddlewarePipeline @@ -1157,7 +1225,7 @@ async def _auto_invoke_function( # this function is called. This function only handles the actual execution of approved, # non-declaration-only functions. - tool: FunctionTool[BaseModel, Any] | None = None + tool: FunctionTool[BaseModel] | None = None if function_call_content.type == "function_call": tool = tool_map.get(function_call_content.name) # type: ignore[arg-type] # Tool should exist because _try_execute_function_calls validates this @@ -1272,8 +1340,8 @@ def _get_tool_map( | Callable[..., Any] | MutableMapping[str, Any] | Sequence[FunctionTool | Callable[..., Any] | MutableMapping[str, Any]], -) -> dict[str, FunctionTool[Any, Any]]: - tool_list: dict[str, FunctionTool[Any, Any]] = {} +) -> dict[str, FunctionTool[Any]]: + tool_list: dict[str, FunctionTool[Any]] = {} for tool_item in tools if isinstance(tools, list) else [tools]: if isinstance(tool_item, FunctionTool): tool_list[tool_item.name] = tool_item diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 7d8b5a7909..79c41c1023 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -55,7 +55,6 @@ __all__ = [ "merge_chat_options", "normalize_messages", "normalize_tools", - "prepare_function_call_results", "prepend_instructions_to_messages", "validate_chat_options", "validate_tool_mode", @@ -1377,36 +1376,6 @@ class Content: # endregion -def _prepare_function_call_results_as_dumpable(content: Content | Any | list[Content | Any]) -> Any: - if isinstance(content, list): - # Particularly deal with lists of Content - return [_prepare_function_call_results_as_dumpable(item) for item in content] - if isinstance(content, dict): - return {k: _prepare_function_call_results_as_dumpable(v) for k, v in content.items()} - if isinstance(content, BaseModel): - return content.model_dump() - if hasattr(content, "to_dict"): - return content.to_dict(exclude={"raw_representation", "additional_properties"}) - # Handle objects with text attribute (e.g., MCP TextContent) - if hasattr(content, "text") and isinstance(content.text, str): - return content.text - return content - - -def prepare_function_call_results(content: Content | Any | list[Content | Any]) -> str: - """Prepare the values of the function call results.""" - if isinstance(content, Content): - # For BaseContent objects, use to_dict and serialize to JSON - # Use default=str to handle datetime and other non-JSON-serializable objects - return json.dumps(content.to_dict(exclude={"raw_representation", "additional_properties"}), default=str) - - dumpable = _prepare_function_call_results_as_dumpable(content) - if isinstance(dumpable, str): - return dumpable - # fallback - use default=str to handle datetime and other non-JSON-serializable objects - return json.dumps(dumpable, default=str) - - # region Chat Response constants RoleLiteral = Literal["system", "user", "assistant", "tool"] diff --git a/python/packages/core/agent_framework/observability.py b/python/packages/core/agent_framework/observability.py index ac3c493309..64ceefe673 100644 --- a/python/packages/core/agent_framework/observability.py +++ b/python/packages/core/agent_framework/observability.py @@ -1448,7 +1448,7 @@ class AgentTelemetryLayer: # region Otel Helpers -def get_function_span_attributes(function: FunctionTool[Any, Any], tool_call_id: str | None = None) -> dict[str, str]: +def get_function_span_attributes(function: FunctionTool[Any], tool_call_id: str | None = None) -> dict[str, str]: """Get the span attributes for the given function. Args: @@ -1678,12 +1678,10 @@ def _to_otel_part(content: Content) -> dict[str, Any] | None: case "function_call": return {"type": "tool_call", "id": content.call_id, "name": content.name, "arguments": content.arguments} case "function_result": - from ._types import prepare_function_call_results - return { "type": "tool_call_response", "id": content.call_id, - "response": prepare_function_call_results(content), + "response": content.result if content.result is not None else "", } case _: # GenericPart in otel output messages json spec. diff --git a/python/packages/core/agent_framework/openai/_assistants_client.py b/python/packages/core/agent_framework/openai/_assistants_client.py index 218dacbea8..03899d4891 100644 --- a/python/packages/core/agent_framework/openai/_assistants_client.py +++ b/python/packages/core/agent_framework/openai/_assistants_client.py @@ -45,7 +45,6 @@ from .._types import ( Message, ResponseStream, UsageDetails, - prepare_function_call_results, ) from ..exceptions import ServiceInitializationError from ..observability import ChatTelemetryLayer @@ -805,10 +804,11 @@ class OpenAIAssistantsClient( # type: ignore[misc] if tool_outputs is None: tool_outputs = [] - if function_result_content.result: - output = prepare_function_call_results(function_result_content.result) - else: - output = "No output received." + output = ( + function_result_content.result + if function_result_content.result is not None + else "No output received." + ) tool_outputs.append(ToolOutput(tool_call_id=call_id, output=output)) return run_id, tool_outputs diff --git a/python/packages/core/agent_framework/openai/_chat_client.py b/python/packages/core/agent_framework/openai/_chat_client.py index b806848b75..fa232c20a1 100644 --- a/python/packages/core/agent_framework/openai/_chat_client.py +++ b/python/packages/core/agent_framework/openai/_chat_client.py @@ -37,7 +37,6 @@ from .._types import ( Message, ResponseStream, UsageDetails, - prepare_function_call_results, ) from ..exceptions import ( ServiceInitializationError, @@ -556,9 +555,7 @@ class RawOpenAIChatClient( # type: ignore[misc] args["tool_call_id"] = content.call_id # Always include content for tool results - API requires it even if empty # Functions returning None should still have a tool result message - args["content"] = ( - prepare_function_call_results(content.result) if content.result is not None else "" - ) + args["content"] = content.result if content.result is not None else "" case "text_reasoning" if (protected_data := content.protected_data) is not None: all_messages[-1]["reasoning_details"] = json.loads(protected_data) case _: diff --git a/python/packages/core/agent_framework/openai/_responses_client.py b/python/packages/core/agent_framework/openai/_responses_client.py index 3b1f1c37c7..5f2b637f1c 100644 --- a/python/packages/core/agent_framework/openai/_responses_client.py +++ b/python/packages/core/agent_framework/openai/_responses_client.py @@ -57,7 +57,6 @@ from .._types import ( TextSpanRegion, UsageDetails, detect_media_type_from_base64, - prepare_function_call_results, prepend_instructions_to_messages, validate_tool_mode, ) @@ -1037,7 +1036,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc] args: dict[str, Any] = { "call_id": content.call_id, "type": "function_call_output", - "output": prepare_function_call_results(content.result), + "output": content.result if content.result is not None else "", } return args case "function_approval_request": diff --git a/python/packages/core/tests/core/test_mcp.py b/python/packages/core/tests/core/test_mcp.py index f3775a4f0a..21ff396a52 100644 --- a/python/packages/core/tests/core/test_mcp.py +++ b/python/packages/core/tests/core/test_mcp.py @@ -25,7 +25,7 @@ from agent_framework._mcp import ( _get_input_model_from_mcp_tool, _normalize_mcp_name, _parse_content_from_mcp, - _parse_contents_from_mcp_tool_result, + _parse_tool_result_from_mcp, _parse_message_from_mcp, _prepare_content_for_mcp, _prepare_message_for_mcp, @@ -68,144 +68,60 @@ def test_mcp_prompt_message_to_ai_content(): assert ai_content.raw_representation == mcp_message -def test_parse_contents_from_mcp_tool_result(): - """Test conversion from MCP tool result to AI contents.""" +def test_parse_tool_result_from_mcp(): + """Test conversion from MCP tool result to string representation.""" mcp_result = types.CallToolResult( content=[ types.TextContent(type="text", text="Result text"), - types.ImageContent(type="image", data="eHl6", mimeType="image/png"), # base64 for "xyz" - types.ImageContent(type="image", data="YWJj", mimeType="image/webp"), # base64 for "abc" + types.ImageContent(type="image", data="eHl6", mimeType="image/png"), + types.ImageContent(type="image", data="YWJj", mimeType="image/webp"), ] ) - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) + result = _parse_tool_result_from_mcp(mcp_result) - assert len(ai_contents) == 3 - assert ai_contents[0].type == "text" - assert ai_contents[0].text == "Result text" - assert ai_contents[1].type == "data" - assert ai_contents[1].uri == "data:image/png;base64,eHl6" - assert ai_contents[1].media_type == "image/png" - assert ai_contents[2].type == "data" - assert ai_contents[2].uri == "data:image/webp;base64,YWJj" - assert ai_contents[2].media_type == "image/webp" + # Multiple items produce a JSON array of strings + assert isinstance(result, str) + import json + + parsed = json.loads(result) + assert len(parsed) == 3 + assert parsed[0] == "Result text" + # Image items are JSON-encoded strings within the array + img1 = json.loads(parsed[1]) + assert img1["type"] == "image" + assert img1["data"] == "eHl6" + img2 = json.loads(parsed[2]) + assert img2["type"] == "image" + assert img2["data"] == "YWJj" -def test_mcp_call_tool_result_with_meta_error(): - """Test conversion from MCP tool result with _meta field containing isError=True.""" - # Create a mock CallToolResult with _meta field containing error information +def test_parse_tool_result_from_mcp_single_text(): + """Test conversion from MCP tool result with a single text item.""" + mcp_result = types.CallToolResult( + content=[types.TextContent(type="text", text="Simple result")] + ) + result = _parse_tool_result_from_mcp(mcp_result) + + # Single text item returns just the text + assert result == "Simple result" + + +def test_parse_tool_result_from_mcp_meta_not_in_string(): + """Test that _meta data is not included in the string result (it's tool-level, not content-level).""" mcp_result = types.CallToolResult( content=[types.TextContent(type="text", text="Error occurred")], - _meta={"isError": True, "errorCode": "TOOL_ERROR", "errorMessage": "Tool execution failed"}, + _meta={"isError": True, "errorCode": "TOOL_ERROR"}, ) - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) - - assert len(ai_contents) == 1 - assert ai_contents[0].type == "text" - assert ai_contents[0].text == "Error occurred" - - # Check that _meta data is merged into additional_properties - assert ai_contents[0].additional_properties is not None - assert ai_contents[0].additional_properties["isError"] is True - assert ai_contents[0].additional_properties["errorCode"] == "TOOL_ERROR" - assert ai_contents[0].additional_properties["errorMessage"] == "Tool execution failed" + result = _parse_tool_result_from_mcp(mcp_result) + assert result == "Error occurred" -def test_mcp_call_tool_result_with_meta_arbitrary_data(): - """Test conversion from MCP tool result with _meta field containing arbitrary metadata. - - Note: The _meta field is optional and can contain any structure that a specific - MCP server chooses to provide. This test uses example metadata to verify that - whatever is provided gets preserved in additional_properties. - """ - mcp_result = types.CallToolResult( - content=[types.TextContent(type="text", text="Success result")], - _meta={ - "serverVersion": "2.1.0", - "executionId": "exec_abc123", - "metrics": {"responseTime": 1.25, "memoryUsed": "64MB"}, - "source": "example-mcp-server", - "customField": "arbitrary_value", - }, - ) - - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) - - assert len(ai_contents) == 1 - assert ai_contents[0].type == "text" - assert ai_contents[0].text == "Success result" - - # Check that _meta data is preserved in additional_properties - props = ai_contents[0].additional_properties - assert props is not None - assert props["serverVersion"] == "2.1.0" - assert props["executionId"] == "exec_abc123" - assert props["metrics"] == {"responseTime": 1.25, "memoryUsed": "64MB"} - assert props["source"] == "example-mcp-server" - assert props["customField"] == "arbitrary_value" - - -def test_mcp_call_tool_result_with_meta_merging_existing_properties(): - """Test that _meta data merges correctly with existing additional_properties.""" - # Create content with existing additional_properties - text_content = types.TextContent(type="text", text="Test content") - mcp_result = types.CallToolResult(content=[text_content], _meta={"newField": "newValue", "isError": False}) - - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) - - assert len(ai_contents) == 1 - content = ai_contents[0] - - # Check that _meta data is present in additional_properties - assert content.additional_properties is not None - assert content.additional_properties["newField"] == "newValue" - assert content.additional_properties["isError"] is False - - -def test_mcp_call_tool_result_with_meta_none(): - """Test that missing _meta field is handled gracefully.""" - mcp_result = types.CallToolResult(content=[types.TextContent(type="text", text="No meta test")]) - # No _meta field set - - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) - - assert len(ai_contents) == 1 - assert ai_contents[0].type == "text" - assert ai_contents[0].text == "No meta test" - - # Should handle gracefully when no _meta field exists - # additional_properties may be None or empty dict - props = ai_contents[0].additional_properties - assert props is None or props == {} - - -def test_mcp_call_tool_result_regression_successful_workflow(): - """Regression test to ensure existing successful workflows remain unchanged.""" - # Test the original successful workflow still works - mcp_result = types.CallToolResult( - content=[ - types.TextContent(type="text", text="Success message"), - types.ImageContent(type="image", data="YWJjMTIz", mimeType="image/jpeg"), # base64 for "abc123" - ] - ) - - ai_contents = _parse_contents_from_mcp_tool_result(mcp_result) - - # Verify basic conversion still works correctly - assert len(ai_contents) == 2 - - text_content = ai_contents[0] - assert text_content.type == "text" - assert text_content.text == "Success message" - - image_content = ai_contents[1] - assert image_content.type == "data" - assert image_content.uri == "data:image/jpeg;base64,YWJjMTIz" - assert image_content.media_type == "image/jpeg" - - # Should have no additional_properties when no _meta field - assert text_content.additional_properties is None or text_content.additional_properties == {} - assert image_content.additional_properties is None or image_content.additional_properties == {} +def test_parse_tool_result_from_mcp_empty_content(): + """Test that empty content produces empty string.""" + mcp_result = types.CallToolResult(content=[]) + result = _parse_tool_result_from_mcp(mcp_result) + assert result == "" def test_mcp_content_types_to_ai_content_text(): @@ -874,17 +790,7 @@ async def test_mcp_tool_call_tool_with_meta_integration(): func = server.functions[0] result = await func.invoke(param="test_value") - assert len(result) == 1 - assert result[0].type == "text" - assert result[0].text == "Tool executed with metadata" - - # Verify that _meta data is present in additional_properties - props = result[0].additional_properties - assert props is not None - assert props["executionTime"] == 1.5 - assert props["cost"] == {"usd": 0.002} - assert props["isError"] is False - assert props["toolVersion"] == "1.2.3" + assert result == "Tool executed with metadata" async def test_local_mcp_server_function_execution(): @@ -923,9 +829,7 @@ async def test_local_mcp_server_function_execution(): func = server.functions[0] result = await func.invoke(param="test_value") - assert len(result) == 1 - assert result[0].type == "text" - assert result[0].text == "Tool executed successfully" + assert result == "Tool executed successfully" async def test_local_mcp_server_function_execution_with_nested_object(): @@ -972,8 +876,7 @@ async def test_local_mcp_server_function_execution_with_nested_object(): # Call with nested object result = await func.invoke(params={"customer_id": 251}) - assert len(result) == 1 - assert result[0].type == "text" + assert result == '{"name": "John Doe", "id": 251}' # Verify the session.call_tool was called with the correct nested structure server.session.call_tool.assert_called_once() @@ -1057,11 +960,7 @@ async def test_local_mcp_server_prompt_execution(): prompt = server.functions[0] result = await prompt.invoke(arg="test_value") - assert len(result) == 1 - assert isinstance(result[0], Message) - assert result[0].role == "user" - assert len(result[0].contents) == 1 - assert result[0].contents[0].text == "Test message" + assert result == "Test message" @pytest.mark.parametrize( @@ -1249,7 +1148,8 @@ async def test_streamable_http_integration(): assert hasattr(func, "description") result = await func.invoke(query="What is Agent Framework?") - assert result[0].text is not None + assert isinstance(result, str) + assert len(result) > 0 @pytest.mark.flaky @@ -1314,11 +1214,11 @@ async def test_mcp_connection_reset_integration(): # Verify tools are still available after reconnection assert len(tool.functions) > 0 - # Both results should be valid (we don't compare content as it may vary) - if hasattr(first_result[0], "text"): - assert first_result[0].text is not None - if hasattr(second_result[0], "text"): - assert second_result[0].text is not None + # Both results should be valid strings (we don't compare content as it may vary) + assert isinstance(first_result, str) + assert len(first_result) > 0 + assert isinstance(second_result, str) + assert len(second_result) > 0 async def test_mcp_tool_message_handler_notification(): diff --git a/python/packages/core/tests/core/test_middleware.py b/python/packages/core/tests/core/test_middleware.py index e5bd23751f..f37c855ba3 100644 --- a/python/packages/core/tests/core/test_middleware.py +++ b/python/packages/core/tests/core/test_middleware.py @@ -74,7 +74,7 @@ class TestAgentContext: class TestFunctionInvocationContext: """Test cases for FunctionInvocationContext.""" - def test_init_with_defaults(self, mock_function: FunctionTool[Any, Any]) -> None: + def test_init_with_defaults(self, mock_function: FunctionTool[Any]) -> None: """Test FunctionInvocationContext initialization with default values.""" arguments = FunctionTestArgs(name="test") context = FunctionInvocationContext(function=mock_function, arguments=arguments) @@ -83,7 +83,7 @@ class TestFunctionInvocationContext: assert context.arguments == arguments assert context.metadata == {} - def test_init_with_custom_metadata(self, mock_function: FunctionTool[Any, Any]) -> None: + def test_init_with_custom_metadata(self, mock_function: FunctionTool[Any]) -> None: """Test FunctionInvocationContext initialization with custom metadata.""" arguments = FunctionTestArgs(name="test") metadata = {"key": "value"} @@ -420,7 +420,7 @@ class TestFunctionMiddlewarePipeline: await call_next() raise MiddlewareTermination - async def test_execute_with_pre_next_termination(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_execute_with_pre_next_termination(self, mock_function: FunctionTool[Any]) -> None: """Test pipeline execution with termination before next() raises MiddlewareTermination.""" middleware = self.PreNextTerminateFunctionMiddleware() pipeline = FunctionMiddlewarePipeline(middleware) @@ -439,7 +439,7 @@ class TestFunctionMiddlewarePipeline: # Handler should not be called when terminated before next() assert execution_order == [] - async def test_execute_with_post_next_termination(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_execute_with_post_next_termination(self, mock_function: FunctionTool[Any]) -> None: """Test pipeline execution with termination after next() raises MiddlewareTermination.""" middleware = self.PostNextTerminateFunctionMiddleware() pipeline = FunctionMiddlewarePipeline(middleware) @@ -480,7 +480,7 @@ class TestFunctionMiddlewarePipeline: pipeline = FunctionMiddlewarePipeline(test_middleware) assert pipeline.has_middlewares - async def test_execute_no_middleware(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_execute_no_middleware(self, mock_function: FunctionTool[Any]) -> None: """Test pipeline execution with no middleware.""" pipeline = FunctionMiddlewarePipeline() arguments = FunctionTestArgs(name="test") @@ -494,7 +494,7 @@ class TestFunctionMiddlewarePipeline: result = await pipeline.execute(context, final_handler) assert result == expected_result - async def test_execute_with_middleware(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_execute_with_middleware(self, mock_function: FunctionTool[Any]) -> None: """Test pipeline execution with middleware.""" execution_order: list[str] = [] @@ -787,7 +787,7 @@ class TestClassBasedMiddleware: assert context.metadata["after"] is True assert metadata_updates == ["before", "handler", "after"] - async def test_function_middleware_execution(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_execution(self, mock_function: FunctionTool[Any]) -> None: """Test class-based function middleware execution.""" metadata_updates: list[str] = [] @@ -847,7 +847,7 @@ class TestFunctionBasedMiddleware: assert context.metadata["function_middleware"] is True assert execution_order == ["function_before", "handler", "function_after"] - async def test_function_function_middleware(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_function_middleware(self, mock_function: FunctionTool[Any]) -> None: """Test function-based function middleware.""" execution_order: list[str] = [] @@ -905,7 +905,7 @@ class TestMixedMiddleware: assert result is not None assert execution_order == ["class_before", "function_before", "handler", "function_after", "class_after"] - async def test_mixed_function_middleware(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_mixed_function_middleware(self, mock_function: FunctionTool[Any]) -> None: """Test mixed class and function-based function middleware.""" execution_order: list[str] = [] @@ -1017,7 +1017,7 @@ class TestMultipleMiddlewareOrdering: ] assert execution_order == expected_order - async def test_function_middleware_execution_order(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_execution_order(self, mock_function: FunctionTool[Any]) -> None: """Test that multiple function middleware execute in registration order.""" execution_order: list[str] = [] @@ -1143,7 +1143,7 @@ class TestContextContentValidation: result = await pipeline.execute(context, final_handler) assert result is not None - async def test_function_context_validation(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_context_validation(self, mock_function: FunctionTool[Any]) -> None: """Test that function context contains expected data.""" class ContextValidationMiddleware(FunctionMiddleware): @@ -1489,7 +1489,7 @@ class TestMiddlewareExecutionControl: assert not handler_called assert context.result is None - async def test_function_middleware_no_next_no_execution(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_no_next_no_execution(self, mock_function: FunctionTool[Any]) -> None: """Test that when function middleware doesn't call next(), no execution happens.""" class FunctionTestArgs(BaseModel): @@ -1666,9 +1666,9 @@ def mock_agent() -> SupportsAgentRun: @pytest.fixture -def mock_function() -> FunctionTool[Any, Any]: +def mock_function() -> FunctionTool[Any]: """Mock function for testing.""" - function = MagicMock(spec=FunctionTool[Any, Any]) + function = MagicMock(spec=FunctionTool[Any]) function.name = "test_function" return function diff --git a/python/packages/core/tests/core/test_middleware_context_result.py b/python/packages/core/tests/core/test_middleware_context_result.py index c5744fdca5..ba6bfb9c4a 100644 --- a/python/packages/core/tests/core/test_middleware_context_result.py +++ b/python/packages/core/tests/core/test_middleware_context_result.py @@ -103,7 +103,7 @@ class TestResultOverrideMiddleware: assert updates[0].text == "overridden" assert updates[1].text == " stream" - async def test_function_middleware_result_override(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_result_override(self, mock_function: FunctionTool[Any]) -> None: """Test that function middleware can override result.""" override_result = "overridden function result" @@ -252,7 +252,7 @@ class TestResultOverrideMiddleware: assert execute_result.messages[0].text == "executed response" assert handler_called - async def test_function_middleware_conditional_no_next(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_conditional_no_next(self, mock_function: FunctionTool[Any]) -> None: """Test that when function middleware conditionally doesn't call next(), no execution happens.""" class ConditionalNoNextFunctionMiddleware(FunctionMiddleware): @@ -335,7 +335,7 @@ class TestResultObservability: assert observed_responses[0].messages[0].text == "executed response" assert result == observed_responses[0] - async def test_function_middleware_result_observability(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_result_observability(self, mock_function: FunctionTool[Any]) -> None: """Test that middleware can observe function result after execution.""" observed_results: list[str] = [] @@ -402,7 +402,7 @@ class TestResultObservability: assert result is not None assert result.messages[0].text == "modified after execution" - async def test_function_middleware_post_execution_override(self, mock_function: FunctionTool[Any, Any]) -> None: + async def test_function_middleware_post_execution_override(self, mock_function: FunctionTool[Any]) -> None: """Test that middleware can override function result after observing execution.""" class PostExecutionOverrideMiddleware(FunctionMiddleware): @@ -444,8 +444,8 @@ def mock_agent() -> SupportsAgentRun: @pytest.fixture -def mock_function() -> FunctionTool[Any, Any]: +def mock_function() -> FunctionTool[Any]: """Mock function for testing.""" - function = MagicMock(spec=FunctionTool[Any, Any]) + function = MagicMock(spec=FunctionTool[Any]) function.name = "test_function" return function diff --git a/python/packages/core/tests/core/test_tools.py b/python/packages/core/tests/core/test_tools.py index 436ae7fdd1..dbcf7aac6f 100644 --- a/python/packages/core/tests/core/test_tools.py +++ b/python/packages/core/tests/core/test_tools.py @@ -138,7 +138,7 @@ async def test_tool_decorator_with_schema_invoke(): return a + b result = await calculate.invoke(arguments=CalcInput(a=3, b=7)) - assert result == 10 + assert result == "10" def test_tool_decorator_with_schema_overrides_annotations(): @@ -436,7 +436,7 @@ async def test_tool_invoke_telemetry_enabled(span_exporter: InMemorySpanExporter result = await telemetry_test_tool.invoke(x=1, y=2, tool_call_id="test_call_id") # Verify result - assert result == 3 + assert result == "3" # Verify telemetry calls spans = span_exporter.get_finished_spans() @@ -480,7 +480,7 @@ async def test_tool_invoke_telemetry_sensitive_disabled(span_exporter: InMemoryS result = await telemetry_test_tool.invoke(x=1, y=2, tool_call_id="test_call_id") # Verify result - assert result == 3 + assert result == "3" # Verify telemetry calls spans = span_exporter.get_finished_spans() @@ -545,7 +545,7 @@ async def test_tool_invoke_telemetry_with_pydantic_args(span_exporter: InMemoryS result = await pydantic_test_tool.invoke(arguments=args_model, tool_call_id="pydantic_call") # Verify result - assert result == 15 + assert result == "15" spans = span_exporter.get_finished_spans() assert len(spans) == 1 span = spans[0] @@ -613,7 +613,7 @@ async def test_tool_invoke_telemetry_async_function(span_exporter: InMemorySpanE result = await async_telemetry_test.invoke(x=3, y=4, tool_call_id="async_call") # Verify result - assert result == 12 + assert result == "12" spans = span_exporter.get_finished_spans() assert len(spans) == 1 span = spans[0] diff --git a/python/packages/core/tests/core/test_types.py b/python/packages/core/tests/core/test_types.py index 0be7b123bd..7a5acdedf7 100644 --- a/python/packages/core/tests/core/test_types.py +++ b/python/packages/core/tests/core/test_types.py @@ -26,7 +26,6 @@ from agent_framework import ( UsageDetails, detect_media_type_from_base64, merge_chat_options, - prepare_function_call_results, tool, ) from agent_framework._types import ( @@ -2072,7 +2071,7 @@ def test_text_content_with_annotations_serialization(): assert all(isinstance(ann["annotated_regions"][0], dict) for ann in reconstructed.annotations) -# region prepare_function_call_results with Pydantic models +# region FunctionTool.parse_result with Pydantic models class WeatherResult(BaseModel): @@ -2089,10 +2088,10 @@ class NestedModel(BaseModel): weather: WeatherResult -def test_prepare_function_call_results_pydantic_model(): +def test_parse_result_pydantic_model(): """Test that Pydantic BaseModel subclasses are properly serialized using model_dump().""" result = WeatherResult(temperature=22.5, condition="sunny") - json_result = prepare_function_call_results(result) + json_result = FunctionTool.parse_result(result) # The result should be a valid JSON string assert isinstance(json_result, str) @@ -2100,13 +2099,13 @@ def test_prepare_function_call_results_pydantic_model(): assert '"condition": "sunny"' in json_result or '"condition":"sunny"' in json_result -def test_prepare_function_call_results_pydantic_model_in_list(): +def test_parse_result_pydantic_model_in_list(): """Test that lists containing Pydantic models are properly serialized.""" results = [ WeatherResult(temperature=20.0, condition="cloudy"), WeatherResult(temperature=25.0, condition="sunny"), ] - json_result = prepare_function_call_results(results) + json_result = FunctionTool.parse_result(results) # The result should be a valid JSON string representing a list assert isinstance(json_result, str) @@ -2116,13 +2115,13 @@ def test_prepare_function_call_results_pydantic_model_in_list(): assert "sunny" in json_result -def test_prepare_function_call_results_pydantic_model_in_dict(): +def test_parse_result_pydantic_model_in_dict(): """Test that dicts containing Pydantic models are properly serialized.""" results = { "current": WeatherResult(temperature=22.0, condition="partly cloudy"), "forecast": WeatherResult(temperature=24.0, condition="sunny"), } - json_result = prepare_function_call_results(results) + json_result = FunctionTool.parse_result(results) # The result should be a valid JSON string representing a dict assert isinstance(json_result, str) @@ -2132,10 +2131,10 @@ def test_prepare_function_call_results_pydantic_model_in_dict(): assert "sunny" in json_result -def test_prepare_function_call_results_nested_pydantic_model(): +def test_parse_result_nested_pydantic_model(): """Test that nested Pydantic models are properly serialized.""" result = NestedModel(name="Seattle", weather=WeatherResult(temperature=18.0, condition="rainy")) - json_result = prepare_function_call_results(result) + json_result = FunctionTool.parse_result(result) # The result should be a valid JSON string assert isinstance(json_result, str) @@ -2144,10 +2143,10 @@ def test_prepare_function_call_results_nested_pydantic_model(): assert "18.0" in json_result or "18" in json_result -# region prepare_function_call_results with MCP TextContent-like objects +# region FunctionTool.parse_result with MCP TextContent-like objects -def test_prepare_function_call_results_text_content_single(): +def test_parse_result_text_content_single(): """Test that objects with text attribute (like MCP TextContent) are properly handled.""" @dataclass @@ -2155,14 +2154,14 @@ def test_prepare_function_call_results_text_content_single(): text: str result = [MockTextContent("Hello from MCP tool!")] - json_result = prepare_function_call_results(result) + json_result = FunctionTool.parse_result(result) # Should extract text and serialize as JSON array of strings assert isinstance(json_result, str) assert json_result == '["Hello from MCP tool!"]' -def test_prepare_function_call_results_text_content_multiple(): +def test_parse_result_text_content_multiple(): """Test that multiple TextContent-like objects are serialized correctly.""" @dataclass @@ -2170,14 +2169,14 @@ def test_prepare_function_call_results_text_content_multiple(): text: str result = [MockTextContent("First result"), MockTextContent("Second result")] - json_result = prepare_function_call_results(result) + json_result = FunctionTool.parse_result(result) # Should extract text from each and serialize as JSON array assert isinstance(json_result, str) assert json_result == '["First result", "Second result"]' -def test_prepare_function_call_results_text_content_with_non_string_text(): +def test_parse_result_text_content_with_non_string_text(): """Test that objects with non-string text attribute are not treated as TextContent.""" class BadTextContent: @@ -2185,12 +2184,40 @@ def test_prepare_function_call_results_text_content_with_non_string_text(): self.text = 12345 # Not a string! result = [BadTextContent()] - json_result = prepare_function_call_results(result) + json_result = FunctionTool.parse_result(result) # Should not extract text since it's not a string, will serialize the object assert isinstance(json_result, str) +def test_parse_result_none_returns_empty_string(): + """Test that None returns an empty string.""" + assert FunctionTool.parse_result(None) == "" + + +def test_parse_result_string_passthrough(): + """Test that strings are returned as-is.""" + assert FunctionTool.parse_result("hello world") == "hello world" + assert FunctionTool.parse_result('{"key": "value"}') == '{"key": "value"}' + + +def test_parse_result_content_object(): + """Test that Content objects are serialized via to_dict.""" + content = Content.from_text("hello") + result = FunctionTool.parse_result(content) + assert isinstance(result, str) + assert "hello" in result + + +def test_parse_result_list_of_content(): + """Test that list[Content] is serialized to JSON.""" + contents = [Content.from_text("hello"), Content.from_text("world")] + result = FunctionTool.parse_result(contents) + assert isinstance(result, str) + assert "hello" in result + assert "world" in result + + # endregion diff --git a/python/packages/core/tests/openai/test_openai_chat_client.py b/python/packages/core/tests/openai/test_openai_chat_client.py index 6458a38402..e6e5de8314 100644 --- a/python/packages/core/tests/openai/test_openai_chat_client.py +++ b/python/packages/core/tests/openai/test_openai_chat_client.py @@ -17,7 +17,6 @@ from agent_framework import ( Content, Message, SupportsChatGetResponse, - prepare_function_call_results, tool, ) from agent_framework.exceptions import ServiceInitializationError, ServiceResponseException @@ -281,17 +280,21 @@ def test_chat_response_content_order_text_before_tool_calls(openai_unit_test_env def test_function_result_falsy_values_handling(openai_unit_test_env: dict[str, str]): - """Test that falsy values (like empty list) in function result are properly handled.""" + """Test that falsy values (like empty list) in function result are properly handled. + + Note: In practice, FunctionTool.invoke() always returns a pre-parsed string. + These tests verify that the OpenAI client correctly passes through string results. + """ client = OpenAIChatClient() - # Test with empty list (falsy but not None) + # Test with empty list serialized as JSON string (as FunctionTool.invoke would produce) message_with_empty_list = Message( - role="tool", contents=[Content.from_function_result(call_id="call-123", result=[])] + role="tool", contents=[Content.from_function_result(call_id="call-123", result="[]")] ) openai_messages = client._prepare_message_for_openai(message_with_empty_list) assert len(openai_messages) == 1 - assert openai_messages[0]["content"] == "[]" # Empty list should be JSON serialized + assert openai_messages[0]["content"] == "[]" # Empty list JSON string # Test with empty string (falsy but not None) message_with_empty_string = Message( @@ -302,12 +305,14 @@ def test_function_result_falsy_values_handling(openai_unit_test_env: dict[str, s assert len(openai_messages) == 1 assert openai_messages[0]["content"] == "" # Empty string should be preserved - # Test with False (falsy but not None) - message_with_false = Message(role="tool", contents=[Content.from_function_result(call_id="call-789", result=False)]) + # Test with False serialized as JSON string (as FunctionTool.invoke would produce) + message_with_false = Message( + role="tool", contents=[Content.from_function_result(call_id="call-789", result="false")] + ) openai_messages = client._prepare_message_for_openai(message_with_false) assert len(openai_messages) == 1 - assert openai_messages[0]["content"] == "false" # False should be JSON serialized + assert openai_messages[0]["content"] == "false" # False JSON string def test_function_result_exception_handling(openai_unit_test_env: dict[str, str]): @@ -332,9 +337,11 @@ def test_function_result_exception_handling(openai_unit_test_env: dict[str, str] assert openai_messages[0]["tool_call_id"] == "call-123" -def test_prepare_function_call_results_string_passthrough(): +def test_parse_result_string_passthrough(): """Test that string values are passed through directly without JSON encoding.""" - result = prepare_function_call_results("simple string") + from agent_framework import FunctionTool + + result = FunctionTool.parse_result("simple string") assert result == "simple string" assert isinstance(result, str) diff --git a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py index d1475f04a3..f8e60c9f3e 100644 --- a/python/packages/github_copilot/agent_framework_github_copilot/_agent.py +++ b/python/packages/github_copilot/agent_framework_github_copilot/_agent.py @@ -499,7 +499,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]): return copilot_tools - def _tool_to_copilot_tool(self, ai_func: FunctionTool[Any, Any]) -> CopilotTool: + def _tool_to_copilot_tool(self, ai_func: FunctionTool[Any]) -> CopilotTool: """Convert an FunctionTool to a Copilot SDK tool.""" async def handler(invocation: ToolInvocation) -> ToolResult: diff --git a/python/packages/lab/tau2/agent_framework_lab_tau2/_tau2_utils.py b/python/packages/lab/tau2/agent_framework_lab_tau2/_tau2_utils.py index b785eae6d7..42d03393f8 100644 --- a/python/packages/lab/tau2/agent_framework_lab_tau2/_tau2_utils.py +++ b/python/packages/lab/tau2/agent_framework_lab_tau2/_tau2_utils.py @@ -27,7 +27,7 @@ from tau2.environment.tool import Tool # type: ignore[import-untyped] _original_set_state = Environment.set_state -def convert_tau2_tool_to_function_tool(tau2_tool: Tool) -> FunctionTool[Any, Any]: +def convert_tau2_tool_to_function_tool(tau2_tool: Tool) -> FunctionTool[Any]: """Convert a tau2 Tool to a FunctionTool for agent framework compatibility. Creates a wrapper that preserves the tool's interface while ensuring diff --git a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py index e574528395..367855e4c2 100644 --- a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py +++ b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py @@ -325,7 +325,7 @@ class HandoffAgentExecutor(AgentExecutor): existing_tools = list(default_options.get("tools") or []) existing_names = {getattr(tool, "name", "") for tool in existing_tools if hasattr(tool, "name")} - new_tools: list[FunctionTool[Any, Any]] = [] + new_tools: list[FunctionTool[Any]] = [] for target in targets: handoff_tool = self._create_handoff_tool(target.target_id, target.description) if handoff_tool.name in existing_names: @@ -341,7 +341,7 @@ class HandoffAgentExecutor(AgentExecutor): else: default_options["tools"] = existing_tools - def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool[Any, Any]: + def _create_handoff_tool(self, target_id: str, description: str | None = None) -> FunctionTool[Any]: """Construct the synthetic handoff tool that signals routing to `target_id`.""" tool_name = get_handoff_tool_name(target_id) doc = description or f"Handoff to the {target_id} agent." diff --git a/python/uv.lock b/python/uv.lock index ff7328ebf1..bab915d16b 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -84,14 +84,14 @@ wheels = [ [[package]] name = "ag-ui-protocol" -version = "0.1.10" +version = "0.1.11" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/bb/5a5ec893eea5805fb9a3db76a9888c3429710dfb6f24bbb37568f2cf7320/ag_ui_protocol-0.1.10.tar.gz", hash = "sha256:3213991c6b2eb24bb1a8c362ee270c16705a07a4c5962267a083d0959ed894f4", size = 6945, upload-time = "2025-11-06T15:17:17.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/c1/33ab11dc829c6c28d0d346988b2f394aa632d3ad63d1d2eb5f16eccd769b/ag_ui_protocol-0.1.11.tar.gz", hash = "sha256:b336dfebb5751e9cc2c676a3008a4bce4819004e6f6f8cba73169823564472ae", size = 6249, upload-time = "2026-02-11T12:41:36.085Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/78/eb55fabaab41abc53f52c0918a9a8c0f747807e5306273f51120fd695957/ag_ui_protocol-0.1.10-py3-none-any.whl", hash = "sha256:c81e6981f30aabdf97a7ee312bfd4df0cd38e718d9fc10019c7d438128b93ab5", size = 7889, upload-time = "2025-11-06T15:17:15.325Z" }, + { url = "https://files.pythonhosted.org/packages/14/83/5c6f4cb24d27d9cbe0c31ba2f3b4d1ff42bc6f87ba9facfa9e9d44046c6b/ag_ui_protocol-0.1.11-py3-none-any.whl", hash = "sha256:b0cc25570462a8eba8e57a098e0a2d6892a1f571a7bea7da2d4b60efd5d66789", size = 8392, upload-time = "2026-02-11T12:41:35.303Z" }, ] [[package]] @@ -1853,7 +1853,7 @@ wheels = [ [[package]] name = "fastapi" -version = "0.128.7" +version = "0.128.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -1862,9 +1862,9 @@ dependencies = [ { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/fc/af386750b3fd8d8828167e4c82b787a8eeca2eca5c5429c9db8bb7c70e04/fastapi-0.128.7.tar.gz", hash = "sha256:783c273416995486c155ad2c0e2b45905dedfaf20b9ef8d9f6a9124670639a24", size = 375325, upload-time = "2026-02-10T12:26:40.968Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/72/0df5c58c954742f31a7054e2dd1143bae0b408b7f36b59b85f928f9b456c/fastapi-0.128.8.tar.gz", hash = "sha256:3171f9f328c4a218f0a8d2ba8310ac3a55d1ee12c28c949650288aee25966007", size = 375523, upload-time = "2026-02-11T15:19:36.69Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/1a/f983b45661c79c31be575c570d46c437a5409b67a939c1b3d8d6b3ed7a7f/fastapi-0.128.7-py3-none-any.whl", hash = "sha256:6bd9bd31cb7047465f2d3fa3ba3f33b0870b17d4eaf7cdb36d1576ab060ad662", size = 103630, upload-time = "2026-02-10T12:26:39.414Z" }, + { url = "https://files.pythonhosted.org/packages/9f/37/37b07e276f8923c69a5df266bfcb5bac4ba8b55dfe4a126720f8c48681d1/fastapi-0.128.8-py3-none-any.whl", hash = "sha256:5618f492d0fe973a778f8fec97723f598aa9deee495040a8d51aaf3cf123ecf1", size = 103630, upload-time = "2026-02-11T15:19:35.209Z" }, ] [[package]] @@ -3897,7 +3897,7 @@ wheels = [ [[package]] name = "openai-agents" -version = "0.8.3" +version = "0.8.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -3908,9 +3908,9 @@ dependencies = [ { name = "types-requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/6b/f86002a00f16b387b0570860e461475660d81eb00e2817391926d3947933/openai_agents-0.8.3.tar.gz", hash = "sha256:07a6e900b0fe4b7fd8f91a06ed9ab4fec9df335ed676f1c9e1125f60cb57919b", size = 2378346, upload-time = "2026-02-10T00:11:07.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/e0/9fa9eac9baf2816bc63cee28967d35a7ed9dc2f25e9fd2004f48ed6c8820/openai_agents-0.8.4.tar.gz", hash = "sha256:5d4c4861aedd56a82b15c6ddf6c53031a39859a222f08bbd5645d5967efa05e8", size = 2389744, upload-time = "2026-02-11T19:14:30.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/38/d77602daf5308395ee067954ffa7e96cb9ecf9292ad3b5f398f1c77e0b36/openai_agents-0.8.3-py3-none-any.whl", hash = "sha256:e562ec1a70177abaa34ca6f0428241a9dbeb6b3d73f88a7f4ba3ee3d72b3b98d", size = 378042, upload-time = "2026-02-10T00:11:04.967Z" }, + { url = "https://files.pythonhosted.org/packages/55/dc/10df015aebb0797a8367aab65200ac4f5221df20bbae76930f5b6ac8e001/openai_agents-0.8.4-py3-none-any.whl", hash = "sha256:2383c6e8e59ed4146b89d1b6f53e34e55caf94bc14ae3fd704e7aad5021f4ff1", size = 380662, upload-time = "2026-02-11T19:14:28.864Z" }, ] [[package]] @@ -4539,7 +4539,7 @@ wheels = [ [[package]] name = "posthog" -version = "7.8.5" +version = "7.8.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -4549,9 +4549,9 @@ dependencies = [ { name = "six", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b0/10/8e74a5e997c8286f0b63c69da522e503b1ab11627217ab76a06c7b62d647/posthog-7.8.5.tar.gz", hash = "sha256:e4f3796ce18323d8e05139bf419a04d318ccc4ad77b210f4d9d7c7546aea4f35", size = 169117, upload-time = "2026-02-09T22:59:49.207Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/c9/a7c67c039f23f16a0b87d17561ba2a1c863b01f054a226c92437c539a7b6/posthog-7.8.6.tar.gz", hash = "sha256:6f67e18b5f19bf20d7ef2e1a80fa1ad879a5cd309ca13cfb300f45a8105968c4", size = 169304, upload-time = "2026-02-11T13:59:42.558Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/b3/59b61d4b90e2efd138abaa34d98c7a89a4a352850cc3a079a60a46780655/posthog-7.8.5-py3-none-any.whl", hash = "sha256:979d306f07e61a8e837746e5dc432aafc49827fecac91bd6c624dcf3a1967448", size = 194647, upload-time = "2026-02-09T22:59:47.744Z" }, + { url = "https://files.pythonhosted.org/packages/56/c7/41664398a838f52ddfc89141e4c38b88eaa01b9e9a269c5ac184bd8586c6/posthog-7.8.6-py3-none-any.whl", hash = "sha256:21809f73e8e8f09d2bc273b09582f1a9f997b66f51fc626ef5bd3c5bdffd8bcd", size = 194801, upload-time = "2026-02-11T13:59:41.26Z" }, ] [[package]] @@ -6464,16 +6464,30 @@ wheels = [ ] [[package]] -name = "typer-slim" -version = "0.21.2" +name = "typer" +version = "0.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "shellingham", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/ca/0d9d822fd8a4c7e830cba36a2557b070d4b4a9558a0460377a61f8fb315d/typer_slim-0.21.2.tar.gz", hash = "sha256:78f20d793036a62aaf9c3798306142b08261d4b2a941c6e463081239f062a2f9", size = 120497, upload-time = "2026-02-10T19:33:45.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/e6/44e073787aa57cd71c151f44855232feb0f748428fd5242d7366e3c4ae8b/typer-0.23.0.tar.gz", hash = "sha256:d8378833e47ada5d3d093fa20c4c63427cc4e27127f6b349a6c359463087d8cc", size = 120181, upload-time = "2026-02-11T15:22:18.637Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/03/e09325cfc40a33a82b31ba1a3f1d97e85246736856a45a43b19fcb48b1c2/typer_slim-0.21.2-py3-none-any.whl", hash = "sha256:4705082bb6c66c090f60e47c8be09a93158c139ce0aa98df7c6c47e723395e5f", size = 56790, upload-time = "2026-02-10T19:33:47.221Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ed/d6fca788b51d0d4640c4bc82d0e85bad4b49809bca36bf4af01b4dcb66a7/typer-0.23.0-py3-none-any.whl", hash = "sha256:79f4bc262b6c37872091072a3cb7cb6d7d79ee98c0c658b4364bdcde3c42c913", size = 56668, upload-time = "2026-02-11T15:22:21.075Z" }, +] + +[[package]] +name = "typer-slim" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/8a/881cfd399a119db89619dc1b93d36e2fb6720ddb112bceff41203f1abd72/typer_slim-0.23.0.tar.gz", hash = "sha256:be8b60243df27cfee444c6db1b10a85f4f3e54d940574f31a996f78aa35a8254", size = 4773, upload-time = "2026-02-11T15:22:19.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/3e/ba3a222c80ee070d9497ece3e1fe77253c142925dd4c90f04278aac0a9eb/typer_slim-0.23.0-py3-none-any.whl", hash = "sha256:1d693daf22d998a7b1edab8413cdcb8af07254154ce3956c1664dc11b01e2f8b", size = 3399, upload-time = "2026-02-11T15:22:17.792Z" }, ] [[package]]