Prompt Flow is a development framework in Azure AI Foundry for building, testing, and deploying LLM-powered workflows. It provides a visual graph-based interface for connecting prompts, tools, and code into production-ready pipelines.
Official Documentation: Prompt Flow in Azure AI Foundry
Prompt Flow lets you build Directed Acyclic Graphs (DAGs) of steps — each step is a node that can be a prompt template, a Python function, or a tool call. The output of one node feeds into the next.
┌───────────┐ ┌──────────────┐ ┌───────────┐ ┌────────────┐
│ Input │───▶│ Classify │───▶│ Route │───▶│ Generate │
│ (user │ │ (LLM node) │ │ (Python │ │ Response │
│ query) │ │ │ │ node) │ │ (LLM node)│
└───────────┘ └──────────────┘ └───────────┘ └────────────┘
| Benefit | Description |
|---|---|
| Visual authoring | Build and debug flows with a graph UI |
| Modularity | Break complex logic into testable nodes |
| Evaluation | Built-in eval flows for quality, groundedness, relevance |
| Versioning | Track flow versions alongside code |
| Deployment | Deploy flows as managed endpoints with one click |
| Tracing | Built-in tracing for debugging and monitoring |
General-purpose flow for any LLM task. Use for:
Optimized for conversational applications. Includes:
chat_history and chat_input inputsSpecial flow type for measuring the quality of other flows. Use for:
# Install the prompt flow SDK
# pip install promptflow promptflow-tools
from promptflow.core import tool
@tool
def classify_intent(query: str) -> str:
"""Classify user intent from a query string."""
query_lower = query.lower()
if any(word in query_lower for word in ["price", "cost", "discount", "buy"]):
return "sales"
elif any(word in query_lower for word in ["broken", "error", "fix", "help"]):
return "support"
elif any(word in query_lower for word in ["return", "refund", "exchange"]):
return "returns"
else:
return "general"
Create a flow.dag.yaml file:
$schema: https://azuremlschemas.azureedge.net/promptflow/latest/Flow.schema.json
inputs:
user_query:
type: string
default: "How much does the Pro plan cost?"
outputs:
response:
type: string
reference: ${generate_response.output}
nodes:
- name: classify_intent
type: python
source:
type: code
path: classify_intent.py
inputs:
query: ${inputs.user_query}
- name: generate_response
type: llm
source:
type: code
path: generate_response.jinja2
inputs:
deployment_name: gpt-4o-prod
intent: ${classify_intent.output}
query: ${inputs.user_query}
connection: my_aoai_connection
api: chat
Calls a language model with a prompt template (Jinja2 format):
generate_response.jinja2:
system:
You are a helpful assistant for Contoso Electronics.
The user's intent has been classified as:
Respond appropriately based on the intent:
- sales: Provide pricing information and upsell relevant products.
- support: Ask clarifying questions and provide troubleshooting steps.
- returns: Explain the return policy and guide the process.
- general: Answer the question helpfully.
user:
Custom logic using Python functions:
from promptflow.core import tool
@tool
def route_by_intent(intent: str, query: str) -> dict:
"""Route the query based on classified intent."""
routing_config = {
"sales": {
"temperature": 0.7,
"system_context": "Focus on product benefits and pricing.",
"max_tokens": 500
},
"support": {
"temperature": 0.3,
"system_context": "Be precise and technical. Ask diagnostic questions.",
"max_tokens": 800
},
"returns": {
"temperature": 0.2,
"system_context": "Follow the return policy exactly. Be empathetic.",
"max_tokens": 400
},
"general": {
"temperature": 0.5,
"system_context": "Be helpful and concise.",
"max_tokens": 300
}
}
config = routing_config.get(intent, routing_config["general"])
config["query"] = query
config["intent"] = intent
return config
A simplified node that wraps a prompt template without full LLM configuration — useful for building prompt strings that feed into other nodes.
from promptflow.client import PFClient
pf = PFClient()
# Run flow against a test dataset
run = pf.run(
flow="./my_flow",
data="./test_data.jsonl", # JSONL file with test inputs
column_mapping={
"user_query": "${data.question}"
},
stream=True
)
# Get results
details = pf.get_details(run)
print(details.head(10))
test_data.jsonl:
{"question": "How much does the Pro plan cost?"}
{"question": "My laptop screen is flickering."}
{"question": "I want to return my headphones."}
{"question": "What products do you sell?"}
{"question": "Can I get a discount for buying in bulk?"}
import pytest
from classify_intent import classify_intent
def test_classify_sales():
assert classify_intent("How much does it cost?") == "sales"
def test_classify_support():
assert classify_intent("My device is broken") == "support"
def test_classify_returns():
assert classify_intent("I want to return this") == "returns"
def test_classify_general():
assert classify_intent("What are your business hours?") == "general"
Azure AI Foundry provides built-in evaluation flows for common quality metrics:
| Metric | What It Measures |
|---|---|
| Groundedness | Is the response supported by the provided context? |
| Relevance | Does the response address the user’s question? |
| Coherence | Is the response logically consistent and well-structured? |
| Fluency | Is the response grammatically correct and natural? |
| Similarity | How similar is the response to a ground truth answer? |
| F1 Score | Token-level overlap with ground truth |
from promptflow.client import PFClient
pf = PFClient()
# Run the main flow first
main_run = pf.run(
flow="./my_chat_flow",
data="./eval_dataset.jsonl",
column_mapping={"user_query": "${data.question}"}
)
# Run evaluation against the main flow's outputs
eval_run = pf.run(
flow="./evaluation_flow",
data="./eval_dataset.jsonl",
run=main_run, # Evaluate against this run's outputs
column_mapping={
"question": "${data.question}",
"answer": "${run.outputs.response}",
"context": "${data.context}",
"ground_truth": "${data.expected_answer}"
}
)
# View evaluation results
metrics = pf.get_metrics(eval_run)
print(f"Groundedness: {metrics['groundedness']:.2f}")
print(f"Relevance: {metrics['relevance']:.2f}")
print(f"Coherence: {metrics['coherence']:.2f}")
from azure.ai.ml import MLClient
from azure.ai.ml.entities import (
ManagedOnlineEndpoint,
ManagedOnlineDeployment,
Model,
Environment,
)
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
ml_client = MLClient(credential, "your-sub-id", "your-rg", "your-workspace")
# Create endpoint
endpoint = ManagedOnlineEndpoint(
name="my-flow-endpoint",
description="Customer support chat flow",
auth_mode="key"
)
ml_client.online_endpoints.begin_create_or_update(endpoint).result()
# Create deployment
deployment = ManagedOnlineDeployment(
name="v1",
endpoint_name="my-flow-endpoint",
model=Model(path="./my_flow"),
environment=Environment(
image="mcr.microsoft.com/azureml/promptflow/promptflow-runtime:latest",
conda_file="./my_flow/requirements.txt"
),
instance_type="Standard_DS3_v2",
instance_count=1
)
ml_client.online_deployments.begin_create_or_update(deployment).result()
import requests
endpoint_url = "https://my-flow-endpoint.eastus2.inference.ml.azure.com/score"
api_key = "your-endpoint-key"
response = requests.post(
endpoint_url,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
},
json={"user_query": "How much does the Pro plan cost?"}
)
print(response.json())
my_flow/
├── flow.dag.yaml # Flow definition (nodes, connections)
├── classify_intent.py # Python node
├── route_by_intent.py # Python node
├── generate_response.jinja2 # LLM prompt template
├── requirements.txt # Python dependencies
├── .env # Connection strings (gitignored)
├── data/
│ ├── test_data.jsonl # Test inputs
│ └── eval_data.jsonl # Evaluation dataset
└── tests/
└── test_nodes.py # Unit tests for Python nodes
# Connections store credentials for LLM endpoints, search services, etc.
# Create connections in the portal or via CLI:
# az ml connection create --file connection.yaml --resource-group your-rg --workspace-name your-ws
# connection.yaml example:
name: my_aoai_connection
type: azure_open_ai
api_base: https://your-resource.openai.azure.com/
api_type: azure
api_version: 2024-12-01-preview
💡 Start simple. Build a 2-node flow (input → LLM → output) first. Add complexity (routing, evaluation, error handling) only after the basic path works.
💡 Test each node independently before connecting them. A failing flow is harder to debug than a failing function.
💡 Use evaluation flows in CI/CD. Run evaluations automatically on PR merges to catch quality regressions before they hit production.
💡 Version your flows in Git.
flow.dag.yamland all node files are plain text — treat them like application code.
💡 Use the tracing feature to understand latency bottlenecks. The visual trace view shows exactly how long each node takes and what data flows between them.
💡 Prompt Flow vs direct SDK calls: Use Prompt Flow when you need multi-step orchestration, built-in evaluation, or visual debugging. For simple single-call scenarios, direct SDK calls are simpler and faster.
Next: Knowledge Check