AI Knowledge & Logic

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

  1. A customer message triggers a workflow
  2. The condition provider runs — calling your APIs to evaluate the current state
  3. It returns a set of boolean conditions (e.g., isShipped: true, isRefunded: false)
  4. The system checks each variant top to bottom — the first variant whose required conditions all match is selected
  5. 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.

PositionVariantConditionsPurpose
0Not foundorderNotFoundError recovery
1Already refundedisRefundedInform customer, no further action
2Already shippedisShippedExplain shipping status, offer alternatives
LastDefault(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:

FieldDescription
conditionsBoolean flags the system uses to match against variant requirements
dataAdditional 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 workflows
  • email — for customer lookup
  • subscriptionId — 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:

#VariantConditionsWhat the AI does
0Not foundsubscriptionNotFoundAsk customer to double-check their email
1Lifetime planisLifetimeExplain lifetime plans can't be cancelled in chat → transferConversation
2Already refundedisRefundedInform customer their subscription was already refunded
3Default(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:

#VariantConditionsWhat the AI does
0Not foundorderNotFoundAsk customer to verify their order ID
1Already refundedisRefundedInform the order was already refunded
2DeliveredisDeliveredRedirect to the returns workflow
3ShippedisShippedExplain the order is in transit, offer return on arrival
4Too lateisOlderThan5DaysExplain the order may have shipped, suggest checking status
5Default(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.

  1. Open your condition provider in the dashboard
  2. Fill in test values for each argument (e.g., an order ID or email)
  3. 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
  4. Click Run Test
  5. The dashboard executes your code and shows the returned conditions and data

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 data response 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.

On this page