Skip to content
Open
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
28 changes: 16 additions & 12 deletions .github/instructions/workshop.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,22 @@ and `python -m pytest .workshop/scripts/tests`.
add them to later `.workshop/step_files/NN`.
- **Keep it teaching-quality.** Never minify code in `.workshop/step_files/` or
`.workshop/solutions/` — keep it clean and expanded (KISS).
- **Links and images in step docs are repo-root-relative.** Step docs render
into the participant's **root** `README.md`, so author relative links and
image paths as they resolve *from the repo root* — e.g.
`.workshop/docs/assets/NN-*.png` and `.workshop/solutions/NN-*/`, not
`docs/assets/...` or `../assets/...`. Consequence: viewing a source step doc
directly on GitHub (under `.workshop/docs/steps/`) shows those relative links
as broken — that is expected; the **rendered `README.md` is the canonical
artifact**. For links that must leave the docs, use absolute URLs:
`https://github.com/{{OWNER}}/{{REPO}}/...` when the link must target the
participant's **own** instance (e.g. their Actions workflows), and a hardcoded
`https://github.com/Azure-Samples/foundry-hosted-agents-workshop/...` when it must reach
the **template/upstream** repo (e.g. the workshop-feedback issue template).
- **Links and images in step docs and partials are source-relative.** Author
relative links and image paths **relative to the folder the source file lives
in**, so they resolve when the file is viewed directly on GitHub. From a step
doc under `.workshop/docs/steps/`, that means `../assets/NN-*.png`,
`../../solutions/NN-*/`, and `NN-intro.md#anchor` for a sibling step; from a
partial under `.workshop/docs/partials/`, use `../steps/00-intro.md#anchor`.
The renderer (`render_readme.py`) rebases these to repo-root-relative when it
inlines them into the root `README.md`, so both the source doc and the rendered
README have working links. `lint_steps.py` check **K** rejects root-relative
links and any link/image whose target doesn't exist. For links that must leave
the docs, use absolute URLs: `https://github.com/{{OWNER}}/{{REPO}}/...` when
the link must target the participant's **own** instance (e.g. their Actions
workflows), and a hardcoded
`https://github.com/Azure-Samples/foundry-hosted-agents-workshop/...` when it
must reach the **template/upstream** repo (e.g. the workshop-feedback issue
template).

## Before you open a PR

