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
47 changes: 26 additions & 21 deletions .workshop/docs/steps/07-multi-agent.md

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions .workshop/docs/steps/08-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ def create_flights_agent(client, credential=None) -> Agent:
tools=[get_weather, get_local_time, convert_currency, toolbox], default_options={"store": False},
)

# create_hotels_agent (currency + RAG) and create_activities_agent
# (toolbox + RAG + itinerary skill) follow the same pattern.
# create_hotels_agent (currency + toolbox web + RAG) and
# create_activities_agent (toolbox web + RAG) follow the same pattern.
```

The point is a **single source of truth**: the runtime Coordinator and the workflow now build identical specialists.

### 2. Create `travel_assistant/workflow.py`

The workflow has three custom executors plus agent nodes. `GatherPreferences` fans the request out to all three specialists; `Consolidate` aggregates their answers, checkpoints the draft, then sends the finalize prompt; `finalize_itinerary` is an `AgentExecutor` that writes the plan.
The workflow has three custom executors plus agent nodes. `GatherPreferences` fans the request out to all three specialists; `Consolidate` aggregates their answers, checkpoints the draft, then sends the finalize prompt; `finalize_itinerary` is an `AgentExecutor` that writes the plan and — taking over the Step 7 Coordinator's role — owns the final deliverable, using the `travel-guide` skill to render the shareable PDF and the `response-guardrails` skill to check the answer.

```python
# travel_assistant/workflow.py
Expand All @@ -100,6 +100,7 @@ from agent_framework import (
)

from coordinator import (
_build_skills_provider,
create_activities_agent, create_flights_agent, create_hotels_agent, make_client,
)

Expand Down Expand Up @@ -170,7 +171,11 @@ def build_workflow(require_approval: bool = False):
hotels = AgentExecutor(create_hotels_agent(client), id="hotels", context_mode="last_agent")
activities = AgentExecutor(create_activities_agent(client), id="activities", context_mode="last_agent")
finalize = AgentExecutor(
Agent(client=client, name="finalize_itinerary", instructions=FINALIZE_INSTRUCTIONS),
Agent(
client=client, name="finalize_itinerary", instructions=FINALIZE_INSTRUCTIONS,
context_providers=[_build_skills_provider()], # travel-guide PDF + response-guardrails
default_options={"store": False},
),
id="finalize_itinerary", context_mode="last_agent",
)
gather, consolidate = GatherPreferences(), Consolidate()
Expand Down Expand Up @@ -202,6 +207,8 @@ def build_workflow_agent(require_approval: bool = False) -> Agent:

Do **not** copy the specialist prompts into `workflow.py` — import the factories so Steps 7 and 8 stay aligned.

> **Skipped the Foundry skill?** The finalize step inherits the Step 7 rule: if you left `FOUNDRY_SKILL_NAMES` unset, carry your local-only skills provider here instead of the solution's `_build_skills_provider`, and drop the `response-guardrails` line from `FINALIZE_INSTRUCTIONS`. The local `travel-guide` skill still renders the PDF.

### 3. Point `main.py` at the workflow

`main.py` hosts the workflow-as-agent through the same server as every other step:
Expand Down
9 changes: 5 additions & 4 deletions .workshop/docs/steps/09-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A conversation already has **in-conversation context** — the messages still in

**Scope is the key design detail.** Memories are partitioned by a scope string. Store under one scope and read under another and recall silently fails. For a hosted agent the correct scope is the special placeholder `scope="{{$userId}}"`, which the hosting runtime replaces with the authenticated caller's user id — so every traveler automatically gets their own isolated memories. (In a purely local script you would instead pass a stable id you control.)

**Where memory attaches.** We add the provider inside the Step 8 specialist factories, so the workflow's `flights` / `hotels` / `activities` executors all become memory-aware. The graph, checkpoints, hosting, and `workflow.as_agent()` are untouched — memory rides along as context.
**Where memory attaches.** We add the provider inside the Step 8 specialist factories, so the workflow's `flights` / `hotels` / `activities` executors all become memory-aware. The `finalize_itinerary` step is deliberately left out: the specialists already fold each traveler's recalled preferences into the draft, so finalize only has to render and guardrail the consolidated answer — it needs the skills, not per-user recall. The graph, checkpoints, hosting, and `workflow.as_agent()` are untouched — memory rides along as context.

