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
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"extensions": [
"ms-python.python",
"ms-azuretools.vscode-docker",
"ms-windows-ai-studio.windows-ai-studio"
"ms-windows-ai-studio.windows-ai-studio",
"marp-team.marp-vscode"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
Expand Down
33 changes: 33 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,39 @@ casually. The coach must always:
Use `<!-- TODO(nitya): ... -->` in markdown and `# TODO(nitya):` in code for
maintainer follow-ups (screenshots, exact commands, model pinning, etc.).

## Known gotchas

- **Foundry hosted-agent 409 on `azd deploy`.** Reusing the same `azd env` name
after a torn-down deploy can hit stale server-side state on the Foundry agents
data plane, producing `RESPONSE 409: 409 Conflict … modified concurrently`
even though `azd ai agent doctor` sees no agent. Recovery: `azd down --purge
--force` → `azd env new <different-name>` → `azd provision` → `azd deploy`.
Captured in the Lab 05 gotcha callout — keep those two in sync.
- **Portal cannot *create* hosted agents.** The Foundry portal can view and
manage hosted agents but has no create flow — `azd deploy` is the only
supported creation path today. Do not add "portal path" instructions for
hosted agents unless the portal ships that flow.
- **`azd provision` fails with `invalid character 'n' after object key:value
pair`.** The four `*Json` params in `infra/main.parameters.json` are
quoted-string substitutions (e.g. `"value": "${AI_PROJECT_DEPLOYMENTS=[]}"`)
and azd 1.30 does not JSON-escape embedded `"` on substitution. Any non-empty
override (e.g. a user-supplied `AI_PROJECT_DEPLOYMENTS` array) breaks the
parameters file. Workaround: `azd env set AI_PROJECT_DEPLOYMENTS "[]"` and
re-provision — the defaults in `infra/main.bicep` still deploy
`gpt-5.4-mini` + `gpt-5.4-judge`. Structural fix (`TODO(nitya)` in
`infra/main.bicep`): change the four params to `array`/`object` types, drop
the `json()` calls, and substitute without surrounding quotes. Kept in sync
with Lab 03 + Lab 05 gotcha callouts.
- **`azd deploy` fails with `RESPONSE 404: ResourceNotFound — Subdomain does
not map to a resource`.** The Foundry agents data plane returns 404 (not
401) when the caller's bearer token is expired or revoked — typically
`AADSTS50173` after a password change, credential rotation, or Conditional
Access policy update. The account is fine; auth is stale. `azd` and `az`
cache tokens independently, so both must be refreshed: `az logout && az
login --tenant <id>` then `azd auth logout && azd auth login --tenant-id
<id>`, then re-run `azd deploy`. Kept in sync with the Lab 05 gotcha
callout.

## Tests

Run before opening a PR:
Expand Down
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,24 @@ gh --version

> Prefer local setup? Create a virtual environment and install the tools manually. You will need Python 3.13+, `az`, `azd`, `copilot` and `gh` on your PATH.

You are ready to get started on the first lab.
- **Open [`labs/fundamentals/00-overview.md`](./labs/fundamentals/00-overview.md)**
and follow the trail from there.

<br/>

## Fundamentals

The Fundamentals track gets your Foundry substrate, models, and both agents (Prompt + Hosted) provisioned and verified — so you have a **green baseline** before starting the Core Labs. Complete them in order; Labs 01 and 02 are alternative paths (pick one).

| # | Lab | Loop node |
|---|-----|-----------|
| 0 | [Course overview & the Agent DevOps loop](./labs/fundamentals/00-overview.md) | Plan |
| 1 | [Provision Foundry with `azd`](./labs/fundamentals/01-provision-azd.md) _(CLI path)_ | Build |
| 2 | [Provision Foundry with the Portal](./labs/fundamentals/02-provision-portal.md) _(UI path)_ | Build |
| 3 | [Deploy the required models](./labs/fundamentals/03-deploy-models.md) | Build |
| 4 | [Create the Prompt Agent](./labs/fundamentals/04-create-prompt-agent.md) | Build |
| 5 | [Deploy the Hosted Agent](./labs/fundamentals/05-deploy-hosted-agent.md) | Deploy |
| 6 | [End-to-end verification](./labs/fundamentals/06-verify.md) | Evaluate |

Start here → **[`labs/fundamentals/00-overview.md`](./labs/fundamentals/00-overview.md)**.


<br/>
Expand Down
2 changes: 1 addition & 1 deletion artifacts/prompts/reference/prompt-agent-baseline-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

## Role

You are the **Contoso Travel Concierge** at Contoso Travel, a fictitious premium
You are the **Contoso Travel Concierge** at Contoso Travel, a premium
travel agency that books flights, hotels, and car rentals across Paris, London,
Tokyo, Rome, and Cancún. Be warm, professional, and concise.

Expand Down
8 changes: 8 additions & 0 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ param aiFoundryResourceName string = ''
param aiFoundryProjectName string = 'ai-project-${environmentName}'

@description('List of model deployments')
// TODO(nitya): The four *Json params below are strings that hold JSON, because
// main.parameters.json substitutes `"${AI_PROJECT_DEPLOYMENTS=[]}"` (quoted).
// azd 1.30 does not escape embedded `"` on substitution, so any non-empty
// override crashes provisioning with `invalid character 'n' after object
// key:value pair`. Structural fix: change these to `array`/`object` params,
// drop the `json()` calls below, and update main.parameters.json to substitute
// without surrounding quotes (`"value": ${AI_PROJECT_DEPLOYMENTS=[]}`). Until
// then, users must keep the override empty — see Lab 03/05 gotcha callouts.
param aiProjectDeploymentsJson string = '[]'

@description('List of connections')
Expand Down
2 changes: 1 addition & 1 deletion labs/core/05-capstone-hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Do each step yourself. Refer back to the linked lab if you get stuck.
cd . # repo root
azd down --purge --force
```
This tears down everything `azd up` created so you stop paying for it.
This tears down everything you provisioned earlier so you stop paying for it.

## ✅ Verify

Expand Down
18 changes: 9 additions & 9 deletions labs/fundamentals/00-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ flowchart LR

## 📋 Steps

1. **Read the [scenario](#scenario) below.**
You should now be able to name the four data sources the Concierge queries.
2. **Skim the [Agent DevOps loop](#agent-devops-loop) diagram.**
You should now recognize the seven nodes and how they connect.
3. **Pick your provisioning path** for the next lab: `azd up` (self-guided) or
1. **Know the [scenario](#scenario).**
You should be able to name the sources for the Concierge queries.
2. **Know the [Agent DevOps loop](#agent-devops-loop).**
You should recognize the nodes and how they connect.
3. **Pick your provisioning path** for the next lab: `azd` (self-guided) or
Foundry portal (UI-first).

### Scenario

**Contoso Travel** is a fictitious travel agency. Their AI-powered **Contoso
Travel Concierge** orchestrates specialist sub-agents that read three CSVs:
Travel Concierge** orchestrates specialist sub-agents that read three CSVs (below) - also available in JSON format.

| Data source | What it holds |
|---|---|
Expand Down Expand Up @@ -69,9 +69,9 @@ cycle you'll run whenever the agent underperforms.

Answer these to yourself (no tool required):

- Which three CSVs does the Concierge use?
- Which are the three CSVs used by the Travel Concierge?
- What are the seven nodes of the Agent DevOps loop?
- Which path (`azd up` or portal) will you take in Lab 01?
- Which path (`azd` or portal) will you take in Lab 01?

If you can answer all three, you're ready to provision.

Expand All @@ -85,5 +85,5 @@ If you can answer all three, you're ready to provision.

Choose one:

- Self-guided → **[Lab 01 — Provision with `azd up`](./01-provision-azd.md)**
- Code-first → **[Lab 01 — Provision with `azd`](./01-provision-azd.md)**
- UI-first → **[Lab 02 — Provision with the portal](./02-provision-portal.md)**
76 changes: 48 additions & 28 deletions labs/fundamentals/01-provision-azd.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Lab 01 — Provision Foundry with `azd up`
# Lab 01 — Provision Foundry with `azd`

> **What you'll do:** Stand up a Foundry project, deploy the `gpt-5.4-mini` and
> `gpt-5.4-judge` models, and wire up Application Insights and Log Analytics in
> your Azure subscription with a single command. The container registry and
> hosted agent come later in [Lab 05](./05-deploy-hosted-agent.md).
> your Azure subscription with a single `azd provision` command. The container
> registry and hosted agent come later in
> [Lab 05](./05-deploy-hosted-agent.md).
> **Time:** ~15 min · **Prerequisites:** [Lab 00](./00-overview.md)
>
> ⏩ **Taking the portal path instead?** Skip to [Lab 02](./02-provision-portal.md).
Expand Down Expand Up @@ -87,16 +88,21 @@ Confirm you have:
> project, run 'azd init'`, you're not in the repo root. `cd` to the folder
> that contains `azure.yaml` and rerun the command.

4. **Deploy.**
4. **Provision.**
```bash
azd up
azd provision
```
You'll be prompted twice:
- **Select an Azure Subscription** — pick the subscription to deploy into.
- **`aiDeploymentsLocation` infrastructure parameter** — choose
**`(US) East US 2 (eastus2)`** (or one of the alternates above).

`azd up` provisions the Bicep in [`../../infra/`](../../infra/):
> 💡 **Why `azd provision` and not `azd up`?** `azd up` = `azd provision +
> azd deploy`. This lab only needs the Foundry substrate — the hosted-agent
> **deploy** is a deliberate step in [Lab 05](./05-deploy-hosted-agent.md).
> Splitting them keeps failures easy to diagnose and retries cheap.

`azd provision` runs the Bicep in [`../../infra/`](../../infra/):
- resource group (`rg-contoso-travel`)
- Foundry account + project
- **`gpt-5.4-mini`** (concierge) + **`gpt-5.4-judge`** model deployments
Expand All @@ -117,31 +123,37 @@ Confirm you have:
(✓) Done: Application Insights: appi-xxxxxxxxxxxxx
(✓) Done: Foundry project connection: .../appi-xxxxxxxxxxxxx

SUCCESS: Your application was provisioned and deployed to Azure in 1 minute.
SUCCESS: Your application was provisioned in Azure in 1 minute.
```

> 💡 **Want different models?** The golden path deploys `gpt-5.4-mini` and a
> `gpt-5.4-judge`. To swap them, set the `AI_PROJECT_DEPLOYMENTS` env var
> before `azd up` — see [Lab 03](./03-deploy-models.md).
> before `azd provision` — see [Lab 03](./03-deploy-models.md).

> 💡 **What's *not* here yet:** the Container Registry and the hosted agent
> are **not** provisioned by `azd up` — you create them when you deploy the
> hosted agent in [Lab 05](./05-deploy-hosted-agent.md).
> are **not** provisioned in this lab — you enable and create them when you
> deploy the hosted agent in [Lab 05](./05-deploy-hosted-agent.md).

<!-- TODO(nitya): screenshot of a successful `azd up` output with the project endpoint -->
<!-- DONE: screenshot of a successful `azd provision` output with the project endpoint replaced by code-fenced output above -->

5. **Read the outputs.**
The tail of `azd up` prints the **Foundry project endpoint** and related
environment values. Copy the endpoint into your notes — you'll use it in
every later lab. (The agent name and playground link appear once you deploy
the hosted agent in [Lab 05](./05-deploy-hosted-agent.md).)
The tail of `azd provision` prints the link to the deployed resource group in the Azure Portal. Click to visit the portal - you should see a resource group, with provisioned Foundry project and other resources as shown.

> 💡 **Tip:** `azd env get-values` prints everything `azd` knows about your
> environment. Later labs use this to pick up endpoints automatically.
>
> ⚠️ **Gotcha:** if `azd up` fails on quota, retry in a different region from
> the list above using `azd env set AZURE_LOCATION swedencentral` then
> `azd provision` again.
![Provisioned `rg-contoso-travel` resource group in the Azure Portal](./images/01-provision-infra-01.png)

6. **Visit the Foundry portal.**
From the resource group, click into the **Foundry** (`ai-account-xxx`) resource — the overview blade has a **Go to Foundry portal** button in the top toolbar that jumps you straight into your project in the Foundry portal, no separate URL required.

![Go to Foundry portal button on the Foundry resource overview](./images/01-provision-infra-02.png)

7. **Explore the Foundry project.**
You'll land in the **new Microsoft Foundry portal** — confirm the **New Foundry** toggle in the top-right is **on** (the classic view hides most of what we use later). The top nav reflects the developer journey from **Discover** and **Build**, to **Operate** and **Manage**; we'll spend most of the workshop in the **Build** tab (agents, models, prompts). Note that the project Homepage has handy links to the Foundry **endpoint** and **API key** values that you may need later. You can also get these directly using `azd env get-values` - when you provision with azd. Later labs use this to pick up endpoints automatically.

![New Microsoft Foundry portal — New Foundry toggle on, Build tab highlighted, endpoint and API key visible on the overview](./images/01-provision-infra-03.png)

> ⚠️ **Gotcha:** if `azd provision` fails on quota, retry in a different region
> from the list above using `azd env set AZURE_LOCATION swedencentral` then
> `azd provision` again.

## ✅ Verify

Expand All @@ -163,26 +175,34 @@ AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"

Then open <https://portal.azure.com>, go to **Resource groups**, and confirm the
`rg-contoso-travel` group contains a Foundry account, a Foundry project,
the `gpt-5.4-mini` and `gpt-5.4-judge` model deployments, Application Insights,
and a Log Analytics workspace.
Application Insights, and a Log Analytics workspace.

> 💡 **Where are the models?** Model deployments (`gpt-5.4-mini`,
> `gpt-5.4-judge`) are sub-resources of the Foundry account, so they don't show
> as separate rows in the Azure Portal resource-group view. To see them, open
> the **Foundry portal**, enable the **new Foundry** toggle (top-right), then
> go to **Build → Models**. Lab 03 walks through this.

> 💡 The Container Registry, AI Search, and Storage show up **later** — after
> [Lab 05](./05-deploy-hosted-agent.md) (hosted agent + registry).
> 💡 The Container Registry, AI Search, and Storage show up **after** you enable
> hosted-agent hosting in [Lab 05](./05-deploy-hosted-agent.md) — the
> `azd env set ENABLE_HOSTED_AGENTS true && azd provision` step there adds them
> to this same resource group, before you ever run `azd deploy`.

<!-- TODO(nitya): screenshot of the resource group in the Azure portal -->

## 🧠 Recap

- `azd up` provisioned the Foundry substrate — account, project, the
- `azd provision` stood up the Foundry substrate — account, project, the
`gpt-5.4-mini` and `gpt-5.4-judge` models, and observability (App Insights +
Log Analytics) — in one shot.
- Splitting **provision** (this lab) from **deploy** ([Lab 05](./05-deploy-hosted-agent.md))
mirrors the Agent DevOps loop and keeps retries scoped.
- Environment values are stored per-`azd env` and reused by later labs.
- Next you'll **confirm the model deployments** and learn how to change them.

## ➡️ Next

**[Lab 03 — Deploy required models](./03-deploy-models.md)** to confirm the
model deployment came up, or jump ahead to
**[Lab 03 — Deploy required models](./03-deploy-models.md)** to verify model deployments succeeded, or jump ahead to
**[Lab 05 — Deploy the hosted agent](./05-deploy-hosted-agent.md)** to ship the
container.

Expand Down
15 changes: 11 additions & 4 deletions labs/fundamentals/03-deploy-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
## 🎯 Goal

Deploy the language model(s) your agents and evaluators will call. On the
`azd` path this already happened — `azd up` deployed `gpt-5.4-mini` and
`azd` path this already happened — `azd provision` deployed `gpt-5.4-mini` and
`gpt-5.4-judge` for you — so this lab is mostly about **confirming** the
deployments and knowing how to add or swap models. On the portal path you
deploy the model by hand here.
Expand Down Expand Up @@ -37,7 +37,7 @@ flowchart LR

## 📋 Steps

**If you provisioned with `azd up` (Lab 01):**
**If you provisioned with `azd` (Lab 01):**

1. Confirm the deployments already exist:
```bash
Expand All @@ -56,13 +56,20 @@ flowchart LR

> 💡 **Want different models?** The golden path deploys `gpt-5.4-mini` and
> `gpt-5.4-judge`. To change them, set the `AI_PROJECT_DEPLOYMENTS` env var to a
> JSON array **before** `azd up` (or set it and rerun `azd provision`). Setting
> it replaces the default entirely, so include **every** model you want:
> JSON array **before** `azd provision` (or set it and rerun `azd provision`).
> Setting it replaces the default entirely, so include **every** model you want:
>
> ```bash
> azd env set AI_PROJECT_DEPLOYMENTS '[{"name":"gpt-5.4-mini","model":{"name":"gpt-5.4-mini","format":"OpenAI","version":"2026-03-17"},"sku":{"name":"GlobalStandard","capacity":100}},{"name":"gpt-5.4-judge","model":{"name":"gpt-5.4","format":"OpenAI","version":"2026-03-05"},"sku":{"name":"GlobalStandard","capacity":100}}]'
> azd provision
> ```
>
> ⚠️ **Known limitation:** once this override is set, later re-provisions
> (e.g. enabling hosted agents in [Lab 05](./05-deploy-hosted-agent.md)) fail
> with `invalid character 'n' after object key:value pair` — azd 1.30 doesn't
> escape the JSON when it substitutes into the Bicep parameters file. Clear it
> before re-provisioning (`azd env set AI_PROJECT_DEPLOYMENTS "[]"`). Tracked
> as `TODO(nitya)` in [`infra/main.bicep`](../../infra/main.bicep).

**If you provisioned with the portal (Lab 02):**

Expand Down
Loading
Loading