-
Notifications
You must be signed in to change notification settings - Fork 2k
Python: Add FHA declarative workflow sample #6897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
.../04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.dockerignore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .venv | ||
| __pycache__ | ||
| *.pyc | ||
| *.pyo | ||
| *.pyd | ||
| .Python | ||
| .env |
2 changes: 2 additions & 0 deletions
2
...s/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/.env.example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| FOUNDRY_PROJECT_ENDPOINT="..." | ||
| AZURE_AI_MODEL_DEPLOYMENT_NAME="..." |
31 changes: 31 additions & 0 deletions
31
...les/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/Dockerfile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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"] | ||
152 changes: 152 additions & 0 deletions
152
...sting/foundry-hosted-agents/responses/13_declarative_customer_support/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. | ||
|
TaoChenOSU marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## 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`) | ||
|
TaoChenOSU marked this conversation as resolved.
Outdated
|
||
|
|
||
| ### 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 | ||
25 changes: 25 additions & 0 deletions
25
...sting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.manifest.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
12 changes: 12 additions & 0 deletions
12
...les/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/agent.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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} |
143 changes: 143 additions & 0 deletions
143
...amples/04-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/main.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
6 changes: 6 additions & 0 deletions
6
...-hosting/foundry-hosted-agents/responses/13_declarative_customer_support/requirements.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.