A chat sample that uses the real Foundry Agents SDK (azure-ai-agents) to
create a server-side agent, run it against server-side threads, and resolve
function-tool calls — all from a Python app hosted on Embr.
This sample is intentionally distinct from its cousins:
| Sample | SDK / API | Where state lives | Where the agent loop runs |
|---|---|---|---|
embr-foundry-chat-sample-python |
openai chat completions |
App memory | Client (Microsoft Agent Framework) |
embr-foundry-stateful-agent-sample |
openai Conversations |
Foundry (Conversations) | Client orchestrates Responses |
| this one | azure-ai-agents Agents SDK |
Foundry (Agent + Threads) | Foundry (server-side run loop) |
The point of this sample is to validate the actual Foundry control plane
(create_agent, threads.create, runs.create_and_process,
runs.submit_tool_outputs) end-to-end from inside an Embr-hosted app.
app/
agent.py # AgentsClient wrapper + tool implementations + run loop
main.py # FastAPI: /api/chat, /api/threads, /api/agent, /api/config
static/
index.html # markdown-rendered chat UI with tool-call surfacing
embr.yaml # Embr platform config (Python 3.12, port 8000, /health)
requirements.txt
The agent is created with two function tools that run on the Embr side:
get_weather(city)— returns a hardcoded blurb for a cityroll_dice(sides)— rolls a die with N sides
The Foundry agent loop emits requires_action events when it wants to call
either tool; the app resolves them locally and submits ToolOutput back via
runs.submit_tool_outputs. Foundry then continues the run.
The Agents control plane requires AAD/RBAC, not API keys:
Identity does not have permissions for Microsoft.MachineLearningServices/workspaces/agents/action actions
This is fine in tooling (the local az login works), but Embr does not
expose a managed identity to app code. The only way to obtain an AAD token
from inside an Embr app today is to bundle a service principal's client
secret as environment variables.
This sample handles both:
- Locally: falls back to
DefaultAzureCredential, which uses youraz login/ VS Code creds. - On Embr: requires
AZURE_TENANT_ID,AZURE_CLIENT_ID,AZURE_CLIENT_SECRETto be set. If they're missing, the sample will fail on first agent operation with a clear error — which is the documented finding: Embr needs first-class managed-identity support so customers don't have to bundle client secrets.
| Var | Required | Purpose |
|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
one of | Foundry project endpoint, e.g. https://{name}.services.ai.azure.com/api/projects/{project} |
FOUNDRY_BASE_URL |
one of | Same as above; the /openai/v1 suffix used by the chat sample is auto-stripped |
FOUNDRY_MODEL_DEPLOYMENT |
yes | Foundry model deployment name (e.g. gpt-5.4-mini-1) |
AZURE_TENANT_ID |
on Embr | SP tenant id |
AZURE_CLIENT_ID |
on Embr | SP client id |
AZURE_CLIENT_SECRET |
on Embr | SP client secret |
EMBR_AGENT_FORCE_RECREATE |
no | If set to 1, recreate the agent every cold start instead of reusing-by-name |
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
az login # provides DefaultAzureCredential
export FOUNDRY_PROJECT_ENDPOINT="https://{name}.services.ai.azure.com/api/projects/{project}"
export FOUNDRY_MODEL_DEPLOYMENT="gpt-5.4-mini-1"
uvicorn app.main:app --reload --port 8000Open http://localhost:8000 and try roll a d20, what's the weather in Seattle,
or any general question (the agent picks tools as needed).
embr quickstart deploy embr-devs/embr-foundry-agent-samplethen set env vars (replace placeholders with your SP credentials):
PROJ=<projectId>; ENV=<environmentId>
embr variables set FOUNDRY_PROJECT_ENDPOINT 'https://...services.ai.azure.com/api/projects/proj-default' -p $PROJ -e $ENV
embr variables set FOUNDRY_MODEL_DEPLOYMENT 'gpt-5.4-mini-1' -p $PROJ -e $ENV
embr variables set AZURE_TENANT_ID '<tenant-guid>' -p $PROJ -e $ENV
embr variables set AZURE_CLIENT_ID '<sp-client-id>' -p $PROJ -e $ENV
embr variables set AZURE_CLIENT_SECRET '<sp-secret>' -p $PROJ -e $ENV --secret
embr deployments trigger -c HEAD -p $PROJ -e $ENV- Server-side state. Click "Show server-side thread" — the messages live in Foundry, not in this app. Restart the app and the same thread still has every message.
- Agent reuse. The agent is created with name
embr-foundry-agent-sample. On restart, the app callslist_agents()and reuses the existing one instead of creating a duplicate. - Tool calls. Ask "roll a d20" or "what's the weather in Tokyo?" — the
UI surfaces the tool-call events as green
🔧lines so you can see exactly what Foundry asked the app to do. - Latency. Each turn shows a
latency_msline —runs.create_and_processblocks until the run terminates, so this includes the full agent-think + tool-call round trips.