Recipe: Support Agent with a Knowledge Base (RAG)
此内容尚不支持你的语言。
This recipe builds a support agent that answers questions using your own documentation instead of the model’s general knowledge. It’s the single most common starting point for a Wetel integration, and it touches four pieces documented elsewhere in isolation — Knowledge Base (RAG), the LLM node, the Response node, and Agents — assembled into one working configuration.
What you’ll build: a knowledge base containing a product FAQ, an agent with a support persona, and a workflow that answers every incoming message using that FAQ as grounding material.
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 yet.
Step 1: Create a knowledge base
Section titled “Step 1: Create a knowledge base”A knowledge base is tenant-scoped and independent of any one agent — you attach it to a workflow’s llm node, not to the agent record itself. See Knowledge Base (RAG) for the full ingestion API this step is drawn from.
mutation CreateKB { createKnowledgeBase( input: { name: "Product FAQ" description: "Frequently asked questions about our product and billing" } ) { id name createdAt }}Headers:
x-huat-platform: customerAuthorization: Bearer <your-dashboard-access-token>Note the returned id — you’ll need it in Step 2 and Step 4. For this recipe, assume it came back as 88.
Step 2: Ingest your FAQ content
Section titled “Step 2: Ingest your FAQ content”For a document this size (a typical FAQ page), inline ingestion is the simplest path — no upload/poll cycle needed. ingestDocument chunks the text (roughly 512 words per chunk, with overlap), embeds each chunk, and the knowledge base is queryable immediately after the call returns.
mutation IngestFAQ { ingestDocument( input: { knowledgeBaseId: 88 fileName: "product-faq.txt" text: "Q: How do I reset my password?\nA: Go to Settings > Account > Reset Password. A reset link is emailed within a few minutes.\n\nQ: What's your refund policy?\nA: Full refunds are available within 30 days of purchase. Contact support with your order number to start a refund.\n\nQ: Do you offer annual billing?\nA: Yes — annual plans are billed once per year and include a 15% discount versus monthly billing. Switch anytime from Settings > Billing." } )}ingestDocument returns the chunk count created (a plain integer), not an object — that’s expected, not a sign something went wrong.
If your real FAQ is a PDF or a larger DOCX file (up to 20 MB), use the three-step presigned-upload flow instead — requestKnowledgeDocumentUpload → your client uploads directly to the returned URL → confirmKnowledgeDocumentUpload → poll knowledgeDocuments until parseStatus reaches READY. See Knowledge Base (RAG) for that flow in full — this recipe doesn’t repeat it since inline text covers most FAQ-sized content.
Sanity-check retrieval before wiring it into a workflow
Section titled “Sanity-check retrieval before wiring it into a workflow”Before spending time on the workflow graph, confirm the knowledge base actually surfaces the right chunks for realistic phrasing:
query TestRetrieval { retrieveChunks( knowledgeBaseId: 88 query: "how do I get my money back" topK: 5 ) { chunkId content score }}This runs the exact retrieval logic an llm node uses at conversation time. If the refund-policy chunk doesn’t show up near the top for a paraphrased question like this, the wording or chunk boundaries of your source document are worth revisiting before you build the rest of the workflow around it.
Step 3: Create the agent
Section titled “Step 3: Create the agent”mutation CreateSupportAgent { createAgent( input: { name: "Product Support Assistant" position: "Support Agent" personaPrompt: "You are a helpful, concise product support assistant. Answer questions using the reference material you're given. If something isn't covered by your reference material, say you don't have that information and suggest the user contact human support — never guess at policy details." useCase: SUPPORT voiceTier: STANDARD } ) { id }}personaPrompt defines identity and tone — including the explicit instruction not to guess when the knowledge base doesn’t cover something, which matters once retrieval is wired in (see the fail-open behavior note in Step 5). voiceTier is required even though this is a text-only agent; the value is simply unused unless you activate voice later. Assume this returned agent id 61.
Step 4: Build the workflow
Section titled “Step 4: Build the workflow”This workflow has no branching — every message goes through the same RAG-grounded llm node and back out through a response node. That’s intentional: for a first support agent, keeping the graph this simple makes it easy to confirm retrieval is actually working before adding intent classification or escalation branches on top.
Flow: start → answer from the knowledge base (llm with knowledgeBaseId: 88) → reply (response) → end_session.
mutation CreateWorkflow($input: CreateWorkflowInput!) { createWorkflow(input: $input) { id name isPublished }}{ "input": { "name": "Product Support (RAG)", "description": "Answers product questions grounded in the Product FAQ knowledge base." }}Assume this returned workflow id 42. Now populate the graph:
mutation UpdateWorkflow($input: UpdateWorkflowInput!) { updateWorkflow(input: $input) { id version }}{ "input": { "id": 42, "nodes": [ { "id": "start", "type": "start", "label": "Start", "config": {}, "position": { "x": 0, "y": 0 } }, { "id": "answer_from_kb", "type": "llm", "label": "Answer From Product FAQ", "config": { "promptTemplate": "You are a helpful support assistant. Answer the user's question using the provided reference material where relevant: {{userMessage}}", "knowledgeBaseId": 88, "outputVar": "kb_answer" }, "position": { "x": 0, "y": 100 } }, { "id": "respond", "type": "response", "label": "Respond With Answer", "config": { "messageTemplate": "{{kb_answer}}", "mood": "helpful" }, "position": { "x": 0, "y": 200 } }, { "id": "end", "type": "end_session", "label": "End", "config": { "runEvaluation": true }, "position": { "x": 0, "y": 300 } } ], "edges": [ { "id": "e1", "source": "start", "target": "answer_from_kb" }, { "id": "e2", "source": "answer_from_kb", "target": "respond" }, { "id": "e3", "source": "respond", "target": "end" } ] }}respond’s messageTemplate is a thin pass-through of kb_answer — the llm node already produced good, user-facing text, so there’s no reason to add a second paraphrasing step in between. See Workflow Best Practices for why this matters (extra hops add latency and a chance of the model subtly changing the meaning).
Step 5: Publish the workflow and attach it
Section titled “Step 5: Publish the workflow and attach it”A workflow that’s been updated but never published executes zero nodes for every session — this is the single most common first-integration mistake, covered in Agents. Publish it, then attach it to the agent:
mutation PublishWorkflow($id: Int!) { publishWorkflow(id: $id) { id isPublished version }}{ "id": 42 }mutation AttachWorkflow { updateAgent(id: "61", input: { workflowId: 42 }) { id workflowId }}Confirm isPublished: true came back from publishWorkflow before testing — if the agent seems to ignore the graph entirely, that flag is the first thing to check.
What happens when this runs
Section titled “What happens when this runs”A user starts a session against this agent and sends: “How do I get a refund on my order?”
- Execution enters at
startand moves toanswer_from_kb. - Because
knowledgeBaseId: 88is set, the current message is embedded and matched against the FAQ’s chunks by similarity. The refund-policy chunk you ingested in Step 2 is retrieved and joined into a reference block, which is injected into the model’s context before it responds — the model sees this as grounding material, not as something the user typed. - The model answers using that grounding material, and its output is written to
kb_answer. respondsendskb_answerback to the user verbatim: something like “Full refunds are available within 30 days of purchase — just contact support with your order number to get started.”end_sessioncloses the turn’s session lifecycle and, becauserunEvaluation: trueis set, queues a post-session evaluation you can fetch later via theevaluationquery (see Evaluation & Export).
If a user asks something the FAQ genuinely doesn’t cover, the personaPrompt’s explicit instruction not to guess is what keeps the agent from fabricating an answer — retrieval itself doesn’t filter anything out, it just changes what material is available to the model.
A note on retrieval failure
Section titled “A note on retrieval failure”If the retrieval step itself fails at runtime (a transient vector-store error, not “no relevant chunks found”), the llm node fails open — it proceeds without grounding rather than failing the whole node, so the agent still responds rather than the turn erroring out. This is a deliberate reliability tradeoff: a knowledge base being briefly unreachable shouldn’t take your agent offline. See Knowledge Base (RAG) for how to distinguish “retrieval ran and found nothing” from “retrieval didn’t run at all” if you need to confirm which happened for a given reply.
Next steps
Section titled “Next steps”- Knowledge Base (RAG) — full ingestion API, including the file-upload flow for larger documents.
- LLM Node — the full
llmnode config reference. - Cookbook: Order-Status & Product-Support Agent — extends this exact RAG pattern with a live order-lookup webhook call.
- Workflow Best Practices — templating and LLM-reliability patterns worth applying as you extend this workflow with intent classification or escalation branches.
- Troubleshooting — if the agent isn’t using your knowledge base the way you expect.