Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions IMPLEMENTATION_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,13 @@ Each model registration includes:
segment of the deployment resource ID

Model creation omits inline policies for compatibility with the live RP. The
alias `gpt-latest` routes to the full deployment at priority 0 and fails over to
the mini deployment at priority 1. Both backends explicitly set a weight of 100;
aliases can instead place multiple backends at the same priority with relative
weights for traffic splitting. The separate `gpt-mini-latest` alias addresses
the mini deployment directly. Each alias backend points to the gateway child
model resource ID, while the client sends the alias name as the OpenAI `model`.
alias `gpt-latest` routes through a three-tier failover chain: `gpt-6.1-sol` at
priority 0, `gpt-6-sol` at priority 1, and `gpt-5.4-mini` at priority 2. Each
backend explicitly sets a weight of 100; aliases can instead place multiple
backends at the same priority with relative weights for traffic splitting. The
separate `gpt-mini-latest` alias addresses the mini deployment directly. Each
alias backend points to the gateway child model resource ID, while the client
sends the alias name as the OpenAI `model`.

## Regions and model defaults

Expand All @@ -341,15 +342,19 @@ The AI Gateway preview region allowlist is:
- `swedencentral`
- `eastus2`

The model defaults are:
The model defaults use Global Standard deployments:

| Purpose | Foundry deployment / Gateway alias | Backing model | Version | Capacity |
| --- | --- | --- | --- | --- |
| Full | `gpt-latest` | `gpt-5.6-sol` | `2026-07-09` | 20 |
| Mini | `gpt-mini-latest` | `gpt-5.4-mini` | `2026-03-17` | 200 |
| Purpose | Foundry deployment / Gateway alias | Backing model | Version | SKU | Capacity |
| --- | --- | --- | --- | --- | --- |
| Primary | `gpt-latest` | `gpt-6.1-sol` | `2026-09-29` | `GlobalStandard` | 20 |
| Secondary failover | `gpt-6-sol` | `gpt-6-sol` | `2026-09-22` | `GlobalStandard` | 20 |
| Mini safety net | `gpt-mini-latest` | `gpt-5.4-mini` | `2026-03-17` | `GlobalStandard` | 200 |

If `gpt-5.6-sol` is unavailable, set `modelName=gpt-5.5` and
`modelVersion=2026-04-24`.
If the primary deployment is unavailable or its quota is exhausted, AI Gateway
automatically tries `gpt-6-sol` in a separate quota pool, then the higher-
capacity `gpt-5.4-mini` deployment. If a model/version is unavailable in the
selected region, override its corresponding name and version parameters with a
supported catalog deployment and keep the remaining fallback tiers configured.

Global Standard deployment capacity is provisioned on the Foundry model
account. Mini capacity 200 accommodates requests from the GitHub Copilot app,
Expand Down
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Daily Repo Digest with AI Gateway

This sample runs a Microsoft Agent Framework repo-digest agent in Foundry Hosted Agents. Its model calls use AI Gateway, and Foundry Toolbox routes read-only GitHub MCP calls through AI Gateway.
This sample demonstrates a **hub-and-spoke governance architecture** for Foundry Hosted Agents using **AI Gateway** as the central hub. The Microsoft Agent Framework repo-digest agent runs in an isolated Foundry project spoke (`infra/foundry-agents/`) with zero model deployments and no direct credentials to backing models or external MCP servers. All model inference (`gpt-latest` three-tier failover across `gpt-6.1-sol` → `gpt-6-sol` → `gpt-5.4-mini`, plus `gpt-mini-latest`) and read-only GitHub MCP tool calls (via Foundry Toolbox) are routed **exclusively through AI Gateway (`infra/ai-gateway/`)** — there is no other access path from the agent to the model account (`infra/foundry-models/`) or to GitHub MCP.

## Prerequisites

