Repository: Azure/azure-sdk-for-net
Pull request #49293: Support toolcall
Author: login: howieleung
Labels: name: AI Model Inference
name: AI Projects
Created: 2025-04-08T23:12:06Z | Merged: 2025-04-29T23:59:10Z

SDK now make toolcalls automatically using reflection.
Also pipeline fail because of it 
```
Error CS1705: Assembly 'Azure.Search.Documents' with identity 'Azure.Search.Documents, Version=11.7.0.0, Culture=neutral, PublicKeyToken=92742159e12e44c8' uses 'Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' which has a higher version than referenced assembly 'Microsoft.Bcl.AsyncInterfaces' with identity 'Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
```
I fixed the package reference.   But without specific version number, I get this error because the package version in Package.Data.prop is too old.   So I override it.   Without versionOverride, I get this error:

<img width="1132" alt="image" src="https://github.com/user-attachments/assets/6fc8bf0d-8aea-4ab1-ac79-a2ca06b5d9a1" />

And create an issue, https://github.com/Azure/azure-sdk-for-net/issues/49730




Diff:
diff --git a/eng/Packages.Data.props b/eng/Packages.Data.props
index cd1c25b3da38..31b08daace92 100644
--- a/eng/Packages.Data.props
+++ b/eng/Packages.Data.props
@@ -106,7 +106,7 @@
 		<PackageReference Update="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
 
     <!-- Azure SDK packages -->
-    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.2" />
+    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.4" />
     <PackageReference Update="Azure.AI.OpenAI" Version="2.0.0" />
     <PackageReference Update="Azure.AI.Projects" Version="1.0.0-beta.2" />
     <PackageReference Update="Azure.Communication.Identity" Version="1.3.1" />
