diff --git a/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.csproj b/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.csproj
index 8aa91a3406b..788f6a3f3ff 100644
--- a/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.csproj
+++ b/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/AGUIServer.csproj
@@ -9,8 +9,8 @@
-
+
diff --git a/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/Program.cs b/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/Program.cs
index e3de957c1e7..6f1ed5ba3ef 100644
--- a/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/Program.cs
+++ b/dotnet/samples/05-end-to-end/AGUIClientServer/AGUIServer/Program.cs
@@ -1,13 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.ClientModel.Primitives;
using System.ComponentModel;
using AGUIServer;
-using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
-using OpenAI.Chat;
+using OpenAI;
+using OpenAI.Responses;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
@@ -19,35 +20,15 @@
const string AgentName = "AGUIAssistant";
-// Create the AI agent with tools
+// Create a Responses-backed OpenAI client that sends a bearer token to the Azure endpoint.
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
-var agent = new AzureOpenAIClient(
- new Uri(endpoint),
- new DefaultAzureCredential())
- .GetChatClient(deploymentName)
- .AsAIAgent(
- name: AgentName,
- tools: [
- AIFunctionFactory.Create(
- () => DateTimeOffset.UtcNow,
- name: "get_current_time",
- description: "Get the current UTC time."
- ),
- AIFunctionFactory.Create(
- ([Description("The weather forecast request")]ServerWeatherForecastRequest request) => {
- return new ServerWeatherForecastResponse()
- {
- Summary = "Sunny",
- TemperatureC = 25,
- Date = request.Date
- };
- },
- name: "get_server_weather_forecast",
- description: "Gets the forecast for a specific location and date",
- AGUIServerSerializerContext.Default.Options)
- ]);
+IChatClient chatClient = new OpenAIClient(
+ new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"),
+ new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
+ .GetResponsesClient()
+ .AsIChatClientWithStoredOutputDisabled(model: deploymentName);
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
// make sure to also register an AgentIsolationKeyProvider to scope sessions by principal in multi-user
@@ -57,7 +38,26 @@
// Register the agent with the host and configure it to use an in-memory session store
// so that conversation state is maintained across requests. In production, you may want to use a persistent session store.
builder
- .AddAIAgent(AgentName, (_, _) => agent)
+ .AddAIAgent(AgentName, "You are a helpful assistant.", chatClient)
+ .WithAITools(
+ new HostedWebSearchTool(),
+ AIFunctionFactory.Create(
+ () => DateTimeOffset.UtcNow,
+ name: "get_current_time",
+ description: "Get the current UTC time."),
+ AIFunctionFactory.Create(
+ ([Description("The weather forecast request")] ServerWeatherForecastRequest request) =>
+ {
+ return new ServerWeatherForecastResponse()
+ {
+ Summary = "Sunny",
+ TemperatureC = 25,
+ Date = request.Date
+ };
+ },
+ name: "get_server_weather_forecast",
+ description: "Gets the forecast for a specific location and date",
+ AGUIServerSerializerContext.Default.Options))
.WithInMemorySessionStore();
WebApplication app = builder.Build();
diff --git a/dotnet/samples/05-end-to-end/AGUIClientServer/README.md b/dotnet/samples/05-end-to-end/AGUIClientServer/README.md
index 0501ec54694..30adeb1eef2 100644
--- a/dotnet/samples/05-end-to-end/AGUIClientServer/README.md
+++ b/dotnet/samples/05-end-to-end/AGUIClientServer/README.md
@@ -18,11 +18,18 @@ The demonstration has two components:
Configure the required Azure OpenAI environment variables:
```powershell
-$env:AZURE_OPENAI_ENDPOINT="<>"
+$env:AZURE_OPENAI_ENDPOINT="https://.openai.azure.com/openai/v1/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
```
-> **Note:** This sample uses `DefaultAzureCredential` for authentication. Make sure you're authenticated with Azure (e.g., via `az login`, Visual Studio, or environment variables).
+> [!NOTE]
+> Include `/openai/v1/` in the endpoint. The OpenAI SDK uses `DefaultAzureCredential` to obtain a bearer token. Make sure you're authenticated with Azure, for example through `az login`, Visual Studio, or environment variables.
+
+> [!NOTE]
+> This sample calls Azure OpenAI inference directly through the resource endpoint. It does not require a Microsoft Foundry project. A project-scoped application would instead use a Foundry project endpoint with `Azure.AI.Projects` and the Agent Framework Foundry provider.
+
+> [!NOTE]
+> The server uses the Azure OpenAI Responses API because hosted web search is a Responses API tool. It sets `store` to `false` so Agent Framework persists chat history in the configured session store instead of depending on service-retained responses. Web search uses Grounding with Bing and may incur additional charges; review the [web search documentation and data usage terms](https://learn.microsoft.com/azure/foundry/openai/how-to/web-search) before using it.
## Running the Sample
@@ -117,13 +124,18 @@ User (:q or quit to exit): :q
The `AGUIServer` uses the `MapAGUIServer` extension method to expose an agent through the AG-UI protocol:
```csharp
-AIAgent agent = new OpenAIClient(apiKey)
- .GetChatClient(model)
- .AsAIAgent(
- instructions: "You are a helpful assistant.",
- name: "AGUIAssistant");
-
-app.MapAGUIServer("/", agent);
+IChatClient chatClient = new OpenAIClient(
+ new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"),
+ new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
+ .GetResponsesClient()
+ .AsIChatClientWithStoredOutputDisabled(model: deploymentName);
+
+builder
+ .AddAIAgent("AGUIAssistant", "You are a helpful assistant.", chatClient)
+ .WithAITool(new HostedWebSearchTool())
+ .WithInMemorySessionStore();
+
+app.MapAGUIServer("AGUIAssistant", "/");
```
This automatically handles:
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs
new file mode 100644
index 00000000000..587af5ff339
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+using OpenAI;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.UnitTests;
+
+public sealed class AgentHostingServiceCollectionExtensionsTests
+{
+ [Fact]
+ public async Task HostedWebSearchTool_WithResponsesClient_UsesResponsesWireFormatAsync()
+ {
+ // Arrange
+ using var handler = new RecordingHandler();
+#pragma warning disable CA5399
+ using var httpClient = new HttpClient(handler);
+#pragma warning restore CA5399
+ using IChatClient chatClient = new ResponsesClient(
+ new ApiKeyCredential("test-key"),
+ new OpenAIClientOptions
+ {
+ Endpoint = new Uri("https://example.test/v1"),
+ Transport = new HttpClientPipelineTransport(httpClient)
+ })
+ .AsIChatClientWithStoredOutputDisabled(model: "test-model");
+
+ var services = new ServiceCollection();
+ services
+ .AddAIAgent("test-agent", "You are a helpful assistant.", chatClient)
+ .WithAITool(new HostedWebSearchTool());
+ using ServiceProvider serviceProvider = services.BuildServiceProvider();
+ AIAgent agent = serviceProvider.GetRequiredKeyedService("test-agent");
+
+ // Act
+ await agent.RunAsync("What happened in the news today?");
+
+ // Assert
+ using JsonDocument request = JsonDocument.Parse(Assert.IsType(handler.RequestBody));
+ Assert.False(request.RootElement.GetProperty("store").GetBoolean());
+ Assert.Contains(
+ request.RootElement.GetProperty("include").EnumerateArray(),
+ property => property.GetString() == "reasoning.encrypted_content");
+ Assert.False(request.RootElement.TryGetProperty("web_search_options", out _));
+ JsonElement webSearchTool = Assert.Single(request.RootElement.GetProperty("tools").EnumerateArray());
+ Assert.Equal("web_search", webSearchTool.GetProperty("type").GetString());
+ Assert.Equal("/v1/responses", handler.RequestUri?.AbsolutePath);
+ }
+
+ private sealed class RecordingHandler : HttpMessageHandler
+ {
+ public Uri? RequestUri { get; private set; }
+
+ public string? RequestBody { get; private set; }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ this.RequestUri = request.RequestUri;
+ this.RequestBody = await request.Content!.ReadAsStringAsync(cancellationToken);
+
+ return new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = new StringContent(
+ """
+ {
+ "id": "resp_1",
+ "object": "response",
+ "created_at": 1700000000,
+ "status": "completed",
+ "model": "test-model",
+ "output": [],
+ "usage": {
+ "input_tokens": 1,
+ "output_tokens": 1,
+ "total_tokens": 2
+ }
+ }
+ """,
+ Encoding.UTF8,
+ "application/json"),
+ RequestMessage = request
+ };
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
index 17d9742436c..355e271d767 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
@@ -19,6 +19,7 @@
+