Documentation

Workflows

A workflow is several typed fields answered in one call, where later fields can use earlier answers, and run only when they apply. This page builds one for a support ticket, a step at a time.

Start with the context

The context is the input the workflow works on: here, the customer's ticket. Pass it once, as context. Every field reads it, at every step, so you never list it in depends_on; the fields below only name the other fields they need.

The context
ticket = "I was charged twice for my Pro upgrade ($49 each). My renewal is tomorrow. Please fix this today."

1. Ask independent questions in parallel

Fields run in parallel by default. Each reads the context on its own, without seeing the other answers.

one callticketcontextcategoryenumurgentbooleanin parallel
Python
response = client.generate(context=ticket, questions={
    "category": {
        "type": "string",
        "enum": ["billing", "technical", "account"],
        "instructions": "Which team should handle this ticket?",
    },
    "urgent": {"type": "boolean", "instructions": "Does it need an answer today?"},
})

2. Ask questions that depend on earlier answers

A field with depends_on runs after the fields it names, and sees their answers along with the context. handling_advice waits for category and urgent, so its advice follows from them.

one call · every field reads the contextticketcontextcategoryenumurgentbooleanhandling_advicestringlayer 1: in parallellayer 2: after its inputs
Python
response = client.generate(context=ticket, questions={
    "category": {
        "type": "string",
        "enum": ["billing", "technical", "account"],
        "instructions": "Which team should handle this ticket?",
    },
    "urgent": {"type": "boolean", "instructions": "Does it need an answer today?"},
    "handling_advice": {
        "type": "string",
        "instructions": "Suggest next steps for the support agent. Do not claim actions were already taken.",
        "depends_on": ["category", "urgent"],
    },
})

TypeLLM works out the order from depends_on: fields with no inputs pending run together, layer by layer. Results come back in the order you declared them.

  • A field without depends_on, or with depends_on=[], reads only the context.
  • A field can depend on several fields, which may be declared later in the input.
  • Unknown names, duplicate dependencies, self-dependencies and cycles are rejected before inference.

3. Ask a question only when a condition is met

A refund amount means something for a billing issue, not a bug report. A field with when runs only if earlier answers pass its condition. Otherwise it is skipped: it has no key in result, and response.skipped lists it.

Python
response = client.generate(context=ticket, questions={
    "category": {
        "type": "string",
        "enum": ["billing", "technical", "account"],
        "instructions": "Which team should handle this ticket?",
    },
    "urgent": {"type": "boolean", "instructions": "Does it need an answer today?"},
    "handling_advice": {
        "type": "string",
        "instructions": "Suggest next steps for the support agent. Do not claim actions were already taken.",
        "depends_on": ["category", "urgent"],
    },
    "refund_amount": {
        "type": "number",
        "instructions": "How much should be refunded, in dollars?",
        "when": {"category": "billing"},
    },
})

For the double charge, category is billing, so refund_amount runs:

one call · every field reads the contextticketcontextwhen category = billingcategoryenumurgentbooleanhandling_advicestringrefund_amountnumberlayer 1: in parallellayer 2: after its inputs

For a broken export button, category is technical, so refund_amount is skipped:

Another ticket
ticket = "Since this morning's update, the export button does nothing. I need the report for a meeting on Friday."
one call · every field reads the contextticketcontextwhen category = billingcategoryenumurgentbooleanhandling_advicestringrefund_amountskippedlayer 1: in parallellayer 2: after its inputs

The fields when names are dependencies, whether or not depends_on lists them, so a conditional field also sees their answers.

Conditions

when maps other fields to a condition on their answers:

"when": {"category": "bug"}                            # equals
"when": {"category": ["bug", "incident"]}              # one of; also {"in": [...]}
"when": {"category": {"not_in": ["feature_request"]}}  # none of
"when": {"quantity": {"ne": 0}}                        # not equal
"when": {"amount": {"gte": 1000}}                      # gt, gte, lt and lte compare numbers
"when": {"score": {"gt": 0, "lte": 60}}                # several operators: all must pass
"when": {"tip": {"ne": None}}                          # not null
  • With several fields, every condition must pass.
  • Values must be answers the field can give: an enum value, True or False, a number, or None for a nullable field. Text fields can only be compared with None.
  • gt, gte, lt and lte work on number fields only, and a None answer fails them.
  • Fields that depend on a skipped field are skipped too, even when their other dependencies ran.
  • Conditions run in code on the typed answers, so they add no requests. A skipped field costs only its definition in the call's input tokens.
  • An unknown operator, a value the field cannot give, or a condition on an unknown field is rejected before inference.

All in one call

Each example above is one request. TypeLLM runs the layers on the server, checks the conditions between them, and returns every answer together, typed. Your code does not parse one answer to build the next request, and the ticket is read once.