Expand Down
2 changes: 1 addition & 1 deletion .workshop/docs/partials/_header.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ Built on the upstream [foundry-samples](https://github.com/microsoft-foundry/fou

</details>

If something looks broken see [Troubleshooting](.workshop/docs/steps/00-intro.md#troubleshooting).
If something looks broken see [Troubleshooting](../steps/00-intro.md#troubleshooting).
2 changes: 1 addition & 1 deletion .workshop/docs/partials/_push_to_advance.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ After the **Advance workshop on push to main** Action finishes, run **`git pull`

> Each push to `main` advances the workshop by exactly **one** step, so push once — when this step is done.

> **Prefer to stay local?** Run `python .workshop/scripts/advance_step.py --expected-current-step {{CURRENT_STEP}} --auto-commit` (or `make advance`) instead. That advances locally and records it in the same commit, so your next push won't advance again. See [Working fully locally](.workshop/docs/steps/00-intro.md#5-working-fully-locally-no-github-actions).
> **Prefer to stay local?** Run `python .workshop/scripts/advance_step.py --expected-current-step {{CURRENT_STEP}} --auto-commit` (or `make advance`) instead. That advances locally and records it in the same commit, so your next push won't advance again. See [Working fully locally](../steps/00-intro.md#5-working-fully-locally-no-github-actions).

<sub>Made a mistake? Use the [Reset workshop](https://github.com/{{OWNER}}/{{REPO}}/actions/workflows/reset-workshop.yml) workflow, or run `python .workshop/scripts/advance_step.py --reset --auto-commit` locally.</sub>
2 changes: 1 addition & 1 deletion .workshop/docs/partials/_start_button.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ From Step {{NEXT_STEP_NUMBER}} onward you don't click a button to advance — yo

> 💡 **Button returns 404?** Your repo's one-time **Initialize workshop** Action hasn't run yet. Open the **Actions** tab, run **Initialize workshop → Run workflow**, then refresh this page.

> **Prefer to stay local?** Run `python .workshop/scripts/advance_step.py --expected-current-step {{CURRENT_STEP}} --auto-commit` (or `make advance`) instead of clicking the button. See [Working fully locally](.workshop/docs/steps/00-intro.md#5-working-fully-locally-no-github-actions) for the full local flow.
> **Prefer to stay local?** Run `python .workshop/scripts/advance_step.py --expected-current-step {{CURRENT_STEP}} --auto-commit` (or `make advance`) instead of clicking the button. See [Working fully locally](../steps/00-intro.md#5-working-fully-locally-no-github-actions) for the full local flow.

<sub>Made a mistake? Use the [Reset workshop](https://github.com/{{OWNER}}/{{REPO}}/actions/workflows/reset-workshop.yml) workflow, or run `python .workshop/scripts/advance_step.py --reset --auto-commit` locally.</sub>
10 changes: 5 additions & 5 deletions .workshop/docs/steps/01-basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

In this step you **complete** the one small edit called out below (TravelBuddy's instructions in `main.py`) — you don't create the files from scratch, and the two YAML files are ready to use as-is. The workshop is **incremental**: when you advance, the next step's files are laid on top of your `travel_assistant/` folder. Files from earlier steps that the next step doesn't touch stay exactly as they are — nothing is deleted. Files the next step ships (for example an updated `main.py`) are refreshed to that step's version, and your current work is backed up under `.workshop_instance/workshop_backups/step-<N>/` first, so you can always recover your own wording.

> **Before you start:** make sure you completed the setup in Step 0 — [Install the tools you'll need](.workshop/docs/steps/00-intro.md#install-the-tools-youll-need) and [Set up your local environment](.workshop/docs/steps/00-intro.md#set-up-your-local-environment-one-time). If `python .workshop/scripts/preflight.py --step 1` is green, you're ready.
> **Before you start:** make sure you completed the setup in Step 0 — [Install the tools you'll need](00-intro.md#install-the-tools-youll-need) and [Set up your local environment](00-intro.md#set-up-your-local-environment-one-time). If `python .workshop/scripts/preflight.py --step 1` is green, you're ready.

## Concept (5-min read)

Expand Down Expand Up @@ -98,7 +98,7 @@ Open `travel_assistant/agent.manifest.yaml` and read through it — the inline c

**What happens at run/deploy time:** locally, this process serves an OpenAI-compatible Responses endpoint on `http://localhost:8088`. After deployment, Foundry starts the same code as the hosted container entry point.

Open `travel_assistant/main.py` and complete the `TODO`. If you get stuck, the finished file is in [`.workshop/solutions/01-basic/`](.workshop/solutions/01-basic/).
Open `travel_assistant/main.py` and complete the `TODO`. If you get stuck, the finished file is in [`.workshop/solutions/01-basic/`](../../solutions/01-basic/).

`ResponsesHostServer` is the hosted-agent contract: when started locally it serves an OpenAI-compatible Responses endpoint on `http://localhost:8088`; when packaged and deployed to Foundry it becomes the container entry point. The same code runs in both places.

Expand Down Expand Up @@ -227,7 +227,7 @@ You can run and deploy TravelBuddy two ways: with the [**Azure Developer CLI** (

Prefer a UI? With the local agent still running, open the **Agent Inspector** from the Foundry Toolkit (Command Palette → **Foundry Toolkit: Open Agent Inspector**, or the **Agent Inspector** entry under **Developer Tools**). It connects to `http://localhost:8088` and lets you chat with TravelBuddy and watch the streamed Responses events.

![Foundry Toolkit Agent Inspector connected to the local TravelBuddy agent on localhost:8088, showing the Playground chat and streamed response events](.workshop/docs/assets/01-agent-inspector.png)
![Foundry Toolkit Agent Inspector connected to the local TravelBuddy agent on localhost:8088, showing the Playground chat and streamed response events](../assets/01-agent-inspector.png)

5. **Deploy to Foundry**. Subsequent workshop steps only need `azd deploy`:

Expand All @@ -247,7 +247,7 @@ You can run and deploy TravelBuddy two ways: with the [**Azure Developer CLI** (

Prefer a UI? Open the **Hosted Agent Playground** from the Foundry Toolkit (under **Developer Tools** → **Build** → **Hosted Agent Playground**). Pick your deployed agent and version, then chat with TravelBuddy and inspect session details, logs, and traces directly in VS Code.

![Foundry Toolkit Hosted Agent Playground with the deployed TravelBuddy agent selected, showing the Playground chat and session details panel](.workshop/docs/assets/01-hosted-agent-playground.png)
![Foundry Toolkit Hosted Agent Playground with the deployed TravelBuddy agent selected, showing the Playground chat and session details panel](../assets/01-hosted-agent-playground.png)

When you select **Hosted Agent Playground** in the Foundry Toolkit's **Developer Tools**, you'll be prompted to sign in. If the interactive sign-in doesn't complete, cancel it and choose the **device code** flow instead. Once signed in, select the Foundry project that hosts your deployed agent.

Expand Down Expand Up @@ -280,7 +280,7 @@ Whichever option you picked, try a few prompts:

## Solution

> If you get stuck: [`.workshop/solutions/01-basic/`](.workshop/solutions/01-basic/)
> If you get stuck: [`.workshop/solutions/01-basic/`](../../solutions/01-basic/)

## Upstream sample

Expand Down
4 changes: 2 additions & 2 deletions .workshop/docs/steps/02-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ Then update the `Agent(...)` call. **Keep your Step 1 instructions exactly as th
)
```

That's the whole change: the `from tools import ...` line and the `tools=[...]` argument do the wiring; the appended instructions just tell the model when to reach for the tools. Everything else in `main.py` is unchanged from Step 1. If you get stuck, the finished file is in [`.workshop/solutions/02-tools/`](.workshop/solutions/02-tools/).
That's the whole change: the `from tools import ...` line and the `tools=[...]` argument do the wiring; the appended instructions just tell the model when to reach for the tools. Everything else in `main.py` is unchanged from Step 1. If you get stuck, the finished file is in [`.workshop/solutions/02-tools/`](../../solutions/02-tools/).

### 3. Update the metadata in `travel_assistant/agent.manifest.yaml`

Expand Down Expand Up @@ -346,7 +346,7 @@ For the comparison prompt, the model may call `get_weather` once per city.

## Solution

> If you get stuck: [`.workshop/solutions/02-tools/`](.workshop/solutions/02-tools/)
> If you get stuck: [`.workshop/solutions/02-tools/`](../../solutions/02-tools/)

## Upstream sample

Expand Down
4 changes: 2 additions & 2 deletions .workshop/docs/steps/03-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Then **keep your Step 2 instructions exactly as they are** and append one MCP se
tools=tools, # <-- the list you just extended above
```

That's the whole code change. `client.get_mcp_tool(...)` reads the label and URL from the environment (the same values you just added to `.env`) and hands the agent a remote tool. Everything else in `main.py` — the `FoundryChatClient` setup, the three function tools, `default_options={"store": False}`, and `ResponsesHostServer(agent).run()` — is unchanged from Step 2. If you get stuck, the finished file is in [`.workshop/solutions/03-mcp/`](.workshop/solutions/03-mcp/).
That's the whole code change. `client.get_mcp_tool(...)` reads the label and URL from the environment (the same values you just added to `.env`) and hands the agent a remote tool. Everything else in `main.py` — the `FoundryChatClient` setup, the three function tools, `default_options={"store": False}`, and `ResponsesHostServer(agent).run()` — is unchanged from Step 2. If you get stuck, the finished file is in [`.workshop/solutions/03-mcp/`](../../solutions/03-mcp/).

> **Why `os.environ[...]` and not a hardcoded URL?** Reading the label and URL from the environment keeps them out of source control and lets you point at a different MCP server (or the authenticated variant in Troubleshooting) by editing `.env` — no code change. The hosted runtime gets the same values from the manifest at deploy time.

Expand Down Expand Up @@ -338,7 +338,7 @@ Make the prompt explicit: give an origin, a destination (IATA codes work well),

## Solution

> If you get stuck: [`.workshop/solutions/03-mcp/`](.workshop/solutions/03-mcp/)
> If you get stuck: [`.workshop/solutions/03-mcp/`](../../solutions/03-mcp/)

## Upstream sample

Expand Down
6 changes: 3 additions & 3 deletions .workshop/docs/steps/04-toolbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ TOOLBOX_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>/

Once the toolbox is deployed, you can also see it in the **Foundry Toolkit** for VS Code: expand your project under **My Resources → Tools** and open the **Toolboxes** tab to view your toolbox along with its **Endpoint URL**.

![Foundry Toolkit for VS Code showing the deployed toolbox under My Resources → Tools, with the Toolboxes tab listing the travel toolbox and its Endpoint URL](.workshop/docs/assets/04-toolbox-toolkit.png)
![Foundry Toolkit for VS Code showing the deployed toolbox under My Resources → Tools, with the Toolboxes tab listing the travel toolbox and its Endpoint URL](../assets/04-toolbox-toolkit.png)

### 3. Swap the MCP tool for the toolbox in `travel_assistant/main.py`

Expand Down Expand Up @@ -221,7 +221,7 @@ and put the toolbox sentence in its place:
),
```

That's the whole code change. Everything else — the `FoundryChatClient` setup, the three function tools, `default_options={"store": False}`, and the synchronous `ResponsesHostServer(agent).run()` — is unchanged from Step 3. If you get stuck, the finished file is in [`.workshop/solutions/04-toolbox/`](.workshop/solutions/04-toolbox/).
That's the whole code change. Everything else — the `FoundryChatClient` setup, the three function tools, `default_options={"store": False}`, and the synchronous `ResponsesHostServer(agent).run()` — is unchanged from Step 3. If you get stuck, the finished file is in [`.workshop/solutions/04-toolbox/`](../../solutions/04-toolbox/).

> **Why one `credential`?** The toolbox and the chat client both authenticate to Foundry. Sharing a single `DefaultAzureCredential()` avoids two sign-in flows and keeps token caching in one place.

Expand Down Expand Up @@ -461,7 +461,7 @@ The final `tools` list should contain **four** entries: the three function tools

## Solution

> If you get stuck: [`.workshop/solutions/04-toolbox/`](.workshop/solutions/04-toolbox/)
> If you get stuck: [`.workshop/solutions/04-toolbox/`](../../solutions/04-toolbox/)

## Upstream sample

Expand Down
2 changes: 1 addition & 1 deletion .workshop/docs/steps/05-rag.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ You can also create both from the **Azure portal**. See the links under **Learn

## Solution

> If you get stuck: [`.workshop/solutions/05-rag/`](.workshop/solutions/05-rag/)
> If you get stuck: [`.workshop/solutions/05-rag/`](../../solutions/05-rag/)

## Upstream sample

Expand Down
6 changes: 3 additions & 3 deletions .workshop/docs/steps/06-skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ resources: []

Prefer a UI? Open the **Hosted Agent Playground** from the Foundry Toolkit (**Developer Tools** → **Build** → **Hosted Agent Playground**), pick your deployed agent and version, and ask for a guide — the generated PDF shows up under **Session Details → Files**.

![Foundry Toolkit Hosted Agent Playground with the deployed TravelBuddy agent, showing the run_skill_script tool call and the generated reykjavik-3-day-travel-guide.pdf listed under Session Details → Files](.workshop/docs/assets/06-travel-guide-skill-playground.png)
![Foundry Toolkit Hosted Agent Playground with the deployed TravelBuddy agent, showing the run_skill_script tool call and the generated reykjavik-3-day-travel-guide.pdf listed under Session Details → Files](../assets/06-travel-guide-skill-playground.png)

---

Expand Down Expand Up @@ -404,7 +404,7 @@ It prints the uploaded skill's version and `skill_id`, then confirms the project

Prefer a UI? Open the Foundry Toolkit and select your project under **My Resources → Tools → Skills** — the uploaded `response-guardrails` skill appears there with its version and description.

![Foundry Toolkit Skills tab under My Resources → Tools, listing the uploaded response-guardrails skill with its Responsible-AI description and version v1](.workshop/docs/assets/06-foundry-skill-provisioned.png)
![Foundry Toolkit Skills tab under My Resources → Tools, listing the uploaded response-guardrails skill with its Responsible-AI description and version v1](../assets/06-foundry-skill-provisioned.png)

### B2. Add the download and extend the provider in `main.py`

Expand Down Expand Up @@ -727,7 +727,7 @@ That folder is a **runtime cache** in the OS temp dir — `main.py` deletes and

## Solution

- [`.workshop/solutions/06-skills/`](.workshop/solutions/06-skills/) — the local skill **and** the required Foundry skill, with `foundry_skills/` as a sibling of `travel_assistant/`.
- [`.workshop/solutions/06-skills/`](../../solutions/06-skills/) — the local skill **and** the required Foundry skill, with `foundry_skills/` as a sibling of `travel_assistant/`.

## Upstream sample

Expand Down
2 changes: 1 addition & 1 deletion .workshop/docs/steps/07-multi-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Keep import names aligned with the files you created in Steps 5–6. `coordinato

## Solution

> If you get stuck: [`.workshop/solutions/07-multi-agent/`](.workshop/solutions/07-multi-agent/)
> If you get stuck: [`.workshop/solutions/07-multi-agent/`](../../solutions/07-multi-agent/)

## Upstream sample

Expand Down
2 changes: 1 addition & 1 deletion .workshop/docs/steps/08-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ Hosted workflow-agents surface `request_info` as a **function call**, not a prom

## Solution

> If you get stuck: [`.workshop/solutions/08-workflow/`](.workshop/solutions/08-workflow/)
> If you get stuck: [`.workshop/solutions/08-workflow/`](../../solutions/08-workflow/)

## Upstream sample

Expand Down
2 changes: 1 addition & 1 deletion .workshop/docs/steps/09-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ If cleanup lists it as skipped, the name has no prefix match — delete it manua

## Solution

> If you get stuck: [`.workshop/solutions/09-memory/`](.workshop/solutions/09-memory/)
> If you get stuck: [`.workshop/solutions/09-memory/`](../../solutions/09-memory/)

## Upstream sample

Expand Down
Loading
Loading