Skip to content

Recipe: Order-Status & Product-Support Agent

This content is not available in your language yet.

This recipe builds a single support agent that handles two different kinds of question in the same conversation: general product/policy questions, answered from a knowledge base, and “where’s my order” questions, answered with a live call to an order-lookup API. It combines the RAG pattern from the first cookbook recipe with a router node and a webhook node — this page assumes you’ve read that first recipe and won’t re-explain knowledge base creation or ingestion from scratch.

Every request below needs the x-huat-platform: customer header and a valid dashboard Authorization: Bearer <token> — see Getting Started and Authentication if you haven’t set those up.

Step 1: Create a knowledge base for product/policy questions

Section titled “Step 1: Create a knowledge base for product/policy questions”

Same call as Support Agent with a Knowledge Base — create it, then ingest your product catalog and FAQ content with ingestDocument (inline text) or the presigned-upload flow for larger catalog files. Assume this returns knowledge base id 91.

mutation CreateKB {
createKnowledgeBase(
input: {
name: "Product Catalog & FAQ"
description: "Product descriptions, sizing, warranty, and returns policy"
}
) {
id
}
}
mutation CreateOrderSupportAgent {
createAgent(
input: {
name: "Order & Product Support"
position: "Support Agent"
personaPrompt: "You are a helpful e-commerce support assistant. You can answer questions about products, sizing, warranty, and returns using your reference material, and you can look up the live status of an order when a customer asks. If you don't have information to answer something, say so plainly rather than guessing."
useCase: SUPPORT
voiceTier: STANDARD
}
) {
id
}
}

Assume this returns agent id 103.

The workflow classifies each message into one of two intents and dispatches accordingly with a router node — the same classify-then-dispatch shape used throughout Worked Examples & Demos, extended here with a real, live data source on one branch instead of a static response.

Flow: start → classify intent (llm) → dispatch (router) → either:

  • order_status branch: extract the order ID (llm, extractFirstInteger) → look up the order (webhook, calls your order API) → reply with the live result (response)
  • product_question branch: answer from the knowledge base (llm with knowledgeBaseId: 91) → reply (response)

…then end_session on either path.

mutation CreateWorkflow($input: CreateWorkflowInput!) {
createWorkflow(input: $input) {
id
}
}
{
"input": {
"name": "Order & Product Support",
"description": "Answers product questions from a knowledge base and live order-status questions via webhook."
}
}

Assume this returns workflow id 67.

mutation UpdateWorkflow($input: UpdateWorkflowInput!) {
updateWorkflow(input: $input) {
id
version
}
}
{
"input": {
"id": 67,
"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: order_status, or product_question.",
"streamOutput": false
},
"position": { "x": 0, "y": 100 }
},
{
"id": "dispatch",
"type": "router",
"label": "Intent Dispatcher",
"config": {
"variable": "intent",
"defaultTarget": "answer_from_kb"
},
"position": { "x": 0, "y": 180 }
},
{
"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.",
"streamOutput": false
},
"position": { "x": -250, "y": 260 }
},
{
"id": "lookup_order",
"type": "webhook",
"label": "Check Order Status",
"config": {
"url": "https://orders.example.com/api/status",
"method": "POST",
"headers": { "content-type": "application/json" },
"bodyTemplate": "{\"orderId\": {{orderId}}}",
"outputVar": "order_status_result",
"timeoutMs": 8000
},
"position": { "x": -250, "y": 340 }
},
{
"id": "respond_order",
"type": "response",
"label": "Respond With Order Status",
"config": {
"messageTemplate": "Here's what I found for order #{{orderId}}: {{order_status_result}}",
"mood": "helpful"
},
"position": { "x": -250, "y": 420 }
},
{
"id": "answer_from_kb",
"type": "llm",
"label": "Answer From Product Catalog",
"config": {
"promptTemplate": "You are a helpful e-commerce support assistant. Answer the customer's question using the provided reference material where relevant: {{userMessage}}",
"knowledgeBaseId": 91,
"outputVar": "kb_answer"
},
"position": { "x": 250, "y": 260 }
},
{
"id": "respond_kb",
"type": "response",
"label": "Respond With Product Info",
"config": {
"messageTemplate": "{{kb_answer}}",
"mood": "helpful"
},
"position": { "x": 250, "y": 340 }
},
{
"id": "end",
"type": "end_session",
"label": "End",
"config": { "runEvaluation": true },
"position": { "x": 0, "y": 500 }
}
],
"edges": [
{ "id": "e1", "source": "start", "target": "classify_intent" },
{ "id": "e2", "source": "classify_intent", "target": "dispatch" },
{
"id": "e3",
"source": "dispatch",
"target": "extract_order_id",
"label": "order_status"
},
{
"id": "e4",
"source": "dispatch",
"target": "answer_from_kb",
"label": "product_question"
},
{ "id": "e5", "source": "extract_order_id", "target": "lookup_order" },
{ "id": "e6", "source": "lookup_order", "target": "respond_order" },
{ "id": "e7", "source": "respond_order", "target": "end" },
{ "id": "e8", "source": "answer_from_kb", "target": "respond_kb" },
{ "id": "e9", "source": "respond_kb", "target": "end" }
]
}
}

