From c4348236dc2a9627184bce5015d307fd0c4bcb07 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 2 Jul 2026 12:04:27 -0700 Subject: [PATCH 1/3] Add FHA declarative workflow sample --- .../.dockerignore | 7 + .../.env.example | 2 + .../Dockerfile | 31 ++++ .../13_declarative_customer_support/README.md | 152 ++++++++++++++++++ .../agent.manifest.yaml | 25 +++ .../agent.yaml | 12 ++ .../13_declarative_customer_support/main.py | 143 ++++++++++++++++ .../requirements.txt | 6 + .../workflow.yaml | 89 ++++++++++ 9 files changed, 467 insertions(+) create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.dockerignore create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.env.example create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.manifest.yaml create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.yaml create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt create mode 100644 python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/workflow.yaml diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.dockerignore b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.dockerignore new file mode 100644 index 00000000000..31ed562a7e5 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.dockerignore @@ -0,0 +1,7 @@ +.venv +__pycache__ +*.pyc +*.pyo +*.pyd +.Python +.env diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.env.example b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.env.example new file mode 100644 index 00000000000..2a38d9c9b81 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.env.example @@ -0,0 +1,2 @@ +FOUNDRY_PROJECT_ENDPOINT="..." +AZURE_AI_MODEL_DEPLOYMENT_NAME="..." diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile new file mode 100644 index 00000000000..66d3b74ec65 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile @@ -0,0 +1,31 @@ +FROM python:3.12-slim + +WORKDIR /app + +# .NET 10 runtime is required by the `powerfx` package, which +# agent-framework-declarative uses to evaluate `=...` expressions in the +# workflow YAML (e.g. =Local.Triage.NeedsClarification). Without it, +# ConditionGroup evaluation raises and the workflow produces no output. +RUN apt-get update \ + && apt-get install -y --no-install-recommends wget ca-certificates libicu-dev \ + && wget -qO /tmp/dotnet-install.sh https://dot.net/v1/dotnet-install.sh \ + && chmod +x /tmp/dotnet-install.sh \ + && /tmp/dotnet-install.sh --runtime dotnet --channel 10.0 --install-dir /usr/share/dotnet \ + && ln -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet \ + && rm -f /tmp/dotnet-install.sh \ + && rm -rf /var/lib/apt/lists/* + +ENV DOTNET_ROOT=/usr/share/dotnet + +COPY . user_agent/ +WORKDIR /app/user_agent + +RUN if [ -f requirements.txt ]; then \ + pip install -r requirements.txt; \ + else \ + echo "No requirements.txt found"; \ + fi + +EXPOSE 8088 + +CMD ["python", "main.py"] diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md new file mode 100644 index 00000000000..3aefa57cc9c --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md @@ -0,0 +1,152 @@ +# What this sample demonstrates + +A realistic **multi-turn** [Agent Framework](https://github.com/microsoft/agent-framework) **declarative workflow** — defined entirely in YAML — hosted using the **Responses protocol**. It shows how a declarative workflow that invokes multiple Foundry-hosted agents can run end-to-end on every user turn while reading the prior conversation through `Conversation.messages` (populated automatically by `Workflow.as_agent()`). + +> Read more about declarative workflows in the [Agent Framework documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/declarative/?pivots=programming-language-python) and about workflow-as-an-agent in the [Workflow as an Agent documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/as-agents?pivots=programming-language-python). + +> [!IMPORTANT] +> Deploy this sample as a **container** (not Code/ZIP). Its declarative workflow uses Power Fx, which needs the .NET runtime included in the `Dockerfile`. Choose **Container** in every deploy flow. + +## How It Works + +### The Workflow + +[`workflow.yaml`](workflow.yaml) describes a customer-support triage flow: + +1. `InvokeAzureAgent: TriageAgent` — looks at the full conversation so far and emits a structured `TriageResponse` (`Category`, `NeedsClarification`, `ClarificationQuestion`, `Reply`). +2. `ConditionGroup` routes on the triage decision: + - **NeedsClarification** → `SendActivity` asks one focused follow-up question and ends the turn. + - **Category = "Technical"** → `SendActivity` confirms the handoff, then `InvokeAzureAgent: TechSupportAgent` answers with `autoSend: true` so its reply streams directly to the caller. + - **Category = "Billing"** → same pattern, routed to `BillingAgent`. + - **else** → `SendActivity` returns the triage agent's `Reply` directly (good for greetings or general questions). + +Each user message re-runs the workflow from the trigger. Because `Workflow.as_agent()` populates `Conversation.messages` with the prior turns of the conversation, every `InvokeAzureAgent` call sees the full history — which is what makes the triage decision and the specialist follow-ups coherent across turns. + +### Agent Hosting + +[`main.py`](main.py) builds three `Agent` instances on top of a shared `FoundryChatClient` (one per workflow role), registers them with the `WorkflowFactory` so the YAML's `InvokeAzureAgent` actions can resolve them by name, loads the workflow, wraps it with `.as_agent(...)`, and hands the agent to `ResponsesHostServer`, which provisions a REST API endpoint compatible with the OpenAI Responses protocol. + +The triage agent is configured with `response_format=TriageResponse` (a Pydantic model) so the workflow can read its structured fields via `Local.Triage.*`. The specialist agents are plain text and use `autoSend: true` to deliver their reply straight to the caller. + +## Option 1: Azure Developer CLI (`azd`) + +### Prerequisites + +1. **Azure Developer CLI (`azd`)** — [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) +2. Install the AI agent extension: + ```bash + azd ext install microsoft.foundry + ``` +3. Authenticate: + ```bash + azd auth login + ``` + +### Initialize the agent project + +No cloning required. Create a new folder and initialize from the manifest: + +```bash +mkdir my-declarative-agent && cd my-declarative-agent + +azd ai agent init -m https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/agent.manifest.yaml +``` + +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. + +### Provision Azure resources (if needed) + +If you don't already have a Foundry project and model deployment: + +```bash +azd provision +``` + +### Run the agent locally + +```bash +azd ai agent run +``` + +The agent host will start on `http://localhost:8088`. + +### Invoke the local agent + +In a separate terminal, from the project directory: + +```bash +azd ai agent invoke --local "I have a problem" +``` + +A typical multi-turn session: + +```bash +azd ai agent invoke --local "I have a problem" +# → "Could you tell me a bit more about what's going on?" + +azd ai agent invoke --local "My laptop won't turn on" +# → "Connecting you with technical support..." +# → TechSupportAgent: "Let's start simple — is the charger LED on when plugged in?" + +azd ai agent invoke --local "Yes the LED is on" +# → TechSupportAgent: "Great. Try a hard reset: hold the power button for 30 seconds..." +``` + +Or for billing: + +```bash +azd ai agent invoke --local "I was double-charged this month" +# → "Connecting you with billing support..." +# → BillingAgent: "I'm sorry about that. Can you share the last 4 digits of the card on file?" +``` + +### Deploy to Foundry + +Once tested locally, deploy to Microsoft Foundry: + +```bash +azd deploy +``` + +For the full deployment guide, see [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent). + +### Invoke the deployed agent + +```bash +azd ai agent invoke "I have a problem" +``` + +## Option 2: VS Code (Foundry Toolkit) + +### Prerequisites + +1. **VS Code** with the **[Foundry Toolkit](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.azure-ai-foundry)** extension installed. +2. Sign in to Azure in VS Code. + +### Create the project + +1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Create Hosted Agent**. +2. Select this sample from the gallery. The extension scaffolds the project into a new workspace and generates `agent.yaml`, `.env`, and `.vscode/tasks.json` + `launch.json` automatically. +3. Complete the **Foundry Project Setup** to pick the subscription and Foundry project (or create a new one). + +### Run and debug the agent + +Press **F5** to start the agent in debug mode. The agent host will start on `http://localhost:8088`. + +### Test with Agent Inspector + +1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Open Agent Inspector**. +2. The Inspector connects to the running agent. Send messages to chat and view streamed responses. + +### Deploy to Foundry + +1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Deploy Hosted Agent**. The extension opens a **Deploy Hosted Agent** wizard and reads `agent.yaml` to auto-populate settings. +2. If prompted, complete **Foundry Project Setup** to select subscription and project. +3. On the **Basics** tab, choose **Container** as the deployment method (this sample requires it — see the note above) and confirm the agent name. +4. On **Review + Deploy**, confirm runtime details, pick **CPU and Memory** size, and click **Deploy**. +5. After deployment, invoke the agent in the Agent Playground and stream live logs from the **Logs** tab. + +## Next steps + +- [Quickstart: Create a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent) — end-to-end walkthrough using `azd` +- [Manage hosted agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/manage-hosted-agent) — monitor and manage deployed agents diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.manifest.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.manifest.yaml new file mode 100644 index 00000000000..584120c871b --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.manifest.yaml @@ -0,0 +1,25 @@ +name: agent-framework-declarative-customer-support-responses +description: > + A multi-turn Agent Framework declarative (YAML-defined) customer-support + triage workflow hosted by Foundry. +metadata: + tags: + - Agent Framework + - AI Agent Hosting + - Azure AI AgentServer + - Responses Protocol + - Declarative Workflow + - Multi-turn +template: + name: agent-framework-declarative-customer-support-responses + kind: hosted + protocols: + - protocol: responses + version: 2.0.0 + environment_variables: + - name: AZURE_AI_MODEL_DEPLOYMENT_NAME + value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}" +resources: + - kind: model + id: gpt-5.4-mini + name: AZURE_AI_MODEL_DEPLOYMENT_NAME diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.yaml new file mode 100644 index 00000000000..44ad3ede5b6 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.yaml @@ -0,0 +1,12 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml +kind: hosted +name: agent-framework-declarative-customer-support-responses +protocols: + - protocol: responses + version: 2.0.0 +resources: + cpu: '0.25' + memory: '0.5Gi' +environment_variables: + - name: AZURE_AI_MODEL_DEPLOYMENT_NAME + value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME} diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py new file mode 100644 index 00000000000..84f59a4515f --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py @@ -0,0 +1,143 @@ +# Copyright (c) Microsoft. All rights reserved. + +import os +from pathlib import Path +from typing import Literal + +from agent_framework import Agent +from agent_framework.foundry import FoundryChatClient +from agent_framework_declarative import WorkflowFactory +from agent_framework_foundry_hosting import ResponsesHostServer +from azure.identity import DefaultAzureCredential +from dotenv import load_dotenv +from pydantic import BaseModel, Field + +# Load environment variables from .env file +load_dotenv() + + +# --- Structured triage response -------------------------------------------------- + +class TriageResponse(BaseModel): + """Triage decision produced from the conversation so far.""" + + Category: Literal["Technical", "Billing", "General"] = Field( + description=( + "The best category for the user's request. " + "Use 'Technical' for hardware/software/network issues, " + "'Billing' for invoices/subscriptions/refunds, and " + "'General' for anything else (greetings, FAQs, small talk)." + ), + ) + NeedsClarification: bool = Field( + description=( + "True if you cannot confidently classify the request yet and " + "need to ask the user one focused follow-up question." + ), + ) + ClarificationQuestion: str = Field( + default="", + description=( + "A single, polite follow-up question to ask the user. " + "Required when NeedsClarification is true; otherwise empty." + ), + ) + Reply: str = Field( + default="", + description=( + "A natural-language reply to the user. " + "Used when Category is 'General'; otherwise may be left empty." + ), + ) + + +# --- Agent instructions ---------------------------------------------------------- + +TRIAGE_INSTRUCTIONS = """ +You are the front-line triage agent for a customer support workflow. + +You will see the full conversation so far. Decide whether to: +- Ask the user one focused follow-up question (set NeedsClarification = true), or +- Route the conversation to the right specialist by setting Category, or +- Answer directly for general/small-talk requests via Reply. + +Be efficient: do not ask a clarification if a category is already clear. +""".strip() + +TECH_SUPPORT_INSTRUCTIONS = """ +You are a senior technical support specialist. The conversation history shows +what the user has told you so far and which steps were already attempted. + +Provide one concrete next troubleshooting step at a time, then wait for the +user's response. Be concise and friendly. If the issue appears resolved, +congratulate the user and ask if there's anything else. +""".strip() + +BILLING_INSTRUCTIONS = """ +You are a customer billing specialist. The conversation history shows what +the user has asked. + +Help the user with invoice, subscription, refund, and payment-method +questions. If you need account details (e.g., last 4 of card, account email), +ask for them one at a time. Keep responses short and polite. +""".strip() + + +# --- Host setup ------------------------------------------------------------------ + +def main() -> None: + workflow_path = Path(__file__).parent / "workflow.yaml" + + client = FoundryChatClient( + project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + credential=DefaultAzureCredential(), + ) + + # The workflow's InvokeAzureAgent actions reference these agents by name. + triage_agent = Agent( + client=client, + name="TriageAgent", + instructions=TRIAGE_INSTRUCTIONS, + default_options={"response_format": TriageResponse, "store": False}, + ) + tech_support_agent = Agent( + client=client, + name="TechSupportAgent", + instructions=TECH_SUPPORT_INSTRUCTIONS, + default_options={"store": False}, + ) + billing_agent = Agent( + client=client, + name="BillingAgent", + instructions=BILLING_INSTRUCTIONS, + default_options={"store": False}, + ) + + factory = WorkflowFactory( + agents={ + "TriageAgent": triage_agent, + "TechSupportAgent": tech_support_agent, + "BillingAgent": billing_agent, + }, + ) + + workflow = factory.create_workflow_from_yaml_path(str(workflow_path)) + + # Wrap the declarative workflow as an AIAgent so it can be served behind + # the Responses protocol. Each user turn re-runs the workflow with the + # full conversation history available via Conversation.messages. + workflow_agent = workflow.as_agent( + name="declarative-customer-support", + description=( + "A multi-turn customer-support triage workflow that routes " + "between technical and billing specialists based on the " + "conversation history." + ), + ) + + ResponsesHostServer(workflow_agent).run() + + +if __name__ == "__main__": + main() diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt new file mode 100644 index 00000000000..a00f2173846 --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt @@ -0,0 +1,6 @@ +agent-framework-declarative +agent-framework-foundry +agent-framework-foundry-hosting>=1.0.0a260630 + +# debugpy enables local debugging of this agent with the Foundry Toolkit VS Code extension. +debugpy \ No newline at end of file diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/workflow.yaml b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/workflow.yaml new file mode 100644 index 00000000000..c4593d98d3a --- /dev/null +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/workflow.yaml @@ -0,0 +1,89 @@ +# A multi-turn customer-support triage workflow defined declaratively. +# +# Each user message re-runs the workflow with the full conversation history +# available to the invoked agents via Conversation.messages. The TriageAgent +# decides on every turn whether to ask a follow-up question, route to a +# technical specialist, route to a billing specialist, or close the ticket +# with a general response. + +kind: Workflow +trigger: + kind: OnConversationStart + id: customer_support_triage + actions: + + # Look at the full conversation so far and decide what to do. + # The agent's structured response drives the routing below. + # autoSend=false keeps the raw structured JSON out of the user-facing + # output stream — the ConditionGroup below is what produces the reply. + - kind: InvokeAzureAgent + id: triage + agent: + name: TriageAgent + output: + autoSend: false + responseObject: Local.Triage + + # Branch on the triage decision. + - kind: ConditionGroup + id: route + conditions: + + # Not enough information yet — ask the user a targeted follow-up. + - condition: =Local.Triage.NeedsClarification + id: ask_followup + actions: + - kind: SendActivity + id: send_followup + activity: + text: =Local.Triage.ClarificationQuestion + - kind: GotoAction + id: end_after_followup + actionId: all_done + + # Technical issue — hand off to the technical specialist. + # autoSend streams the agent's reply directly to the caller. + - condition: =Local.Triage.Category = "Technical" + id: route_technical + actions: + - kind: SendActivity + id: log_technical + activity: + text: "Connecting you with technical support..." + - kind: InvokeAzureAgent + id: tech_support + agent: + name: TechSupportAgent + output: + autoSend: true + - kind: GotoAction + id: end_after_technical + actionId: all_done + + # Billing issue — hand off to the billing specialist. + - condition: =Local.Triage.Category = "Billing" + id: route_billing + actions: + - kind: SendActivity + id: log_billing + activity: + text: "Connecting you with billing support..." + - kind: InvokeAzureAgent + id: billing_support + agent: + name: BillingAgent + output: + autoSend: true + - kind: GotoAction + id: end_after_billing + actionId: all_done + + # Default: a general question or chit-chat — send the triage reply. + elseActions: + - kind: SendActivity + id: send_general + activity: + text: =Local.Triage.Reply + + - kind: EndWorkflow + id: all_done From c637370cbde396683228b4770c637c63a532160e Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 2 Jul 2026 15:55:14 -0700 Subject: [PATCH 2/3] Address comments --- .../13_declarative_customer_support/README.md | 119 ++---------------- .../13_declarative_customer_support/main.py | 14 ++- .../requirements.txt | 1 + 3 files changed, 20 insertions(+), 114 deletions(-) diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md index 3aefa57cc9c..be3ff9a393b 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md @@ -4,9 +4,6 @@ A realistic **multi-turn** [Agent Framework](https://github.com/microsoft/agent- > Read more about declarative workflows in the [Agent Framework documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/declarative/?pivots=programming-language-python) and about workflow-as-an-agent in the [Workflow as an Agent documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/as-agents?pivots=programming-language-python). -> [!IMPORTANT] -> Deploy this sample as a **container** (not Code/ZIP). Its declarative workflow uses Power Fx, which needs the .NET runtime included in the `Dockerfile`. Choose **Container** in every deploy flow. - ## How It Works ### The Workflow @@ -28,71 +25,21 @@ Each user message re-runs the workflow from the trigger. Because `Workflow.as_ag The triage agent is configured with `response_format=TriageResponse` (a Pydantic model) so the workflow can read its structured fields via `Local.Triage.*`. The specialist agents are plain text and use `autoSend: true` to deliver their reply straight to the caller. -## Option 1: Azure Developer CLI (`azd`) - -### Prerequisites - -1. **Azure Developer CLI (`azd`)** — [Install azd](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd) -2. Install the AI agent extension: - ```bash - azd ext install microsoft.foundry - ``` -3. Authenticate: - ```bash - azd auth login - ``` - -### Initialize the agent project - -No cloning required. Create a new folder and initialize from the manifest: +## Running the Agent Host -```bash -mkdir my-declarative-agent && cd my-declarative-agent - -azd ai agent init -m https://github.com/microsoft-foundry/foundry-samples/blob/main/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/agent.manifest.yaml -``` +Follow the instructions in the [Running the Agent Host Locally](../../README.md#running-the-agent-host-locally) section of the README in the parent directory to run the agent host. -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. +## Interacting with the agent -### Provision Azure resources (if needed) - -If you don't already have a Foundry project and model deployment: - -```bash -azd provision -``` - -### Run the agent locally - -```bash -azd ai agent run -``` - -The agent host will start on `http://localhost:8088`. - -### Invoke the local agent - -In a separate terminal, from the project directory: - -```bash -azd ai agent invoke --local "I have a problem" -``` +> Depending on how you run the agent host, you can invoke the agent using `curl` (`Invoke-WebRequest` in PowerShell) or `azd`. Please refer to the [parent README](../../README.md) for more details. Use this README for sample queries you can send to the agent. -A typical multi-turn session: +Send a POST request to the server with a JSON body containing an `"input"` field to interact with the agent. For example: ```bash -azd ai agent invoke --local "I have a problem" -# → "Could you tell me a bit more about what's going on?" - -azd ai agent invoke --local "My laptop won't turn on" -# → "Connecting you with technical support..." -# → TechSupportAgent: "Let's start simple — is the charger LED on when plugged in?" - -azd ai agent invoke --local "Yes the LED is on" -# → TechSupportAgent: "Great. Try a hard reset: hold the power button for 30 seconds..." +curl -X POST http://localhost:8088/responses -H "Content-Type: application/json" -d '{"input": "I have a problem"}' ``` -Or for billing: +Invoke with `azd`: ```bash azd ai agent invoke --local "I was double-charged this month" @@ -100,53 +47,9 @@ azd ai agent invoke --local "I was double-charged this month" # → BillingAgent: "I'm sorry about that. Can you share the last 4 digits of the card on file?" ``` -### Deploy to Foundry - -Once tested locally, deploy to Microsoft Foundry: - -```bash -azd deploy -``` - -For the full deployment guide, see [Deploy a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent). - -### Invoke the deployed agent - -```bash -azd ai agent invoke "I have a problem" -``` - -## Option 2: VS Code (Foundry Toolkit) +## Deploying the Agent to Foundry -### Prerequisites +To host the agent on Foundry, follow the instructions in the [Deploying the Agent to Foundry](../../README.md#deploying-the-agent-to-foundry) section of the README in the parent directory. -1. **VS Code** with the **[Foundry Toolkit](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.azure-ai-foundry)** extension installed. -2. Sign in to Azure in VS Code. - -### Create the project - -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Create Hosted Agent**. -2. Select this sample from the gallery. The extension scaffolds the project into a new workspace and generates `agent.yaml`, `.env`, and `.vscode/tasks.json` + `launch.json` automatically. -3. Complete the **Foundry Project Setup** to pick the subscription and Foundry project (or create a new one). - -### Run and debug the agent - -Press **F5** to start the agent in debug mode. The agent host will start on `http://localhost:8088`. - -### Test with Agent Inspector - -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Open Agent Inspector**. -2. The Inspector connects to the running agent. Send messages to chat and view streamed responses. - -### Deploy to Foundry - -1. Open the Command Palette (`Ctrl+Shift+P`) and run **Foundry Toolkit: Deploy Hosted Agent**. The extension opens a **Deploy Hosted Agent** wizard and reads `agent.yaml` to auto-populate settings. -2. If prompted, complete **Foundry Project Setup** to select subscription and project. -3. On the **Basics** tab, choose **Container** as the deployment method (this sample requires it — see the note above) and confirm the agent name. -4. On **Review + Deploy**, confirm runtime details, pick **CPU and Memory** size, and click **Deploy**. -5. After deployment, invoke the agent in the Agent Playground and stream live logs from the **Logs** tab. - -## Next steps - -- [Quickstart: Create a hosted agent](https://learn.microsoft.com/en-us/azure/foundry/agents/quickstarts/quickstart-hosted-agent) — end-to-end walkthrough using `azd` -- [Manage hosted agents](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/manage-hosted-agent) — monitor and manage deployed agents +> [!IMPORTANT] +> Deploy this sample as a **container** (not Code/ZIP). Its declarative workflow uses Power Fx, which needs the .NET runtime included in the `Dockerfile`. Choose **Container** in every deploy flow. \ No newline at end of file diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py index 84f59a4515f..6c6eba1e2a6 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py @@ -2,12 +2,13 @@ import os from pathlib import Path -from typing import Literal +from typing import Any, Literal from agent_framework import Agent from agent_framework.foundry import FoundryChatClient from agent_framework_declarative import WorkflowFactory from agent_framework_foundry_hosting import ResponsesHostServer +from agent_framework_openai import OpenAIChatOptions from azure.identity import DefaultAzureCredential from dotenv import load_dotenv from pydantic import BaseModel, Field @@ -18,6 +19,7 @@ # --- Structured triage response -------------------------------------------------- + class TriageResponse(BaseModel): """Triage decision produced from the conversation so far.""" @@ -45,8 +47,7 @@ class TriageResponse(BaseModel): Reply: str = Field( default="", description=( - "A natural-language reply to the user. " - "Used when Category is 'General'; otherwise may be left empty." + "A natural-language reply to the user. Used when Category is 'General'; otherwise may be left empty." ), ) @@ -85,6 +86,7 @@ class TriageResponse(BaseModel): # --- Host setup ------------------------------------------------------------------ + def main() -> None: workflow_path = Path(__file__).parent / "workflow.yaml" @@ -99,19 +101,19 @@ def main() -> None: client=client, name="TriageAgent", instructions=TRIAGE_INSTRUCTIONS, - default_options={"response_format": TriageResponse, "store": False}, + default_options=OpenAIChatOptions[Any](response_format=TriageResponse, store=False), ) tech_support_agent = Agent( client=client, name="TechSupportAgent", instructions=TECH_SUPPORT_INSTRUCTIONS, - default_options={"store": False}, + default_options=OpenAIChatOptions(store=False), ) billing_agent = Agent( client=client, name="BillingAgent", instructions=BILLING_INSTRUCTIONS, - default_options={"store": False}, + default_options=OpenAIChatOptions(store=False), ) factory = WorkflowFactory( diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt index a00f2173846..c4220da654d 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt @@ -1,6 +1,7 @@ agent-framework-declarative agent-framework-foundry agent-framework-foundry-hosting>=1.0.0a260630 +agent-framework-openai # debugpy enables local debugging of this agent with the Foundry Toolkit VS Code extension. debugpy \ No newline at end of file From fe0cfb49513e61ba2e6b8731f0e8b17623a3d438 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Thu, 2 Jul 2026 16:10:53 -0700 Subject: [PATCH 3/3] Address comments --- .../Dockerfile | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile index 66d3b74ec65..82316e2210b 100644 --- a/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile +++ b/python/samples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile @@ -1,20 +1,22 @@ -FROM python:3.12-slim - -WORKDIR /app - # .NET 10 runtime is required by the `powerfx` package, which # agent-framework-declarative uses to evaluate `=...` expressions in the # workflow YAML (e.g. =Local.Triage.NeedsClarification). Without it, # ConditionGroup evaluation raises and the workflow produces no output. +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS dotnet + +FROM python:3.12-slim + +WORKDIR /app + +# libicu is required by the .NET runtime for globalization support. RUN apt-get update \ - && apt-get install -y --no-install-recommends wget ca-certificates libicu-dev \ - && wget -qO /tmp/dotnet-install.sh https://dot.net/v1/dotnet-install.sh \ - && chmod +x /tmp/dotnet-install.sh \ - && /tmp/dotnet-install.sh --runtime dotnet --channel 10.0 --install-dir /usr/share/dotnet \ - && ln -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet \ - && rm -f /tmp/dotnet-install.sh \ + && apt-get install -y --no-install-recommends ca-certificates libicu-dev \ && rm -rf /var/lib/apt/lists/* +# Copy the pinned .NET runtime from the official image. +COPY --from=dotnet /usr/share/dotnet /usr/share/dotnet +RUN ln -s /usr/share/dotnet/dotnet /usr/local/bin/dotnet + ENV DOTNET_ROOT=/usr/share/dotnet COPY . user_agent/