Worked Examples & Demos
This content is not available in your language yet.
This page walks through three complete workflows, each combining several node types into a realistic, runnable-looking graph. Each example includes the full nodes/edges JSON you’d pass to updateWorkflow, exactly as described in Workflows Overview.
For the individual node reference pages, see LLM, Condition, Router, Response, Webhook, Tool, Action, and End Session. For templating rules and LLM-reliability patterns applied throughout these examples, see Workflow Best Practices.
Example 1: FAQ / knowledge-base assistant
Section titled “Example 1: FAQ / knowledge-base assistant”A generic support assistant that classifies each message as either a knowledge-base question or a general greeting/small-talk, and answers knowledge-base questions with a RAG-grounded LLM node.
Flow: start → classify intent (llm) → dispatch (router) → either answer from the knowledge base (llm with knowledgeBaseId) or give a generic greeting (response) → reply (response) → end_session.
{ "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "classify_intent", "type": "llm", "label": "Classify Intent", "config": { "outputVar": "intent", "promptTemplate": "Classify this message: \"{{userMessage}}\". Respond with ONLY one word: question, or greeting." }, "position": { "x": 0, "y": 100 } }, { "id": "dispatch", "type": "router", "label": "Intent Dispatcher", "config": { "variable": "intent", "defaultTarget": "respond_greeting" }, "position": { "x": 0, "y": 180 } }, { "id": "answer_from_kb", "type": "llm", "label": "Answer From Knowledge Base", "config": { "promptTemplate": "You are a helpful support assistant. Answer the user's question using the provided reference material where relevant: {{userMessage}}", "knowledgeBaseId": 45, "outputVar": "kb_answer" }, "position": { "x": 250, "y": 250 } }, { "id": "respond_answer", "type": "response", "label": "Respond With Answer", "config": { "messageTemplate": "{{kb_answer}}", "mood": "helpful" }, "position": { "x": 350, "y": 250 } }, { "id": "respond_greeting", "type": "response", "label": "Respond to Greeting", "config": { "messageTemplate": "Hi there! I'm happy to help — what would you like to know?", "mood": "neutral" }, "position": { "x": 350, "y": 50 } }, { "id": "end", "type": "end_session", "label": "End", "config": { "runEvaluation": true }, "position": { "x": 450, "y": 150 } } ], "edges": [ { "id": "e1", "source": "start", "target": "classify_intent" }, { "id": "e2", "source": "classify_intent", "target": "dispatch" }, { "id": "e3", "source": "dispatch", "target": "answer_from_kb", "label": "question" }, { "id": "e4", "source": "dispatch", "target": "respond_greeting", "label": "greeting" }, { "id": "e5", "source": "answer_from_kb", "target": "respond_answer" }, { "id": "e6", "source": "respond_answer", "target": "end" }, { "id": "e7", "source": "respond_greeting", "target": "end" } ]}Why it’s built this way:
- The classifier’s prompt lists both possible outputs (
question,greeting) explicitly — a router can only ever route to intents the classifier’s own prompt knows to produce. answer_from_kbusesknowledgeBaseIdrather than a separate retrieval step — this is the standard way to ground an LLM node’s response in your own documents, as covered in the knowledge base guide referenced from MCP Connectors and the workflow overview.respond_answer’smessageTemplateis a thin pass-through ofkb_answer— no second LLM paraphrase step, per the Best Practices guidance to avoid unnecessary extra hops.
Example 2: Appointment-booking assistant
Section titled “Example 2: Appointment-booking assistant”A scheduling assistant that extracts a requested date from free text, checks availability via a tool node calling a generic “check availability” MCP tool, and confirms before ending the session.
Flow: start → extract date (llm, pipeline-step framing) → check availability (tool) → respond with the result (response) → confirm and end (end_session with requireConfirmation: true).
{ "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "extract_date", "type": "llm", "label": "Extract Requested Date", "config": { "outputVar": "requestedDate", "promptTemplate": "You are a data extraction step in an automated pipeline, not the assistant talking to the user — you must NOT generate a reply, confirmation, explanation, or any commentary. Output ONLY the requested value, nothing else. Extract ONLY the appointment date from this message: \"{{userMessage}}\". Respond with ONLY the date in YYYY-MM-DD format, nothing else." }, "position": { "x": 0, "y": 100 } }, { "id": "check_availability", "type": "tool", "label": "Check Appointment Availability", "config": { "connectorId": 12, "toolName": "check_availability", "argsTemplate": "{\"date\": \"{{requestedDate}}\", \"durationMinutes\": 30}", "outputVar": "availability_result", "timeoutMs": 8000 }, "position": { "x": 0, "y": 180 } }, { "id": "respond_availability", "type": "response", "label": "Respond With Availability", "config": { "messageTemplate": "Here's what I found for {{requestedDate}}: {{availability_result}}", "mood": "helpful" }, "position": { "x": 0, "y": 260 } }, { "id": "confirm_end", "type": "end_session", "label": "Confirm and End", "config": { "requireConfirmation": true, "confirmationPromptTemplate": "Is there anything else I can help you with, or should I close this chat?", "runEvaluation": true }, "position": { "x": 0, "y": 340 } } ], "edges": [ { "id": "e1", "source": "start", "target": "extract_date" }, { "id": "e2", "source": "extract_date", "target": "check_availability" }, { "id": "e3", "source": "check_availability", "target": "respond_availability" }, { "id": "e4", "source": "respond_availability", "target": "confirm_end" } ]}Why it’s built this way:
extract_date’s prompt explicitly frames the model as a pipeline step, not the assistant — this is a deliberate, load-bearing choice, not boilerplate. Without it, a fast model asked only to extract a date can drift into generating a full “Your appointment is booked!” reply instead of just the date, because the surrounding conversation is thematically about booking. See Workflow Best Practices for the full explanation of this failure mode.- Extraction and the tool call are kept in separate nodes, each with one job — easier to debug independently if the wrong thing comes out of either step.
confirm_endusesrequireConfirmation: truerather than ending immediately, since the assistant just surfaced availability information the user may want to act on further before the conversation closes. See End Session Node for the full confirmation-flow mechanics.
Example 3: Order-status lookup with extraction validation
Section titled “Example 3: Order-status lookup with extraction validation”An order-status assistant that extracts an order ID from the user’s message, then uses a condition node to check whether extraction actually succeeded before calling a tool node — avoiding a failed downstream tool call on a message that never contained an order ID at all.
Flow: start → extract order ID (llm) → did extraction succeed? (condition) → if true, look up the order (tool) → respond with the result (response); if false, ask the user to provide an order ID (response) → end_session.
{ "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "extract_order_id", "type": "llm", "label": "Extract Order ID", "config": { "outputVar": "orderId", "extractFirstInteger": true, "promptTemplate": "You are a data extraction step in an automated pipeline, not the assistant talking to the user — you must NOT generate a reply, confirmation, explanation, or any commentary. Extract ONLY the numeric order ID from this message: \"{{userMessage}}\". If no order ID is present, respond with exactly: 0. Respond with ONLY the number, nothing else." }, "position": { "x": 0, "y": 100 } }, { "id": "has_order_id", "type": "condition", "label": "Was an Order ID Found?", "config": { "expression": "orderId != '0'" }, "position": { "x": 0, "y": 180 } }, { "id": "lookup_order", "type": "tool", "label": "Look Up Order Status", "config": { "connectorId": 12, "toolName": "get_order_status", "argsTemplate": "{\"orderId\": {{orderId}}}", "outputVar": "order_result", "timeoutMs": 8000 }, "position": { "x": 250, "y": 250 } }, { "id": "respond_order", "type": "response", "label": "Respond With Order Status", "config": { "messageTemplate": "Order #{{orderId}}: {{order_result}}", "mood": "helpful" }, "position": { "x": 350, "y": 250 } }, { "id": "ask_for_order_id", "type": "response", "label": "Ask For Order ID", "config": { "messageTemplate": "I couldn't find an order number in your message — could you share your order ID so I can look it up?", "mood": "neutral" }, "position": { "x": 350, "y": 50 } }, { "id": "end", "type": "end_session", "label": "End", "config": { "runEvaluation": true }, "position": { "x": 450, "y": 150 } } ], "edges": [ { "id": "e1", "source": "start", "target": "extract_order_id" }, { "id": "e2", "source": "extract_order_id", "target": "has_order_id" }, { "id": "e3", "source": "has_order_id", "target": "lookup_order", "label": "true" }, { "id": "e4", "source": "has_order_id", "target": "ask_for_order_id", "label": "false" }, { "id": "e5", "source": "lookup_order", "target": "respond_order" }, { "id": "e6", "source": "respond_order", "target": "end" }, { "id": "e7", "source": "ask_for_order_id", "target": "end" } ]}Why it’s built this way:
extract_order_idsetsextractFirstInteger: trueand instructs the model to output0when no order ID is present — a documented, deterministic “no match” convention. Combined with the extraction node’s pipeline-step framing, this makes the model’s output safe to check with a plain equality expression downstream.has_order_idis a condition node, not a router — this is a genuine two-way boolean check (did extraction succeed, yes or no), which is exactly whatconditionis for, versusrouter’s multi-way dispatch on a classified value. Its two outgoing edges are labeled exactly"true"and"false", matching the condition node’s requirement.lookup_order’sargsTemplatereferences{{orderId}}unquoted ({"orderId": {{orderId}}}) because it’s already a bare number by this point — quoting it would send a string where the tool likely expects an integer. This is safe specifically becauseextractFirstIntegerguarantees the value is a plain digit string, never arbitrary text.- The condition node prevents a wasted (and confusing) tool call on a message that never contained an order ID, instead giving the user a clear, specific ask.
Next steps
Section titled “Next steps”- Workflows Overview — the full node/edge model these examples build on.
- Workflow Best Practices — the templating and extraction-reliability patterns applied throughout.
- LLM Node, Condition Node, Router Node, Response Node, Webhook Node, Tool Node, Action Node, End Session Node — full per-type reference.
- MCP Connectors — registering the connectors these examples’
toolnodes call. - Workflows API Reference — the mutations for creating, updating, and publishing a workflow like these.