.NET: Foundry agent-endpoint constructor uses ProjectOpenAIClient directly to fix hosted-agent URL routing#5677
Draft
rogerbarreto wants to merge 2 commits intomicrosoft:mainfrom
Draft
Conversation
…ectly to fix hosted-agent URL routing
Fixes the experimental FoundryAgent(Uri agentEndpoint, AuthenticationTokenProvider, ...)
constructor so it actually works against Foundry hosted agents.
The previous implementation routed through AzureAIProjectChatClient, which
internally called aiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForAgent(...).
For an agent-endpoint URL of the canonical shape
https://<host>/api/projects/<project>/agents/<agentName>/endpoint/protocols/openai
the chain produced
POST https://<host>/api/projects/<project>/openai/v1/responses
(project-level path, no /agents/ segment). The Foundry service rejects this with
HTTP 400 "Hosted agents can only be called through the agent endpoint:
.../agents/<agentName>/endpoint/protocols/openai/responses".
The constructor also extracted the agent name via
agentEndpoint.Segments[^1].TrimEnd('/'), which returns "openai" (the last segment),
not the agent name.
What changed
- Public ctor signature: clientOptions parameter type changed from
AIProjectClientOptions? to ProjectOpenAIClientOptions?. The constructor is
fundamentally building a ProjectOpenAIClient; accepting AIProjectClientOptions
was a leaky abstraction whose translation silently dropped any pipeline
policies the caller added via AddPolicy(...). With the direct type, caller
policies pass through to the per-agent traffic verbatim.
- Per-agent client construction: `new ProjectOpenAIClient(BearerTokenPolicy, ProjectOpenAIClientOptions)`
with Endpoint and AgentName set, then `GetProjectResponsesClient().AsIChatClient()`.
The SDK auto-appends ?api-version=v1 when AgentName is set.
- New private static ParseAgentEndpoint helper: single source of truth for both
agent-name extraction and project-root derivation. Tolerates trailing slash,
case variants on /agents/ and the suffix segment, strips query/fragment, and
throws ArgumentException with paramName=nameof(agentEndpoint) for malformed input.
- Project-level client (used by CreateConversationSessionAsync) is built fresh
from the derived project root with primitive properties copied
(RetryPolicy/NetworkTimeout/Transport/UserAgentApplicationId) plus MEAI UA.
- New GetService<ProjectOpenAIClient>() entry alongside the existing
GetService<AIProjectClient>() (the latter returns null in agent-endpoint mode
since no AIProjectClient is constructed on that path).
- Endpoint and AgentName on caller-supplied ProjectOpenAIClientOptions are
overridden by values derived from agentEndpoint.
Compatibility
- FoundryAgent is [Experimental(OPENAI001)]. No GA surface touched. The Foundry
project does not maintain PublicAPI.*.txt baselines so there is no shipped
baseline to update.
- The Microsoft.Agents.AI.Foundry csproj pins
Azure.AI.Projects to VersionOverride 2.1.0-beta.1 (matching what the IT and
hosting projects already use); the central pin in Directory.Packages.props
stays at 2.0.0.
- WireClientHeaders from PR microsoft#5652 is invoked on the agent-endpoint path so
per-call x-client-* headers behave identically across both ctors.
Tests
- 23 new unit tests in FoundryAgentTests.cs:
- 12 for the agent-endpoint constructor (URL routing for non-streaming and
streaming, conversations URL shape, MEAI UA stamping, caller-policy
passthrough on the per-agent pipeline, Endpoint/AgentName override
semantics, GetService matrix, ProjectOpenAIClient propagation,
UserAgentApplicationId propagation, null-arg validation, ID/Name slug)
- 9 for ParseAgentEndpoint (standard shape, trailing slash, casing,
sovereign-cloud host without /api/projects/ literal prefix, special chars
in agent name, query/fragment stripping, three negative cases)
- 2 null-arg tests for the public ctor
- All 250 Microsoft.Agents.AI.Foundry.UnitTests pass (was 221 baseline plus
29 from PR microsoft#5652 plus 23 new in this PR equals 273; pre-existing tests
collapsed by the rebase merge keep the total at 250).
- All 225 Microsoft.Agents.AI.Foundry.Hosting.UnitTests pass; no behavioral
change to the hosting layer.
- dotnet build clean across net8/9/10/netstandard2.0/net472 with
TreatWarningsAsErrors=true.
- dotnet format --verify-no-changes clean for the touched src and test projects.
…rosoft.Agents.AI.Foundry to preview Required to fix the NU1109 downgrade chain that broke CI on the agent-endpoint constructor rewire (microsoft#5677). Microsoft.Agents.AI.Foundry now depends on ProjectOpenAIClientOptions.AgentName and the (AuthenticationPolicy, options) constructor that only exist in Azure.AI.Projects 2.1.0-beta.1. Changes: * Directory.Packages.props: Azure.AI.Projects 2.0.0 -> 2.1.0-beta.1. * Microsoft.Agents.AI.Foundry.csproj: drop IsReleased=true so the package ships as preview (matches the beta SDK we now depend on). Add a comment noting the flip is temporary and should revert once Azure.AI.Projects ships a stable 2.1.0. * Drop redundant VersionOverride="2.1.0-beta.1" from the 10 csprojs that had it as a workaround; the central pin now suffices. Verified: * dotnet build agent-framework-dotnet.slnx --warnaserror clean across all TFMs. * Microsoft.Agents.AI.Foundry.UnitTests 250/250 pass. * Microsoft.Agents.AI.Foundry.Hosting.UnitTests 211/211 pass. * dotnet format --verify-no-changes clean for the touched src and test projects.
8428195 to
7520c08
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the experimental
FoundryAgent(Uri agentEndpoint, AuthenticationTokenProvider, ...)constructor so it actually works against Foundry hosted agents.The bug
For an agent-endpoint URL of the canonical shape
the previous implementation routed through
AzureAIProjectChatClient, which internally calledaiProjectClient.GetProjectOpenAIClient().GetProjectResponsesClientForAgent(...). That chain produced(project-level path, no
/agents/segment). The Foundry service rejected this with HTTP 400:The constructor also extracted the agent name via
agentEndpoint.Segments[^1].TrimEnd('/'), which returns"openai"(the last segment), not the agent name.The fix
Build a per-agent
ProjectOpenAIClientdirectly via the(AuthenticationPolicy, ProjectOpenAIClientOptions)constructor withEndpointandAgentNameset, thenGetProjectResponsesClient().AsIChatClient(). The SDK auto-appends?api-version=v1whenAgentNameis set.Verified live against
it-happy-pathon the Tao project: returnsECHO. The same wire-capture now shows the URL is…/agents/<name>/endpoint/protocols/openai/responses?api-version=v1.What changed
clientOptionsparameter type changed fromAIProjectClientOptions?toProjectOpenAIClientOptions?. The constructor is fundamentally building aProjectOpenAIClient; acceptingAIProjectClientOptionswas a leaky abstraction whose translation silently dropped any pipeline policies the caller added viaAddPolicy(...). With the direct type, caller policies pass through to the per-agent traffic verbatim.EndpointandAgentNameon caller-suppliedProjectOpenAIClientOptionsare overridden by values derived fromagentEndpoint(documented in the XML doc).ParseAgentEndpointhelper: single source of truth for both agent-name extraction and project-root derivation. Tolerates trailing slash, casing variants on/agents/and the suffix segment, strips query/fragment, and throwsArgumentExceptionwithparamName=nameof(agentEndpoint)for malformed input.CreateConversationSessionAsync) is built fresh from the derived project root with primitive properties copied (RetryPolicy/NetworkTimeout/Transport/UserAgentApplicationId) plus MEAI UA. CallerAddPolicydoes not propagate to this pipeline (ClientPipelineOptionsdoes not publicly enumerate its policies); documented as a limitation.GetService<ProjectOpenAIClient>()entry alongside the existingGetService<AIProjectClient>(). The latter now returnsnullin agent-endpoint mode since noAIProjectClientis constructed on that path.WireClientHeadersfrom .NET: Bump MEAI to 10.5.1 and add Foundry per-call x-client header support #5652 is invoked on the agent-endpoint path so per-callx-client-*headers behave identically across both ctors.Compatibility
FoundryAgentcarries[Experimental(OPENAI001)]. No GA surface touched. TheMicrosoft.Agents.AI.Foundryproject does not maintainPublicAPI.*.txtbaselines so there is no shipped baseline to update.Microsoft.Agents.AI.Foundrycsproj pinsAzure.AI.ProjectstoVersionOverride="2.1.0-beta.1"(matching what the IT and hosting projects already use); the central pin inDirectory.Packages.propsstays at2.0.0.Tests
23 new unit tests in
FoundryAgentTests.cs:Endpoint/AgentNameoverride semantics,GetServicematrix,ProjectOpenAIClientpropagation,UserAgentApplicationIdpropagation, null-arg validation, ID/Name slug.ParseAgentEndpoint: standard shape, trailing slash, casing, sovereign-cloud host without/api/projects/literal prefix, special chars in agent name, query/fragment stripping, three negative cases.Verification
Microsoft.Agents.AI.Foundry.UnitTestspass.Microsoft.Agents.AI.Foundry.Hosting.UnitTestspass; no behavioral change to the hosting layer.dotnet buildclean acrossnet8/net9/net10/netstandard2.0/net472withTreatWarningsAsErrors=true.dotnet format --verify-no-changesclean for the touched src and test projects.