Skip to content

Migrate AI Gateway infrastructure to standalone RP and aliases - #23

Merged
Paul Yuknewicz (paulyuk) merged 6 commits into
mainfrom
copilot/migrate-bicep-to-aigateways
Oct 24, 2026
Merged

Paul Yuknewicz (paulyuk) merged 6 commits into
mainfrom
copilot/migrate-bicep-to-aigateways

Conversation

Copilot AI commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Move provisioning from the legacy APIM service hierarchy to Microsoft.ApiManagement/aigateways, and expose client-facing model aliases with multi-model failover and weighted routing.

  • Infrastructure: Use the live 2025-09-01-preview contract, Consumption SKU, and explicit workspaces/default parent. Register models using the live displayName, apiFormat, endpoint paths, and deployment-name requirements; omit inline model policies.
  • Aliases: Add gateway-child model IDs as alias backends. The default gpt-latest alias fails over from the full model to mini; gpt-mini-latest targets mini directly. modelAliasBackends can configure weighted traffic splits.
backendModels: [
  { modelArmName: modelDeploymentName, priority: 0, weight: 100 }
  { modelArmName: miniModelDeploymentName, priority: 1, weight: 100 }
]
  • Lifecycle and runtime: Update scripts to use the aigateways resource ID, Consumption ownership checks, and frontend.defaultHostname with a gatewayUrl fallback. Skip legacy APIM deleted-services cleanup.
  • Outputs and docs: Export alias IDs and the default alias name; update migration and routing documentation.

Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>
Copilot AI changed the title [WIP] Migrate infra to Microsoft.ApiManagement/aigateways RP and add model aliases Migrate AI Gateway infrastructure to standalone RP and aliases Oct 24, 2026
@paulyuk

Copy link
Copy Markdown
Member

Copilot bump dependencies to latest support versions so we have no vulnerable ones or dependabot activity (see other PRs).

Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Copilot bump dependencies to latest support versions so we have no vulnerable ones or dependabot activity (see other PRs).

Updated the direct dependency floors and refreshed uv.lock to the latest compatible releases, consolidating the open Dependabot updates. Advisory checks reported no vulnerabilities, and the Python test suite passed. Commit: d95ed50.

@paulyuk

Copy link
Copy Markdown
Member

Copilot After deploying and invoking commit d95ed50 in Foundry Hosted Agents, POST /responses fails during agent initialization because the dependency bump jumped across breaking versions (agent-framework-core 1.11.0 -> 1.23.0, agent-framework-foundry-hosting 1.0.0a260630 -> 1.0.0b261022, agent-framework-openai 1.10.1 -> 1.17.0, azure-ai-agentserver-responses 1.0.0b8 -> 2.3.0, mcp 1.28.1 -> 2.3.0, openai 2.46.0 -> 3.37.0) without updating the app for those changes.

Here is the container failure from azd ai agent monitor:

13:46:44  stderr   2026-10-24 20:46:44,435 INFO azure.ai.agentserver: Creating response caresp_...: streaming=True background=False store=True model= conversation_id=conv_... previous_response_id=None has_user_id=True has_call_id=True
13:46:44  stderr   2026-10-24 20:46:44,632 INFO azure.ai.agentserver: Foundry storage GET ***/storage/history/item_ids?api-version=v1 -> 200
13:46:44  stderr   2026-10-24 20:46:44,900 INFO azure.ai.agentserver: Foundry storage POST ***/storage/responses?api-version=v1 -> 201
13:46:45  stderr   2026-10-24 20:46:45,493 INFO httpx2: HTTP Request: POST https://.../api/projects/.../toolboxes/repo-digest-tools/versions/1/mcp?api-version=v1 "HTTP/1.1 200 OK"
13:46:45  stderr   2026-10-24 20:46:45,498 ERROR agent_framework_foundry_hosting._responses: Hosted Responses failure (phase=agent initialization, response_id=caresp_..., error_type=ToolExecutionException).
13:46:45  stderr   Traceback (most recent call last):
13:46:45  stderr     File "/app/.venv/lib/python3.13/site-packages/agent_framework/_mcp.py", line 1792, in _listen_capability_list_changes
13:46:45  stderr       from tenacity import AsyncRetrying, before_sleep_log, retry_if_exception, wait_exponential
13:46:45  stderr   ModuleNotFoundError: No module named 'tenacity'
...
13:46:45  stderr   agent_framework.exceptions.ToolExecutionException: ('Failed to enter context manager.', ModuleNotFoundError('MCP subscription recovery requires `tenacity`. Please install `tenacity`.'))

