Fix model override bug and add client REPL sample

- InputConverter: stop propagating request.Model to ChatOptions.ModelId
  Hosted agents use their own model; client-provided model values like
  'hosted-agent' were being passed through and causing server errors.
- Add FoundryResponsesRepl sample: interactive CLI client that connects
  to a Foundry Responses endpoint using ResponsesClient.AsAIAgent()
- Bump package version to 0.9.0-hosted.260403.1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
alliscode
2026-04-02 19:44:44 -07:00
Unverified
parent 3b03c6dd55
commit 21dc5311be
7 changed files with 144 additions and 7 deletions
+4 -2
View File
@@ -1,4 +1,4 @@
<Solution>
<Solution>
<Configurations>
<BuildType Name="Debug" />
<BuildType Name="Publish" />
@@ -260,7 +260,9 @@
<Project Path="samples/03-workflows/_StartHere/06_MixedWorkflowAgentsAndExecutors/06_MixedWorkflowAgentsAndExecutors.csproj" />
<Project Path="samples/03-workflows/_StartHere/07_WriterCriticWorkflow/07_WriterCriticWorkflow.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/" />
<Folder Name="/Samples/04-hosting/">
<Project Path="samples/04-hosting/FoundryResponsesRepl/FoundryResponsesRepl.csproj" />
</Folder>
<Folder Name="/Samples/04-hosting/FoundryResponsesHosting/">
<Project Path="samples/04-hosting/FoundryResponsesHosting/FoundryResponsesHosting.csproj" />
</Folder>
+1 -1
View File
@@ -8,7 +8,7 @@
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.260402.1</PackageVersion>
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
<!-- Branch-specific override to distinguish from mainline packages -->
<PackageVersion>0.9.0-hosted.260402.2</PackageVersion>
<PackageVersion>0.9.0-hosted.260403.1</PackageVersion>
<GitTag>1.0.0</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
<NoWarn>$(NoWarn);NU1903;NU1605</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Core" />
<PackageReference Include="OpenAI" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -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!");
@@ -0,0 +1,12 @@
{
"profiles": {
"FoundryResponsesRepl": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:61980;http://localhost:61981"
}
}
}
@@ -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").
};
}
@@ -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);
}
}