diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index ab74c2650a..5d746ccd97 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -1,4 +1,4 @@ - + @@ -260,7 +260,9 @@ - + + + diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props index b89b823219..6d1c657fa8 100644 --- a/dotnet/nuget/nuget-package.props +++ b/dotnet/nuget/nuget-package.props @@ -8,7 +8,7 @@ $(VersionPrefix)-preview.260402.1 $(VersionPrefix) - 0.9.0-hosted.260402.2 + 0.9.0-hosted.260403.1 1.0.0 Debug;Release;Publish diff --git a/dotnet/samples/04-hosting/FoundryResponsesRepl/FoundryResponsesRepl.csproj b/dotnet/samples/04-hosting/FoundryResponsesRepl/FoundryResponsesRepl.csproj new file mode 100644 index 0000000000..15cdf820eb --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryResponsesRepl/FoundryResponsesRepl.csproj @@ -0,0 +1,21 @@ + + + + Exe + net10.0 + enable + enable + false + $(NoWarn);NU1903;NU1605 + + + + + + + + + + + + diff --git a/dotnet/samples/04-hosting/FoundryResponsesRepl/Program.cs b/dotnet/samples/04-hosting/FoundryResponsesRepl/Program.cs new file mode 100644 index 0000000000..be0e6ccf7b --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryResponsesRepl/Program.cs @@ -0,0 +1,98 @@ +// Foundry Responses Client REPL +// +// Connects to a Foundry Responses agent running on a given endpoint +// and provides an interactive multi-turn chat REPL. +// +// Usage: +// dotnet run (connects to http://localhost:8088) +// dotnet run -- --endpoint http://localhost:9090 +// dotnet run -- --endpoint https://my-foundry-project.services.ai.azure.com +// +// The endpoint should be running a Foundry Responses server (POST /responses). + +using System.ClientModel; +using Microsoft.Agents.AI; +using OpenAI; +using OpenAI.Responses; + +// ── Parse args ──────────────────────────────────────────────────────────────── + +string endpointUrl = "http://localhost:8088"; +for (int i = 0; i < args.Length - 1; i++) +{ + if (args[i] is "--endpoint" or "-e") + { + endpointUrl = args[i + 1]; + } +} + +// ── Create an agent-framework agent backed by the remote Responses endpoint ── + +// The OpenAI SDK's ResponsesClient can target any OpenAI-compatible endpoint. +// We use a dummy API key since our local server doesn't require auth. +var credential = new ApiKeyCredential( + Environment.GetEnvironmentVariable("RESPONSES_API_KEY") ?? "no-key-needed"); + +var openAiClient = new OpenAIClient( + credential, + new OpenAIClientOptions { Endpoint = new Uri(endpointUrl) }); + +ResponsesClient responsesClient = openAiClient.GetResponsesClient(); + +// Wrap as an agent-framework AIAgent via the OpenAI extensions. +// We pass an empty model since hosted agents use their own model configuration. +AIAgent agent = responsesClient.AsAIAgent( + model: "", + name: "remote-agent"); + +// Create a session so multi-turn context is preserved via previous_response_id +AgentSession session = await agent.CreateSessionAsync(); + +// ── REPL ────────────────────────────────────────────────────────────────────── + +Console.ForegroundColor = ConsoleColor.Cyan; +Console.WriteLine("╔══════════════════════════════════════════════════════════╗"); +Console.WriteLine("║ Foundry Responses Client REPL ║"); +Console.WriteLine($"║ Connected to: {endpointUrl,-41}║"); +Console.WriteLine("║ Type a message or 'quit' to exit ║"); +Console.WriteLine("╚══════════════════════════════════════════════════════════╝"); +Console.ResetColor(); +Console.WriteLine(); + +while (true) +{ + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("You> "); + Console.ResetColor(); + + string? input = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(input)) continue; + if (input.Equals("quit", StringComparison.OrdinalIgnoreCase) || + input.Equals("exit", StringComparison.OrdinalIgnoreCase)) + break; + + try + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write("Agent> "); + Console.ResetColor(); + + // Stream the response token-by-token + await foreach (var update in agent.RunStreamingAsync(input, session)) + { + Console.Write(update); + } + + Console.WriteLine(); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"Error: {ex.Message}"); + Console.ResetColor(); + } + + Console.WriteLine(); +} + +Console.WriteLine("Goodbye!"); diff --git a/dotnet/samples/04-hosting/FoundryResponsesRepl/Properties/launchSettings.json b/dotnet/samples/04-hosting/FoundryResponsesRepl/Properties/launchSettings.json new file mode 100644 index 0000000000..4a939fc693 --- /dev/null +++ b/dotnet/samples/04-hosting/FoundryResponsesRepl/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "FoundryResponsesRepl": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:61980;http://localhost:61981" + } + } +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/InputConverter.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/InputConverter.cs index e6d607c793..10554d4e5c 100644 --- a/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/InputConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Hosting/InputConverter.cs @@ -69,7 +69,10 @@ internal static class InputConverter Temperature = (float?)request.Temperature, TopP = (float?)request.TopP, MaxOutputTokens = (int?)request.MaxOutputTokens, - ModelId = request.Model, + // Note: We intentionally do NOT set ModelId from request.Model here. + // The hosted agent already has its own model configured, and passing + // the client-provided model would override it (causing failures when + // clients send placeholder values like "hosted-agent"). }; } diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs index 7f36fd10d7..48c1a22ee8 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs @@ -157,7 +157,7 @@ public class InputConverterTests Assert.Equal(0.7f, options.Temperature); Assert.Equal(0.9f, options.TopP); Assert.Equal(1000, options.MaxOutputTokens); - Assert.Equal("gpt-4o", options.ModelId); + Assert.Null(options.ModelId); } [Fact] @@ -659,12 +659,13 @@ public class InputConverterTests } [Fact] - public void ConvertToChatOptions_ModelId_SetFromRequest() + public void ConvertToChatOptions_ModelId_NotSetFromRequest() { var request = AzureAIAgentServerResponsesModelFactory.CreateResponse(model: "my-model"); var options = InputConverter.ConvertToChatOptions(request); - Assert.Equal("my-model", options.ModelId); + // Model from the request is intentionally NOT propagated — the hosted agent uses its own model. + Assert.Null(options.ModelId); } }