Please keep the upgraded MAF and Foundry SDKs and fix all breaking changes caused by the version bump:

  1. Add missing tenacity dependency (agent-framework-core 1.23.0 + mcp 2.3.0):

    • agent_framework/_mcp.py (_listen_capability_list_changes, called from FoundryToolbox.connect()) now unconditionally imports tenacity, which agent-framework-core==1.23.0 only declares under extra == "all" (tenacity<9.2.0,>=9.1.4).
    • Add tenacity>=9.1.4,<9.2.0 to pyproject.toml and update uv.lock.
  2. Audit and align main.py and repo_digest_agent.py with agent-framework-foundry-hosting 1.0.0b261022 and azure-ai-agentserver-responses 2.3.0:

    • In agent-framework-foundry-hosting 1.0.0b261022 (see microsoft/agent-framework python/samples/04-hosting/foundry-hosted-agents/responses/foundry_toolbox/main.py), check whether ResponsesHostServer should receive agent=build_agent as a request-scoped factory callable (rather than a process-lifetime build_agent() singleton) and explicit history_source="agent_server" so FoundryToolbox connections and platform headers are cleanly scoped per request.
    • Verify how ResponsesHostServer (history_source="agent_server"), FoundryStorageProvider (store=True inbound requests from Foundry), OpenAIChatClient (STORES_BY_DEFAULT = True), and Agent(..., default_options={"store": False, "max_tokens": DEFAULT_MAX_OUTPUT_TOKENS}) interact in 1.0.0b261022 + azure-ai-agentserver-responses 2.3.0 so neither stored Foundry requests (store=True, streaming) nor local chat.py requests (store=False, non-streaming) trigger configuration or stored_output_violation errors.
    • Check if any other APIs in repo_digest_agent.py, github_mcp_middleware.py, or main.py changed across agent-framework-core 1.23.0, agent-framework-openai 1.17.0 (openai 3.37.0), mcp 2.3.0, and azure-ai-agentserver-* 2.x.
  3. Add runtime/integration unit tests:

    • Existing unit tests only cover github_mcp_middleware.py and github_credential_validator.py, which is why CI missed the runtime failure in main.py / repo_digest_agent.py.
    • Add tests that exercise build_agent(), ResponsesHostServer, FoundryToolbox connection setup (verifying _listen_capability_list_changes / tenacity is importable and doesn't fail during connect()), and an end-to-end mocked /responses request (both store=True streaming and store=False).

Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Copilot After deploying and invoking commit d95ed50 in Foundry Hosted Agents, POST /responses fails during agent initialization because the dependency bump jumped across breaking versions (`agent...

Added the constrained tenacity dependency, switched the hosted server to a request-scoped agent factory with history_source="agent_server", and added Toolbox-connect plus stored/unstored /responses tests. All 15 Python tests and both shell tests pass; CodeQL found no alerts. Commit: 60ba4c0.

@paulyuk

Copy link
Copy Markdown
Member

Copilot Nice fix in 60ba4c0! Let's also make sure future PRs and dependency updates can't break the sample silently, without requiring complicated cloud auth in PR CI:

  1. Add a zero-auth GitHub Actions CI workflow (.github/workflows/ci.yml) on push to main and pull_request that:

    • Validates Bicep templates compile cleanly (az bicep build --file infra/main.bicep and az bicep build --file infra/ai-gateway/main.bicep).
    • Runs the infrastructure shell tests (bash tests/test-apim-lifecycle.sh and bash tests/test-ai-gateway-model-registration.sh).
    • Runs the Python unit test suite (uv run --frozen python -m unittest discover -s tests -v).
    • Builds the Dockerfile image and runs a container smoke test against a lightweight local mock HTTP server (faking the AI Gateway /default/models/openai/v1/responses endpoint and Foundry Toolbox MCP endpoint) that starts the real main.py container process and verifies both GET /readiness (200) and a real POST /responses round-trip succeed with zero Azure/GitHub credentials.
  2. Add a simple live azd smoke script (tests/test-azd-smoke.sh):

    • Uses the operator's existing azd / az login and current azd environment (no extra auth configuration required).
    • Supports optional --deploy (azd deploy) or --up (azd up --no-prompt) flags before testing, and by default invokes the deployed hosted agent (azd ai agent invoke "Create a concise daily repo digest for microsoft/agent-framework."), asserting that the invocation exits 0 and returns a valid digest rather than an agent initialization or tool execution failure.
    • Document the local smoke test and test-azd-smoke.sh command briefly in README.md.

Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Copilot Nice fix in 60ba4c0! Let's also make sure future PRs and dependency updates can't break the sample silently, without requiring complicated cloud auth in PR CI:

  1. **Add a zero-auth GitHub ...

Added the zero-auth CI workflow, a real-container smoke test using local AI Gateway/Toolbox mocks, and the optional --deploy/--up azd smoke script with README instructions. Container readiness and both Responses modes, all 15 Python tests, Bicep builds, infrastructure shell tests, and azd script branches with stubs passed. CodeQL found no alerts. Commit: ac3a048.

@paulyuk

Copy link
Copy Markdown
Member

Copilot Testing the refreshed dependencies against a live Azure AI Gateway and Foundry Toolbox MCP endpoint surfaced two runtime breaking changes from the openai==3.37.0 and agent-framework-core==1.23.0 bump that the mock tests did not catch:

1. openai==3.37.0 sends both Authorization: Bearer workload-identity-auth and Api-Key (HTTP 400 from AI Gateway)

  • In openai==3.37.0 (openai/_client.py), AsyncOpenAI._auth_headers(security) now delegates to self._bearer_auth instead of self.auth_headers. Unlike auth_headers, _bearer_auth does not check api_key == WORKLOAD_IDENTITY_API_KEY_PLACEHOLDER ("workload-identity-auth"), so every request sends both Authorization: Bearer workload-identity-auth and Api-Key: <gateway_key>.
  • Live Azure AI Gateway rejects this with:
    openai.BadRequestError: Error code: 400 - {'statusCode': 400, 'message': 'Specify either an API key or an Authorization header, not both.'}
  • Note that putting "Authorization": Omit() in default_headers alone triggers AsyncOpenAI._validate_headers (TypeError: Could not resolve authentication method), which checks _has_omitted_header(custom_headers, "Authorization") on per-request options.headers (extra_headers).
  • Fix: Ensure outbound requests from _gateway_client() / build_agent() send Api-Key: <gateway_key> and omit Authorization (for example by passing "extra_headers": {"Authorization": Omit()} in default_options or configuring the underlying client so _build_headers omits Authorization while passing _validate_headers).

2. agent-framework-core==1.23.0 rejects per_page on github_actions_list (_FunctionArgumentValidationError)

  • agent-framework-core==1.23.0 strictly validates tool arguments against the MCP tool's inputSchema before calling the tool.
  • The GitHub MCP server's github_actions_list tool schema defines perPage (camelCase), not per_page:
    agent_framework._tools._FunctionArgumentValidationError: Unexpected argument(s) for 'aigw-github___github_actions_list': per_page
  • Fix:
    • In github_mcp_middleware.py, update _normalize_github_arguments for github_actions_list to pop any per_page argument (arguments.pop("per_page", None)) and set perPage=100 (matching github_list_pull_requests and github_list_issues).
    • In repo_digest_agent.py, update INSTRUCTIONS to say perPage 100 instead of per_page 100.
    • Update tests/test_github_mcp_middleware.py accordingly (and verify that if the model passes per_page, middleware normalizes it to perPage=100).

3. Tighten tests/container_smoke.py and tests/test_agent_runtime.py so these cannot regress

  • In tests/container_smoke.py:
    • On GATEWAY_PATH (/default/models/openai/v1/responses), assert that Api-Key is present and Authorization is not present in self.headers (return 400 matching AI Gateway if both are sent or Api-Key is missing).
    • Have the mock model response exercise a tool-call round (or test tool-call normalization + schema validation in tests/test_agent_runtime.py using the real MCP inputSchema with perPage on github_actions_list).
  • In tests/test_agent_runtime.py:
    • Add a test verifying that _gateway_client() / build_agent() outbound HTTP requests include Api-Key and do not include an Authorization header.

Co-authored-by: paulyuk <1968137+paulyuk@users.noreply.github.com>

Copilot AI commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Copilot Testing the refreshed dependencies against a live Azure AI Gateway and Foundry Toolbox MCP endpoint surfaced two runtime breaking changes from the openai==3.37.0 and `agent-framework-core==...

Fixed in 193c280. Gateway requests now send Api-Key without Authorization; github_actions_list drops per_page and normalizes to perPage=100. Added runtime, MCP-schema, and strict container-mock regression tests. All 16 Python tests, container readiness/Responses smoke tests, and infrastructure shell tests pass; CodeQL found no alerts. The latest CI run is approval-gated (action_required) and has zero jobs, so hosted CI did not execute.

@paulyuk
Paul Yuknewicz (paulyuk) marked this pull request as ready for review October 24, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate infra/ Bicep to Microsoft.ApiManagement/aigateways RP and add multi-model aliases

2 participants