Condition Providers
Dynamically route workflows based on external data. Condition providers evaluate customer state and select the right workflow variant.
Most workflows need only one set of instructions — the AI follows the same steps every time. But when the same situation requires different handling depending on external state, you need a condition provider.
A condition provider is a Python script that runs when a workflow is triggered. It evaluates external data — order status, subscription state, payment method — and returns boolean conditions that the system uses to select the right workflow variant.
When you need a condition provider
You don't need one if:
- Your workflow always follows the same steps
- Your branching is simple enough to handle with if/else in the bot instructions
You need one when:
- The same trigger (e.g., "cancel my order") requires fundamentally different responses based on external state (shipped vs. not shipped vs. already refunded)
- You want the system to pre-fetch data before the AI starts responding
- You need to route to completely different instruction sets — not just small if/else branches
How it works
- A customer message triggers a workflow
- The condition provider runs — calling your APIs to evaluate the current state
- It returns a set of boolean conditions (e.g.,
isShipped: true,isRefunded: false) - The system checks each variant top to bottom — the first variant whose required conditions all match is selected
- The AI follows that variant's instructions
Example flow:
Customer: "I want to cancel my order"
→ Condition provider runs with orderId
Returns: { orderNotFound: false, isRefunded: false, isShipped: true }
→ Variant 0: requires [orderNotFound] → no match
→ Variant 1: requires [isRefunded] → no match
→ Variant 2: requires [isShipped] → match ✓
→ AI follows variant 2: "Your order has already shipped..."Setting up variants
Each variant in a workflow has:
- Title — a descriptive name (e.g., "Already shipped", "Default")
- Required conditions — boolean conditions that must all be true for this variant to be selected
- Actions — which actions this variant can use
- Bot instructions — what the AI should do when this variant is selected
Ordering matters. Variants are evaluated top to bottom. The system picks the first match. Put specific cases first and the default last.
| Position | Variant | Conditions | Purpose |
|---|---|---|---|
| 0 | Not found | orderNotFound | Error recovery |
| 1 | Already refunded | isRefunded | Inform customer, no further action |
| 2 | Already shipped | isShipped | Explain shipping status, offer alternatives |
| Last | Default | (none) | Fallback — always matches |
The last variant should have no required conditions. This ensures every situation is handled, even if none of the specific conditions match.
Writing a condition provider
A condition provider implements an evaluate_conditions function. It receives context (including workflow arguments and conversation data), calls your APIs, and returns conditions and optional data.
import requests
def evaluate_conditions(context):
order_id = context["args"]["orderId"]
# Fetch order from your system
response = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
timeout=30,
)
if response.status_code != 200:
return {
"conditions": {"orderNotFound": True},
"data": {},
}
order = response.json()
return {
"conditions": {
"orderNotFound": False,
"isRefunded": order.get("refundedAt") is not None,
"isShipped": order.get("shippedAt") is not None,
"isDelivered": order.get("deliveredAt") is not None,
},
"data": {
"orderStatus": order.get("status"),
"trackingNumber": order.get("trackingNumber"),
},
}The function returns two things:
| Field | Description |
|---|---|
conditions | Boolean flags the system uses to match against variant requirements |
data | Additional data passed to the selected variant — the AI can reference this in its response |
Arguments
Condition providers receive arguments defined in the workflow configuration. The bot collects these from the customer before the condition provider runs.
Common argument patterns:
orderId— for order-related workflowsemail— for customer lookupsubscriptionId— for subscription workflows
Example: Subscription cancellation
A language learning app handles cancellation requests differently based on subscription state.
Condition provider:
import requests
def evaluate_conditions(context):
email = context["args"]["email"]
sub_id = context["args"]["subscriptionId"]
response = requests.get(
"https://api.example.com/subscriptions",
params={"email": email, "id": sub_id},
timeout=30,
)
if response.status_code != 200:
return {"conditions": {"subscriptionNotFound": True}, "data": {}}
sub = response.json()
return {
"conditions": {
"subscriptionNotFound": False,
"isRefunded": sub.get("refundedAt") is not None,
"isLifetime": sub.get("type") == "lifetime",
},
"data": {
"planName": sub.get("planName"),
"startDate": sub.get("startDate"),
},
}Variants:
| # | Variant | Conditions | What the AI does |
|---|---|---|---|
| 0 | Not found | subscriptionNotFound | Ask customer to double-check their email |
| 1 | Lifetime plan | isLifetime | Explain lifetime plans can't be cancelled in chat → transferConversation |
| 2 | Already refunded | isRefunded | Inform customer their subscription was already refunded |
| 3 | Default | (none) | Confirm intent → call cancelSubscription → confirm to customer |
Example: Order cancellation with shipping check
An e-commerce store needs to handle cancellation requests based on fulfillment state.
Condition provider:
import requests
from datetime import datetime, timedelta
def evaluate_conditions(context):
order_id = context["args"]["orderId"]
response = requests.get(
"https://api.example.com/orders",
params={"id": order_id},
timeout=30,
)
if response.status_code != 200:
return {"conditions": {"orderNotFound": True}, "data": {}}
order = response.json()
created = datetime.fromisoformat(order["createdAt"])
is_old = (datetime.now() - created) > timedelta(days=5)
return {
"conditions": {
"orderNotFound": False,
"isRefunded": order.get("refundedAt") is not None,
"isShipped": order.get("shippedAt") is not None,
"isDelivered": order.get("deliveredAt") is not None,
"isOlderThan5Days": is_old,
},
"data": {
"orderStatus": order.get("status"),
"items": order.get("items", []),
"shippedAt": order.get("shippedAt"),
},
}Variants:
| # | Variant | Conditions | What the AI does |
|---|---|---|---|
| 0 | Not found | orderNotFound | Ask customer to verify their order ID |
| 1 | Already refunded | isRefunded | Inform the order was already refunded |
| 2 | Delivered | isDelivered | Redirect to the returns workflow |
| 3 | Shipped | isShipped | Explain the order is in transit, offer return on arrival |
| 4 | Too late | isOlderThan5Days | Explain the order may have shipped, suggest checking status |
| 5 | Default | (none) | Confirm intent → call cancelOrder → confirm cancellation |
Advanced patterns
Passing data to variants
The data field in the condition provider response is available to the AI in the selected variant. Use this to pre-fetch information the AI will need — order details, tracking info, account status — so it can respond immediately without calling separate actions.
return {
"conditions": {"isShipped": True},
"data": {
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"estimatedDelivery": "2025-04-15",
},
}The AI in the "Already shipped" variant can then tell the customer: "Your order is on its way via UPS. Tracking number: 1Z999AA10123456784, estimated delivery: April 15."
Using LLM classification
Condition providers can use llm_classify_binary() and llm_classify_category() to make AI-powered routing decisions. This is useful when routing depends on conversation tone or intent rather than structured data.
def evaluate_conditions(context):
messages = context["conversation"]["messages"]
is_threat = llm_classify_binary(
"Has the customer explicitly threatened a chargeback, "
"bank dispute, or legal action? Frustration or anger "
"alone does not count — look for explicit financial threats.",
messages,
fallback=False,
)
return {
"conditions": {"isExplicitThreat": is_threat},
"data": {},
}For category-based routing:
def evaluate_conditions(context):
messages = context["conversation"]["messages"]
category = llm_classify_category(
"Based on the customer's description, classify the product issue.",
messages,
["medical_concern", "physical_defect", "not_working", "general_dissatisfaction"],
)
return {
"conditions": {
"isMedicalConcern": category == "medical_concern",
"isPhysicalDefect": category == "physical_defect",
"isNotWorking": category == "not_working",
},
"data": {"issueCategory": category},
}A/B testing
Use get_ab_test_variant() to consistently route customers to different variants for experimentation:
def evaluate_conditions(context):
variant = get_ab_test_variant(context, "retention-flow", ["a", "b"])
return {
"conditions": {
"isVariantA": variant == "a",
"isVariantB": variant == "b",
},
"data": {},
}Each conversation is consistently assigned the same variant, so customers don't see different behavior if they send multiple messages.
Testing
Condition providers can be tested directly from the dashboard before connecting them to a workflow.
- Open your condition provider in the dashboard
- Fill in test values for each argument (e.g., an order ID or email)
- Optionally enter a Conversation ID — this populates
context["conversation"]with real conversation data. Use any conversation from your dashboard — copy the ID or public ID from the conversation detail view - Click Run Test
- The dashboard executes your code and shows the returned
conditionsanddata
Without a conversation ID, context["conversation"] is None. This is fine for providers that only use context["args"] to call external APIs, but if your provider reads conversation messages (e.g., for LLM classification), provide a conversation ID to test with real data.
Tip: Test your condition provider with different argument values to verify each condition path. For example, test with an order ID that's been refunded, one that's been shipped, and one that doesn't exist — and check that the right conditions come back each time.
Best practices
- Keep conditions boolean. Each condition should be a simple true/false. Put complex logic in the provider code, not in condition names.
- Order variants by specificity. Most specific first (error states, edge cases), most general last (default fallback).
- Always include a fallback. The last variant should have no required conditions so it catches anything unexpected.
- Pre-fetch useful data. If the AI will need order or subscription details, include them in the
dataresponse to avoid extra action calls. - Name conditions clearly.
isRefunded,orderNotFound,isShipped— the name should make the condition obvious when reading the variant list. - Handle API failures. If your API is down, return an appropriate condition (like
orderNotFound) rather than crashing — the AI can still give the customer a helpful response.