diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 40b95738aa..a0b980cca1 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -22,7 +22,7 @@
-
+
@@ -37,7 +37,7 @@
-
+
@@ -52,9 +52,9 @@
-
+
+
-
@@ -81,12 +81,12 @@
-
+
-
+
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
index 4522e79be2..d08e3e24e6 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
@@ -3,6 +3,7 @@
// This sample shows how to create and use a simple AI agent with Azure OpenAI as the backend, to produce structured output using JSON schema from a class.
using System;
+using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.AI.OpenAI;
@@ -20,10 +21,7 @@ ChatClientAgentOptions agentOptions = new(name: "HelpfulAssistant", instructions
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(
- schema: AIJsonUtilities.CreateJsonSchema(typeof(PersonInfo)),
- schemaName: "PersonInfo",
- schemaDescription: "Information about a person including their name, age, and occupation")
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
};
@@ -62,6 +60,7 @@ namespace SampleApp
///
/// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
///
+ [Description("Information about a person including their name, age, and occupation")]
public class PersonInfo
{
[JsonPropertyName("name")]
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs
index 0286b67542..fc3ebd0436 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Program.cs
@@ -15,7 +15,7 @@ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? th
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
// Create an MCPClient for the GitHub server
-await using var mcpClient = await McpClientFactory.CreateAsync(new StdioClientTransport(new()
+await using var mcpClient = await McpClient.CreateAsync(new StdioClientTransport(new()
{
Name = "MCPServer",
Command = "npx",
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 4b5dff62f7..59691f6c2f 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Program.cs
@@ -32,20 +32,20 @@ var consoleLoggerFactory = LoggerFactory.Create(builder => builder.AddConsole())
// Create SSE client transport for the MCP server
var serverUrl = "http://localhost:7071/";
-var transport = new SseClientTransport(new()
+var transport = new HttpClientTransport(new()
{
Endpoint = new Uri(serverUrl),
Name = "Secure Weather Client",
OAuth = new()
{
- ClientName = "ProtectedMcpClient",
+ ClientId = "ProtectedMcpClient",
RedirectUri = new Uri("http://localhost:1179/callback"),
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
}
}, httpClient, consoleLoggerFactory);
// Create an MCPClient for the protected MCP server
-await using var mcpClient = await McpClientFactory.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
+await using var mcpClient = await McpClient.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
// Retrieve the list of tools available on the GitHub server
var mcpTools = await mcpClient.ListToolsAsync().ConfigureAwait(false);
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
index 932e8255ed..3f91950175 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
@@ -128,7 +128,7 @@ internal sealed class SloganWriterExecutor
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(SloganResult)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
};
@@ -199,7 +199,7 @@ internal sealed class FeedbackExecutor : ReflectingExecutor, I
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(FeedbackResult)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
};
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
index 6610c62a95..0363325545 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
@@ -92,7 +92,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(DetectionResult)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -105,7 +105,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(EmailResponse)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
}
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
index fb9d1cfe26..13c9cef7f1 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
@@ -107,7 +107,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(DetectionResult)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -120,7 +120,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(EmailResponse)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
}
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
index 8757ff6cb6..8030e0d715 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
@@ -148,7 +148,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(AnalysisResult)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -161,7 +161,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(EmailResponse)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -174,7 +174,7 @@ public static class Program
{
ChatOptions = new()
{
- ResponseFormat = ChatResponseFormat.ForJsonSchema(AIJsonUtilities.CreateJsonSchema(typeof(EmailSummary)))
+ ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
}
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/NewPersistentAgentsChatClient.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/NewPersistentAgentsChatClient.cs
deleted file mode 100644
index d355a7c9e9..0000000000
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/NewPersistentAgentsChatClient.cs
+++ /dev/null
@@ -1,715 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-#pragma warning disable CA1852 // Use sealed class
-#pragma warning disable IDE0161 // Convert to file-scoped namespace
-#pragma warning disable CA1063 // Implement IDisposable Correctly
-#pragma warning disable CA1816 // Implement IDisposable Correctly
-
-// Proposal for a new Persistent Agents Chat Client code based on the Azure.AI.Agents.Persistent library.
-// Source: https://raw.githubusercontent.com/Azure/azure-sdk-for-net/0497c087147/sdk/ai/Azure.AI.Agents.Persistent/src/Custom/PersistentAgentsChatClient.cs
-
-#nullable enable
-
-using System.Collections;
-using System.Runtime.CompilerServices;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using Microsoft.Extensions.AI;
-
-namespace Azure.AI.Agents.Persistent
-{
- /// Represents an for an Azure.AI.Agents.Persistent .
- internal partial class NewPersistentAgentsChatClient : IChatClient
- {
- /// The name of the chat client provider.
- private const string ProviderName = "azure";
-
- /// The underlying .
- private readonly PersistentAgentsClient? _client;
-
- /// Metadata for the client.
- private readonly ChatClientMetadata? _metadata;
-
- /// The ID of the agent to use.
- private readonly string? _agentId;
-
- /// The thread ID to use if none is supplied in .
- private readonly string? _defaultThreadId;
-
- /// Lazily-retrieved agent instance. Used for its properties.
- private PersistentAgent? _agent;
-
- /// Initializes a new instance of the class for the specified .
- public NewPersistentAgentsChatClient(PersistentAgentsClient client, string agentId, string? defaultThreadId = null)
- {
- Argument.AssertNotNull(client, nameof(client));
- Argument.AssertNotNullOrWhiteSpace(agentId, nameof(agentId));
-
- _client = client;
- _agentId = agentId;
- _defaultThreadId = defaultThreadId;
-
- _metadata = new(ProviderName);
- }
-
- ///
- public virtual object? GetService(Type serviceType, object? serviceKey = null) =>
- serviceType is null ? throw new ArgumentNullException(nameof(serviceType)) :
- serviceKey is not null ? null :
- serviceType == typeof(ChatClientMetadata) ? _metadata :
- serviceType == typeof(PersistentAgentsClient) ? _client :
- serviceType.IsInstanceOfType(this) ? this :
- null;
-
- ///
- public virtual async Task GetResponseAsync(
- IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
- {
- // Changing the original implementation to provide a RawRepresentation as a list of RawRepresentations of the updates.
- // This wouldn't be needed if the API Change Proposal below is accepted:
- // https://github.com/dotnet/extensions/issues/6746
- var updates = await GetStreamingResponseAsync(messages, options, cancellationToken).ToListAsync(cancellationToken).ConfigureAwait(false);
- var response = updates.ToChatResponse();
-
- // Expose all the raw representations of the updates.
- response.RawRepresentation = updates.Select(u => u.RawRepresentation).ToArray();
- return response;
- }
-
- ///
- public virtual async IAsyncEnumerable GetStreamingResponseAsync(
- IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- Argument.AssertNotNull(messages, nameof(messages));
-
- // Extract necessary state from messages and options.
- (ThreadAndRunOptions runOptions, List? toolResults) =
- await CreateRunOptionsAsync(messages, options, cancellationToken).ConfigureAwait(false);
-
- // Get the thread ID.
- string? threadId = options?.ConversationId ?? _defaultThreadId;
- if (threadId is null && toolResults is not null)
- {
- throw new ArgumentException("No thread ID was provided, but chat messages includes tool results.", nameof(messages));
- }
-
- // Get any active run ID for this thread.
- ThreadRun? threadRun = null;
- if (threadId is not null)
- {
- await foreach (ThreadRun? run in _client!.Runs.GetRunsAsync(threadId, limit: 1, ListSortOrder.Descending, cancellationToken: cancellationToken).ConfigureAwait(false))
- {
- if (run.Status != RunStatus.Completed && run.Status != RunStatus.Cancelled && run.Status != RunStatus.Failed && run.Status != RunStatus.Expired)
- {
- threadRun = run;
- break;
- }
- }
- }
-
- // Submit the request.
- IAsyncEnumerable updates;
- if (toolResults is not null &&
- threadRun is not null &&
- ConvertFunctionResultsToToolOutput(toolResults, out List? toolOutputs) is { } toolRunId &&
- toolRunId == threadRun.Id)
- {
- // There's an active run and we have tool results to submit, so submit the results and continue streaming.
- // This is going to ignore any additional messages in the run options, as we are only submitting tool outputs,
- // but there doesn't appear to be a way to submit additional messages, and having such additional messages is rare.
- updates = _client!.Runs.SubmitToolOutputsToStreamAsync(threadRun, toolOutputs, cancellationToken);
- }
- else
- {
- if (threadId is null)
- {
- // No thread ID was provided, so create a new thread.
- PersistentAgentThread thread = await _client!.Threads.CreateThreadAsync(runOptions.ThreadOptions.Messages, runOptions.ToolResources, runOptions.Metadata, cancellationToken).ConfigureAwait(false);
- runOptions.ThreadOptions.Messages.Clear();
- threadId = thread.Id;
- }
- else if (threadRun is not null)
- {
- // There was an active run; we need to cancel it before starting a new run.
- await _client!.Runs.CancelRunAsync(threadId, threadRun.Id, cancellationToken).ConfigureAwait(false);
- }
-
- // Now create a new run and stream the results.
- CreateRunStreamingOptions opts = new()
- {
- OverrideModelName = runOptions.OverrideModelName,
- OverrideInstructions = runOptions.OverrideInstructions,
- AdditionalInstructions = null,
- AdditionalMessages = runOptions.ThreadOptions.Messages,
- OverrideTools = runOptions.OverrideTools,
- ToolResources = runOptions.ToolResources,
- Temperature = runOptions.Temperature,
- TopP = runOptions.TopP,
- MaxPromptTokens = runOptions.MaxPromptTokens,
- MaxCompletionTokens = runOptions.MaxCompletionTokens,
- TruncationStrategy = runOptions.TruncationStrategy,
- ToolChoice = runOptions.ToolChoice,
- ResponseFormat = runOptions.ResponseFormat,
- ParallelToolCalls = runOptions.ParallelToolCalls,
- Metadata = runOptions.Metadata
- };
-
- // This method added for compatibility, before the include parameter support was enabled.
- updates = _client!.Runs.CreateRunStreamingAsync(
- threadId: threadId,
- agentId: _agentId,
- options: opts,
- cancellationToken: cancellationToken
- );
- }
-
- // Process each update.
- string? responseId = null;
- await foreach (StreamingUpdate? update in updates.ConfigureAwait(false))
- {
- switch (update)
- {
- case ThreadUpdate tu:
- threadId ??= tu.Value.Id;
- goto default;
-
- case RunUpdate ru:
- threadId ??= ru.Value.ThreadId;
- responseId ??= ru.Value.Id;
-
- ChatResponseUpdate ruUpdate = new()
- {
- AuthorName = ru.Value.AssistantId,
- ConversationId = threadId,
- CreatedAt = ru.Value.CreatedAt,
- MessageId = responseId,
- ModelId = ru.Value.Model,
- RawRepresentation = ru,
- ResponseId = responseId,
- Role = ChatRole.Assistant,
- };
-
- if (ru.Value.Usage is { } usage)
- {
- ruUpdate.Contents.Add(new UsageContent(new()
- {
- InputTokenCount = usage.PromptTokens,
- OutputTokenCount = usage.CompletionTokens,
- TotalTokenCount = usage.TotalTokens,
- }));
- }
-
- if (ru is RequiredActionUpdate rau && rau.ToolCallId is string toolCallId && rau.FunctionName is string functionName)
- {
- ruUpdate.Contents.Add(
- new FunctionCallContent(
- JsonSerializer.Serialize([ru.Value.Id, toolCallId], AgentsChatClientJsonContext.Default.StringArray),
- functionName,
- JsonSerializer.Deserialize(rau.FunctionArguments, AgentsChatClientJsonContext.Default.IDictionaryStringObject)!));
- }
-
- yield return ruUpdate;
- break;
-
- case MessageContentUpdate mcu:
- ChatResponseUpdate textUpdate = new(mcu.Role == MessageRole.User ? ChatRole.User : ChatRole.Assistant, mcu.Text)
- {
- AuthorName = _agentId,
- ConversationId = threadId,
- MessageId = responseId,
- RawRepresentation = mcu,
- ResponseId = responseId,
- };
-
- // Add any annotations from the text update. The OpenAI Assistants API does not support passing these back
- // into the model (MessageContent.FromXx does not support providing annotations), so they end up being one way and are dropped
- // on subsequent requests.
- if (mcu.TextAnnotation is { } tau)
- {
- string? fileId = null;
- string? toolName = null;
- if (!string.IsNullOrWhiteSpace(tau.InputFileId))
- {
- fileId = tau.InputFileId;
- toolName = "file_search";
- }
- else if (!string.IsNullOrWhiteSpace(tau.OutputFileId))
- {
- fileId = tau.OutputFileId;
- toolName = "code_interpreter";
- }
-
- if (fileId is not null)
- {
- if (textUpdate.Contents.Count == 0)
- {
- // In case a chunk doesn't have text content, create one with empty text to hold the annotation.
- textUpdate.Contents.Add(new TextContent(string.Empty));
- }
-
- (((TextContent)textUpdate.Contents[0]).Annotations ??= []).Add(new CitationAnnotation
- {
- RawRepresentation = tau,
- AnnotatedRegions = [new TextSpanAnnotatedRegion { StartIndex = tau.StartIndex, EndIndex = tau.EndIndex }],
- FileId = fileId,
- ToolName = toolName,
- });
- }
- }
-
- yield return textUpdate;
- break;
-
- default:
- yield return new ChatResponseUpdate
- {
- AuthorName = _agentId,
- ConversationId = threadId,
- MessageId = responseId,
- RawRepresentation = update,
- ResponseId = responseId,
- Role = ChatRole.Assistant,
- };
- break;
- }
- }
- }
-
- ///
- public void Dispose() { }
-
- ///
- /// Creates the to use for the request and extracts any function result contents
- /// that need to be submitted as tool results.
- ///
- private async ValueTask<(ThreadAndRunOptions RunOptions, List? ToolResults)> CreateRunOptionsAsync(
- IEnumerable messages, ChatOptions? options, CancellationToken cancellationToken)
- {
- // Create the options instance to populate, either a fresh or using one the caller provides.
- ThreadAndRunOptions runOptions =
- options?.RawRepresentationFactory?.Invoke(this) as ThreadAndRunOptions ??
- new();
-
- // Load details about the agent if not already loaded.
- if (_agent is null)
- {
- PersistentAgent agent = await _client!.Administration.GetAgentAsync(_agentId, cancellationToken).ConfigureAwait(false);
- Interlocked.CompareExchange(ref _agent, agent, null);
- }
-
- // Populate the run options from the ChatOptions, if provided.
- if (options is not null)
- {
- runOptions.OverrideInstructions ??= options.Instructions ?? _agent.Instructions;
- runOptions.MaxCompletionTokens ??= options.MaxOutputTokens;
- runOptions.OverrideModelName ??= options.ModelId;
- runOptions.TopP ??= options.TopP;
- runOptions.Temperature ??= options.Temperature;
- runOptions.ParallelToolCalls ??= options.AllowMultipleToolCalls;
- // Ignored: options.TopK, options.FrequencyPenalty, options.Seed, options.StopSequences
-
- if (options.Tools is { Count: > 0 } tools)
- {
- List toolDefinitions = [];
- ToolResources? toolResources = null;
-
- // If the caller has provided any tool overrides, we'll assume they don't want to use the agent's tools.
- // But if they haven't, the only way we can provide our tools is via an override, whereas we'd really like to
- // just add them. To handle that, we'll get all of the agent's tools and add them to the override list
- // along with our tools.
- if (runOptions.OverrideTools?.Any() is not true)
- {
- toolDefinitions.AddRange(_agent.Tools);
- }
-
- // The caller can provide tools in the supplied ThreadAndRunOptions.
- if (runOptions.OverrideTools is not null)
- {
- toolDefinitions.AddRange(runOptions.OverrideTools);
- }
-
- // Now add the tools from ChatOptions.Tools.
- foreach (AITool tool in tools)
- {
- switch (tool)
- {
- case AIFunction aiFunction:
- toolDefinitions.Add(new FunctionToolDefinition(
- aiFunction.Name,
- aiFunction.Description,
- BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(aiFunction.JsonSchema, AgentsChatClientJsonContext.Default.JsonElement))));
- break;
-
- case HostedCodeInterpreterTool codeTool:
- toolDefinitions.Add(new CodeInterpreterToolDefinition());
-
- if (codeTool.Inputs is { Count: > 0 })
- {
- foreach (var input in codeTool.Inputs)
- {
- switch (input)
- {
- case HostedFileContent hostedFile:
- // If the input is a HostedFileContent, we can use its ID directly.
- (toolResources ??= new() { CodeInterpreter = new() }).CodeInterpreter.FileIds.Add(hostedFile.FileId);
- break;
- }
- }
- }
- break;
-
- case HostedFileSearchTool fileSearchTool:
- toolDefinitions.Add(new FileSearchToolDefinition()
- {
- FileSearch = new() { MaxNumResults = fileSearchTool.MaximumResultCount }
- });
-
- if (fileSearchTool.Inputs is { Count: > 0 })
- {
- foreach (var input in fileSearchTool.Inputs)
- {
- switch (input)
- {
- case HostedVectorStoreContent hostedVectorStore:
- (toolResources ??= new() { FileSearch = new() }).FileSearch.VectorStoreIds.Add(hostedVectorStore.VectorStoreId);
- break;
- }
- }
- }
- break;
-
- case HostedWebSearchTool webSearch when webSearch.AdditionalProperties?.TryGetValue("connectionId", out object? connectionId) is true:
- toolDefinitions.Add(new BingGroundingToolDefinition(new BingGroundingSearchToolParameters([new BingGroundingSearchConfiguration(connectionId!.ToString())])));
- break;
- }
- }
-
- if (toolDefinitions.Count > 0)
- {
- runOptions.OverrideTools = toolDefinitions;
- }
-
- if (toolResources is not null)
- {
- runOptions.ToolResources = toolResources;
- }
- }
-
- // Store the tool mode, if relevant.
- if (runOptions.ToolChoice is null)
- {
- switch (options.ToolMode)
- {
- case NoneChatToolMode:
- runOptions.ToolChoice = BinaryData.FromString("\"none\"");
- break;
-
- case RequiredChatToolMode required:
- runOptions.ToolChoice = required.RequiredFunctionName is string functionName ?
- BinaryData.FromString($$"""{"type": "function", "function": {"name": "{{functionName}}"} }""") :
- BinaryData.FromString("required");
- break;
- case AutoChatToolMode:
- runOptions.ToolChoice = BinaryData.FromString("\"auto\"");
- break;
- }
- }
-
- // Store the response format, if relevant.
- if (runOptions.ResponseFormat is null)
- {
- if (options.ResponseFormat is ChatResponseFormatJson jsonFormat)
- {
- if (jsonFormat.Schema is JsonElement schema)
- {
- var schemaNode = JsonSerializer.SerializeToNode(schema, AgentsChatClientJsonContext.Default.JsonElement)!;
-
- var jsonSchemaObject = new JsonObject
- {
- ["schema"] = schemaNode
- };
-
- if (jsonFormat.SchemaName is not null)
- {
- jsonSchemaObject["name"] = jsonFormat.SchemaName;
- }
- if (jsonFormat.SchemaDescription is not null)
- {
- jsonSchemaObject["description"] = jsonFormat.SchemaDescription;
- }
-
- runOptions.ResponseFormat =
- BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(new()
- {
- ["type"] = "json_schema",
- ["json_schema"] = jsonSchemaObject,
- }, AgentsChatClientJsonContext.Default.JsonObject));
- }
- else
- {
- runOptions.ResponseFormat = BinaryData.FromString("""{ "type": "json_object" }""");
- }
- }
- else if (options.ResponseFormat is ChatResponseFormatText)
- {
- runOptions.ResponseFormat = BinaryData.FromString("""{ "type": "text" }""");
- }
- }
- }
-
- // Process ChatMessages. System messages are turned into additional instructions.
- // All other messages are added 1:1, treating assistant messages as agent messages
- // and everything else as user messages.
- StringBuilder? instructions = null;
- List? functionResults = null;
-
- runOptions.ThreadOptions ??= new();
-
- bool treatInstructionsAsOverride = false;
- if (runOptions.OverrideInstructions is not null)
- {
- treatInstructionsAsOverride = true;
- (instructions ??= new()).Append(runOptions.OverrideInstructions);
- }
-
- if (options?.Instructions is not null)
- {
- (instructions ??= new()).Append(options.Instructions);
- }
-
- foreach (ChatMessage chatMessage in messages)
- {
- List messageContents = [];
-
- if (chatMessage.Role == ChatRole.System ||
- chatMessage.Role == new ChatRole("developer"))
- {
- instructions ??= new();
- foreach (TextContent textContent in chatMessage.Contents.OfType())
- {
- _ = instructions.Append(textContent);
- }
-
- continue;
- }
-
- foreach (AIContent content in chatMessage.Contents)
- {
- switch (content)
- {
- case TextContent text:
- messageContents.Add(new MessageInputTextBlock(text.Text));
- break;
-
- case DataContent image when image.HasTopLevelMediaType("image"):
- messageContents.Add(new MessageInputImageUriBlock(new MessageImageUriParam(image.Uri)));
- break;
-
- case UriContent image when image.HasTopLevelMediaType("image"):
- messageContents.Add(new MessageInputImageUriBlock(new MessageImageUriParam(image.Uri.AbsoluteUri)));
- break;
-
- case FunctionResultContent result:
- (functionResults ??= []).Add(result);
- break;
-
- default:
- if (content.RawRepresentation is MessageInputContentBlock rawContent)
- {
- messageContents.Add(rawContent);
- }
- break;
- }
- }
-
- if (messageContents.Count > 0)
- {
- runOptions.ThreadOptions.Messages.Add(new ThreadMessageOptions(
- chatMessage.Role == ChatRole.Assistant ? MessageRole.Agent : MessageRole.User,
- messageContents));
- }
- }
-
- if (instructions is not null)
- {
- // If runOptions.OverrideInstructions was set by the caller, then all instructions are treated
- // as an override. Otherwise, we want all of the instructions to augment the agent's instructions,
- // so insert the agent's at the beginning.
- if (!treatInstructionsAsOverride && !string.IsNullOrEmpty(_agent.Instructions))
- {
- instructions.Insert(0, _agent.Instructions);
- }
-
- runOptions.OverrideInstructions = instructions.ToString();
- }
-
- return (runOptions, functionResults);
- }
-
- /// Convert instances to instances.
- /// The tool results to process.
- /// The generated list of tool outputs, if any could be created.
- /// The run ID associated with the corresponding function call requests.
- private static string? ConvertFunctionResultsToToolOutput(List? toolResults, out List? toolOutputs)
- {
- string? runId = null;
- toolOutputs = null;
- if (toolResults?.Count > 0)
- {
- foreach (FunctionResultContent frc in toolResults)
- {
- // When creating the FunctionCallContext, we created it with a CallId == [runId, callId].
- // We need to extract the run ID and ensure that the ToolOutput we send back to Azure
- // is only the call ID.
- string[]? runAndCallIDs;
- try
- {
- runAndCallIDs = JsonSerializer.Deserialize(frc.CallId, AgentsChatClientJsonContext.Default.StringArray);
- }
- catch
- {
- continue;
- }
-
- if (runAndCallIDs is null ||
- runAndCallIDs.Length != 2 ||
- string.IsNullOrWhiteSpace(runAndCallIDs[0]) || // run ID
- string.IsNullOrWhiteSpace(runAndCallIDs[1]) || // call ID
- (runId is not null && runId != runAndCallIDs[0]))
- {
- continue;
- }
-
- runId = runAndCallIDs[0];
- (toolOutputs ??= []).Add(new(runAndCallIDs[1], frc.Result?.ToString() ?? string.Empty));
- }
- }
-
- return runId;
- }
-
- [JsonSerializable(typeof(JsonElement))]
- [JsonSerializable(typeof(JsonNode))]
- [JsonSerializable(typeof(JsonObject))]
- [JsonSerializable(typeof(string[]))]
- [JsonSerializable(typeof(IDictionary))]
- private sealed partial class AgentsChatClientJsonContext : JsonSerializerContext;
- }
-
- internal static class Argument
- {
- public static void AssertNotNull(T value, string name)
- {
- if (value is null)
- {
- throw new ArgumentNullException(name);
- }
- }
-
- public static void AssertNotNull(T? value, string name)
- where T : struct
- {
- if (!value.HasValue)
- {
- throw new ArgumentNullException(name);
- }
- }
-
- public static void AssertNotNullOrEmpty(IEnumerable value, string name)
- {
- if (value is null)
- {
- throw new ArgumentNullException(name);
- }
- if (value is ICollection collectionOfT && collectionOfT.Count == 0)
- {
- throw new ArgumentException("Value cannot be an empty collection.", name);
- }
- if (value is ICollection collection && collection.Count == 0)
- {
- throw new ArgumentException("Value cannot be an empty collection.", name);
- }
- using IEnumerator e = value.GetEnumerator();
- if (!e.MoveNext())
- {
- throw new ArgumentException("Value cannot be an empty collection.", name);
- }
- }
-
- public static void AssertNotNullOrEmpty(string value, string name)
- {
- if (value is null)
- {
- throw new ArgumentNullException(name);
- }
- if (value.Length == 0)
- {
- throw new ArgumentException("Value cannot be an empty string.", name);
- }
- }
-
- public static void AssertNotNullOrWhiteSpace(string value, string name)
- {
- if (value is null)
- {
- throw new ArgumentNullException(name);
- }
- if (string.IsNullOrWhiteSpace(value))
- {
- throw new ArgumentException("Value cannot be empty or contain only white-space characters.", name);
- }
- }
-
- public static void AssertNotDefault(ref T value, string name)
- where T : struct, IEquatable
- {
- if (value.Equals(default))
- {
- throw new ArgumentException("Value cannot be empty.", name);
- }
- }
-
- public static void AssertInRange(T value, T minimum, T maximum, string name)
- where T : notnull, IComparable
- {
- if (minimum.CompareTo(value) > 0)
- {
- throw new ArgumentOutOfRangeException(name, "Value is less than the minimum allowed.");
- }
- if (maximum.CompareTo(value) < 0)
- {
- throw new ArgumentOutOfRangeException(name, "Value is greater than the maximum allowed.");
- }
- }
-
- public static void AssertEnumDefined(Type enumType, object value, string name)
- {
- if (!Enum.IsDefined(enumType, value))
- {
- throw new ArgumentException($"Value not defined for {enumType.FullName}.", name);
- }
- }
-
- public static T CheckNotNull(T value, string name)
- where T : class
- {
- AssertNotNull(value, name);
- return value;
- }
-
- public static string CheckNotNullOrEmpty(string value, string name)
- {
- AssertNotNullOrEmpty(value, name);
- return value;
- }
-
- public static void AssertNull(T value, string name, string? message = null)
- {
- if (value is not null)
- {
- throw new ArgumentException(message ?? "Value must be null.", name);
- }
- }
- }
-}
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentResponseExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentResponseExtensions.cs
index f7e443aed3..62eee83529 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentResponseExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentResponseExtensions.cs
@@ -48,7 +48,7 @@ internal static class PersistentAgentResponseExtensions
throw new ArgumentNullException(nameof(persistentAgentsClient));
}
- var chatClient = persistentAgentsClient.AsNewIChatClient(persistentAgentMetadata.Id);
+ var chatClient = persistentAgentsClient.AsIChatClient(persistentAgentMetadata.Id);
if (clientFactory is not null)
{
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs
index 108dd4f1eb..96b4d0524f 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/PersistentAgentsClientExtensions.cs
@@ -177,14 +177,4 @@ public static class PersistentAgentsClientExtensions
// Get a local proxy for the agent to work with.
return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken);
}
-
- ///
- /// Creates a new instance of an configured for the specified assistant.
- ///
- /// The instance used to initialize the chat client. Cannot be .
- /// The unique identifier of the assistant. Cannot be or whitespace.
- /// The optional default thread identifier for the chat client. Can be .
- /// A new instance configured with the specified assistant and optional default thread.
- public static IChatClient AsNewIChatClient(this PersistentAgentsClient client, string assistantId, string? defaultThreadId = null)
- => new NewPersistentAgentsChatClient(Argument.CheckNotNull(client, nameof(client)), Argument.CheckNotNullOrEmpty(assistantId, nameof(assistantId)), defaultThreadId);
}
diff --git a/dotnet/src/Microsoft.Agents.AI/FunctionInvocationDelegatingAgent.cs b/dotnet/src/Microsoft.Agents.AI/FunctionInvocationDelegatingAgent.cs
index daaa9fa6e7..eda230675c 100644
--- a/dotnet/src/Microsoft.Agents.AI/FunctionInvocationDelegatingAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI/FunctionInvocationDelegatingAgent.cs
@@ -33,7 +33,7 @@ internal sealed class FunctionInvocationDelegatingAgent : DelegatingAIAgent
if (options is ChatClientAgentRunOptions aco)
{
var originalFactory = aco.ChatClientFactory;
- aco.ChatClientFactory = (IChatClient chatClient) =>
+ aco.ChatClientFactory = chatClient =>
{
var builder = chatClient.AsBuilder();
@@ -44,9 +44,7 @@ internal sealed class FunctionInvocationDelegatingAgent : DelegatingAIAgent
return builder.ConfigureOptions(co
=> co.Tools = co.Tools?.Select(tool => tool is AIFunction aiFunction
- ? aiFunction is ApprovalRequiredAIFunction approvalRequiredAiFunction
- ? new ApprovalRequiredAIFunction(new MiddlewareEnabledFunction(this, approvalRequiredAiFunction, this._delegateFunc))
- : new MiddlewareEnabledFunction(this.InnerAgent, aiFunction, this._delegateFunc)
+ ? new MiddlewareEnabledFunction(this.InnerAgent, aiFunction, this._delegateFunc)
: tool)
.ToList())
.Build();
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
index bf2fdcf85d..42f643fb34 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
@@ -118,39 +118,6 @@ public sealed class PersistentAgentsClientExtensionsTests
Assert.Equal("persistentAgentsClient", exception.ParamName);
}
- ///
- /// Verify that AsNewIChatClient throws ArgumentNullException when client is null.
- ///
- [Fact]
- public void AsNewIChatClient_WithNullClient_ThrowsArgumentNullException()
- {
- // Act & Assert
- var exception = Assert.Throws(() =>
- ((PersistentAgentsClient)null!).AsNewIChatClient("test-agent"));
-
- Assert.Equal("client", exception.ParamName);
- }
-
- ///
- /// Verify that AsNewIChatClient throws ArgumentException when assistantId is null or empty.
- ///
- [Fact]
- public void AsNewIChatClient_WithNullOrEmptyAssistantId_ThrowsArgumentException()
- {
- // Arrange
- var mockClient = new Mock();
-
- // Act & Assert - null assistantId throws ArgumentNullException
- var exception1 = Assert.Throws(() =>
- mockClient.Object.AsNewIChatClient(null!));
- Assert.Equal("assistantId", exception1.ParamName);
-
- // Act & Assert - empty assistantId throws ArgumentException
- var exception2 = Assert.Throws(() =>
- mockClient.Object.AsNewIChatClient(""));
- Assert.Equal("assistantId", exception2.ParamName);
- }
-
///
/// Verify that GetAIAgent with clientFactory parameter correctly applies the factory.
///