A few choices worth calling out:

  • dispatch’s defaultTarget points at answer_from_kb, not a generic fallback response. If the classifier ever outputs something outside the two categories it was given, treating the message as a product question and letting the knowledge base attempt an answer is a more useful default than a dead-end “I didn’t understand” reply — see Router Node for the full defaultTarget/circuit-breaker mechanics if you want to harden this further as usage grows.
  • extract_order_id uses extractFirstInteger: true and is explicitly framed as a pipeline step, not the assistant. This is the same pattern from Worked Examples & Demos — it guarantees lookup_order’s bodyTemplate receives a bare number rather than breaking on an off-task reply like “could you clarify the order number?”
  • lookup_order is a webhook node, not a tool node, because this recipe assumes you haven’t registered your order API as an MCP connector yet — a webhook node calls a URL directly with no prior registration step. If you later stand up an MCP server for this API (or already have one), a tool node is the better long-term fit; the config shape and argsTemplate/bodyTemplate templating rules are the same either way.
  • {{orderId}} is unquoted in bodyTemplate ({"orderId": {{orderId}}}) because extractFirstInteger guarantees it’s already a bare digit string — quoting it would send a string where the endpoint likely expects an integer. This is only safe because of that guarantee; don’t do this with a raw, unvalidated LLM output.
  • answer_from_kb reuses the exact RAG pattern from the first recipe — same knowledgeBaseId field, same fail-open retrieval behavior if the vector store is briefly unreachable.
mutation PublishWorkflow($id: Int!) {
publishWorkflow(id: $id) {
id
isPublished
version
}
}
{ "id": 67 }
mutation AttachWorkflow {
updateAgent(id: "103", input: { workflowId: 67 }) {
id
workflowId
}
}

As always, confirm isPublished: true before testing — an unpublished workflow runs zero nodes, with no error anywhere. See Agents.

lookup_order.config.url above is a placeholder. Two things to know before pointing it at your real endpoint:

  • The URL is checked for SSRF risk at save time, not just execution time — a url pointing at a private/internal IP range is rejected by updateWorkflow before the workflow is even stored. This is the same guard applied to action node URLs, and it means your order-lookup API needs to be reachable from the public internet (or through whatever gateway you expose it behind) for this node to call it at all.
  • timeoutMs is clamped to a hard ceiling of 30 seconds, regardless of what you configure. If your real order-lookup endpoint can genuinely take longer than that under load, a webhook node isn’t the right fit for it as-is — see Webhook Node for the full list of constraints.

A customer sends: “Where’s my order #48213?”

  1. classify_intent reads the message and outputs order_status.
  2. dispatch routes to extract_order_id, which pulls 48213 into orderId (as a string, thanks to extractFirstInteger).
  3. lookup_order calls https://orders.example.com/api/status with {"orderId": 48213} and waits (blocking, since async is unset) for the response, storing it in order_status_result.
  4. respond_order sends the result back: something like “Here’s what I found for order #48213: shipped, arriving Thursday.”

A different customer sends: “Does the men’s jacket run true to size?”

  1. classify_intent outputs product_question.
  2. dispatch routes to answer_from_kb, which retrieves the relevant sizing-guide chunk from knowledge base 91 and answers grounded in it.
  3. respond_kb sends that answer back verbatim.

Both paths converge on the same end_session node, which queues a post-session evaluation (customer-satisfaction-flavored, since useCase: SUPPORT) you can fetch via the evaluation query — see Evaluation & Export.

  • Cookbook: Support Agent with a Knowledge Base (RAG) — the RAG setup this recipe builds on.
  • Webhook Node — full config reference, including blocking vs. async: true fire-and-forget mode.
  • Router Node — the defaultTarget/circuit-breaker mechanics for hardening the classifier’s fallback behavior.
  • Tool Node — the registered-connector alternative to a webhook node, once you have an MCP server for your order API.
  • Troubleshooting — if a webhook call is failing silently or a branch isn’t firing.