Expand Down Expand Up @@ -39,14 +39,14 @@ follow **[Set up the GitHub credential](IMPLEMENTATION_NOTES.md#tighten-the-gith

## Run locally

Create the local `.env` from the selected azd environment:
`azd up` and `azd provision` automatically generate `.env` from the active azd environment. You can also refresh `.env` manually at any time:

```bash
# macOS or Linux
./scripts/create-dev-env.sh
./scripts/create-dev-env.sh --force

# Windows
pwsh ./scripts/create-dev-env.ps1
pwsh ./scripts/create-dev-env.ps1 -Force
```

Start the agent:
Expand Down Expand Up @@ -114,4 +114,8 @@ The default is `microsoft/agent-framework`. The included scheduled routine keeps

**Start with [`repo_digest_agent.py`](repo_digest_agent.py).** It connects the Agent Framework agent directly to the AI Gateway model route and uses Foundry Toolbox for the AI Gateway GitHub MCP route. [`github_mcp_middleware.py`](github_mcp_middleware.py) keeps GitHub results bounded, and [`main.py`](main.py) hosts the agent in Foundry.

- **Hub (`infra/ai-gateway/`)**: Standalone AI Gateway (`Microsoft.ApiManagement/aigateways`) that enforces central routing, token rate limits, failover (`gpt-latest`), and MCP tool brokering (`toolServers/github`). Only AI Gateway's managed identity receives cross-resource runtime access to the model account through the configured Foundry User RBAC assignment. Only AI Gateway holds the GitHub MCP token.
- **Agent Spoke (`infra/foundry-agents/`)**: Hosts the Foundry project, ACR, Storage, and Hosted Agent runtime. Its project account has no model deployments and no RBAC assignment on the separate model account, ensuring the agent cannot bypass AI Gateway to call models or GitHub MCP directly.
- **Model Spoke (`infra/foundry-models/`)**: Dedicated model-only Azure AI Services account (`allowProjectManagement: false`, `disableLocalAuth: true`) hosting `gpt-6.1-sol`, `gpt-6-sol`, and `gpt-5.4-mini`, accessible to the agent only through AI Gateway.

See [`IMPLEMENTATION_NOTES.md`](IMPLEMENTATION_NOTES.md), including the [GitHub credential boundary](IMPLEMENTATION_NOTES.md#github-credential-boundary) and [how to tighten the GitHub credential to least privilege](IMPLEMENTATION_NOTES.md#tighten-the-github-credential-to-least-privilege), for architecture, authentication, AI Gateway contracts, security boundaries, configuration, monitoring, and deployment details.
56 changes: 56 additions & 0 deletions infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Infrastructure

Run the commands in this README from the `infra/` directory.

The subscription-scoped [`main.bicep`](main.bicep) template creates three
resource groups and deploys one module into each:

- [`foundry-models/`](foundry-models/README.md) creates a model-only Azure AI
Services account and the GPT deployments.
- [`foundry-agents/`](foundry-agents/README.md) creates the Foundry project and
hosted-agent runtime dependencies.
- [`ai-gateway/`](ai-gateway/README.md) creates AI Gateway, model registrations,
and aliases.

The [`scripts/`](scripts/README.md) directory contains the preprovision,
postprovision, and postdown lifecycle hooks. `azd provision` runs the
preprovision and postprovision hooks from `azure.yaml`; postprovision configures
the GitHub MCP ToolServer and Foundry Toolbox after Bicep creates the Azure
resources.

## Deploy the full stack

`azd provision` also works from `infra/`: azd walks up to find
[`../azure.yaml`](../azure.yaml), then provisions the full stack and runs its
preprovision and postprovision hooks:

```bash
azd provision
```

Alternatively, deploy the subscription-scoped Bicep template directly:

```bash
az deployment sub create \
--location eastus2 \
--template-file ./main.bicep \
--parameters environmentName=dailydigest \
location=eastus2 \
principalId="$(az ad signed-in-user show --query id -o tsv)"
```

The direct deployment creates the Azure resources but does not run azd hooks.
For GitHub MCP and Toolbox setup after a direct deployment, see
[`scripts/README.md`](scripts/README.md) and provide the deployment outputs to
the configuration script.

## Deploy individual modules

Each resource-group-scoped template can also be deployed independently. Create
its resource group first, then follow the module-specific parameters and
examples:

- [Foundry model account and deployments](foundry-models/README.md)
- [Foundry project and hosted-agent runtime](foundry-agents/README.md)
- [AI Gateway and model routing](ai-gateway/README.md)
- [Lifecycle and postprovision scripts](scripts/README.md)
139 changes: 139 additions & 0 deletions infra/ai-gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# AI Gateway

Run the commands in this README from the `infra/ai-gateway/` directory.

[`main.bicep`](main.bicep) is a resource-group-scoped module for an AI Gateway
with Consumption SKU. With the default `deployApimGatewayViaBicep: true`, it
creates `Microsoft.ApiManagement/aigateways`, the
`Microsoft.Web/connectorGateways` Connector Namespace, and `apiKeys/default`.
When monitoring is enabled, it also provisions Log Analytics, Application
Insights, and the Gateway telemetry exporter.

When `deployFoundryProvider` is enabled, the module assigns the Gateway identity
the Foundry User role on each account in `foundryResourceIds`, registers the
supplied `foundryModels` under `modelProviders/foundry`, and creates the
configured aliases. The sample routes `gpt-latest` through three tiers:
`gpt-6.1-sol` → `gpt-6-sol` → `gpt-5.4-mini` (priorities 0, 1, and 2,
respectively, with weight 100 each). `gpt-mini-latest` targets the mini model
directly.

## Gateway without a Foundry provider

Create a resource group, then deploy just the Gateway and Connector Namespace.
This minimal mode leaves `deployFoundryProvider` disabled, so it does not
register model catalog entries or aliases:

```bash
az group create --name rg-ai-gateway --location eastus2

az deployment group create \
--resource-group rg-ai-gateway \
--template-file ./main.bicep \
--parameters location=eastus2 gatewayName=aigw-example
```

## Connect an existing Foundry model account

The existing account must already contain deployments named `gpt-latest`,
`gpt-6-sol`, and `gpt-mini-latest` (or update the model names below to match its
deployments). The following example gets the account ID and endpoint, creates a
parameters file, and deploys the Gateway with registrations and aliases. The
operator needs permission to create the Gateway and assign its identity the
Foundry User role on the account.

```bash
FOUNDRY_RESOURCE_GROUP=rg-foundry-models
FOUNDRY_ACCOUNT_NAME=aimodels-example
FOUNDRY_ACCOUNT_ID="$(az cognitiveservices account show \
--resource-group "$FOUNDRY_RESOURCE_GROUP" \
--name "$FOUNDRY_ACCOUNT_NAME" \
--query id --output tsv)"
FOUNDRY_ENDPOINT="$(az cognitiveservices account show \
--resource-group "$FOUNDRY_RESOURCE_GROUP" \
--name "$FOUNDRY_ACCOUNT_NAME" \
--query properties.endpoint --output tsv)"

az group create --name rg-ai-gateway --location eastus2

cat > /tmp/ai-gateway.parameters.json <<EOF
{
"\$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {"value": "eastus2"},
"gatewayName": {"value": "aigw-example"},
"enableMonitoring": {"value": true},
"appInsightsName": {"value": "aigw-example-insights"},
"logAnalyticsWorkspaceName": {"value": "aigw-example-logs"},
"deployFoundryProvider": {"value": true},
"foundryEndpoint": {"value": "$FOUNDRY_ENDPOINT"},
"foundryResourceIds": {"value": ["$FOUNDRY_ACCOUNT_ID"]},
"foundryModels": {"value": [
{
"armName": "gpt-latest",
"modelName": "gpt-6.1-sol",
"modelVersion": "2026-09-29",
"apiFormat": "OpenAIChatCompletions",
"supportedEndpoints": ["/openai/v1/chat/completions", "/openai/v1/responses"],
"resourceId": "$FOUNDRY_ACCOUNT_ID/deployments/gpt-latest"
},
{
"armName": "gpt-6-sol",
"modelName": "gpt-6-sol",
"modelVersion": "2026-09-22",
"apiFormat": "OpenAIChatCompletions",
"supportedEndpoints": ["/openai/v1/chat/completions", "/openai/v1/responses"],
"resourceId": "$FOUNDRY_ACCOUNT_ID/deployments/gpt-6-sol"
},
{
"armName": "gpt-mini-latest",
"modelName": "gpt-5.4-mini",
"modelVersion": "2026-03-17",
"apiFormat": "OpenAIChatCompletions",
"supportedEndpoints": ["/openai/v1/chat/completions", "/openai/v1/responses"],
"resourceId": "$FOUNDRY_ACCOUNT_ID/deployments/gpt-mini-latest"
}
]},
"modelAliasesConfig": {"value": [
{
"armName": "primary-router",
"aliasName": "gpt-latest",
"description": "Three-tier GPT model failover.",
"backendModels": [
{"modelArmName": "gpt-latest", "priority": 0, "weight": 100},
{"modelArmName": "gpt-6-sol", "priority": 1, "weight": 100},
{"modelArmName": "gpt-mini-latest", "priority": 2, "weight": 100}
]
},
{
"armName": "mini-router",
"aliasName": "gpt-mini-latest",
"description": "Direct alias for the mini deployment.",
"backendModels": [
{"modelArmName": "gpt-mini-latest", "priority": 0, "weight": 100}
]
}
]}
}
}
EOF

az deployment group create \
--resource-group rg-ai-gateway \
--template-file ./main.bicep \
--parameters @/tmp/ai-gateway.parameters.json
```

After the Gateway and Foundry project exist, run the postprovision configuration
script to add the GitHub MCP ToolServer and create the Foundry project
connection and `repo-digest-tools` Toolbox:

```bash
bash ../scripts/configure-ai-gateway.sh
```

See [the scripts README](../scripts/README.md) for required azd values and
GitHub authentication. A standalone Gateway deployment does not create the
Foundry project; deploy the
[Foundry agents module](../foundry-agents/README.md) as well before configuring
the Toolbox.
44 changes: 44 additions & 0 deletions infra/foundry-agents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Foundry project and hosted-agent runtime

Run the commands in this README from the `infra/foundry-agents/` directory.

[`main.bicep`](main.bicep) creates a project-enabled Azure AI Services account
(`allowProjectManagement: true`), a Foundry project, and its hosted-agent
dependencies:

- Storage Account and project storage connection.
- Premium Azure Container Registry (ACR) and project registry connection.
- Log Analytics workspace and Application Insights component/connection.
- Project and optional developer RBAC assignments, including the permissions
needed for the project to use its account, storage, and registry.

Deploy this resource-group-scoped module independently by supplying names for
its required resources:

```bash
az group create --name rg-foundry-agents --location eastus2

az deployment group create \
--resource-group rg-foundry-agents \
--template-file ./main.bicep \
--parameters location=eastus2 \
aiServicesName=aiagents-example \
aiProjectName=repo-digest \
aiProjectFriendlyName="Daily Repo Digest" \
aiProjectDescription="Foundry Hosted Agent sample." \
storageName=stexample001 \
containerRegistryName=acrexample123 \
appInsightsName=aiagents-example-insights \
logAnalyticsWorkspaceName=aiagents-example-logs \
principalId="$(az ad signed-in-user show --query id -o tsv)"
```

Resource names must meet the naming rules for their Azure resource types and be
available. Omit `principalId` if the deploying developer does not need optional
user-level role assignments.

Useful deployment outputs:

- `projectEndpoint`: Foundry project endpoint for clients and Toolbox setup.
- `containerRegistryEndpoint`: ACR login server for hosted-agent images.
- `aiProjectResourceId`: resource ID of the Foundry project.
48 changes: 48 additions & 0 deletions infra/foundry-models/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Foundry model account

Run the commands in this README from the `infra/foundry-models/` directory.

[`main.bicep`](main.bicep) deploys a model-serving Azure AI Services account
with `allowProjectManagement: false` and `disableLocalAuth: true`. It creates
the model deployments passed through the required `modelDeployments` array and
can optionally grant the supplied `principalId` developer roles on the
account.

The sample's default deployment set is:

| Deployment name | Model | Version | SKU | Capacity |
| --- | --- | --- | --- | --- |
| `gpt-latest` | `gpt-6.1-sol` | `2026-09-29` | `GlobalStandard` | 20 |
| `gpt-6-sol` | `gpt-6-sol` | `2026-09-22` | `GlobalStandard` | 20 |
| `gpt-mini-latest` | `gpt-5.4-mini` | `2026-03-17` | `GlobalStandard` | 200 |

The subscription-scoped [`../main.bicep`](../main.bicep) provides this array
automatically. For an independent deployment, create a resource group and pass
the model definitions explicitly:

```bash
az group create --name rg-foundry-models --location eastus2

az deployment group create \
--resource-group rg-foundry-models \
--template-file ./main.bicep \
--parameters location=eastus2 \
aiServicesName=aimodels-example \
principalId="$(az ad signed-in-user show --query id -o tsv)" \
modelDeployments='[
{"deploymentName":"gpt-latest","modelName":"gpt-6.1-sol","modelFormat":"OpenAI","modelVersion":"2026-09-29","skuName":"GlobalStandard","capacity":20},
{"deploymentName":"gpt-6-sol","modelName":"gpt-6-sol","modelFormat":"OpenAI","modelVersion":"2026-09-22","skuName":"GlobalStandard","capacity":20},
{"deploymentName":"gpt-mini-latest","modelName":"gpt-5.4-mini","modelFormat":"OpenAI","modelVersion":"2026-03-17","skuName":"GlobalStandard","capacity":200}
]'
```

`aiServicesName` must be globally unique and valid for an Azure AI Services
account. Set `principalId` to an authorized developer's object ID, or omit it
for no optional user role assignments.

Useful deployment outputs:

- `aiServicesId`: resource ID of the model account.
- `aiServicesEndpoint`: endpoint used by the Gateway Foundry provider.
- `modelDeploymentIds`: resource IDs for the deployments, in the same order as
the `modelDeployments` input.
Loading
Loading