```mermaid
flowchart LR
Expand Down Expand Up @@ -134,23 +134,24 @@ def _build_memory_provider(client: FoundryChatClient) -> FoundryMemoryProvider:

`update_delay` is the debounce before the store extracts and persists new facts. It **defaults to 300 seconds (5 minutes)**, which batches writes to reduce cost in production. For the workshop we set `update_delay=0` so a fact you state in one turn is recallable on the next; leave the default (or raise it) in a real deployment.

Each factory builds the provider from its `client` and appends it — keeping every carried-over tool, toolbox, RAG, and skill:
Each factory builds the provider from its `client` and appends it — keeping every carried-over tool, toolbox, and RAG:

```python
# travel_assistant/coordinator.py (delta)
def create_hotels_agent(client, credential=None) -> Agent:
credential = credential or DefaultAzureCredential()
toolbox = FoundryToolbox(credential)
search = _build_search_provider(credential)
memory = _build_memory_provider(client) # NEW
return Agent(
client=client, name="HotelsSpecialist", instructions=HOTELS_INSTRUCTIONS,
tools=[convert_currency],
tools=[convert_currency, toolbox],
context_providers=[search, memory], # + memory
default_options={"store": False},
)
```

`create_flights_agent` gains `context_providers=[memory]` (its first provider); `create_activities_agent` becomes `[search, skills, memory]`. Reading `MEMORY_STORE_NAME` with `os.environ["..."]` makes memory a required capability — a missing value fails fast with a clear `KeyError` instead of silently starting without recall.
`create_flights_agent` gains `context_providers=[memory]` (its first provider); `create_activities_agent` becomes `[search, memory]`. Reading `MEMORY_STORE_NAME` with `os.environ["..."]` makes memory a required capability — a missing value fails fast with a clear `KeyError` instead of silently starting without recall.

Reusing `client.project_client` (instead of constructing a second `AIProjectClient`) keeps a single authentication context, and putting memory in the factories means the runtime Coordinator (Step 7) and the hosted workflow (Step 8) both pick it up from one source of truth. `main.py` and `workflow.py` need **no** changes.

Expand Down
10 changes: 5 additions & 5 deletions .workshop/solutions/07-multi-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Builds on Step 6: keeps every carried capability (function tools, Foundry Toolbo

## Layout

- `travel_assistant/` — the agent code. `coordinator.py` builds the handoff graph and each specialist gets a sliced capability set; it also downloads the Foundry skill at runtime into a writable temp dir (`<tempdir>/foundry_downloaded_skills/`) and serves it plus the local skill via one `SkillsProvider`. `agents/{flights,hotels,activities}/` hold per-specialist `agent.yaml` + `agent.manifest.yaml` slices that document each role's tool/RAG/skill boundary. Snapshotted by `azd ai agent init`.
- `travel_assistant/` — the agent code. `coordinator.py` builds the handoff graph and each specialist gets a sliced capability set; the Coordinator downloads the Foundry skill at runtime into a writable temp dir (`<tempdir>/foundry_downloaded_skills/`) and serves it plus the local skill via one `SkillsProvider`. `agents/{flights,hotels,activities}/` hold per-specialist `agent.yaml` + `agent.manifest.yaml` slices that document each role's tool/RAG/skill boundary. Snapshotted by `azd ai agent init`.
- `travel_indexer/` — the out-of-band Search indexer (`provision_index.py`, `data/destinations.json`), a sibling of `travel_assistant/` (from Step 5).
- `foundry_skills/` — the out-of-band Foundry-skill authoring + upload (`provision_skills.py`, `skills/response-guardrails/SKILL.md`), a sibling of `travel_assistant/` (from Step 6). Never deployed — `azd ai agent init` snapshots only `travel_assistant/`.
- `travel_toolbox/` — the toolbox definition (`toolbox.yaml`).
Expand All @@ -16,10 +16,10 @@ Builds on Step 6: keeps every carried capability (function tools, Foundry Toolbo

| Specialist | Tools | RAG | Skill |
| --- | --- | --- | --- |
| Coordinator | — | — | |
| Flights | `get_weather`, `convert_currency`, toolbox (flight search) | — | — |
| Hotels | `convert_currency` | destinations index | — |
| Activities | toolbox (web/reference) | destinations index | travel-guide, response-guardrails |
| Coordinator | — | — | travel-guide, response-guardrails |
| Flights | `get_weather`, `get_local_time`, `convert_currency`, toolbox (flight fares) | — | — |
| Hotels | `convert_currency`, toolbox (web) | destinations index | — |
| Activities | toolbox (web/reference) | destinations index | |

## Run it

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ metadata:
- name: response-guardrails
description: >
Foundry Responsible-AI guardrails Skill downloaded from the project and attached to
the Activities specialist; shareable across agents and required by this step.
the Coordinator; shareable across agents and required by this step.
Uploaded out-of-band via foundry_skills/provision_skills.py.
type: skill
handoff:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
name: activities-specialist
version: 0.1.0
description: Tool, RAG, and skill slice for the Activities specialist.
description: Tool and RAG slice for the Activities specialist.
tools:
- name: travel-toolbox
source: foundry-toolbox
rag:
- name: destinations-index
source: azure-ai-search
skills:
- name: travel-guide
source: travel_assistant.skills.travel-guide
- name: response-guardrails
source: foundry
skills: []
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ instructions: |

Scope:
- Suggest experiences, day trips, food areas, museum days, outdoor options, and rainy-day alternatives.
- Use grounded destination knowledge before making specific recommendations.
- Use the toolbox for web search of current events, advisories, and source-backed guidance.
- Use the travel-guide skill to render a downloadable, shareable PDF trip guide.

Tools (always use these rather than answering from memory):
- Grounded destination knowledge (the destinations index) before making specific recommendations.
- The toolbox's web search for current events, advisories, and source-backed guidance.

Boundaries:
- Do not choose flights or hotels.
- If the itinerary requires flight or hotel constraints, hand back to the Coordinator with the missing details.
- Always hand back to the Coordinator when you finish your part, when the itinerary needs flight or hotel constraints, or when a missing detail blocks your specialist work. The Coordinator is the only agent that talks to the traveler, so never ask the traveler directly; hand back and let the Coordinator relay any question.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ instructions: |
You are the Flights specialist for TravelBuddy.

Scope:
- Help compare flight timing, routing, nearby airports, layovers, and arrival windows.
- Use flight search from the toolbox when the traveler asks for specific routes; if no departure date is given, call get_local_time and use the date part of its iso_time as today's date.
- Use weather when travel timing or disruption risk matters.
- Use currency conversion when the traveler gives or asks for prices in another currency.
- Compare flight timing, routing, nearby airports, layovers, and arrival windows.
- Always report concrete fares/prices for the flights you recommend, and convert them to the traveler's currency when asked.

Tools (always use these rather than answering from memory):
- Flight search in the toolbox for real routes, times, and fares. If no departure date is given, call get_local_time first and use the date part of its iso_time as today's date.
- get_weather when travel timing or disruption risk matters.
- convert_currency when the traveler gives or asks for prices in another currency.

Boundaries:
- Do not choose hotels or activities.
- When the user asks about lodging, experiences, or the complete plan, hand back to the Coordinator.
- Always hand back to the Coordinator when you finish your part, when the request turns to lodging, experiences, or the complete plan, or when a missing detail blocks your specialist work. The Coordinator is the only agent that talks to the traveler, so never ask the traveler directly; hand back and let the Coordinator relay any question.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
name: hotels-specialist
version: 0.1.0
description: Tool and retrieval slice for the Hotels specialist.
description: Tool, web, and retrieval slice for the Hotels specialist.
tools:
- name: convert_currency
source: travel_assistant.tools
- name: travel-toolbox
source: foundry-toolbox
rag:
- name: destinations-index
source: azure-ai-search
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ instructions: |
You are the Hotels specialist for TravelBuddy.

Scope:
- Recommend neighbourhoods and lodging trade-offs using grounded destination knowledge.
- Recommend neighbourhoods and lodging trade-offs.
- Respect budget, dates, accessibility, room type, and must-have amenities.
- Use currency conversion for nightly budgets and total-stay estimates.

Tools (always use these rather than answering from memory):
- Grounded destination knowledge (the destinations index) before recommending neighbourhoods or areas.
- The toolbox's web search for current rates, availability signals, and source-backed lodging guidance.
- convert_currency for nightly budgets and total-stay estimates.

Boundaries:
- Do not invent live availability.
- Do not plan full-day activities unless they affect neighbourhood choice.
- When the user asks for flights, activities, or a complete itinerary, hand back to the Coordinator.
- Always hand back to the Coordinator when you finish your part, when the request turns to flights, activities, or a complete itinerary, or when a missing detail blocks your specialist work. The Coordinator is the only agent that talks to the traveler, so never ask the traveler directly; hand back and let the Coordinator relay any question.
Loading
Loading