Skip to content

Commit d19e673

Browse files
samples(python/13-foundry-memory): one-command azd provisioning via postprovision hook (#576)
Add a postprovision hook (.ps1/.sh) that creates the Foundry Memory Store and wires MEMORY_STORE_NAME so a single 'azd provision' is enough. The hook also patches the init'd agent.yaml so the resolved store name reaches the deployed container (azd ai agent init resolves \ to empty at init time). Pin mcp<2 (agent-framework-foundry-hosting imports McpError, renamed in mcp 2.x). Document the project-scope Cognitive Services OpenAI User role needed for the embedding call. Exclude hooks/ from container/azd packaging.
1 parent 423e805 commit d19e673

6 files changed

Lines changed: 210 additions & 51 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
agent.manifest.yaml
22
agent.yaml
33
.env.example
4+
hooks/

samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ __pycache__
66
.Python
77
.env
88
provision_memory_store.py
9+
hooks/

samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/README.md

Lines changed: 72 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,47 +33,19 @@ The agent is hosted using the [Agent Framework](https://github.com/microsoft/age
3333

3434
### Required RBAC
3535

36-
Your identity (or the Managed Identity running the container in production) needs **Azure AI User** on the Foundry project scope. This single role covers both provisioning the memory store with `provision_memory_store.py` and reading/writing memories from `main.py`.
36+
Your identity (or the Managed Identity running the container in production) needs **Azure AI User** on the Foundry project scope. This role covers provisioning the memory store with `provision_memory_store.py` and reading/writing memories from `main.py`.
3737

38-
## Provisioning the memory store (one time)
38+
The memory store embeds and retrieves memories through the project's inference endpoint, so the same identity also needs **Cognitive Services OpenAI User** on the Foundry project scope to call the embedding deployment. Without it, memory writes fail with a `401` (`Authentication to the Azure OpenAI resource failed`) and the store stays empty. When deploying, grant both roles to the hosted agent's runtime identity (the `…-AgentIdentity` service principal) at the project scope.
3939

40-
[`provision_memory_store.py`](provision_memory_store.py) creates a Foundry Memory Store with the user-profile capability enabled (and chat-summary disabled) using `AIProjectClient.beta.memory_stores.create`. It is safe to re-run: if a store with the same name already exists, the script leaves it alone.
41-
42-
From this directory, with the venv activated and `az login` done:
43-
44-
```bash
45-
export FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
46-
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"
47-
export AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="text-embedding-3-small"
48-
export MEMORY_STORE_NAME="agent_framework_memory"
49-
python provision_memory_store.py
50-
```
51-
52-
Or in PowerShell:
53-
54-
```powershell
55-
$env:FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
56-
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"
57-
$env:AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="text-embedding-3-small"
58-
$env:MEMORY_STORE_NAME="agent_framework_memory"
59-
python provision_memory_store.py
60-
```
61-
62-
Expected output (first run):
63-
64-
```text
65-
Creating memory store 'agent_framework_memory'...
66-
Created memory store 'agent_framework_memory' (id=memstore_...).
67-
```
68-
69-
> To delete the store manually, call `project.beta.memory_stores.delete("<name>")` on an `AIProjectClient` constructed with `allow_preview=True`.
7040

7141
## Option 1: Azure Developer CLI (`azd`)
7242

73-
### Prerequisites
43+
With the bundled `postprovision` hook, a single `azd provision` creates the Foundry Memory Store and sets `MEMORY_STORE_NAME` for you.
44+
45+
### 1. Install prerequisites
7446

75-
1. **Azure Developer CLI (`azd`)**[Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd)
76-
2. Install the AI agent extension:
47+
1. **Azure Developer CLI (`azd`)**[Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) (1.25 or later)
48+
2. Install the unified Foundry CLI extension bundle:
7749
```bash
7850
azd ext install microsoft.foundry
7951
```
@@ -82,7 +54,7 @@ Created memory store 'agent_framework_memory' (id=memstore_...).
8254
azd auth login
8355
```
8456

85-
### Initialize the agent project
57+
### 2. Initialize the agent project
8658

8759
No cloning required. Create a new folder and initialize from the manifest:
8860

@@ -92,17 +64,42 @@ mkdir my-memory-agent && cd my-memory-agent
9264
azd ai agent init -m https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/agent.manifest.yaml
9365
```
9466

95-
Follow the prompts to configure your Foundry project and model deployment. If you don't have an existing Foundry project, `azd ai agent init` will guide you through creating one.
67+
Follow the prompts to configure your Foundry project and model deployment. If you don't have an existing Foundry project, `azd ai agent init` will guide you through creating one. Initializing also sets the selected project as the active project, and copies this sample's files into a new service directory `src/<agent-name>/` — including [`provision_memory_store.py`](provision_memory_store.py) and the [`hooks/`](hooks/) scripts.
9668

97-
### Provision Azure resources (if needed)
69+
### 3. Enable one-command provisioning (`postprovision` hook)
9870

99-
If you don't already have a Foundry project and model deployment:
71+
Wire the bundled hook into the `azure.yaml` that `azd ai agent init` generated, so the memory store is created automatically every time you run `azd provision`. `postprovision` must be registered at the **top level** of `azure.yaml` (service-scoped hooks only support the package/deploy lifecycle), and the `run:` path must point at the hook inside the generated service directory. Add this top-level block, replacing `<agent-name>` with the service folder `azd ai agent init` created under `src/`:
72+
73+
```yaml
74+
hooks:
75+
postprovision:
76+
posix:
77+
shell: sh
78+
run: ./src/<agent-name>/hooks/postprovision.sh
79+
windows:
80+
shell: pwsh
81+
run: ./src/<agent-name>/hooks/postprovision.ps1
82+
```
83+
84+
The hook ([`hooks/postprovision.sh`](hooks/postprovision.sh) / [`hooks/postprovision.ps1`](hooks/postprovision.ps1)) runs everything the [manual steps](#provision-manually-without-the-hook) below would, in one shot. It locates its own directory, so it works no matter where `azd` runs it from.
85+
86+
### 4. Provision
87+
88+
Point the hook at an embedding model deployment in your Foundry project (it powers the store's semantic memory, not the agent at runtime), then provision:
10089

10190
```bash
91+
azd env set AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME "text-embedding-3-small"
10292
azd provision
10393
```
10494

105-
### Run the agent locally
95+
`azd provision` creates (or reuses) your Foundry project and chat model deployment, then the `postprovision` hook:
96+
97+
1. Runs [`provision_memory_store.py`](provision_memory_store.py) to create the Foundry Memory Store (user-profile capability enabled, chat-summary disabled) and verifies it on the service.
98+
2. Sets `MEMORY_STORE_NAME` so the agent reads and writes that store. It persists the name both to the `azd` environment (for `azd ai agent run`) and into the generated `src/<agent-name>/agent.yaml` (so `azd deploy` ships it to the container — `azd ai agent init` resolves `${MEMORY_STORE_NAME}` to an empty value at init time, before the store name is known).
99+
100+
> The hook defaults `MEMORY_STORE_NAME` to `agent_framework_memory`. To use a different name, set it first: `azd env set MEMORY_STORE_NAME "<your-store-name>"`.
101+
102+
### 5. Run the agent locally
106103

107104
```bash
108105
azd ai agent run
@@ -115,35 +112,60 @@ The agent host will start on `http://localhost:8088`.
115112
In a separate terminal, from the project directory:
116113

117114
```bash
118-
azd ai agent invoke --local "Hi"
115+
azd ai agent invoke --local "Hi, my name is Alex and I'm vegetarian."
119116
```
120117

121118
### Deploy to Foundry
122119

123-
Once tested locally, deploy to Microsoft Foundry:
120+
```bash
121+
azd deploy
122+
```
123+
124+
For the full deployment guide, see [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
125+
126+
The deployed agent's Managed Identity needs **Azure AI User** on the Foundry project to read and write memories at runtime. The `postprovision` hook already created the memory store against that same project.
124127

125-
Make sure `MEMORY_STORE_NAME` is set in your `azd` environment:
128+
### Invoke the deployed agent
126129

127130
```bash
128-
azd env set MEMORY_STORE_NAME "agent_framework_memory"
131+
azd ai agent invoke "Do you remember my name and what I like to eat?"
129132
```
130133

134+
### Provision manually (without the hook)
135+
136+
Prefer to run the step yourself (or skip the hook)? [`provision_memory_store.py`](provision_memory_store.py) creates a Foundry Memory Store with the user-profile capability enabled (and chat-summary disabled) using `AIProjectClient.beta.memory_stores.create`. It is safe to re-run: if a store with the same name already exists, the script leaves it alone.
137+
138+
From the project directory, with the venv activated and `az login` done:
139+
131140
```bash
132-
azd deploy
141+
pip install azure-ai-projects azure-identity aiohttp python-dotenv
142+
143+
export FOUNDRY_PROJECT_ENDPOINT="https://<account>.services.ai.azure.com/api/projects/<project>"
144+
export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4.1-mini"
145+
export AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME="text-embedding-3-small"
146+
export MEMORY_STORE_NAME="agent_framework_memory"
147+
python provision_memory_store.py
133148
```
134149

135-
For the full deployment guide, see [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
150+
In PowerShell, use `$env:NAME="value"` instead of `export`. Then point the agent at the same store name:
136151

137-
The deployed agent's Managed Identity needs **Azure AI User** on the Foundry project to read and write memories at runtime. Make sure you have run `provision_memory_store.py` against the same Foundry project before deploying.
152+
```bash
153+
azd env set MEMORY_STORE_NAME "agent_framework_memory"
154+
```
138155

139-
### Invoke the deployed agent
156+
Expected output (first run):
140157

141-
```bash
142-
azd ai agent invoke "Hi"
158+
```text
159+
Creating memory store 'agent_framework_memory'...
160+
Created memory store 'agent_framework_memory' (id=memstore_...).
143161
```
144162

163+
> To delete the store manually, call `project.beta.memory_stores.delete("<name>")` on an `AIProjectClient` constructed with `allow_preview=True`.
164+
145165
## Option 2: VS Code (Foundry Toolkit)
146166

167+
> The VS Code flow doesn't run the `azd` hook — provision the memory store first with [Provision manually](#provision-manually-without-the-hook).
168+
147169
### Prerequisites
148170

149171
1. **VS Code** with the **[Foundry Toolkit](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.azure-ai-foundry)** extension installed.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env pwsh
2+
# azd postprovision hook (Windows / pwsh).
3+
#
4+
# Runs automatically after `azd provision`. It provisions the Azure AI Foundry
5+
# Memory Store the agent uses and stores MEMORY_STORE_NAME so the agent can
6+
# reach it — collapsing the manual provisioning step into a single `azd provision`.
7+
#
8+
# Wire it up by adding this to the azure.yaml generated by `azd ai agent init`:
9+
#
10+
# hooks:
11+
# postprovision:
12+
# posix:
13+
# shell: sh
14+
# run: ./src/<agent-name>/hooks/postprovision.sh
15+
# windows:
16+
# shell: pwsh
17+
# run: ./src/<agent-name>/hooks/postprovision.ps1
18+
19+
$ErrorActionPreference = "Stop"
20+
21+
# PowerShell does not stop on a non-zero exit code from a native command (like
22+
# azd), so check $LASTEXITCODE after each azd call and fail loudly.
23+
function Invoke-Checked {
24+
param([scriptblock] $Script, [string] $What)
25+
& $Script
26+
if ($LASTEXITCODE -ne 0) { throw "$What failed (exit $LASTEXITCODE)." }
27+
}
28+
29+
# Run from the sample directory (the parent of hooks/) so provision_memory_store.py
30+
# resolves no matter which directory azd invokes the hook from.
31+
Set-Location (Split-Path -Parent $PSScriptRoot)
32+
33+
# The memory store needs an embedding model deployment; it isn't part of the
34+
# agent's own model resources, so it's a prerequisite you set once.
35+
if (-not $env:AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME) {
36+
throw "AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME is not set. Run: azd env set AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME ""text-embedding-3-small"""
37+
}
38+
39+
# Default the memory store name and persist it so the agent (which reads
40+
# MEMORY_STORE_NAME at deploy time) and the provisioning script agree.
41+
if (-not $env:MEMORY_STORE_NAME) {
42+
$env:MEMORY_STORE_NAME = "agent_framework_memory"
43+
Invoke-Checked { azd env set MEMORY_STORE_NAME $env:MEMORY_STORE_NAME } "env set MEMORY_STORE_NAME"
44+
}
45+
46+
Write-Host "Provisioning the Foundry Memory Store '$($env:MEMORY_STORE_NAME)'..."
47+
# provision_memory_store.py uses the async azure-ai-projects client, which needs
48+
# aiohttp as its transport. Install the narrow set the script imports.
49+
Invoke-Checked { python -m pip install -q azure-ai-projects azure-identity aiohttp python-dotenv } "pip install"
50+
# Idempotent: leaves an existing store with the same name untouched.
51+
Invoke-Checked { python provision_memory_store.py } "provision_memory_store.py"
52+
53+
# `azd ai agent init` resolves ${MEMORY_STORE_NAME} in agent.yaml at init time —
54+
# before this hook runs and sets the value — so the manifest is left with an empty
55+
# value and `azd deploy` would ship MEMORY_STORE_NAME="" to the container, leaving
56+
# the agent with no store to read or write. Write the resolved name into agent.yaml
57+
# so the deployed agent gets it. Idempotent.
58+
$manifest = Join-Path (Get-Location) "agent.yaml"
59+
if (Test-Path $manifest) {
60+
$content = [System.IO.File]::ReadAllText($manifest)
61+
$pattern = '(?m)(^[ \t]*-[ \t]*name:[ \t]*MEMORY_STORE_NAME[ \t]*\r?\n[ \t]*value:[ \t]*).*$'
62+
$updated = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, ('${1}' + $env:MEMORY_STORE_NAME))
63+
if ($updated -ne $content) {
64+
[System.IO.File]::WriteAllText($manifest, $updated)
65+
Write-Host "Set MEMORY_STORE_NAME in agent.yaml to '$($env:MEMORY_STORE_NAME)' for deploy."
66+
}
67+
}
68+
69+
Write-Host "Done. MEMORY_STORE_NAME = $($env:MEMORY_STORE_NAME)"
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env sh
2+
# azd postprovision hook (POSIX / sh).
3+
#
4+
# Runs automatically after `azd provision`. It provisions the Azure AI Foundry
5+
# Memory Store the agent uses and stores MEMORY_STORE_NAME so the agent can
6+
# reach it — collapsing the manual provisioning step into a single `azd provision`.
7+
#
8+
# Wire it up by adding this to the azure.yaml generated by `azd ai agent init`:
9+
#
10+
# hooks:
11+
# postprovision:
12+
# posix:
13+
# shell: sh
14+
# run: ./src/<agent-name>/hooks/postprovision.sh
15+
# windows:
16+
# shell: pwsh
17+
# run: ./src/<agent-name>/hooks/postprovision.ps1
18+
19+
set -e
20+
21+
# Run from the sample directory (the parent of hooks/) so provision_memory_store.py
22+
# resolves no matter which directory azd invokes the hook from.
23+
cd "$(CDPATH= cd "$(dirname "$0")/.." && pwd)"
24+
25+
# The memory store needs an embedding model deployment; it isn't part of the
26+
# agent's own model resources, so it's a prerequisite you set once.
27+
if [ -z "$AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME" ]; then
28+
echo "AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME is not set. Run: azd env set AZURE_AI_EMBEDDING_MODEL_DEPLOYMENT_NAME \"text-embedding-3-small\"" >&2
29+
exit 1
30+
fi
31+
32+
# Default the memory store name and persist it so the agent (which reads
33+
# MEMORY_STORE_NAME at deploy time) and the provisioning script agree.
34+
if [ -z "$MEMORY_STORE_NAME" ]; then
35+
MEMORY_STORE_NAME="agent_framework_memory"
36+
export MEMORY_STORE_NAME
37+
azd env set MEMORY_STORE_NAME "$MEMORY_STORE_NAME"
38+
fi
39+
40+
echo "Provisioning the Foundry Memory Store '$MEMORY_STORE_NAME'..."
41+
# provision_memory_store.py uses the async azure-ai-projects client, which needs
42+
# aiohttp as its transport. Install the narrow set the script imports.
43+
python -m pip install -q azure-ai-projects azure-identity aiohttp python-dotenv
44+
# Idempotent: leaves an existing store with the same name untouched.
45+
python provision_memory_store.py
46+
47+
# `azd ai agent init` resolves ${MEMORY_STORE_NAME} in agent.yaml at init time —
48+
# before this hook runs and sets the value — so the manifest is left with an empty
49+
# value and `azd deploy` would ship MEMORY_STORE_NAME="" to the container, leaving
50+
# the agent with no store to read or write. Write the resolved name into agent.yaml
51+
# so the deployed agent gets it. Idempotent.
52+
if [ -f agent.yaml ]; then
53+
awk -v val="$MEMORY_STORE_NAME" '
54+
prev ~ /name:[[:space:]]*MEMORY_STORE_NAME[[:space:]]*$/ && /value:/ {
55+
sub(/value:.*/, "value: " val)
56+
}
57+
{ print; prev = $0 }
58+
' agent.yaml > agent.yaml.tmp && mv agent.yaml.tmp agent.yaml
59+
echo "Set MEMORY_STORE_NAME in agent.yaml to '$MEMORY_STORE_NAME' for deploy."
60+
fi
61+
62+
echo "Done. MEMORY_STORE_NAME = $MEMORY_STORE_NAME"
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Use the narrow Foundry subpackages to keep dependencies light.
22
agent-framework-foundry
33
agent-framework-foundry-hosting
4-
mcp<2,>=1.24.0
4+
# mcp is a transitive dependency of agent-framework-core[all] that is needed
5+
# at runtime but not declared as a required dependency of agent-framework-core.
6+
# Pin below 2.0: agent-framework-foundry-hosting imports ``McpError`` from mcp,
7+
# which the 2.0 line renamed to ``MCPError`` — installing mcp 2.x breaks startup.
8+
mcp>=1.24.0,<2
59
azure-ai-projects

0 commit comments

Comments
 (0)