diff --git a/sdk/ai/Azure.AI.Projects/README.md b/sdk/ai/Azure.AI.Projects/README.md
index abb227d1f929..358e294fcbc0 100644
--- a/sdk/ai/Azure.AI.Projects/README.md
+++ b/sdk/ai/Azure.AI.Projects/README.md
@@ -29,7 +29,8 @@ Use the AI Projects client library to:
     - [Code interpreter attachment](#create-message-with-code-interpreter-attachment)
     - [Create Agent with Bing Grounding](#create-agent-with-bing-grounding)
     - [Azure AI Search](#create-agent-with-azure-ai-search)
-    - [Function call](#function-call)
+    - [Function call Executed Manually](#function-call-executed-manually)
+    - [Function call Executed Automatically](#function-call-executed-automatically)
     - [Azure function Call](#azure-function-call)
     - [OpenAPI](#create-agent-with-openapi)
 - [Troubleshooting](#troubleshooting)
@@ -425,7 +426,7 @@ foreach (ThreadMessage threadMessage in messages)
 }
 ```
 
-#### Function call
+#### Function call executed manually
 
 Tools that reference caller-defined capabilities as functions can be provided to an agent to allow it to
 dynamically resolve and disambiguate during a run.
@@ -647,6 +648,63 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  After creating functions and`FunctionToolDefinition` according to the last section, here is the steps:
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "What's the weather like in my favorite city?");
+```
+
+Setup `AutoFunctionCallOptions`:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> toolDelegates = new();
+toolDelegates.Add(nameof(GetWeatherAtLocation), GetWeatherAtLocation);
+toolDelegates.Add(nameof(GetCityNickname), GetCityNickname);
+toolDelegates.Add(nameof(GetUserFavoriteCity), GetUserFavoriteCity);
+AutoFunctionCallOptions autoFunctionCallOptions = new(toolDelegates, 10);
+```
+
+With autoFunctionCallOptions as parameter for `CreateRunStreamingAsync`, the agent will then call the function automatically when it is needed:
+```C# Snippet:StreamingWithAutoFunctionCallAsync
+await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions))
+{
+    if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
+    {
+        Console.WriteLine("--- Run started! ---");
+    }
+    else if (streamingUpdate is MessageContentUpdate contentUpdate)
+    {
+        Console.Write(contentUpdate.Text);
+    }
+    else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+    {
+        Console.WriteLine();
+        Console.WriteLine("--- Run completed! ---");
+    }
+}
+```
+To allow the agent cast the parameters to the function call, you must use the supported argument types.  They are `string`, `int`, `ushort`, `float`, `uint`, `decimal`, `double`, `long`, and `bool`.   Other tpes such as array, dictionary, or classes are not supported.   
+
 #### Azure function call
 
 We can use Azure Function from inside the agent. In the example below we are calling function "foo", which responds "Bar". In this example we create `AzureFunctionToolDefinition` object, with the function name, description, input and output queues, followed by function parameters. See below for the instructions on function deployment.
diff --git a/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs b/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
index 358e601dd13c..f222838c4d82 100644
--- a/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
+++ b/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
@@ -207,8 +207,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(Azure.AI.Projects.AgentThread thread, Azure.AI.Projects.Agent agent, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> CreateRunAsync(string threadId, Azure.Core.RequestContent content, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, Azure.RequestContext context = null) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, bool? stream = default(bool?), float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
+        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
         public virtual Azure.Response CreateThread(Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual Azure.Response<Azure.AI.Projects.AgentThread> CreateThread(System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> messages = null, Azure.AI.Projects.ToolResources toolResources = null, System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual Azure.Response CreateThreadAndRun(Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
@@ -317,8 +317,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual Azure.Response SubmitToolOutputsToRun(string threadId, string runId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> SubmitToolOutputsToRunAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> SubmitToolOutputsToRunAsync(string threadId, string runId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
-        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStream(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStreamAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStream(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
+        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStreamAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
         public virtual Azure.Response UpdateAgent(string assistantId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual Azure.Response<Azure.AI.Projects.Agent> UpdateAgent(string assistantId, string model = null, string name = null, string description = null, string instructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> tools = null, Azure.AI.Projects.ToolResources toolResources = null, float? temperature = default(float?), float? topP = default(float?), System.BinaryData responseFormat = null, System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> UpdateAgentAsync(string assistantId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
@@ -629,6 +629,10 @@ public enum AuthenticationType
         Custom = 3,
         None = 4,
     }
+    public partial class AutoFunctionCallOptions
+    {
+        public AutoFunctionCallOptions(System.Collections.Generic.Dictionary<string, System.Delegate> toolDelegates, int maxRetry) { }
+    }
     [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
     public readonly partial struct AzureAISearchQueryType : System.IEquatable<Azure.AI.Projects.AzureAISearchQueryType>
     {
diff --git a/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs b/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs
index 358e601dd13c..f222838c4d82 100644
--- a/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs
+++ b/sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs
@@ -207,8 +207,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(Azure.AI.Projects.AgentThread thread, Azure.AI.Projects.Agent agent, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> CreateRunAsync(string threadId, Azure.Core.RequestContent content, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, Azure.RequestContext context = null) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, bool? stream = default(bool?), float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
+        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
         public virtual Azure.Response CreateThread(Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual Azure.Response<Azure.AI.Projects.AgentThread> CreateThread(System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> messages = null, Azure.AI.Projects.ToolResources toolResources = null, System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual Azure.Response CreateThreadAndRun(Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
@@ -317,8 +317,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual Azure.Response SubmitToolOutputsToRun(string threadId, string runId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> SubmitToolOutputsToRunAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> SubmitToolOutputsToRunAsync(string threadId, string runId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
-        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStream(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStreamAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStream(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
+        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> SubmitToolOutputsToStreamAsync(Azure.AI.Projects.ThreadRun run, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolOutput> toolOutputs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
         public virtual Azure.Response UpdateAgent(string assistantId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
         public virtual Azure.Response<Azure.AI.Projects.Agent> UpdateAgent(string assistantId, string model = null, string name = null, string description = null, string instructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> tools = null, Azure.AI.Projects.ToolResources toolResources = null, float? temperature = default(float?), float? topP = default(float?), System.BinaryData responseFormat = null, System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> UpdateAgentAsync(string assistantId, Azure.Core.RequestContent content, Azure.RequestContext context = null) { throw null; }
@@ -629,6 +629,10 @@ public enum AuthenticationType
         Custom = 3,
         None = 4,
     }
+    public partial class AutoFunctionCallOptions
+    {
+        public AutoFunctionCallOptions(System.Collections.Generic.Dictionary<string, System.Delegate> toolDelegates, int maxRetry) { }
+    }
     [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
     public readonly partial struct AzureAISearchQueryType : System.IEquatable<Azure.AI.Projects.AzureAISearchQueryType>
     {
diff --git a/sdk/ai/Azure.AI.Projects/assets.json b/sdk/ai/Azure.AI.Projects/assets.json
index ac03e328d289..1293b181cd6f 100644
--- a/sdk/ai/Azure.AI.Projects/assets.json
+++ b/sdk/ai/Azure.AI.Projects/assets.json
@@ -2,5 +2,5 @@
   "AssetsRepo": "Azure/azure-sdk-assets",
   "AssetsRepoPrefixPath": "net",
   "TagPrefix": "net/ai/Azure.AI.Projects",
-  "Tag": "net/ai/Azure.AI.Projects_0612f24f84"
+  "Tag": "net/ai/Azure.AI.Projects_2d64ec531c"
 }
diff --git a/sdk/ai/Azure.AI.Projects/samples/Sample12_FunctionsWithStreaming.md b/sdk/ai/Azure.AI.Projects/samples/Sample12_FunctionsWithStreaming.md
index fd79340ca0e3..5391f348f672 100644
--- a/sdk/ai/Azure.AI.Projects/samples/Sample12_FunctionsWithStreaming.md
+++ b/sdk/ai/Azure.AI.Projects/samples/Sample12_FunctionsWithStreaming.md
@@ -12,16 +12,16 @@ AgentsClient client = new(connectionString, new DefaultAzureCredential());
 2 Define three toy functions: `GetUserFavoriteCity`that always returns "Seattle, WA" and `GetCityNickname`, which will handle only "Seattle, WA" and will throw exception in response to other city names. The last function `GetWeatherAtLocation` returns weather at Seattle, WA. For each function we need to create `FunctionToolDefinition`, which defines function name, description and parameters.
 ```C# Snippet:FunctionsWithStreaming_DefineFunctionTools
 // Example of a function that defines no parameters
-string GetUserFavoriteCity() => "Seattle, WA";
-FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
+private string GetUserFavoriteCity() => "Seattle, WA";
+private FunctionToolDefinition getUserFavoriteCityTool = new("GetUserFavoriteCity", "Gets the user's favorite city.");
 // Example of a function with a single required parameter
-string GetCityNickname(string location) => location switch
+private string GetCityNickname(string location) => location switch
 {
     "Seattle, WA" => "The Emerald City",
     _ => throw new NotImplementedException(),
 };
-FunctionToolDefinition getCityNicknameTool = new(
-    name: "getCityNickname",
+private FunctionToolDefinition getCityNicknameTool = new(
+    name: "GetCityNickname",
     description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
     parameters: BinaryData.FromObjectAsJson(
         new
@@ -39,13 +39,13 @@ FunctionToolDefinition getCityNicknameTool = new(
         },
         new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
 // Example of a function with one required and one optional, enum parameter
-string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
+private string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
 {
     "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
     _ => throw new NotImplementedException()
 };
-FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
-    name: "getCurrentWeatherAtLocation",
+private FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
+    name: "GetWeatherAtLocation",
     description: "Gets the current weather at a provided location.",
     parameters: BinaryData.FromObjectAsJson(
         new
diff --git a/sdk/ai/Azure.AI.Projects/samples/Sample25_Streaming_with_Auto_Function_Calls.md b/sdk/ai/Azure.AI.Projects/samples/Sample25_Streaming_with_Auto_Function_Calls.md
new file mode 100644
index 000000000000..895ed8dadd18
--- /dev/null
+++ b/sdk/ai/Azure.AI.Projects/samples/Sample25_Streaming_with_Auto_Function_Calls.md
@@ -0,0 +1,185 @@
+# Sample using agents streaming with auto function calls in Azure.AI.Projects.
+
+We demonstrated how to write code to call local functions with the agents in streaming scenarios in a previous example. In this example, we will demonstrate how to configure calling local functions automatically.
+
+1. First we need to create agent client and read the environment variables that will be used in the next steps.
+```C# Snippet:FunctionsWithStreaming_CreateClient
+var connectionString = System.Environment.GetEnvironmentVariable("PROJECT_CONNECTION_STRING");
+var modelDeploymentName = System.Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME");
+AgentsClient client = new(connectionString, new DefaultAzureCredential());
+```
+
+2 Define three toy functions: `GetUserFavoriteCity`that always returns "Seattle, WA" and `GetCityNickname`, which will handle only "Seattle, WA" and will throw exception in response to other city names. The last function `GetWeatherAtLocation` returns weather at Seattle, WA. For each function we need to create `FunctionToolDefinition`, which defines function name, description and parameters.
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+// Example of a function that defines no parameters
+private string GetUserFavoriteCity() => "Seattle, WA";
+private FunctionToolDefinition getUserFavoriteCityTool = new("GetUserFavoriteCity", "Gets the user's favorite city.");
+// Example of a function with a single required parameter
+private string GetCityNickname(string location) => location switch
+{
+    "Seattle, WA" => "The Emerald City",
+    _ => throw new NotImplementedException(),
+};
+private FunctionToolDefinition getCityNicknameTool = new(
+    name: "GetCityNickname",
+    description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
+    parameters: BinaryData.FromObjectAsJson(
+        new
+        {
+            Type = "object",
+            Properties = new
+            {
+                Location = new
+                {
+                    Type = "string",
+                    Description = "The city and state, e.g. San Francisco, CA",
+                },
+            },
+            Required = new[] { "location" },
+        },
+        new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+// Example of a function with one required and one optional, enum parameter
+private string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
+{
+    "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
+    _ => throw new NotImplementedException()
+};
+private FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
+    name: "GetWeatherAtLocation",
+    description: "Gets the current weather at a provided location.",
+    parameters: BinaryData.FromObjectAsJson(
+        new
+        {
+            Type = "object",
+            Properties = new
+            {
+                Location = new
+                {
+                    Type = "string",
+                    Description = "The city and state, e.g. San Francisco, CA",
+                },
+                Unit = new
+                {
+                    Type = "string",
+                    Enum = new[] { "c", "f" },
+                },
+            },
+            Required = new[] { "location" },
+        },
+        new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+
+3. Create Agent with the `FunctionToolDefinitions. 
+
+Synchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
+);
+```
+
+Asynchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCallAsync_CreateAgent
+Agent agent = await client.CreateAgentAsync(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
+);
+```
+
+4. Create `Thread` with the message.
+
+Synchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "What's the weather like in my favorite city?");
+```
+
+Asynchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCallAsync_CreateThreadMessage
+AgentThread thread = await client.CreateThreadAsync();
+
+ThreadMessage message = await client.CreateMessageAsync(
+    thread.Id,
+    MessageRole.User,
+    "What's the weather like in my favorite city?");
+```
+
+5. Setup `AutoFunctionCallOptions` with the function delegates above.
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> toolDelegates = new();
+toolDelegates.Add(nameof(GetWeatherAtLocation), GetWeatherAtLocation);
+toolDelegates.Add(nameof(GetCityNickname), GetCityNickname);
+toolDelegates.Add(nameof(GetUserFavoriteCity), GetUserFavoriteCity);
+AutoFunctionCallOptions autoFunctionCallOptions = new(toolDelegates, 10);
+```
+
+6. Create a stream that will allow us to receive updates from the agent.  With `autoFunctionCallOptions` as parameter, the functions will be called automatically when the agent needs to call them. The `StreamingUpdate` object will contain the results of the function calls.
+ 
+Synchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCall
+CollectionResult<StreamingUpdate> stream = client.CreateRunStreaming(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions);
+foreach (StreamingUpdate streamingUpdate in stream)
+{
+    if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
+    {
+        Console.WriteLine("--- Run started! ---");
+    }
+    else if (streamingUpdate is MessageContentUpdate contentUpdate)
+    {
+        Console.Write(contentUpdate.Text);
+    }
+    else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+    {
+        Console.WriteLine();
+        Console.WriteLine("--- Run completed! ---");
+    }
+}
+```
+
+Asynchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCallAsync
+await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions))
+{
+    if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
+    {
+        Console.WriteLine("--- Run started! ---");
+    }
+    else if (streamingUpdate is MessageContentUpdate contentUpdate)
+    {
+        Console.Write(contentUpdate.Text);
+    }
+    else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+    {
+        Console.WriteLine();
+        Console.WriteLine("--- Run completed! ---");
+    }
+}
+```
+
+7. Finally, we delete all the resources, we have created in this sample.
+
+Synchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCall_Cleanup
+client.DeleteThread(thread.Id);
+client.DeleteAgent(agent.Id);
+```
+
+Asynchronous sample:
+```C# Snippet:StreamingWithAutoFunctionCallAsync_Cleanup
+await client.DeleteThreadAsync(thread.Id);
+await client.DeleteAgentAsync(agent.Id);
+```
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs b/sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
index c93a8ae801e3..c9409a2a0eff 100644
--- a/sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
@@ -1,6 +1,8 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
@@ -10,5 +12,20 @@ public partial class AIProjectClientOptions : ClientOptions
     {
         /// <summary> The size of the client cache. </summary>
         public int ClientCacheSize { get; set; } = 100;
+
+        private void ValidateAutoFunctions(Dictionary<string, Delegate> delegates)
+        {
+            if (delegates == null || delegates.Count == 0)
+            {
+                throw new InvalidOperationException("The delegate dictionary must have at least one delegate.");
+            }
+            foreach (var kvp in delegates)
+            {
+                if (kvp.Value.Method.ReturnType != typeof(string))
+                {
+                    throw new InvalidOperationException($"The Delegates must have string as return type.");
+                }
+            }
+        }
     }
 }
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.Streaming.cs b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.Streaming.cs
index 2c6dfd2e0d1a..fb26286f698a 100644
--- a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.Streaming.cs
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.Streaming.cs
@@ -5,12 +5,10 @@
 using System.ClientModel;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
 using System.Threading.Tasks;
 using System.Threading;
 using Azure.Core;
 using Azure.Core.Pipeline;
-using System.IO;
 
 namespace Azure.AI.Projects;
 
@@ -57,10 +55,11 @@ public partial class AgentsClient
     /// <param name="parallelToolCalls"> If `true` functions will run in parallel during tool use. </param>
     /// <param name="metadata"> A set of up to 16 key/value pairs that can be attached to an object, used for storing additional information about that object in a structured format. Keys may be up to 64 characters in length and values may be up to 512 characters in length. </param>
     /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
     /// <exception cref="ArgumentNullException"> <paramref name="threadId"/> or <paramref name="assistantId"/> is null. </exception>
     /// <exception cref="ArgumentException"> <paramref name="threadId"/> is an empty string, and was expected to be non-empty. </exception>
 #pragma warning disable AZC0015 // Unexpected client method return type.
-    public virtual AsyncCollectionResult<StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, IEnumerable<ThreadMessageOptions> additionalMessages = null, IEnumerable<ToolDefinition> overrideTools = null, float? temperature = null, float? topP = null, int? maxPromptTokens = null, int? maxCompletionTokens = null, TruncationObject truncationStrategy = null, BinaryData toolChoice = null, BinaryData responseFormat = null, bool? parallelToolCalls = null, IReadOnlyDictionary<string, string> metadata = null, CancellationToken cancellationToken = default)
+    public virtual AsyncCollectionResult<StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, IEnumerable<ThreadMessageOptions> additionalMessages = null, IEnumerable<ToolDefinition> overrideTools = null, float? temperature = null, float? topP = null, int? maxPromptTokens = null, int? maxCompletionTokens = null, TruncationObject truncationStrategy = null, BinaryData toolChoice = null, BinaryData responseFormat = null, bool? parallelToolCalls = null, IReadOnlyDictionary<string, string> metadata = null, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
 #pragma warning restore AZC0015 // Unexpected client method return type.
     {
         Argument.AssertNotNullOrEmpty(threadId, nameof(threadId));
@@ -88,8 +87,16 @@ public virtual AsyncCollectionResult<StreamingUpdate> CreateRunStreamingAsync(st
 
         async Task<Response> sendRequestAsync() =>
             await CreateRunStreamingAsync(threadId, createRunRequest.ToRequestContent(), context).ConfigureAwait(false);
-
-        return new AsyncStreamingUpdateCollection(sendRequestAsync, cancellationToken);
+        AsyncCollectionResult<StreamingUpdate> submitToolOutputsToStreamAsync(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currRetry) =>
+            this.SubmitToolOutputsToStreamAsync(run, toolOutputs, currRetry, default);
+        async Task<Response<ThreadRun>> cancelRunAsync(string runId) => await this.CancelRunAsync(threadId, runId).ConfigureAwait(false);
+        return new AsyncStreamingUpdateCollection(
+            cancellationToken,
+            autoFunctionCallOptions,
+            0,
+            sendRequestAsync,
+            cancelRunAsync,
+            submitToolOutputsToStreamAsync);
     }
 
     /// <summary>
@@ -133,10 +140,11 @@ async Task<Response> sendRequestAsync() =>
     /// <param name="parallelToolCalls"> If `true` functions will run in parallel during tool use. </param>
     /// <param name="metadata"> A set of up to 16 key/value pairs that can be attached to an object, used for storing additional information about that object in a structured format. Keys may be up to 64 characters in length and values may be up to 512 characters in length. </param>
     /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
     /// <exception cref="ArgumentNullException"> <paramref name="threadId"/> or <paramref name="assistantId"/> is null. </exception>
     /// <exception cref="ArgumentException"> <paramref name="threadId"/> is an empty string, and was expected to be non-empty. </exception>
 #pragma warning disable AZC0015 // Unexpected client method return type.
-    public virtual CollectionResult<StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, IEnumerable<ThreadMessageOptions> additionalMessages = null, IEnumerable<ToolDefinition> overrideTools = null, float? temperature = null, float? topP = null, int? maxPromptTokens = null, int? maxCompletionTokens = null, TruncationObject truncationStrategy = null, BinaryData toolChoice = null, BinaryData responseFormat = null, bool? parallelToolCalls = null, IReadOnlyDictionary<string, string> metadata = null, CancellationToken cancellationToken = default)
+    public virtual CollectionResult<StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, IEnumerable<ThreadMessageOptions> additionalMessages = null, IEnumerable<ToolDefinition> overrideTools = null, float? temperature = null, float? topP = null, int? maxPromptTokens = null, int? maxCompletionTokens = null, TruncationObject truncationStrategy = null, BinaryData toolChoice = null, BinaryData responseFormat = null, bool? parallelToolCalls = null, IReadOnlyDictionary<string, string> metadata = null, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
 #pragma warning restore AZC0015 // Unexpected client method return type.
     {
         Argument.AssertNotNullOrEmpty(threadId, nameof(threadId));
@@ -163,15 +171,41 @@ public virtual CollectionResult<StreamingUpdate> CreateRunStreaming(string threa
         RequestContext context = FromCancellationToken(cancellationToken);
 
         Response sendRequest() => CreateRunStreaming(threadId, createRunRequest.ToRequestContent(), context);
-        return new StreamingUpdateCollection(sendRequest, cancellationToken);
+        CollectionResult<StreamingUpdate> submitToolOutputsToStream(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currRetry) =>
+            this.SubmitToolOutputsToStream(run, toolOutputs, currRetry);
+        Response<ThreadRun> cancelRun(string runId) => this.CancelRun(threadId, runId);
+
+        return new StreamingUpdateCollection(
+            cancellationToken,
+            autoFunctionCallOptions,
+            0,
+            sendRequest,
+            cancelRun,
+            submitToolOutputsToStream);
     }
+
+    /// <summary> Submits outputs from tools as requested by tool calls in a stream. Stream updates that need submitted tool outputs will have a status of 'RunStatus.RequiresAction'. </summary>
+    /// <param name="run"> The <see cref="ThreadRun"/> that the tool outputs should be submitted to. </param>
+    /// <param name="toolOutputs"> A list of tools for which the outputs are being submitted. </param>
+    /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
+    /// <exception cref="ArgumentNullException"> <paramref name="run"/> or <paramref name="toolOutputs"/> is null. </exception>
+#pragma warning disable AZC0015 // Unexpected client method return type.
+    public virtual CollectionResult<StreamingUpdate> SubmitToolOutputsToStream(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
+#pragma warning restore AZC0015 // Unexpected client method return type.
+    {
+        return SubmitToolOutputsToStream(run, toolOutputs, int.MaxValue, cancellationToken);
+    }
+
     /// <summary> Submits outputs from tools as requested by tool calls in a stream. Stream updates that need submitted tool outputs will have a status of 'RunStatus.RequiresAction'. </summary>
     /// <param name="run"> The <see cref="ThreadRun"/> that the tool outputs should be submitted to. </param>
     /// <param name="toolOutputs"> A list of tools for which the outputs are being submitted. </param>
     /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="currentRetry"> The count of current retry of auto function calls.  Cancel the run if reach to the maxinum. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
     /// <exception cref="ArgumentNullException"> <paramref name="run"/> or <paramref name="toolOutputs"/> is null. </exception>
 #pragma warning disable AZC0015 // Unexpected client method return type.
-    public virtual CollectionResult<StreamingUpdate> SubmitToolOutputsToStream(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, CancellationToken cancellationToken = default)
+    internal virtual CollectionResult<StreamingUpdate> SubmitToolOutputsToStream(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currentRetry = 0, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
 #pragma warning restore AZC0015 // Unexpected client method return type.
     {
         Argument.AssertNotNull(run, nameof(run));
@@ -180,16 +214,41 @@ public virtual CollectionResult<StreamingUpdate> SubmitToolOutputsToStream(Threa
         SubmitToolOutputsToRunRequest submitToolOutputsToRunRequest = new(toolOutputs.ToList(), true, null);
         RequestContext context = FromCancellationToken(cancellationToken);
         Response sendRequest() => SubmitToolOutputsInternal(run.ThreadId, run.Id, true, submitToolOutputsToRunRequest.ToRequestContent(), context);
-        return new StreamingUpdateCollection(sendRequest, cancellationToken);
+        CollectionResult<StreamingUpdate> submitToolOutputsToStream(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currRetry) =>
+            this.SubmitToolOutputsToStream(run, toolOutputs, currentRetry);
+        Response<ThreadRun> cancelRun(string runId) => this.CancelRun(run.ThreadId, runId);
+
+        return new StreamingUpdateCollection(
+            cancellationToken,
+            autoFunctionCallOptions,
+            currentRetry,
+            sendRequest,
+            cancelRun,
+            submitToolOutputsToStream);
+    }
+
+    /// <summary> Submits outputs from tools as requested by tool calls in a stream. Stream updates that need submitted tool outputs will have a status of 'RunStatus.RequiresAction'. </summary>
+    /// <param name="run"> The <see cref="ThreadRun"/> that the tool outputs should be submitted to. </param>
+    /// <param name="toolOutputs"> A list of tools for which the outputs are being submitted. </param>
+    /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
+    /// <exception cref="ArgumentNullException"> <paramref name="run"/> or <paramref name="toolOutputs"/> is null. </exception>
+#pragma warning disable AZC0015 // Unexpected client method return type.
+    public virtual AsyncCollectionResult<StreamingUpdate> SubmitToolOutputsToStreamAsync(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
+#pragma warning restore AZC0015 // Unexpected client method return type.
+    {
+        return SubmitToolOutputsToStreamAsync(run, toolOutputs, cancellationToken);
     }
 
     /// <summary> Submits outputs from tools as requested by tool calls in a stream. Stream updates that need submitted tool outputs will have a status of 'RunStatus.RequiresAction'. </summary>
     /// <param name="run"> The <see cref="ThreadRun"/> that the tool outputs should be submitted to. </param>
     /// <param name="toolOutputs"> A list of tools for which the outputs are being submitted. </param>
     /// <param name="cancellationToken"> The cancellation token to use. </param>
+    /// <param name="currentRetry"> The count of current retry of auto function calls.  Cancel the run if reach to the maxinum. </param>
+    /// <param name="autoFunctionCallOptions">If specified, function calls defined in tools will be called automatically.</param>
     /// <exception cref="ArgumentNullException"> <paramref name="run"/> or <paramref name="toolOutputs"/> is null. </exception>
 #pragma warning disable AZC0015 // Unexpected client method return type.
-    public virtual AsyncCollectionResult<StreamingUpdate> SubmitToolOutputsToStreamAsync(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, CancellationToken cancellationToken = default)
+    internal virtual AsyncCollectionResult<StreamingUpdate> SubmitToolOutputsToStreamAsync(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currentRetry = 0, CancellationToken cancellationToken = default, AutoFunctionCallOptions autoFunctionCallOptions = null)
 #pragma warning restore AZC0015 // Unexpected client method return type.
     {
         Argument.AssertNotNull(run, nameof(run));
@@ -198,7 +257,17 @@ public virtual AsyncCollectionResult<StreamingUpdate> SubmitToolOutputsToStreamA
         SubmitToolOutputsToRunRequest submitToolOutputsToRunRequest = new(toolOutputs.ToList(), true, null);
         RequestContext context = FromCancellationToken(cancellationToken);
         async Task<Response> sendRequestAsync() => await SubmitToolOutputsInternalAsync(run.ThreadId, run.Id, true, submitToolOutputsToRunRequest.ToRequestContent(), context).ConfigureAwait(false);
-        return new AsyncStreamingUpdateCollection(sendRequestAsync, cancellationToken);
+        AsyncCollectionResult<StreamingUpdate> submitToolOutputsToStreamAsync(ThreadRun run, IEnumerable<ToolOutput> toolOutputs, int currRetry) =>
+            this.SubmitToolOutputsToStreamAsync(run, toolOutputs, currRetry);
+        async Task<Response<ThreadRun>> cancelRunAsync(string runId) => await this.CancelRunAsync(run.ThreadId, runId).ConfigureAwait(false);
+
+        return new AsyncStreamingUpdateCollection(
+            cancellationToken,
+            autoFunctionCallOptions,
+            currentRetry,
+            sendRequestAsync,
+            cancelRunAsync,
+            submitToolOutputsToStreamAsync);
     }
 
     internal async Task<Response> CreateRunStreamingAsync(string threadId, RequestContent content, RequestContext context = null)
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AutoFunctionCallOptions.cs b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AutoFunctionCallOptions.cs
new file mode 100644
index 000000000000..dfa44599edd1
--- /dev/null
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/AutoFunctionCallOptions.cs
@@ -0,0 +1,55 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Azure.AI.Projects
+{
+    public class AutoFunctionCallOptions
+    {
+        private Dictionary<string, Delegate> _autoFunctionCallDelegates = new();
+        private int _maxRetry;
+        /// <summary> The size of the client cache. </summary>
+
+        /// <summary>
+        /// Enables auto tool calls to be executed automatically during streaming.  If this is not set, function must be called manually.
+        /// </summary>
+        /// <param name="toolDelegates">Dictionary in name and delegate in pair</param>
+        /// <param name="maxRetry">Maximum number of errors allowed and retry per stream. Default value is 10.</param>
+        public AutoFunctionCallOptions(Dictionary<string, Delegate> toolDelegates, int maxRetry) {
+            ValidateAutoFunctions(toolDelegates);
+            _autoFunctionCallDelegates.Clear();
+
+            foreach (var kvp in toolDelegates)
+            {
+                _autoFunctionCallDelegates[kvp.Key] = kvp.Value;
+            }
+            _maxRetry = maxRetry;
+        }
+
+        private void ValidateAutoFunctions(Dictionary<string, Delegate> toolDelegates)
+        {
+            if (toolDelegates == null || toolDelegates.Count == 0)
+            {
+                throw new InvalidOperationException("The delegate dictionary must have at least one delegate.");
+            }
+            foreach (var kvp in toolDelegates)
+            {
+                if (kvp.Value.Method.ReturnType != typeof(string))
+                {
+                    throw new InvalidOperationException($"The Delegates must have string as return type.");
+                }
+            }
+        }
+
+        internal Dictionary<string, Delegate> AutoFunctionCallDelegates => _autoFunctionCallDelegates;
+
+        internal int MaxRetry => _maxRetry;
+    }
+}
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
index ab3bbe823fbf..24776b888b94 100644
--- a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
@@ -7,6 +7,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.ServerSentEvents;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -21,14 +22,32 @@ internal class AsyncStreamingUpdateCollection : AsyncCollectionResult<StreamingU
 {
     private readonly Func<Task<Response>> _sendRequestAsync;
     private readonly CancellationToken _cancellationToken;
-
-    public AsyncStreamingUpdateCollection(Func<Task<Response>> sendRequestAsync,
-        CancellationToken cancellationToken)
+    private readonly ToolCallsResolver? _toolCallsResolver;
+    private readonly Func<ThreadRun, IEnumerable<ToolOutput>, int, AsyncCollectionResult<StreamingUpdate>> _submitToolOutputsToStreamAsync;
+    private readonly int _maxRetry;
+    private int _currRetry;
+    private readonly Func<string, Task<Response<ThreadRun>>> _cancelRunAsync;
+
+    public AsyncStreamingUpdateCollection(
+        CancellationToken cancellationToken,
+        AutoFunctionCallOptions autoFunctionCallOptions,
+        int currentRetry,
+        Func<Task<Response>> sendRequestAsync,
+        Func<string, Task<Response<ThreadRun>>> cancelRunAsync,
+        Func<ThreadRun, IEnumerable<ToolOutput>, int, AsyncCollectionResult<StreamingUpdate>> submitToolOutputsToStreamAsync)
     {
         Argument.AssertNotNull(sendRequestAsync, nameof(sendRequestAsync));
 
-        _sendRequestAsync = sendRequestAsync;
         _cancellationToken = cancellationToken;
+        _sendRequestAsync = sendRequestAsync;
+        _submitToolOutputsToStreamAsync = submitToolOutputsToStreamAsync;
+        if (autoFunctionCallOptions != null)
+        {
+            _toolCallsResolver = new(autoFunctionCallOptions.AutoFunctionCallDelegates);
+            _maxRetry = autoFunctionCallOptions.MaxRetry;
+        }
+        _currRetry = currentRetry;
+        _cancelRunAsync = cancelRunAsync;
     }
 
     public override ContinuationToken? GetContinuationToken(ClientResult page)
@@ -47,13 +66,64 @@ public async override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
 
     protected async override IAsyncEnumerable<StreamingUpdate> GetValuesFromPageAsync(ClientResult page)
     {
-#pragma warning disable AZC0100 // ConfigureAwait(false) must be used.
-        await using IAsyncEnumerator<StreamingUpdate> enumerator = new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
-#pragma warning restore AZC0100 // ConfigureAwait(false) must be used.
-        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            IAsyncEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStreamAsync(streamRun, toolOutputs, _currRetry).GetAsyncEnumerator(_cancellationToken) :
+                new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
+
+            toolOutputs.Clear();
+
+            try
+            {
+                bool hasError = false;
+                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+                {
+                    var streamingUpdate = enumerator.Current;
+                    if (streamingUpdate is RequiredActionUpdate newActionUpdate && _toolCallsResolver != null)
+                    {
+                        ToolOutput toolOutput;
+                        try
+                        {
+                            toolOutput = _toolCallsResolver.GetResolvedToolOutput(
+                                newActionUpdate.FunctionName,
+                                newActionUpdate.ToolCallId,
+                                newActionUpdate.FunctionArguments
+                            );
+                        }
+                        catch (Exception ex)
+                        {
+                            string errorJson = JsonSerializer.Serialize(new { error = ex.GetBaseException().Message });
+                            toolOutput = new ToolOutput(newActionUpdate.ToolCallId, errorJson);
+                            hasError = true;
+                        }
+                        toolOutputs.Add(toolOutput);
+
+                        streamRun = newActionUpdate.Value;
+                    }
+                    else
+                    {
+                        yield return streamingUpdate;
+                    }
+                }
+                _currRetry = hasError ? _currRetry + 1 : _currRetry;
+
+                if (streamRun != null && _currRetry > _maxRetry)
+                {
+                    // Cancel the run if the max retry is reached
+                    var cancelRunResponse =  await _cancelRunAsync(streamRun.Id).ConfigureAwait(false);
+                    yield return new StreamingUpdate<ThreadRun>(cancelRunResponse.Value, StreamingUpdateReason.RunCancelled);
+                    yield break;
+                }
+            }
+            finally
+            {
+                await enumerator.DisposeAsync().ConfigureAwait(false);
+            }
         }
+        while (toolOutputs.Count > 0);
     }
 
     private sealed class AsyncStreamingUpdateEnumerator : IAsyncEnumerator<StreamingUpdate>
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
index fffc5b865582..c2f0d5f417f4 100644
--- a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
@@ -6,8 +6,11 @@
 using System.ClientModel.Primitives;
 using System.Collections;
 using System.Collections.Generic;
+using System.IO;
 using System.Net.ServerSentEvents;
+using System.Text.Json;
 using System.Threading;
+using System.Threading.Tasks;
 
 #nullable enable
 
@@ -20,15 +23,32 @@ internal class StreamingUpdateCollection : CollectionResult<StreamingUpdate>
 {
     private readonly Func<Response> _sendRequest;
     private readonly CancellationToken _cancellationToken;
+    private readonly ToolCallsResolver? _toolCallsResolver;
+    private readonly Func<ThreadRun, IEnumerable<ToolOutput>, int, CollectionResult<StreamingUpdate>> _submitToolOutputsToStream;
+    private readonly int _maxRetry;
+    private int _currRetry;
+    private readonly Func<string, Response<ThreadRun>> _cancelRun;
 
     public StreamingUpdateCollection(
+        CancellationToken cancellationToken,
+        AutoFunctionCallOptions autoFunctionCallOptions,
+        int currentRetry,
         Func<Response> sendRequest,
-        CancellationToken cancellationToken)
+        Func<string, Response<ThreadRun>> cancelRun,
+        Func<ThreadRun, IEnumerable<ToolOutput>, int, CollectionResult<StreamingUpdate>> submitToolOutputsToStream)
     {
         Argument.AssertNotNull(sendRequest, nameof(sendRequest));
 
-        _sendRequest = sendRequest;
         _cancellationToken = cancellationToken;
+        _sendRequest = sendRequest;
+        _submitToolOutputsToStream = submitToolOutputsToStream;
+        if (autoFunctionCallOptions != null)
+        {
+            _toolCallsResolver = new(autoFunctionCallOptions.AutoFunctionCallDelegates);
+            _maxRetry = autoFunctionCallOptions.MaxRetry;
+        }
+        _currRetry = currentRetry;
+        _cancelRun = cancelRun;
     }
 
     public override ContinuationToken? GetContinuationToken(ClientResult page)
@@ -46,11 +66,55 @@ public override IEnumerable<ClientResult> GetRawPages()
     }
     protected override IEnumerable<StreamingUpdate> GetValuesFromPage(ClientResult page)
     {
-        using IEnumerator<StreamingUpdate> enumerator = new StreamingUpdateEnumerator(page, _cancellationToken);
-        while (enumerator.MoveNext())
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            using IEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStream(streamRun, toolOutputs, _currRetry).GetEnumerator() :
+                new StreamingUpdateEnumerator(page, _cancellationToken);
+            toolOutputs.Clear();
+            bool hasError = false;
+            while (enumerator.MoveNext())
+            {
+                var streamingUpdate = enumerator.Current;
+                if (streamingUpdate is RequiredActionUpdate newActionUpdate && _toolCallsResolver != null)
+                {
+                    ToolOutput toolOutput;
+                    try
+                    {
+                        toolOutput = _toolCallsResolver.GetResolvedToolOutput(
+                            newActionUpdate.FunctionName,
+                            newActionUpdate.ToolCallId,
+                            newActionUpdate.FunctionArguments
+                        );
+                    }
+                    catch (Exception ex)
+                    {
+                        string errorJson = JsonSerializer.Serialize(new { error = ex.GetBaseException().Message });
+                        toolOutput = new ToolOutput(newActionUpdate.ToolCallId, errorJson);
+                        hasError = true;
+                    }
+                    toolOutputs.Add(toolOutput);
+
+                    streamRun = newActionUpdate.Value;
+                }
+                else
+                {
+                    yield return streamingUpdate;
+                }
+            }
+            _currRetry = hasError ? _currRetry + 1 : _currRetry;
+
+            if (streamRun != null && _currRetry > _maxRetry)
+            {
+                // Cancel the run if the max retry is reached
+                var cancelRunResponse = _cancelRun(streamRun.Id);
+                yield return new StreamingUpdate<ThreadRun>(cancelRunResponse.Value, StreamingUpdateReason.RunCancelled);
+                yield break;
+            }
         }
+        while (toolOutputs.Count > 0);
     }
 
     private sealed class StreamingUpdateEnumerator : IEnumerator<StreamingUpdate>
diff --git a/sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
new file mode 100644
index 000000000000..6aa9b5be2e4b
--- /dev/null
+++ b/sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
@@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    internal class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _toolDelegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> toolDelegates)
+        {
+            _toolDelegates = toolDelegates;
+        }
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_toolDelegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            return new ToolOutput(toolCallId, result == null ? "" : result.ToString());
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        internal static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new List<object>();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString();
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            // TODO: the following code has been testedwith Dictionary,
+            // deserializable Address class with address and city fields
+            // and Address[].   But need more testings.
+            //else if (type == typeof(object))
+            //{
+            //    return element.GetRawText();
+            //}
+            //else if (IsDictionaryType(type))
+            //{
+            //    Type[] genericArguments = type.GetGenericArguments();
+            //    Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericArguments[1]);
+
+            //    // Create an instance of the dictionary
+            //    var dict = Activator.CreateInstance(dictionaryType);
+
+            //    MethodInfo addMethod = dictionaryType.GetMethod("Add");
+
+            //    foreach (var prop in element.EnumerateObject())
+            //    {
+            //        object val = GetArgumentValue(prop.Name, genericArguments[1], prop.Value);
+            //        addMethod.Invoke(dict, [prop.Name, val]);
+            //    }
+            //    return dict;
+            //}
+            //else if (type.IsArray)
+            //{
+            //    Type elementType = type.GetElementType()!;
+            //    Array array = Array.CreateInstance(elementType, element.GetArrayLength());
+            //    int i = 0;
+            //    foreach (var item in element.EnumerateArray())
+            //    {
+            //        object val = GetArgumentValue(name, elementType, item);
+            //        array.SetValue(val, i++);
+            //    }
+            //    return array;
+            //}
+            //else if (TryDeserialize(element.GetRawText(), type, out object val))
+            //{
+            //    return val;
+            //}
+            throw new ArgumentException($"Received {element.GetRawText()}, but {name} in function implementation is {type}");
+        }
+
+        /// <summary>
+        /// JsonElement doesn't offer TryGetBoolean but offers GetBoolean that can throw exception
+        /// With that said, we need to create a custom TryGetBoolean method
+        /// </summary>
+        private static bool TryParseBoolean(JsonElement element, out bool? value)
+        {
+            switch (element.ValueKind)
+            {
+                case JsonValueKind.True:
+                    value = true;
+                    return true;
+                case JsonValueKind.False:
+                    value = false;
+                    return true;
+                default:
+                    value = default;
+                    return false;
+            }
+        }
+
+        // Uncommon this to support deserializable class
+        //private static bool TryDeserialize(string input, Type type, out object obj)
+        //{
+        //    try
+        //    {
+        //        obj = JsonSerializer.Deserialize(input, type, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
+        //        return true;
+        //    }
+        //    catch (JsonException)
+        //    {
+        //        obj = null;
+        //        return false;
+        //    }
+        //}
+
+        // Uncommon this to support dictionary
+        //private static bool IsDictionaryType(Type type)
+        //{
+        //    // Check if the type is a generic type
+        //    if (type.IsGenericType)
+        //    {
+        //        // Get the generic type definition
+        //        Type genericTypeDefinition = type.GetGenericTypeDefinition();
+
+        //        // Check if the generic type definition is Dictionary<,>
+        //        if (genericTypeDefinition == typeof(Dictionary<,>))
+        //        {
+        //            return true;
+        //        }
+        //    }
+
+        //    return false;
+        //}
+    }
+}
diff --git a/sdk/ai/Azure.AI.Projects/tests/AgentClientTests.cs b/sdk/ai/Azure.AI.Projects/tests/AgentClientTests.cs
index 9975f17c0c87..d2d5db716ee2 100644
--- a/sdk/ai/Azure.AI.Projects/tests/AgentClientTests.cs
+++ b/sdk/ai/Azure.AI.Projects/tests/AgentClientTests.cs
@@ -9,6 +9,7 @@
 using System.Runtime.CompilerServices;
 using System.Text;
 using System.Text.Json;
+using System.Threading;
 using System.Threading.Tasks;
 using Azure.Core;
 using Azure.Core.TestFramework;
@@ -640,6 +641,129 @@ public async Task TestSubmitToolOutputs(ArgumentType argType, bool parallelToolC
             Assert.AreEqual(parallelToolCalls, toolRun.ParallelToolCalls);
         }
 
+        [RecordedTest]
+        [TestCase(true)]
+        [TestCase(false)]
+        public async Task TestAutomaticSubmitToolOutputs(bool correctDefinition)
+        {
+            if (!IsAsync)
+                Assert.Inconclusive(STREAMING_CONSTRAINT);
+
+            string GetHumidityByAddress(string address)
+            {
+                return address.Contains("Seattle")? "80" : "60";
+            }
+
+            FunctionToolDefinition correctGeHhumidityByAddressTool = new(
+                 name: "GetHumidityByAddress",
+                 description: "Get humidity by address",
+                 parameters: BinaryData.FromObjectAsJson(
+                 new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Address = new
+                         {
+                             Type = "string",
+                             Description = "Address"
+                         }
+                     },
+                     Required = new[] { "address" }
+                 },
+                 new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+            FunctionToolDefinition incorrectGeHhumidityByAddressTool = new(
+                 name: "GetHumidityByAddress",
+                 description: "Get humidity by address",
+                 parameters: BinaryData.FromObjectAsJson(
+                 new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Addresses = new
+                         {
+                             Type = "array",
+                             Description = "A list of addresses",
+                             Items = new
+                             {
+                                 Type = "object",
+                                 Properties = new
+                                 {
+                                     Street = new
+                                     {
+                                         Type = "string",
+                                         Description = "Street"
+                                     },
+                                     City = new
+                                     {
+                                         Type = "string",
+                                         description = "city"
+                                     },
+                                 },
+                                 Required = new[] { "street", "city" }
+                             }
+                         },
+                     },
+                     Required = new[] { "address" }
+                 },
+                 new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+            Dictionary<string, Delegate> toolDelegates = new();
+            toolDelegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+
+            AgentsClient client = GetClient();
+            string output = "";
+            bool completed = false;
+            bool cancelled = false;
+            List<ToolDefinition> tools = new();
+            AutoFunctionCallOptions autoFunctionCallOptions = new(toolDelegates, 0);
+            if (correctDefinition)
+                tools.Add(correctGeHhumidityByAddressTool);
+            else
+                tools.Add(incorrectGeHhumidityByAddressTool);
+
+            Agent agent = await client.CreateAgentAsync(
+                    model: "gpt-4o-mini",
+                    name: AGENT_NAME,
+                    instructions: "Use the provided functions to help answer questions.",
+                    tools: tools
+                );
+            AgentThread thread = await client.CreateThreadAsync();
+
+            ThreadMessage message = await client.CreateMessageAsync(
+                thread.Id,
+                MessageRole.User,
+                "Get humidity for address, 456 2nd Ave in city, Seattle");
+
+            await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions))
+            {
+                if (streamingUpdate is MessageContentUpdate contentUpdate)
+                {
+                    output += contentUpdate.Text;
+                }
+                else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+                {
+                    completed = true;
+                }
+                else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCancelled)
+                {
+                    cancelled = true;
+                }
+            }
+
+            if (correctDefinition)
+            {
+                Assert.True(output.Contains("80"));
+                Assert.True(completed);
+            }
+            else
+            {
+                Assert.True(cancelled);
+            }
+        }
+
         [RecordedTest]
         [TestCase(VecrorStoreTestType.JustVectorStore, true, false)]
         [TestCase(VecrorStoreTestType.Batch, true, false)]
@@ -915,6 +1039,7 @@ public async Task TestCreateVectorStoreOnline()
             {
                 FileSearch=fileSearch
             };
+
             Agent agent = await client.CreateAgentAsync(
                 model: "gpt-4",
                 name: AGENT_NAME,
@@ -1356,13 +1481,14 @@ private static string CreateTempDirMayBe()
             return tempDir;
         }
 
-        private AgentsClient GetClient()
+        private AgentsClient GetClient(AIProjectClientOptions options = null)
         {
+            options ??= new AIProjectClientOptions();
             var connectionString = TestEnvironment.AzureAICONNECTIONSTRING;
             // If we are in the Playback, do not ask for authentication.
             if (Mode == RecordedTestMode.Playback)
             {
-                return InstrumentClient(new AgentsClient(connectionString, new MockCredential(), InstrumentClientOptions(new AIProjectClientOptions())));
+                return InstrumentClient(new AgentsClient(connectionString, new MockCredential(), InstrumentClientOptions(options)));
             }
             // For local testing if you are using non default account
             // add USE_CLI_CREDENTIAL into the .runsettings and set it to true,
@@ -1371,11 +1497,11 @@ private AgentsClient GetClient()
             var cli = System.Environment.GetEnvironmentVariable("USE_CLI_CREDENTIAL");
             if (!string.IsNullOrEmpty(cli) && string.Compare(cli, "true", StringComparison.OrdinalIgnoreCase) == 0)
             {
-                return InstrumentClient(new AgentsClient(connectionString, new AzureCliCredential(), InstrumentClientOptions(new AIProjectClientOptions())));
+                return InstrumentClient(new AgentsClient(connectionString,  new AzureCliCredential(), InstrumentClientOptions(options)));
             }
             else
             {
-                return InstrumentClient(new AgentsClient(connectionString, new DefaultAzureCredential(), InstrumentClientOptions(new AIProjectClientOptions())));
+                return InstrumentClient(new AgentsClient(connectionString, new DefaultAzureCredential(), InstrumentClientOptions(options)));
             }
         }
 
diff --git a/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj b/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
index 837f0b69065e..f5305c68d532 100644
--- a/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
+++ b/sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+﻿<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
 
@@ -22,9 +22,6 @@
   <ItemGroup>
     <ProjectReference Include="$(AzureCoreTestFramework)" />
     <ProjectReference Include="..\src\Azure.AI.Projects.csproj" />
-    <ProjectReference Include="..\..\Azure.AI.Inference\src\Azure.AI.Inference.csproj" />
-    <ProjectReference Include="..\..\..\openai\Azure.AI.OpenAI\src\Azure.AI.OpenAI.csproj" />
-    <ProjectReference Include="..\..\..\search\Azure.Search.Documents\src\Azure.Search.Documents.csproj" />
   </ItemGroup>
   
   <ItemGroup>
@@ -34,6 +31,10 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <!--TODO: remove VersionOverride https://github.com/Azure/azure-sdk-for-net/issues/49730 -->
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
+    <PackageReference Include="Azure.Search.Documents" VersionOverride="11.7.0-beta.3" />
   </ItemGroup>
 
 </Project>
diff --git a/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs b/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs
index 8cb18b641089..6235f691d290 100644
--- a/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs
+++ b/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs
@@ -15,6 +15,65 @@ namespace Azure.AI.Projects.Tests;
 
 public partial class Sample_Agent_Functions_Streaming : SamplesBase<AIProjectsTestEnvironment>
 {
+    #region Snippet:FunctionsWithStreaming_DefineFunctionTools
+    // Example of a function that defines no parameters
+    private string GetUserFavoriteCity() => "Seattle, WA";
+    private FunctionToolDefinition getUserFavoriteCityTool = new("GetUserFavoriteCity", "Gets the user's favorite city.");
+    // Example of a function with a single required parameter
+    private string GetCityNickname(string location) => location switch
+    {
+        "Seattle, WA" => "The Emerald City",
+        _ => throw new NotImplementedException(),
+    };
+    private FunctionToolDefinition getCityNicknameTool = new(
+        name: "GetCityNickname",
+        description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
+        parameters: BinaryData.FromObjectAsJson(
+            new
+            {
+                Type = "object",
+                Properties = new
+                {
+                    Location = new
+                    {
+                        Type = "string",
+                        Description = "The city and state, e.g. San Francisco, CA",
+                    },
+                },
+                Required = new[] { "location" },
+            },
+            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+    // Example of a function with one required and one optional, enum parameter
+    private string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
+    {
+        "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
+        _ => throw new NotImplementedException()
+    };
+    private FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
+        name: "GetWeatherAtLocation",
+        description: "Gets the current weather at a provided location.",
+        parameters: BinaryData.FromObjectAsJson(
+            new
+            {
+                Type = "object",
+                Properties = new
+                {
+                    Location = new
+                    {
+                        Type = "string",
+                        Description = "The city and state, e.g. San Francisco, CA",
+                    },
+                    Unit = new
+                    {
+                        Type = "string",
+                        Enum = new[] { "c", "f" },
+                    },
+                },
+                Required = new[] { "location" },
+            },
+            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+    #endregion
+
     [Test]
     [AsyncOnly]
     public async Task FunctionCallingWithStreamingExampleAsync()
@@ -30,64 +89,6 @@ public async Task FunctionCallingWithStreamingExampleAsync()
         AgentsClient client = new(connectionString, new DefaultAzureCredential());
         #endregion
 
-        #region Snippet:FunctionsWithStreaming_DefineFunctionTools
-        // Example of a function that defines no parameters
-        string GetUserFavoriteCity() => "Seattle, WA";
-        FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
-        // Example of a function with a single required parameter
-        string GetCityNickname(string location) => location switch
-        {
-            "Seattle, WA" => "The Emerald City",
-            _ => throw new NotImplementedException(),
-        };
-        FunctionToolDefinition getCityNicknameTool = new(
-            name: "getCityNickname",
-            description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
-            parameters: BinaryData.FromObjectAsJson(
-                new
-                {
-                    Type = "object",
-                    Properties = new
-                    {
-                        Location = new
-                        {
-                            Type = "string",
-                            Description = "The city and state, e.g. San Francisco, CA",
-                        },
-                    },
-                    Required = new[] { "location" },
-                },
-                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
-        // Example of a function with one required and one optional, enum parameter
-        string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
-        {
-            "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
-            _ => throw new NotImplementedException()
-        };
-        FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
-            name: "getCurrentWeatherAtLocation",
-            description: "Gets the current weather at a provided location.",
-            parameters: BinaryData.FromObjectAsJson(
-                new
-                {
-                    Type = "object",
-                    Properties = new
-                    {
-                        Location = new
-                        {
-                            Type = "string",
-                            Description = "The city and state, e.g. San Francisco, CA",
-                        },
-                        Unit = new
-                        {
-                            Type = "string",
-                            Enum = new[] { "c", "f" },
-                        },
-                    },
-                    Required = new[] { "location" },
-                },
-                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
-        #endregion
         #region Snippet:FunctionsWithStreamingUpdateHandling
         ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
         {
@@ -192,62 +193,6 @@ public void FunctionCallingWithStreamingExample()
 #endif
         AgentsClient client = new(connectionString, new DefaultAzureCredential());
 
-        // Example of a function that defines no parameters
-        string GetUserFavoriteCity() => "Seattle, WA";
-        FunctionToolDefinition getUserFavoriteCityTool = new("getUserFavoriteCity", "Gets the user's favorite city.");
-        // Example of a function with a single required parameter
-        string GetCityNickname(string location) => location switch
-        {
-            "Seattle, WA" => "The Emerald City",
-            _ => throw new NotImplementedException(),
-        };
-        FunctionToolDefinition getCityNicknameTool = new(
-            name: "getCityNickname",
-            description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
-            parameters: BinaryData.FromObjectAsJson(
-                new
-                {
-                    Type = "object",
-                    Properties = new
-                    {
-                        Location = new
-                        {
-                            Type = "string",
-                            Description = "The city and state, e.g. San Francisco, CA",
-                        },
-                    },
-                    Required = new[] { "location" },
-                },
-                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
-        // Example of a function with one required and one optional, enum parameter
-        string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
-        {
-            "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
-            _ => throw new NotImplementedException()
-        };
-        FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
-            name: "getCurrentWeatherAtLocation",
-            description: "Gets the current weather at a provided location.",
-            parameters: BinaryData.FromObjectAsJson(
-                new
-                {
-                    Type = "object",
-                    Properties = new
-                    {
-                        Location = new
-                        {
-                            Type = "string",
-                            Description = "The city and state, e.g. San Francisco, CA",
-                        },
-                        Unit = new
-                        {
-                            Type = "string",
-                            Enum = new[] { "c", "f" },
-                        },
-                    },
-                    Required = new[] { "location" },
-                },
-                new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
         ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
         {
             if (functionName == getUserFavoriteCityTool.Name)
@@ -272,6 +217,7 @@ ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string
             }
             return null;
         }
+
         #region Snippet:FunctionsWithStreamingSync_CreateAgent
         Agent agent = client.CreateAgent(
             model: modelDeploymentName,
diff --git a/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Streaming_with_Auto_Function_Call.cs b/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Streaming_with_Auto_Function_Call.cs
new file mode 100644
index 000000000000..d3195c39a5f1
--- /dev/null
+++ b/sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Streaming_with_Auto_Function_Call.cs
@@ -0,0 +1,212 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable disable
+
+using System;
+using System.ClientModel;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Azure.Core.TestFramework;
+using Microsoft.Extensions.Options;
+using NUnit.Framework;
+
+namespace Azure.AI.Projects.Tests;
+
+public partial class Sample_Agent_Streaming_with_Auto_Function_Call : SamplesBase<AIProjectsTestEnvironment>
+{
+    #region Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+    // Example of a function that defines no parameters
+    private string GetUserFavoriteCity() => "Seattle, WA";
+    private FunctionToolDefinition getUserFavoriteCityTool = new("GetUserFavoriteCity", "Gets the user's favorite city.");
+    // Example of a function with a single required parameter
+    private string GetCityNickname(string location) => location switch
+    {
+        "Seattle, WA" => "The Emerald City",
+        _ => throw new NotImplementedException(),
+    };
+    private FunctionToolDefinition getCityNicknameTool = new(
+        name: "GetCityNickname",
+        description: "Gets the nickname of a city, e.g. 'LA' for 'Los Angeles, CA'.",
+        parameters: BinaryData.FromObjectAsJson(
+            new
+            {
+                Type = "object",
+                Properties = new
+                {
+                    Location = new
+                    {
+                        Type = "string",
+                        Description = "The city and state, e.g. San Francisco, CA",
+                    },
+                },
+                Required = new[] { "location" },
+            },
+            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+    // Example of a function with one required and one optional, enum parameter
+    private string GetWeatherAtLocation(string location, string temperatureUnit = "f") => location switch
+    {
+        "Seattle, WA" => temperatureUnit == "f" ? "70f" : "21c",
+        _ => throw new NotImplementedException()
+    };
+    private FunctionToolDefinition getCurrentWeatherAtLocationTool = new(
+        name: "GetWeatherAtLocation",
+        description: "Gets the current weather at a provided location.",
+        parameters: BinaryData.FromObjectAsJson(
+            new
+            {
+                Type = "object",
+                Properties = new
+                {
+                    Location = new
+                    {
+                        Type = "string",
+                        Description = "The city and state, e.g. San Francisco, CA",
+                    },
+                    Unit = new
+                    {
+                        Type = "string",
+                        Enum = new[] { "c", "f" },
+                    },
+                },
+                Required = new[] { "location" },
+            },
+            new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+    #endregion
+
+    [Test]
+    [AsyncOnly]
+    public async Task AutoFunctionCallingWithStreamingExampleAsync()
+    {
+#if SNIPPET
+        var connectionString = System.Environment.GetEnvironmentVariable("PROJECT_CONNECTION_STRING");
+        var modelDeploymentName = System.Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME");
+#else
+        var connectionString = TestEnvironment.AzureAICONNECTIONSTRING;
+        var modelDeploymentName = TestEnvironment.MODELDEPLOYMENTNAME;
+#endif
+
+        AgentsClient client = new(connectionString, new DefaultAzureCredential());
+
+        #region Snippet:StreamingWithAutoFunctionCallAsync_CreateAgent
+        Agent agent = await client.CreateAgentAsync(
+            model: modelDeploymentName,
+            name: "SDK Test Agent - Functions",
+                instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+                    + "Customize your responses to the user's preferences as much as possible and use friendly "
+                    + "nicknames for cities whenever possible.",
+            tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
+        );
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCallAsync_CreateThreadMessage
+        AgentThread thread = await client.CreateThreadAsync();
+
+        ThreadMessage message = await client.CreateMessageAsync(
+            thread.Id,
+            MessageRole.User,
+            "What's the weather like in my favorite city?");
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+        List<ToolOutput> toolOutputs = new();
+        Dictionary<string, Delegate> toolDelegates = new();
+        toolDelegates.Add(nameof(GetWeatherAtLocation), GetWeatherAtLocation);
+        toolDelegates.Add(nameof(GetCityNickname), GetCityNickname);
+        toolDelegates.Add(nameof(GetUserFavoriteCity), GetUserFavoriteCity);
+        AutoFunctionCallOptions autoFunctionCallOptions = new(toolDelegates, 10);
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCallAsync
+        await foreach (StreamingUpdate streamingUpdate in client.CreateRunStreamingAsync(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions))
+        {
+            if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
+            {
+                Console.WriteLine("--- Run started! ---");
+            }
+            else if (streamingUpdate is MessageContentUpdate contentUpdate)
+            {
+                Console.Write(contentUpdate.Text);
+            }
+            else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+            {
+                Console.WriteLine();
+                Console.WriteLine("--- Run completed! ---");
+            }
+        }
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCallAsync_Cleanup
+        await client.DeleteThreadAsync(thread.Id);
+        await client.DeleteAgentAsync(agent.Id);
+        #endregion
+    }
+
+    [Test]
+    [SyncOnly]
+    public void AutoFunctionCallingWithStreamingExample()
+    {
+#if SNIPPET
+        var connectionString = System.Environment.GetEnvironmentVariable("PROJECT_CONNECTION_STRING");
+        var modelDeploymentName = System.Environment.GetEnvironmentVariable("MODEL_DEPLOYMENT_NAME");
+#else
+        var connectionString = TestEnvironment.AzureAICONNECTIONSTRING;
+        var modelDeploymentName = TestEnvironment.MODELDEPLOYMENTNAME;
+#endif
+
+        AgentsClient client = new(connectionString, new DefaultAzureCredential());
+
+        #region Snippet:StreamingWithAutoFunctionCall_CreateAgent
+        Agent agent = client.CreateAgent(
+            model: modelDeploymentName,
+            name: "SDK Test Agent - Functions",
+                instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+                    + "Customize your responses to the user's preferences as much as possible and use friendly "
+                    + "nicknames for cities whenever possible.",
+            tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
+        );
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+        AgentThread thread = client.CreateThread();
+
+        ThreadMessage message = client.CreateMessage(
+            thread.Id,
+            MessageRole.User,
+            "What's the weather like in my favorite city?");
+        #endregion
+
+        List<ToolOutput> toolOutputs = new();
+        Dictionary<string, Delegate> toolDelegates = new();
+        toolDelegates.Add(nameof(GetWeatherAtLocation), GetWeatherAtLocation);
+        toolDelegates.Add(nameof(GetCityNickname), GetCityNickname);
+        toolDelegates.Add(nameof(GetUserFavoriteCity), GetUserFavoriteCity);
+        AutoFunctionCallOptions autoFunctionCallOptions = new(toolDelegates, 10);
+
+        #region Snippet:StreamingWithAutoFunctionCall
+        CollectionResult<StreamingUpdate> stream = client.CreateRunStreaming(thread.Id, agent.Id, autoFunctionCallOptions: autoFunctionCallOptions);
+        foreach (StreamingUpdate streamingUpdate in stream)
+        {
+            if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCreated)
+            {
+                Console.WriteLine("--- Run started! ---");
+            }
+            else if (streamingUpdate is MessageContentUpdate contentUpdate)
+            {
+                Console.Write(contentUpdate.Text);
+            }
+            else if (streamingUpdate.UpdateKind == StreamingUpdateReason.RunCompleted)
+            {
+                Console.WriteLine();
+                Console.WriteLine("--- Run completed! ---");
+            }
+        }
+        #endregion
+
+        #region Snippet:StreamingWithAutoFunctionCall_Cleanup
+        client.DeleteThread(thread.Id);
+        client.DeleteAgent(agent.Id);
+        #endregion
+    }
+}


Reviews:
user: login: nick863
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-15T04:14:21Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T15:48:42Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T15:59:14Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T16:00:42Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T16:15:52Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T17:03:48Z
user: login: KrzysztofCwalina
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-22T16:01:18Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T22:34:38Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T22:35:19Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:04:52Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T00:39:33Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T14:44:00Z
user: login: KrzysztofCwalina
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-24T17:06:49Z
user: login: nick863
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:09:28Z
user: login: nick863
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:37:31Z
user: login: nick863
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T17:37:56Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:51:45Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:54:03Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T17:56:02Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:06:40Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:18:40Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:25:42Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T18:28:50Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T19:48:49Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:07:09Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:17:52Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:19:15Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:20:04Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:21:13Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:24:59Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:27:21Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T20:29:06Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T15:39:49Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T15:43:50Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T15:44:21Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:06:28Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:06:45Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:06:58Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:13:44Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:32:14Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T16:33:17Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T17:52:02Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T18:00:04Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T18:04:51Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T21:22:04Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T21:44:47Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T22:50:03Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T22:51:13Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T23:37:09Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T23:41:45Z
user: login: KrzysztofCwalina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-25T23:46:21Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T06:37:40Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T06:38:16Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T06:17:24Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T06:25:29Z
user: login: KrzysztofCwalina
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-28T18:33:00Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T21:09:55Z
user: login: nick863
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T16:12:32Z
user: login: jsquire
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-29T18:58:39Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T19:29:43Z
user: login: jsquire
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T20:41:58Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T20:50:42Z
user: login: jsquire
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T21:13:45Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T22:45:44Z
user: login: jsquire
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T23:01:09Z
user: login: howieleung
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T23:16:35Z
user: login: jsquire
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-29T23:59:08Z

Review comments:
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: nick863
body: Nit: 
```C#
internal bool EnableAutoToolCalls => _delegates.Count > 0;
```
created_at: 2025-04-15T03:37:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
+        {
+            get
+            {
+                return _delegates.Count > 0;
+            }
+        }
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// If it fails to resolve the tool call, it returns an empty string as ToolOutput to let agents to continue
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (EnableAutoToolCalls && _delegates.TryGetValue(functionName, out var func))
+            {
+                try
+                {
+                    var rt = Resolve(func, functionArguments);
+                    var rtInStr = JsonSerializer.Serialize(rt);
+                    return new ToolOutput(toolCallId, rtInStr);
+                }
+                catch
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: nick863
body: Should we catch `ArgumentException` here?
created_at: 2025-04-15T03:41:16Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
+        {
+            get
+            {
+                return _delegates.Count > 0;
+            }
+        }
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// If it fails to resolve the tool call, it returns an empty string as ToolOutput to let agents to continue
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (EnableAutoToolCalls && _delegates.TryGetValue(functionName, out var func))
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: nick863
body: May be we can just use 
```C#
if (_delegates.TryGetValue(functionName, out var func))
```
created_at: 2025-04-15T03:57:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: nick863
body: Do we need to initialize `_delegates ` here?
created_at: 2025-04-15T03:58:33Z
author_association: MEMBER
diff_hunk: @@ -46,11 +55,34 @@ public override IEnumerable<ClientResult> GetRawPages()
     }
     protected override IEnumerable<StreamingUpdate> GetValuesFromPage(ClientResult page)
     {
-        using IEnumerator<StreamingUpdate> enumerator = new StreamingUpdateEnumerator(page, _cancellationToken);
-        while (enumerator.MoveNext())
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            using IEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStream(streamRun, toolOutputs).GetEnumerator() :
+                new StreamingUpdateEnumerator(page, _cancellationToken);
+            toolOutputs.Clear();
+            while (enumerator.MoveNext())
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
user: login: nick863
body: May be we can simplify
```C#
foreach(StreamUpdate streamingUpdate in enumerator)
{
    if (streamingUpdate is RequiredActionUpdate newActionUpdate && _toolCallsResolver.EnableAutoToolCalls)
<...>
}
```
created_at: 2025-04-15T04:00:48Z
author_association: MEMBER
diff_hunk: @@ -15,6 +15,232 @@ namespace Azure.AI.Projects.Tests;
 
 public partial class Sample_Agent_Functions_Streaming : SamplesBase<AIProjectsTestEnvironment>
 {
+    private class Address
path: sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs
user: login: nick863
body: Let us split this file and add sample Sample_Agent_Functions_Streaming_With_Delegates and add corresponding .md file. Samplw17_Agent_Functions_Streaming_With_Delegates.
created_at: 2025-04-15T04:04:45Z
author_association: MEMBER
diff_hunk: @@ -47,13 +56,43 @@ public async override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
 
     protected async override IAsyncEnumerable<StreamingUpdate> GetValuesFromPageAsync(ClientResult page)
     {
-#pragma warning disable AZC0100 // ConfigureAwait(false) must be used.
-        await using IAsyncEnumerator<StreamingUpdate> enumerator = new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
-#pragma warning restore AZC0100 // ConfigureAwait(false) must be used.
-        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            IAsyncEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStreamAsync(streamRun, toolOutputs).GetAsyncEnumerator(_cancellationToken) :
+                new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
+
+            toolOutputs.Clear();
+
+            try
+            {
+                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: nick863
body: May be we can simplify
```C#
await foreach (StreamingUpdate streamingUpdate in enumerator)
{
    if (streamingUpdate is RequiredActionUpdate newActionUpdate && _toolCallsResolver.EnableAutoToolCalls)
    <...>
}
```
created_at: 2025-04-15T04:11:10Z
author_association: MEMBER
diff_hunk: @@ -47,13 +56,43 @@ public async override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
 
     protected async override IAsyncEnumerable<StreamingUpdate> GetValuesFromPageAsync(ClientResult page)
     {
-#pragma warning disable AZC0100 // ConfigureAwait(false) must be used.
-        await using IAsyncEnumerator<StreamingUpdate> enumerator = new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
-#pragma warning restore AZC0100 // ConfigureAwait(false) must be used.
-        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            IAsyncEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStreamAsync(streamRun, toolOutputs).GetAsyncEnumerator(_cancellationToken) :
+                new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
+
+            toolOutputs.Clear();
+
+            try
+            {
+                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: howieleung
body: This doesn't work. 
Asynchronous foreach statement cannot operate on variables of type 'IAsyncEnumerator<StreamingUpdate>' because 'IAsyncEnumerator<StreamingUpdate>' does not contain a suitable public instance or extension definition for 'GetAsyncEnumerator'
Same reason I have explain in the comment above.
created_at: 2025-04-21T15:48:42Z
author_association: MEMBER
diff_hunk: @@ -46,11 +55,34 @@ public override IEnumerable<ClientResult> GetRawPages()
     }
     protected override IEnumerable<StreamingUpdate> GetValuesFromPage(ClientResult page)
     {
-        using IEnumerator<StreamingUpdate> enumerator = new StreamingUpdateEnumerator(page, _cancellationToken);
-        while (enumerator.MoveNext())
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            using IEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStream(streamRun, toolOutputs).GetEnumerator() :
+                new StreamingUpdateEnumerator(page, _cancellationToken);
+            toolOutputs.Clear();
+            while (enumerator.MoveNext())
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
user: login: howieleung
body: I can't.   I am getting..
foreach statement cannot operate on variables of type 'IEnumerator<StreamingUpdate>' because 'IEnumerator<StreamingUpdate>' does not contain a public instance or extension definition for 'GetEnumerator'

Because in `foreach(StreamUpdate streamingUpdate in obj)`, that obj should be an object that enumerator.  But in my case object is an enumerator.
created_at: 2025-04-21T15:59:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: howieleung
body: I just want to reduce the nullable check later.
created_at: 2025-04-21T16:00:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
+        {
+            get
+            {
+                return _delegates.Count > 0;
+            }
+        }
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// If it fails to resolve the tool call, it returns an empty string as ToolOutput to let agents to continue
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (EnableAutoToolCalls && _delegates.TryGetValue(functionName, out var func))
+            {
+                try
+                {
+                    var rt = Resolve(func, functionArguments);
+                    var rtInStr = JsonSerializer.Serialize(rt);
+                    return new ToolOutput(toolCallId, rtInStr);
+                }
+                catch
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: howieleung
body: I have changed the design such that when any errors are discovered, it will throw error message and call `CreateErrorToolOutput` with

```
        private static ToolOutput CreateErrorToolOutput(string toolCallId, string errorMessage)
        {
            string errorJson = JsonSerializer.Serialize(new { error = errorMessage });
            return new ToolOutput(toolCallId, errorJson);
        }
        
```
So model is going to know it is an error and trigger to recall the function tool in a different way...
created_at: 2025-04-21T16:15:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
+        {
+            get
+            {
+                return _delegates.Count > 0;
+            }
+        }
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// If it fails to resolve the tool call, it returns an empty string as ToolOutput to let agents to continue
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (EnableAutoToolCalls && _delegates.TryGetValue(functionName, out var func))
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: howieleung
body: Done
created_at: 2025-04-21T17:03:48Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,151 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+The following is a set of function tools and their implementations:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private FunctionToolDefinition geHhumidityByAddressTool = new(
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: Why is this not created using a helper that builds FunctionToolDefinition using reflection (like we discussed)? We should use something like the following to build the definitions: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/cloudmachine/Azure.Projects/samples/HelloRAG/Program.cs#L20
created_at: 2025-04-22T15:52:12Z
author_association: MEMBER
diff_hunk: @@ -243,6 +243,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual Azure.Response<Azure.AI.Projects.VectorStoreFileDeletionStatus> DeleteVectorStoreFile(string vectorStoreId, string fileId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> DeleteVectorStoreFileAsync(string vectorStoreId, string fileId, Azure.RequestContext context) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.VectorStoreFileDeletionStatus>> DeleteVectorStoreFileAsync(string vectorStoreId, string fileId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual void DisableAutoFunctionCalls() { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: if we want this to be configurable, it should be an option on the client options
created_at: 2025-04-22T16:00:10Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3572,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Utility
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: we dont want public namespace like this. Can this type be in the main namespace?
created_at: 2025-04-22T16:00:53Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,151 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+The following is a set of function tools and their implementations:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private FunctionToolDefinition geHhumidityByAddressTool = new(
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: Per our agreement, we will make helper code after GA.
created_at: 2025-04-23T22:34:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,183 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Azure.AI.Projects.Custom.Utility
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls
path: sdk/ai/Azure.AI.Projects/src/Custom/Utility/ToolCallsResolver.cs
user: login: howieleung
body: Done
created_at: 2025-04-23T22:35:19Z
author_association: MEMBER
diff_hunk: @@ -243,6 +243,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual Azure.Response<Azure.AI.Projects.VectorStoreFileDeletionStatus> DeleteVectorStoreFile(string vectorStoreId, string fileId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> DeleteVectorStoreFileAsync(string vectorStoreId, string fileId, Azure.RequestContext context) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.VectorStoreFileDeletionStatus>> DeleteVectorStoreFileAsync(string vectorStoreId, string fileId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual void DisableAutoFunctionCalls() { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: Done
created_at: 2025-04-24T00:04:52Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3572,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Utility
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: Done
created_at: 2025-04-24T00:39:33Z
author_association: MEMBER
diff_hunk: @@ -15,6 +15,232 @@ namespace Azure.AI.Projects.Tests;
 
 public partial class Sample_Agent_Functions_Streaming : SamplesBase<AIProjectsTestEnvironment>
 {
+    private class Address
path: sdk/ai/Azure.AI.Projects/tests/Samples/Agent/Sample_Agent_Functions_Streaming.cs
user: login: howieleung
body: Moved out.
created_at: 2025-04-24T14:44:00Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: Why do you get JSON parsed into Dictionary here? If we don't have time to add higher level helpers, we should just keep using raw JSON, i.e. UTF8 bytes wrapped in BinaryData. Then a reoutine like this would simply use one of the JSON parsers to parse the addresses and unit. 

Also, long term we really need to be able to create methods like this that take Address[] as a parameter. Will we be able to do it? i.e. how will your code know whether the delegate accepts JSON or Address[]?
created_at: 2025-04-24T16:12:44Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: what's the error handling model for automatic tool calls? i.e. where do I handle errors that might happen in tool calls?
created_at: 2025-04-24T16:18:50Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: do we really need streaming runs here? Can we just use a simple completion?
created_at: 2025-04-24T16:22:52Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: I worry that this is on the client options. I suspect callers will want to change the set of tools on every completion. For example, there are MCP servers that return many tools; too many to include in every completion. There are discussions about using embeddings to select a subset of tools for every completion based on the proximity to the prompt embedding vector.   
created_at: 2025-04-24T16:26:36Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3571,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Agent
+{
+    public partial class ToolCallsResolver
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: Why do we need this type to be public?
created_at: 2025-04-24T16:30:34Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: KrzysztofCwalina
body: why is this on project options, not on AgentClientOptions?
created_at: 2025-04-24T16:31:58Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
+        private int _maxRetry;
         /// <summary> The size of the client cache. </summary>
         public int ClientCacheSize { get; set; } = 100;
+
+        /// <summary>
+        /// Enables auto tool calls to be executed automatically during streaming.  If this is not set, function must be called manually.
+        /// </summary>
+        /// <param name="delegates">Dictionary in name and delegate in pair</param>
+        /// <param name="maxRetry">Maximum number of errors allowed and retry per stream. Default value is 10.</param>
+        public virtual void EnableAutoFunctionCalls(Dictionary<string, Delegate> delegates, int maxRetry = 10)
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: KrzysztofCwalina
body: what is the retry for? This calls a local tool. why would the local call need to be retried?
created_at: 2025-04-24T16:32:52Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
+        private int _maxRetry;
         /// <summary> The size of the client cache. </summary>
         public int ClientCacheSize { get; set; } = 100;
+
+        /// <summary>
+        /// Enables auto tool calls to be executed automatically during streaming.  If this is not set, function must be called manually.
+        /// </summary>
+        /// <param name="delegates">Dictionary in name and delegate in pair</param>
+        /// <param name="maxRetry">Maximum number of errors allowed and retry per stream. Default value is 10.</param>
+        public virtual void EnableAutoFunctionCalls(Dictionary<string, Delegate> delegates, int maxRetry = 10)
+        {
+            _autoFUnctionCallDelegates.Clear();
+
+            if (delegates != null)
+            {
+                foreach (var kvp in delegates)
+                {
+                    _autoFUnctionCallDelegates[kvp.Key] = kvp.Value;
+                }
+            }
+            _maxRetry = maxRetry;
+        }
+
+        internal Dictionary<string, Delegate> AutoFunctionCallDelegates
+        {
+            get
+            {
+                return _autoFUnctionCallDelegates;
+            }
+        }
+
+        internal int MaxRetry
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: KrzysztofCwalina
body: you should use expression body or auto property for this, i.e. 
`internal int MaxRetry => _maxRetry;`
or 
`internal int MaxRetry { get; private set; }`
created_at: 2025-04-24T16:35:01Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
+        private int _maxRetry;
         /// <summary> The size of the client cache. </summary>
         public int ClientCacheSize { get; set; } = 100;
+
+        /// <summary>
+        /// Enables auto tool calls to be executed automatically during streaming.  If this is not set, function must be called manually.
+        /// </summary>
+        /// <param name="delegates">Dictionary in name and delegate in pair</param>
+        /// <param name="maxRetry">Maximum number of errors allowed and retry per stream. Default value is 10.</param>
+        public virtual void EnableAutoFunctionCalls(Dictionary<string, Delegate> delegates, int maxRetry = 10)
+        {
+            _autoFUnctionCallDelegates.Clear();
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: KrzysztofCwalina
body: I think it will be unintuitive that subsequent calls to Enable clear the dictionary. Maybe the method should be called AddAutomaticTools
created_at: 2025-04-24T16:36:18Z
author_association: MEMBER
diff_hunk: @@ -19,6 +19,9 @@ namespace Azure.AI.Projects
     [CodeGenClient("Agents")]
     public partial class AgentsClient
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates;
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.cs
user: login: KrzysztofCwalina
body: casing U
created_at: 2025-04-24T16:37:19Z
author_association: MEMBER
diff_hunk: @@ -19,6 +19,9 @@ namespace Azure.AI.Projects
     [CodeGenClient("Agents")]
     public partial class AgentsClient
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates;
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.cs
user: login: KrzysztofCwalina
body: I also worry that when the dictionary is passed from the client options to here, we copy just the reference (correct?). This means modifications to the client options will be reflected here. This in turn means users might be surprised that changes to client options impact existing agent clients. Also, some might discover that they can use this loophole to change tools on per call basis, and then be surprised when they run into concurrency bugs, i.e. you cannot enumerate dictionaries while they are being changed. All these problems might go away if we move the dictionary from project options to agent options. In addition, you might want to make a copy of the dictionary 
created_at: 2025-04-24T16:42:57Z
author_association: MEMBER
diff_hunk: @@ -7,8 +7,11 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.ServerSentEvents;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
+using Azure.AI.Projects.Custom.Agent;
+using Microsoft.VisualBasic;
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: KrzysztofCwalina
body: why is this here?
created_at: 2025-04-24T16:43:14Z
author_association: MEMBER
diff_hunk: @@ -7,8 +7,11 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Net.ServerSentEvents;
+using System.Text.Json;
 using System.Threading;
 using System.Threading.Tasks;
+using Azure.AI.Projects.Custom.Agent;
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: KrzysztofCwalina
body: let's not have this namespace. sooner or later somebody will expose public types from it, which we dont want. 
created_at: 2025-04-24T16:44:12Z
author_association: MEMBER
diff_hunk: @@ -47,13 +56,43 @@ public async override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
 
     protected async override IAsyncEnumerable<StreamingUpdate> GetValuesFromPageAsync(ClientResult page)
     {
-#pragma warning disable AZC0100 // ConfigureAwait(false) must be used.
-        await using IAsyncEnumerator<StreamingUpdate> enumerator = new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
-#pragma warning restore AZC0100 // ConfigureAwait(false) must be used.
-        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            IAsyncEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStreamAsync(streamRun, toolOutputs).GetAsyncEnumerator(_cancellationToken) :
+                new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
+
+            toolOutputs.Clear();
+
+            try
+            {
+                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: KrzysztofCwalina
body: why are you using the enumerator directly and not async foreach on the return value of _submitToolOutputsToStreamAsync? + change AsyncStreamingUpdateEnumerator to enumerable. I agree with @nick863 that it would be good if we could simplify. currently it's hard to read the code
created_at: 2025-04-24T16:51:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: will it be retried?
created_at: 2025-04-24T16:53:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: how will users know that is the allowed type system of the tool call method. i.e. what if a method returns System.IO.Stream (or some other type that when serialized results in nonsense JSON)?
created_at: 2025-04-24T16:56:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: Also, will the serialized JSON here have the right casing?
created_at: 2025-04-24T16:57:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: don't use ArrayList. Use List<object>. ArrayList is pretty much deprecated. 
created_at: 2025-04-24T16:58:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: why dont we want to support null (and coerce it to empty string?)
created_at: 2025-04-24T17:00:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: will this work with AOT (ahead of time compilation in CLR)? I am not sure myslef but I worry. @m-nash. 
Can we not support this in v1 (support just simple types) and then add support in V2 after carefully making sure that it will work for AOT.
created_at: 2025-04-24T17:04:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
+            {
+                Type[] genericArguments = type.GetGenericArguments();
+                Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericArguments[1]);
+
+                // Create an instance of the dictionary
+                var dict = Activator.CreateInstance(dictionaryType);
+
+                MethodInfo addMethod = dictionaryType.GetMethod("Add");
+
+                foreach (var prop in element.EnumerateObject())
+                {
+                    object val = GetArgumentValue(prop.Name, genericArguments[1], prop.Value);
+                    addMethod.Invoke(dict, [prop.Name, val]);
+                }
+                return dict;
+            }
+            else if (type.IsArray)
+            {
+                Type elementType = type.GetElementType()!;
+                Array array = Array.CreateInstance(elementType, element.GetArrayLength());
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: same here. I worry about AOT
created_at: 2025-04-24T17:04:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
+            {
+                Type[] genericArguments = type.GetGenericArguments();
+                Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericArguments[1]);
+
+                // Create an instance of the dictionary
+                var dict = Activator.CreateInstance(dictionaryType);
+
+                MethodInfo addMethod = dictionaryType.GetMethod("Add");
+
+                foreach (var prop in element.EnumerateObject())
+                {
+                    object val = GetArgumentValue(prop.Name, genericArguments[1], prop.Value);
+                    addMethod.Invoke(dict, [prop.Name, val]);
+                }
+                return dict;
+            }
+            else if (type.IsArray)
+            {
+                Type elementType = type.GetElementType()!;
+                Array array = Array.CreateInstance(elementType, element.GetArrayLength());
+                int i = 0;
+                foreach (var item in element.EnumerateArray())
+                {
+                    object val = GetArgumentValue(name, elementType, item);
+                    array.SetValue(val, i++);
+                }
+                return array;
+            }
+            else if (TryDeserialize(element.GetRawText(), type, out object val))
+            {
+                return val;
+            }
+            throw new ArgumentException($"Received {element.GetRawText()}, but {name} in function implementation is {type}");
+        }
+
+        /// <summary>
+        /// JsonElement doesn't offer TryGetBoolean but offers GetBoolean that can throw exception
+        /// With that said, we need to create a custom TryGetBoolean method
+        /// </summary>
+        private static bool TryParseBoolean(JsonElement element, out bool? val)
+        {
+            try
+            {
+                val = element.GetBoolean();
+                return true;
+            }
+            catch
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: you should not be using exceptions for this. Exceptions when thrown are super slow in .NET. Just check if element.Kind is null
created_at: 2025-04-24T17:06:26Z
author_association: MEMBER
diff_hunk: @@ -46,11 +55,34 @@ public override IEnumerable<ClientResult> GetRawPages()
     }
     protected override IEnumerable<StreamingUpdate> GetValuesFromPage(ClientResult page)
     {
-        using IEnumerator<StreamingUpdate> enumerator = new StreamingUpdateEnumerator(page, _cancellationToken);
-        while (enumerator.MoveNext())
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            using IEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStream(streamRun, toolOutputs).GetEnumerator() :
+                new StreamingUpdateEnumerator(page, _cancellationToken);
+            toolOutputs.Clear();
+            while (enumerator.MoveNext())
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/StreamingUpdateCollection.cs
user: login: nick863
body: I see, yes, we need to make `StreamingUpdateEnumerator` IEnumerable in this case, which is not feasible.
created_at: 2025-04-24T17:09:28Z
author_association: MEMBER
diff_hunk: @@ -47,13 +56,43 @@ public async override IAsyncEnumerable<ClientResult> GetRawPagesAsync()
 
     protected async override IAsyncEnumerable<StreamingUpdate> GetValuesFromPageAsync(ClientResult page)
     {
-#pragma warning disable AZC0100 // ConfigureAwait(false) must be used.
-        await using IAsyncEnumerator<StreamingUpdate> enumerator = new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
-#pragma warning restore AZC0100 // ConfigureAwait(false) must be used.
-        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
+        ThreadRun? streamRun = null;
+        List<ToolOutput> toolOutputs = new();
+        do
         {
-            yield return enumerator.Current;
+            IAsyncEnumerator<StreamingUpdate> enumerator = (toolOutputs.Count > 0 && streamRun != null) ?
+                _submitToolOutputsToStreamAsync(streamRun, toolOutputs).GetAsyncEnumerator(_cancellationToken) :
+                new AsyncStreamingUpdateEnumerator(page, _cancellationToken);
+
+            toolOutputs.Clear();
+
+            try
+            {
+                while (await enumerator.MoveNextAsync().ConfigureAwait(false))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/Streaming/AsyncStreamingUpdateCollection.cs
user: login: nick863
body: Yes, it seems we need to rework code, which is not feasible now. Resolving.
created_at: 2025-04-24T17:37:31Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: I am just showing developers that we support Dictionary type.   I can change to Address[]
created_at: 2025-04-24T17:51:45Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: When there is error, the error will be submitted back to the model in Json format liked "{error: "Error message"}".   Then model will raise another function call to retry or figure out by its knowledge instead.
created_at: 2025-04-24T17:54:03Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: Auto function calling only support for streaming.   For non-streaming, they need to have a while loop to call getRun.   When the run say required_action, they need to call the function manually.
created_at: 2025-04-24T17:56:02Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: Yes.    every agent or createStream can specify their function tools to use.     I am not changing this design.   And I am not including more tool.

if they want automatic function calls, they call enableAutoFunctionCalls with delegate.   The streaming will call the delegate if the function name in the thread match the delegate.   
created_at: 2025-04-24T18:06:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Yes.   I did lot of experiments.   Depends on the question, if we submit this error to the model, the model might try to figure out the answer with its knowledge or memory.   It might raise the same function call again.   Or it might give up and say it doesn't have the information.
created_at: 2025-04-24T18:18:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Good question..   Whatever the result retuned by the function, it got to be converted to string because the output type for rest api and SDK is string.    So it is the user responsibility to return data that can be used by agent.

In Python, we only support string  as return type for the function.   But I want to do a bit better to allow returning Serializable class, dictionary, or any simple type.   JsonSerializer.Serialize works great for this.
created_at: 2025-04-24T18:25:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: I would prefer empty string because the data might be null or empty string randomly.   My code can avoid null check on user function.
created_at: 2025-04-24T18:28:50Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3571,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Agent
+{
+    public partial class ToolCallsResolver
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: I want to allow developer to use the `Resolve` .   Is that OK?
created_at: 2025-04-24T19:48:49Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3571,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Agent
+{
+    public partial class ToolCallsResolver
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: what's the scenario for them calling is manually? do you have a sample?
created_at: 2025-04-24T20:07:09Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: I don't think we should support dictionary like that. It just adds complexity and is not a nice API for the users
created_at: 2025-04-24T20:17:52Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: I am not sure this is how we want it to work. It will hide bugs and will be hard to debug. 
created_at: 2025-04-24T20:19:15Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: why would we not support non-streaming? This seems like very suprising design for the user. 
created_at: 2025-04-24T20:20:04Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: Why do you think it's good to not change this design?
created_at: 2025-04-24T20:21:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: Don't we need two exceptions for this to work well. If the model passes a wrong argument, it might indeed retry successfully. But why would the modle give us a wrong function name? We gave it an explicit list of functions that we support
created_at: 2025-04-24T20:24:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: Then let's start with all functions having to return string, i.e. throw if the delegate is being created from a CLR function that does not return string. 
created_at: 2025-04-24T20:27:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: but sometimes there is semantic meaning difference between null and empty. 
created_at: 2025-04-24T20:29:06Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: A similar solution also used in SK and asure-ai-project sdk.   In python, we would do logger.warning function to log the warning.   Can C# log warning?
created_at: 2025-04-25T15:39:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: I will take out "".   I agree there is a semantic difference.   But from the developer perspective, they don't know why the model would send empty string instead of null and vice versa.   
created_at: 2025-04-25T15:43:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
+            {
+                Type[] genericArguments = type.GetGenericArguments();
+                Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericArguments[1]);
+
+                // Create an instance of the dictionary
+                var dict = Activator.CreateInstance(dictionaryType);
+
+                MethodInfo addMethod = dictionaryType.GetMethod("Add");
+
+                foreach (var prop in element.EnumerateObject())
+                {
+                    object val = GetArgumentValue(prop.Name, genericArguments[1], prop.Value);
+                    addMethod.Invoke(dict, [prop.Name, val]);
+                }
+                return dict;
+            }
+            else if (type.IsArray)
+            {
+                Type elementType = type.GetElementType()!;
+                Array array = Array.CreateInstance(elementType, element.GetArrayLength());
+                int i = 0;
+                foreach (var item in element.EnumerateArray())
+                {
+                    object val = GetArgumentValue(name, elementType, item);
+                    array.SetValue(val, i++);
+                }
+                return array;
+            }
+            else if (TryDeserialize(element.GetRawText(), type, out object val))
+            {
+                return val;
+            }
+            throw new ArgumentException($"Received {element.GetRawText()}, but {name} in function implementation is {type}");
+        }
+
+        /// <summary>
+        /// JsonElement doesn't offer TryGetBoolean but offers GetBoolean that can throw exception
+        /// With that said, we need to create a custom TryGetBoolean method
+        /// </summary>
+        private static bool TryParseBoolean(JsonElement element, out bool? val)
+        {
+            try
+            {
+                val = element.GetBoolean();
+                return true;
+            }
+            catch
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Resolved.
created_at: 2025-04-25T15:44:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
+            {
+                Type[] genericArguments = type.GetGenericArguments();
+                Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(typeof(string), genericArguments[1]);
+
+                // Create an instance of the dictionary
+                var dict = Activator.CreateInstance(dictionaryType);
+
+                MethodInfo addMethod = dictionaryType.GetMethod("Add");
+
+                foreach (var prop in element.EnumerateObject())
+                {
+                    object val = GetArgumentValue(prop.Name, genericArguments[1], prop.Value);
+                    addMethod.Invoke(dict, [prop.Name, val]);
+                }
+                return dict;
+            }
+            else if (type.IsArray)
+            {
+                Type elementType = type.GetElementType()!;
+                Array array = Array.CreateInstance(elementType, element.GetArrayLength());
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: I commented out this code since you said we don't want to support array now..
created_at: 2025-04-25T16:06:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
+            foreach (ParameterInfo param in function.Method.GetParameters())
+            {
+                if (argumentsJson.RootElement.TryGetProperty(param.Name ?? "", out JsonElement element))
+                {
+                    object val = GetArgumentValue(param.Name, param.ParameterType, element);
+                    args.Add(val);
+                }
+                else if (param.DefaultValue != null)
+                {
+                    args.Add(param.DefaultValue);
+                }
+                // check if the value is not in arguments, but the parameter is required, throw error
+                else if (Nullable.GetUnderlyingType(param.ParameterType) == null)
+                {
+                    throw new ArgumentException($"Missing required argument {param.Name} for function {function.Method.Name}");
+                }
+            }
+            return function.DynamicInvoke(args.ToArray());
+        }
+
+        private static object GetArgumentValue(string name, Type type, JsonElement element)
+        {
+            if (type == typeof(string))
+            {
+                return element.GetString() ?? "";
+            }
+            else if (type == typeof(int))
+            {
+                if (element.TryGetInt32(out int val))
+                    return val;
+            }
+            else if (type == typeof(ushort))
+            {
+                if (element.TryGetInt16(out short val))
+                    return val;
+            }
+            else if (type == typeof(float))
+            {
+                if (element.TryGetSingle(out float val))
+                    return val;
+            }
+            else if (type == typeof(uint))
+            {
+                if (element.TryGetUInt32(out uint val))
+                    return val;
+            }
+            else if (type == typeof(decimal))
+            {
+                if (element.TryGetDecimal(out decimal val))
+                    return val;
+            }
+            else if (type == typeof(double))
+            {
+                if (element.TryGetDouble(out double val))
+                    return val;
+            }
+            else if (type == typeof(long))
+            {
+                if (element.TryGetInt64(out long val))
+                    return val;
+            }
+            else if (type == typeof(bool))
+            {
+                if (TryParseBoolean(element, out bool? val))
+                    return val;
+            }
+            else if (type == typeof(object))
+            {
+                return element.GetRawText();
+            }
+            else if (IsDictionaryType(type))
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Comment out and test dictionary later.
created_at: 2025-04-25T16:06:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
+            return new ToolOutput(toolCallId, serializedResult);
+        }
+
+        /// <summary>
+        /// Resolves the function call by invoking the delegate associated with the function name.
+        /// </summary>
+        /// <param name="function"></param>
+        /// <param name="functionArguments"></param>
+        /// <returns></returns>
+        public static object Resolve(Delegate function, string functionArguments)
+        {
+            JsonDocument argumentsJson = JsonDocument.Parse(functionArguments);
+            MethodInfo method = function.Method;
+            var args = new ArrayList();
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Done
created_at: 2025-04-25T16:06:58Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: Because for non-streaming, developers write their own while loop to iterate the run.   The function call must be made within the while loop.   If the while loop is inside of the SDK, yes, I would want to call the function for them.   
created_at: 2025-04-25T16:13:44Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: Sorry..   I am lost.    Let me clarify what I am doing:

Before this PR, I think the SDK is good to support manual function calls.   My PR is a `patch` to allow  automatic function call.   It isn't about `changing` design.

Let say user want to have 2 streams.   Agent A uses getCity function.   Agent B uses getWeather function.   To enable auto function calls, they pass both delegates to enableAutoFunctionCalls.   Then they include getCity functionTool to create Agent A, and include getWeather to create Agent B.    During streaming, the agent will raise call according to their individual function tools.   The SDK will than make function calls automatically using the delegates.

created_at: 2025-04-25T16:32:14Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: Took it off.
created_at: 2025-04-25T16:33:17Z
author_association: MEMBER
diff_hunk: @@ -3570,6 +3571,14 @@ protected virtual void JsonModelWriteCore(System.Text.Json.Utf8JsonWriter writer
         System.BinaryData System.ClientModel.Primitives.IPersistableModel<Azure.AI.Projects.WorkspaceProperties>.Write(System.ClientModel.Primitives.ModelReaderWriterOptions options) { throw null; }
     }
 }
+namespace Azure.AI.Projects.Custom.Agent
+{
+    public partial class ToolCallsResolver
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: I updated this sample here.
sdk/ai/Azure.AI.Projects/samples/Sample12_FunctionsWithStreaming.md
created_at: 2025-04-25T17:52:02Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: there is no really a good way to do it. This is why in CM protoype, we had APIs for the caller to issue the tool call directly and handle errors if they want: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/cloudmachine/Azure.Projects/samples/HelloRAG/Program.cs#L57

In addition, in our automatic runner, there is a virtual method that the user can override to hook up any error handlers they want: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/cloudmachine/Azure.Projects.AI/src/Agents/ChatRunner.cs#L146
created_at: 2025-04-25T18:00:04Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: isn't the await foreach below the same as the while loop?
created_at: 2025-04-25T18:04:50Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: I just read the code in the link.    I think it make sense to add handlers streaming, but I would not want to do it now for just onToolCall.     It makes more sense to come up with a list of handler and implement after GA.    And this is not going to be a breaking change.    Are you OK?
created_at: 2025-04-25T21:22:03Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: It isn't because whether it is a for-loop or while -loop.    I
During streaming, the SDK inside run a loop to go through the events and I can add code to call functions.  Regardless it needs to call function or not, we than `yeild return` the content out.    
On the other hand, for non-streaming, users call getRun again and again in a loop until thread status is completed.    Inside of the SDK, getRun does not have loop for me to do function call, submit output, and call function again, and retry if necessary.   Users have to make function call manually inside of their loop.
The same design happens  in Python SDK and SK as well.
created_at: 2025-04-25T21:44:47Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: howieleung
body: AIProjectClientOptions is actually options for AgentClient...

Here is the code I have in my sample:

```
        AIProjectClientOptions options = new();
        options.EnableAutoFunctionCalls(delegates);

        AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);

```
created_at: 2025-04-25T22:17:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: It is possible that the agent is created by Foundary UI or other application.   In their C# app, they call getAgent and attempt to stream.   If this agent has a function tool that isn't in the list of delegate, we want to raise exception.
created_at: 2025-04-25T22:23:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: howieleung
body: Done
created_at: 2025-04-25T22:24:16Z
author_association: MEMBER
diff_hunk: @@ -1,14 +1,53 @@
 ﻿// Copyright (c) Microsoft Corporation. All rights reserved.
 // Licensed under the MIT License.
 
+using System.Collections.Generic;
+using System;
 using Azure.Core;
 
 namespace Azure.AI.Projects
 {
     /// <summary> Client options for AIProjectClient. </summary>
     public partial class AIProjectClientOptions : ClientOptions
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates = new();
+        private int _maxRetry;
         /// <summary> The size of the client cache. </summary>
         public int ClientCacheSize { get; set; } = 100;
+
+        /// <summary>
+        /// Enables auto tool calls to be executed automatically during streaming.  If this is not set, function must be called manually.
+        /// </summary>
+        /// <param name="delegates">Dictionary in name and delegate in pair</param>
+        /// <param name="maxRetry">Maximum number of errors allowed and retry per stream. Default value is 10.</param>
+        public virtual void EnableAutoFunctionCalls(Dictionary<string, Delegate> delegates, int maxRetry = 10)
+        {
+            _autoFUnctionCallDelegates.Clear();
path: sdk/ai/Azure.AI.Projects/src/Custom/AIProjectClientOptions.cs
user: login: howieleung
body: Jarno, AI, and I had discussed the naming already and came up the word, name  `enable_auto_function_calls` for Python SDK
The word of `Add` has been thought about.  `Add ` doesn't give people the sense that it is `enable` this feature.
created_at: 2025-04-25T22:31:25Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: This only works like that in a single user app. In a multi-user server app, a single agents instance (CLR object, not Foundry agent) will be serving many users/requests. You want to control the set of tools per request, not when you instantiate the object. This is how it works in chat completion APIs, i.e. you pass the set of tools when you call COmplete, not when you instantiate the ChatCLient.
created_at: 2025-04-25T22:39:42Z
author_association: MEMBER
diff_hunk: @@ -19,6 +19,9 @@ namespace Azure.AI.Projects
     [CodeGenClient("Agents")]
     public partial class AgentsClient
     {
+        private Dictionary<string, Delegate> _autoFUnctionCallDelegates;
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/AgentsClient.cs
user: login: howieleung
body: I think the option name is confusing.    If you need me to change the location of the function, let me know.   I think I have it correct:
```
AIProjectClientOptions options = new(); 
options.EnableAutoFunctionCalls(delegates); 
AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);

        Agent agent = await client.CreateAgentAsync(......
            tools: [getCityNicknameTool, getCurrentWeatherAtLocationTool, getUserFavoriteCityTool]
        );

```

In addition, although I support enableAutoFunctionCalls supports dictionary as an argument, I iterate the dictionary entry by entry to update the global variable.   
```
        public virtual void EnableAutoFunctionCalls(Dictionary<string, Delegate> delegates, int maxRetry = 10)
        {
            ValidateAutoFunctions(delegates);
            _autoFunctionCallDelegates.Clear();

            foreach (var kvp in delegates)
            {
                _autoFunctionCallDelegates[kvp.Key] = kvp.Value;
            }
            _maxRetry = maxRetry;
        }

```
created_at: 2025-04-25T22:49:38Z
author_association: MEMBER
diff_hunk: @@ -581,23 +582,15 @@ ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string
 {
     if (functionName == getUserFavoriteCityTool.Name)
     {
-        return new ToolOutput(toolCallId, GetUserFavoriteCity());
+        return new ToolOutput(toolCallId, ToolCallsResolver.Resolve(GetUserFavoriteCity, functionArguments).ToString());
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: I don't think we should ship this API as public. If tool calls are not automatic, we want the caller to simply call the CLR API they want. We don't want them to go through some resolver. If you want an API that maps from tol call request arguments to CLR values that can be passed to a CLR method, then I would be fine with that, but honestly such API should be in some shared library, not in AI Project
created_at: 2025-04-25T22:51:07Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: Adding it after GA will be a breaking change, won't it? Non-streaming tool calls will be intercepted and handled automatically. This means custom user code that handles these tool calls won't execute anymore. Unless you add more knobs/options to preserve compat, and then it just adds complexity to the API. 
created_at: 2025-04-25T23:37:09Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: KrzysztofCwalina
body: Then maybe we just move this off the client options and pass it to the streaming completion methods. As of right not, the API is confusing: the user initializes the client to do auto tool calls and then the client won't do auto tool calls unless the user calls the streaming APIs. 
created_at: 2025-04-25T23:41:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,214 @@
+﻿// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.Json;
+using System.Xml.Linq;
+
+namespace Azure.AI.Projects.Custom.Agent
+{
+    /// <summary>
+    /// ToolCallsResolver is used to resolve tool calls in the streaming API.
+    /// </summary>
+    public class ToolCallsResolver
+    {
+        private readonly Dictionary<string, Delegate> _delegates = new();
+
+        internal ToolCallsResolver(Dictionary<string, Delegate> delegates)
+        {
+            _delegates = delegates;
+        }
+
+        /// <summary>
+        /// Indicates whether auto tool calls are enabled.
+        /// </summary>
+        internal bool EnableAutoToolCalls => _delegates.Count > 0;
+
+        /// <summary>
+        /// Resolves the tool call by invoking the delegate associated with the function name.
+        /// It casts the function arguments to the appropriate types based on the delegate's parameters.
+        /// without knowing the answer.
+        /// </summary>
+        internal ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string functionArguments)
+        {
+            if (!_delegates.TryGetValue(functionName, out var func))
+            {
+                string error = $"Function {functionName} not found.";
+                throw new MissingMethodException(error);
+            }
+
+            var result = Resolve(func, functionArguments);
+            string serializedResult = JsonSerializer.Serialize(result);
path: sdk/ai/Azure.AI.Projects/src/Custom/Agent/ToolCallsResolver.cs
user: login: KrzysztofCwalina
body: they you don't need to serialize the result. just try to cast the result to string
created_at: 2025-04-25T23:46:21Z
author_association: MEMBER
diff_hunk: @@ -484,6 +484,7 @@ public partial class AIProjectClientOptions : Azure.Core.ClientOptions
     {
         public AIProjectClientOptions(Azure.AI.Projects.AIProjectClientOptions.ServiceVersion version = Azure.AI.Projects.AIProjectClientOptions.ServiceVersion.V2024_07_01_Preview) { }
         public int ClientCacheSize { get { throw null; } set { } }
+        public virtual void EnableAutoFunctionCalls(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry = 10) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: howieleung
body: Now delegate and max retry are set as argument of create_stream function.
created_at: 2025-04-26T06:37:40Z
author_association: MEMBER
diff_hunk: @@ -581,23 +582,15 @@ ToolOutput GetResolvedToolOutput(string functionName, string toolCallId, string
 {
     if (functionName == getUserFavoriteCityTool.Name)
     {
-        return new ToolOutput(toolCallId, GetUserFavoriteCity());
+        return new ToolOutput(toolCallId, ToolCallsResolver.Resolve(GetUserFavoriteCity, functionArguments).ToString());
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: changed to internal.
created_at: 2025-04-26T06:38:16Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
+
+AgentsClient client = new(connectionString, new DefaultAzureCredential(), options);
+```
+
+
+When you create an agent, you can specify the function call by tools argument similar to the example of manual function calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateAgent
+Agent agent = client.CreateAgent(
+    model: modelDeploymentName,
+    name: "SDK Test Agent - Functions",
+        instructions: "You are a weather bot. Use the provided functions to help answer questions. "
+            + "Customize your responses to the user's preferences as much as possible and use friendly "
+            + "nicknames for cities whenever possible.",
+    tools: [getWeatherByAddressesTool, geHhumidityByAddressTool]
+);
+```
+
+We create a thread and message similar to the example of manual function tool calls:
+```C# Snippet:StreamingWithAutoFunctionCall_CreateThreadMessage
+AgentThread thread = client.CreateThread();
+
+ThreadMessage message = client.CreateMessage(
+    thread.Id,
+    MessageRole.User,
+    "Get weather and humidity for street, 123 1st St in city, Seattle and street, 456 2nd Ave in city, Bellevue");
+```
+
+The agent will then call the function automatically when it is needed during `CreateRunStreamingAsync`:
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: Done..   I added autoFunctionCallOptions to create_stream function.
created_at: 2025-04-28T06:17:24Z
author_association: MEMBER
diff_hunk: @@ -647,6 +648,172 @@ do
 while (toolOutputs.Count > 0);
 ```
 
+#### Function call executed automatically
+
+In addition to the manual function calls, SDK supports automatic function calling.  Here is the example:
+
+Here we use other functions for demonstration:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctions
+private class Address
+{
+    public string Street { get; set; }
+    public string City { get; set; }
+}
+
+private int GetHumidityByAddress(Address address)
+{
+    return (address.City == "Seattle") ? 60 : 80;
+}
+
+private string[] GetWeatherByAddresses(Dictionary<string, string>[] addresses, string unit = "F")
+{
+    string[] temps = new string[addresses.Length];
+    for (int i = 0; i < addresses.Length; i++)
+    {
+        if (addresses[i].TryGetValue("city", out string city))
+        {
+            temps[i] = string.Format("{0}{1}", (city == "Seattle") ? "20" : "50", unit);
+        }
+        else
+        {
+            throw new ArgumentException("Each address must contain 'street' and 'city' keys.");
+        }
+    }
+    return temps;
+}
+```
+Now we define the function definitions:
+```C# Snippet:StreamingWithAutoFunctionCall_DefineFunctionTools
+private FunctionToolDefinition geHhumidityByAddressTool = new(
+     name: "GetHumidityByAddress",
+     description: "Get humidity by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Address = new
+             {
+                 Type = "object",
+                 Properties = new
+                 {
+                     Street = new
+                     {
+                         Type = "string",
+                         Description = "Street"
+                     },
+                     City = new
+                     {
+                         Type = "string",
+                         Description = "city"
+                     },
+                 },
+                 Required = new[] { "street", "city" }
+             }
+         },
+         Required = new[] { "address" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+
+private FunctionToolDefinition getWeatherByAddressesTool = new(
+     name: "GetWeatherByAddresses",
+     description: "Get weather by street and city",
+     parameters: BinaryData.FromObjectAsJson(
+     new
+     {
+         Type = "object",
+         Properties = new
+         {
+             Addresses = new
+             {
+                 Type = "array",
+                 Description = "A list of addresses",
+                 Items = new
+                 {
+                     Type = "object",
+                     Properties = new
+                     {
+                         Street = new
+                         {
+                             Type = "string",
+                             Description = "Street"
+                         },
+                         City = new
+                         {
+                             Type = "string",
+                             description = "city"
+                         },
+                     },
+                     Required = new[] { "street", "city" }
+                 }
+             },
+             Unit = new
+             {
+                 Type = "string",
+                 Enum = new[] { "c", "f" },
+             },
+         },
+         Required = new[] { "addresses" }
+     },
+     new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
+```
+Use `EnableAutoFunctionCall` to enable the auto function call:
+```C# Snippet:StreamingWithAutoFunctionCall_EnableAutoFunctionCalls
+List<ToolOutput> toolOutputs = new();
+Dictionary<string, Delegate> delegates = new();
+delegates.Add(nameof(GetWeatherByAddresses), GetWeatherByAddresses);
+delegates.Add(nameof(GetHumidityByAddress), GetHumidityByAddress);
+AIProjectClientOptions options = new();
+options.EnableAutoFunctionCalls(delegates);
path: sdk/ai/Azure.AI.Projects/README.md
user: login: howieleung
body: No worry!  beside create_run, Python SDK has a function called create_and_process_run.    For non-streaming, users either call create_run followed by writing their own while-loop and call function tool manually.   Or they call create_and_process_run that call create_run and embed the while-loop with auto function calls.   Currently this SDK doesn't have something similar to create_and_process_run.   I have discussed with Jarno this release we will do auto function call for streaming only.   We might consider to create createAndRunProcess with auto function call for non-streaming.
created_at: 2025-04-28T06:25:29Z
author_association: MEMBER
diff_hunk: @@ -207,8 +207,8 @@ public AgentsClient(System.Uri endpoint, string subscriptionId, string resourceG
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(Azure.AI.Projects.AgentThread thread, Azure.AI.Projects.Agent agent, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response> CreateRunAsync(string threadId, Azure.Core.RequestContent content, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, Azure.RequestContext context = null) { throw null; }
         public virtual System.Threading.Tasks.Task<Azure.Response<Azure.AI.Projects.ThreadRun>> CreateRunAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, bool? stream = default(bool?), float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.RunAdditionalFieldList> include = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
-        public virtual System.ClientModel.AsyncCollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreamingAsync(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+        public virtual System.ClientModel.CollectionResult<Azure.AI.Projects.StreamingUpdate> CreateRunStreaming(string threadId, string assistantId, string overrideModelName = null, string overrideInstructions = null, string additionalInstructions = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ThreadMessageOptions> additionalMessages = null, System.Collections.Generic.IEnumerable<Azure.AI.Projects.ToolDefinition> overrideTools = null, float? temperature = default(float?), float? topP = default(float?), int? maxPromptTokens = default(int?), int? maxCompletionTokens = default(int?), Azure.AI.Projects.TruncationObject truncationStrategy = null, System.BinaryData toolChoice = null, System.BinaryData responseFormat = null, bool? parallelToolCalls = default(bool?), System.Collections.Generic.IReadOnlyDictionary<string, string> metadata = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken), Azure.AI.Projects.AutoFunctionCallOptions autoFunctionCallOptions = null) { throw null; }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.net8.0.cs
user: login: KrzysztofCwalina
body: BTW, these signatures are very long. We should just package them into an options type. Nut this is not directly the result of your PR, so possibly lets do this offline
created_at: 2025-04-28T18:31:31Z
author_association: MEMBER
diff_hunk: @@ -629,6 +629,10 @@ public enum AuthenticationType
         Custom = 3,
         None = 4,
     }
+    public partial class AutoFunctionCallOptions
+    {
+        public AutoFunctionCallOptions(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs
user: login: KrzysztofCwalina
body: I think the first parameter should be called toolDelegates
created_at: 2025-04-28T18:32:26Z
author_association: MEMBER
diff_hunk: @@ -629,6 +629,10 @@ public enum AuthenticationType
         Custom = 3,
         None = 4,
     }
+    public partial class AutoFunctionCallOptions
+    {
+        public AutoFunctionCallOptions(System.Collections.Generic.Dictionary<string, System.Delegate> delegates, int maxRetry) { }
path: sdk/ai/Azure.AI.Projects/api/Azure.AI.Projects.netstandard2.0.cs
user: login: howieleung
body: Done
created_at: 2025-04-28T21:09:55Z
author_association: MEMBER
diff_hunk: @@ -106,8 +106,8 @@
 		<PackageReference Update="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
 
     <!-- Azure SDK packages -->
-    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.2" />
-    <PackageReference Update="Azure.AI.OpenAI" Version="2.0.0" />
+    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.4" />
+    <PackageReference Update="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
path: eng/Packages.Data.props
user: login: jsquire
body: The Inference one can stay, the others must be reverted.   Once a package reaches its initial GA, the central version should only be a GA, outside of special cases.

If you have specific needs to move to beta packages, please use a local `VersionOverride` and file an issue to track removing.
created_at: 2025-04-29T18:58:33Z
author_association: MEMBER
diff_hunk: @@ -106,8 +106,8 @@
 		<PackageReference Update="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
 
     <!-- Azure SDK packages -->
-    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.2" />
-    <PackageReference Update="Azure.AI.OpenAI" Version="2.0.0" />
+    <PackageReference Update="Azure.AI.Inference" Version="1.0.0-beta.4" />
+    <PackageReference Update="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
path: eng/Packages.Data.props
user: login: howieleung
body: Done
created_at: 2025-04-29T19:29:43Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: jsquire
body: Please leave a comment that links the issue tracking the removal of the overrides.   We want to avoid these unless there's a very specific need.   The issue should detail what version or feature needs to be available in the GA package to remove.
created_at: 2025-04-29T20:41:52Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: howieleung
body: Thanks for suggestion.   I put it at the top of the PR.
created_at: 2025-04-29T20:50:42Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: jsquire
body: To clarify, this is not meant to be informational for this PR, it is meant to identify and track future work needed to remove the override and should exist as a GH issue and a comment in the .csproj local to these `VersionOverride` elements.
created_at: 2025-04-29T21:13:45Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: howieleung
body: Done.   I created a ticket 
https://github.com/Azure/azure-sdk-for-net/issues/49730
created_at: 2025-04-29T22:45:44Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: jsquire
body: > and a comment in the .csproj local to these VersionOverride elements.

Still waiting for this.  
created_at: 2025-04-29T23:01:09Z
author_association: MEMBER
diff_hunk: @@ -34,6 +31,9 @@
     <PackageReference Include="Moq" />
     <PackageReference Include="Azure.ResourceManager" />
     <PackageReference Include="Azure.ResourceManager.CognitiveServices" />
+    <PackageReference Include="Azure.AI.Inference" />
+    <PackageReference Include="Azure.AI.OpenAI" VersionOverride="2.2.0-beta.4"/>
path: sdk/ai/Azure.AI.Projects/tests/Azure.AI.Projects.Tests.csproj
user: login: howieleung
body: Sorry..   I just comment in .csproj.    
created_at: 2025-04-29T23:16:34Z
author_association: MEMBER

PR comments:
user: login: azure-sdk
created_at: 2025-04-10T23:28:04Z
body: ## API Change Check

APIView identified API level changes in this PR and created the following API reviews

[Azure.AI.Projects](https://spa.apiview.dev/review/45fcc1c2ff9b4a609fa40bab9b308034?activeApiRevisionId=747d41a85b1144578d29a6a0f90ce707)
<!-- Fetch URI: https://apiview.dev/api/pullrequests?pullRequestNumber=49293&repoName=Azure/azure-sdk-for-net&commitSHA=9fefb34ae87f774d5dd90f559fb1886d2c37c643 -->
author_association: COLLABORATOR
user: login: azure-sdk
created_at: 2025-04-24T00:35:33Z
body: **API change check**

APIView has identified API level changes in this PR and created following API reviews.

[Azure.AI.Projects](https://apiview.dev/Assemblies/Review/45fcc1c2ff9b4a609fa40bab9b308034?revisionId=747d41a85b1144578d29a6a0f90ce707)


author_association: COLLABORATOR
user: login: jsquire
created_at: 2025-04-29T17:01:30Z
body: /azp run net - pullrequest
author_association: MEMBER
user: login: azure-pipelines[bot]
created_at: 2025-04-29T17:02:06Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE
user: login: howieleung
created_at: 2025-04-29T17:22:03Z
body: /azp run net - pullrequest
author_association: MEMBER
user: login: azure-pipelines[bot]
created_at: 2025-04-29T17:22:32Z
body: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
author_association: NONE