Smart Support Router with n8n and Ollama

Build a local AI agent that classifies and routes customer support messages.

Introduction

30 Second Summary

Customer support teams deal with thousands of messages every day. Sorting each one by hand is slow and error-prone. What if an AI could instantly read each message, decide what it's about, and route it to the right place?

In this project, you will build a smart support router that uses a local AI model to classify customer messages, route them by category, and generate tailored responses. Everything runs on your machine for free.

What You'll Build

You send a customer message to a webhook, and within seconds get back a JSON response telling you the category, priority, and a draft reply. You can demo this live from your terminal and watch the AI make routing decisions in real time.

By the end of this project, you'll have:

  • A webhook-triggered workflow that accepts customer messages and returns structured JSON responses with a classified category, priority level, and tailored reply.
  • An AI-powered classification system that correctly routes messages like "I want a refund" to billing, "my login is broken" to technical, and "delete my account" to escalation for human review.
  • A stress-tested prompt that you have iteratively refined by observing where the AI misclassifies ambiguous messages and fixing the gaps.
  • Secret Mission: Add conversation memory so the agent remembers context across multiple messages in the same support thread.

Are there any prerequisites?

You need Node.js (v20.19 or later) and Ollama installed on your machine before starting. You also need at least 8GB of RAM to run the local AI model alongside the workflow engine.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. This step is your chance to commit to the goal before any hands-on work begins.

Set Up n8n and Ollama

Your support router needs two engines running side by side. n8n orchestrates the workflow, handling everything from receiving customer messages to sending back responses.

Ollama powers the local AI model that reads and classifies those messages. In this step, you'll get both tools running and confirm they can talk to each other.

In this step, get ready to:
  • Install and start n8n locally.
  • Pull the Phi-3 Mini model in Ollama.
  • Connect n8n to Ollama with a saved credential.
Install and start n8n

n8n is a workflow automation tool that lets you visually connect nodes into pipelines. You'll run it locally using npx, which downloads and launches n8n without permanently installing it on your machine.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Start n8n by running this command:
npx n8n
  • If npx asks to confirm the download, type y and press Enter.
  • Wait for the startup output to finish. This command keeps your terminal busy while n8n runs, so leave this terminal open for the rest of the project.
  • Open http://localhost:5678 in your browser.

You should see the n8n setup screen asking you to create an owner account.

  • Enter your first name and last name.
  • Enter an email address.
  • Create a password.
  • Complete the setup to reach the n8n dashboard.

What is this account?

This is a local-only account stored on your machine. It does not create a cloud subscription or send data anywhere. If n8n offers a license key popup, you can skip it.

Browser showing unable to connect?

Make sure the terminal running npx n8n has finished loading. Look for output mentioning port 5678. If you closed the terminal or pressed Ctrl+C, run npx n8n again.

Help me troubleshoot

Pull the Phi-3 Mini model in Ollama

Phi-3 Mini is a lightweight 3.8B-parameter model that fits comfortably alongside n8n on 8GB of RAM. It will power the classification logic in your support router.

  • Open a second terminal window (press the Windows key, type PowerShell, and press Enter).
  • Download the Phi-3 Mini model by running this command:
ollama pull phi3:mini

This downloads approximately 2.2GB. It may take a few minutes depending on your connection speed.

  • Once the download completes, verify the model works by running:
ollama run phi3:mini "Say hello"

You should see the model respond with a greeting. The first run may take a moment as the model loads into memory.

  • Type /bye and press Enter to exit the model.

Model not responding or download failed?

Make sure Ollama is running. Open another terminal and run ollama list to check if the service is active. If you see a connection error, start Ollama first, then retry the pull command.

Help me fix my Ollama setup

Verify n8n can reach Ollama

Both tools are running, but they need a connection between them. You'll create a saved credential in n8n that tells it where to find Ollama's API.

  • Switch back to your browser at http://localhost:5678.
  • In the left sidebar, click Credentials.
  • Click Add Credential in the upper-right area.
  • Search for Ollama and select it from the list.
  • In the Base URL field, enter:
http://127.0.0.1:11434
  • Leave the API Key field empty.

Why 127.0.0.1 instead of localhost?

On Windows, localhost can resolve to an IPv6 address, which causes connection failures. Using 127.0.0.1 forces IPv4 and avoids this issue.

Before you click the test button, what do you expect to happen when n8n tests the connection to Ollama?

  • Click Test Connection.

You should see a success message confirming n8n can reach Ollama.

  • Click the credential name at the top (it defaults to something like "Ollama account") and rename it to Local Ollama.
  • Click Save.

Connection test failed?

Double-check that the Base URL is exactly http://127.0.0.1:11434 with no trailing slash. Confirm Ollama is still running by opening your second terminal and running ollama list. If Ollama is not running, start it and retry.

Help me troubleshoot my connection

Both engines are running and connected. Next up, you'll build the webhook that accepts customer messages into your workflow.

Build the Intake Webhook

Your local AI stack is ready to go. n8n is serving workflows, Ollama is loaded with Phi-3 Mini, and the two are connected through a credential.

Now you need a way to get customer messages into the system. In production, a chat widget or support form would send messages to a backend. A webhook gives you a URL endpoint you can hit with test requests and get responses back immediately.

In this step, get ready to:
  • Create a new workflow with a webhook trigger that accepts POST requests.
  • Add an Edit Fields node that normalizes incoming data into a consistent shape.
  • Send a test request and see the data flow through the workflow.
Create the Smart Support Router workflow

Every n8n workflow needs a trigger node that kicks it off. Your trigger is a webhook. It creates a URL that accepts incoming requests, just like a real support form would send data to an API endpoint.

  • Switch to the n8n tab in your browser at http://localhost:5678.
  • Click the + button in the top right to create a new workflow.
  • Click the default workflow name at the top of the canvas and rename it to Smart Support Router.
  • Click Add first step on the canvas (or press the + button to add a node).
  • Search for Webhook and select it.
  • In the Webhook node settings panel, set HTTP Method to POST.
  • Set the Path field to support-intake.
  • Under the Respond section, change Respond With to Using 'Respond to Webhook' Node.

Why use Respond to Webhook Node?

By default, a webhook immediately sends back a response the moment data arrives. Choosing this option lets your workflow process the message first (classify it, route it, generate a reply) and then send back a custom response at the end.

This is how real support systems work. They think before they answer.

You should now see the Webhook node on your canvas with a POST label.

Add an Edit Fields node to normalize incoming data

Raw webhook data can arrive in different shapes depending on who sends it. An Edit Fields node reshapes whatever comes in into consistent, predictable field names your downstream nodes can rely on.

  • Click the + connector on the right side of your Webhook node.
  • Search for Edit Fields and select Edit Fields (Set).
  • In the Edit Fields panel, make sure Mode is set to Manual Mapping.
  • Click Add Field and set the Name to customerMessage.
  • For the Value, click the Expression tab (the small toggle above the value input) and enter {{ $json.body.message }}.
  • Click Add Field again. Set Name to customerEmail.
  • Set the Value (as an expression) to {{ $json.body.email }}.
  • Click Add Field one more time. Set Name to timestamp.
  • Set the Value (as an expression) to {{ new Date().toISOString() }}.

What do these fields do?

  • customerMessage extracts the message text from the incoming request body.
  • customerEmail pulls out the sender's email so you can track who sent the request.
  • timestamp stamps the exact time the message arrived, useful for logging and response tracking.

Your canvas should now show two connected nodes: Webhook flowing into Edit Fields.

Test the webhook with a real request

Time to see data flow through your workflow. You will send a simulated customer message to the webhook and watch it arrive in the execution panel.

  • Click on the Webhook node to open its panel.
  • Click Listen for test event at the top of the panel. The node is now waiting for an incoming request.
  • Open a new Command Prompt window (press the Windows key, type cmd, and press Enter).
  • Send a test message to the webhook by running this command:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I need help with a refund\", \"email\": \"customer@example.com\"}"

Using PowerShell instead of Command Prompt?

In Windows PowerShell 5.1, curl is an alias for a different command. Type curl.exe instead of curl at the start of the command to use the real curl binary. PowerShell 7+ does not have this issue.

Before you switch back to n8n, what do you think the Edit Fields node will show? Will you see the raw request body, or the three clean fields you configured?

  • Switch back to the n8n browser tab.

You should see the execution light up across both nodes. Click on the Edit Fields node to inspect its output. You will see your three normalized fields: customerMessage showing "I need help with a refund", customerEmail showing "customer@example.com", and timestamp with the current time.

Not seeing any data in n8n?

  • Make sure you clicked Listen for test event before running the curl command. The webhook only captures data while actively listening.
  • Check that the URL in your curl command matches exactly: http://localhost:5678/webhook-test/support-intake. Note the webhook-test segment. This is the test URL, not the production URL.
  • If you see an error about the connection being refused, confirm the terminal running npx n8n is still active.

Why is my webhook not receiving data?

Your intake webhook is live and processing messages. Next up, you will connect the AI agent that reads these messages and classifies them into billing, technical, or account categories.

Add the AI Classifier Agent

Your webhook captures incoming support messages and normalizes them into a consistent shape. But right now, every message gets the same treatment regardless of what the customer is actually asking about.

A static keyword filter could catch obvious words like "refund," but it would miss nuanced messages like "I was charged twice and I'm furious." In this step, you'll add an AI Agent that reads natural language and classifies intent. This is the core routing intelligence that makes your system "smart" rather than just a switch statement.

In this step, get ready to:
  • Connect an AI Agent node to your workflow and feed it customer messages.
  • Wire up the local Phi-3 Mini model as the AI's language engine.
  • Write a classification prompt that produces structured JSON output.
Add the AI Agent node and configure the prompt

The AI Agent node is where the intelligence lives. It takes an input prompt (the customer's message) and uses a connected language model to generate a response.

  • Return to your Smart Support Router workflow in n8n at http://localhost:5678.
  • Click the + button on the right edge of the Edit Fields node to add a new node after it.
  • Search for AI Agent and select it.
  • In the AI Agent node's settings, find the Prompt parameter.
  • Change it from Take from previous node automatically to Define below.
  • In the Prompt (User Message) field that appears, click the expression toggle (the small gear or code icon) and enter this expression:
{{ $json.customerMessage }}

What does this expression do?

The expression {{ $json.customerMessage }} pulls the customer's message from the Edit Fields node's output. This means whatever text the customer sent in their support request becomes the prompt that the AI model processes.

You should now see the AI Agent node connected to Edit Fields on the canvas.

Connect the Ollama Chat Model

The AI Agent needs a language model to do its thinking. You'll connect the local Phi-3 Mini model through n8n's Ollama Chat Model sub-node.

  • With the AI Agent node open, look at the bottom of the node panel for the connectors. Click the + Chat Model button (or the Model connector).
  • In the list of available models, select Ollama Chat Model.
  • In the Model field, type phi3:mini.
  • In the Credential to connect with dropdown, select your Local Ollama credential from earlier.
  • Expand the Options section at the bottom of the Ollama Chat Model settings.
  • Set Sampling Temperature to 0.1.

Why set temperature so low?

Temperature controls how creative or random the model's output is. A value of 0.1 keeps the model highly deterministic. You want consistent, repeatable classification rather than creative responses. The same message should always land in the same category.

You should see the Ollama Chat Model sub-node appear below the AI Agent node on the canvas, with a line connecting them.

Set the classification system message

The system message tells the AI how to behave before it ever sees a customer message. This is where you define the classification rules, output format, and escalation criteria.

  • Close the Ollama Chat Model sub-node to return to the AI Agent node settings.
  • Scroll down and click Add Option (or look in the Node options section).
  • Select System Message.
  • Paste the following classification prompt into the System Message field:
You are a customer support classifier for an e-commerce/SaaS company. Your job is to analyze the customer message and respond with a JSON object.

Classify the message into exactly one category:
- billing: refunds, charges, invoices, subscription changes, payment issues
- technical: bugs, errors, login issues, feature not working, integration problems
- account: account deletion, data export, profile changes, security concerns

Also assign a priority: low, medium, or high.
Also set escalate to true if the message involves: account deletion, security breach, legal threats, or self-harm.

Respond ONLY with valid JSON in this exact format, no other text:
{"category": "billing", "priority": "medium", "escalate": false, "draft_response": "A brief helpful response to the customer"}

What does this prompt do?

This is prompt engineering in action. The system message establishes four things:

  • The AI's role (customer support classifier).
  • The categories it must choose from (billing, technical, account).
  • The escalation rules for high-risk scenarios.
  • The exact JSON output format. Specifying "no other text" is critical because local models sometimes add explanations around their answers.

Now let's test it. Before you run the test, think about what you expect the AI to output for a refund request. Do you think it will produce clean JSON on the first try?

  • Click Listen for test event on the Webhook node (at the start of your workflow).
  • Open a new terminal window and send a test message by running:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I want a refund for my last order\", \"email\": \"customer@example.com\"}"
  • Switch back to n8n. Click on the AI Agent node in the execution results panel to see its output.

You should see the AI Agent's output containing a JSON response with "category": "billing" in the results. The model identified "refund" as a billing concern and classified it accordingly.

The curl command may time out

Since the Webhook is set to respond via a Respond to Webhook node (which you haven't added yet), the curl request may hang or eventually time out. That's fine. The important thing is the execution results in n8n, which show the data flowing through all nodes including the AI Agent's output.

AI Agent not producing output?

If the AI Agent node shows an error about a Chat Model not being connected, open the AI Agent node and confirm the Ollama Chat Model sub-node is connected to the Model connector (look for the connecting line on the canvas).

If you see a null value error, double-check that your Prompt is set to Define below and the expression {{ $json.customerMessage }} is entered correctly in the Prompt (User Message) field.

If the model responds slowly (30+ seconds), that's normal for a local model on the first request. Subsequent requests are faster.

Help me troubleshoot my AI Agent node in n8n. It is connected to an Ollama Chat Model using phi3:mini and I'm seeing an error when I try to test the workflow.

Your AI classifier is working. It can read a customer message in plain English and produce a structured JSON classification. Next up, you'll build the routing logic that takes this classification and sends each category down a different path.

Route by Category

Your AI Agent now classifies every customer message into a category, assigns a priority, and flags whether it needs escalation. But right now, every message gets the same treatment regardless of what the AI decides.

In this step, you will split the workflow so billing queries, technical issues, and escalation requests each follow a different path. The classifier output becomes the routing intelligence that powers real support automation.

In this step, get ready to:
  • Parse the AI Agent's JSON output into structured data with a Code node.
  • Route escalations to a human-review response using an IF node.
  • Return resolved responses for non-escalated messages and test all three paths.
Parse the AI output with a Code node

The AI Agent returns its classification as text. Small local models sometimes wrap JSON output in markdown code fences or add extra commentary. You need a parser that reliably extracts the JSON regardless of formatting quirks.

  • Return to your Smart Support Router workflow in n8n at http://localhost:5678.
  • Click the + connector on the right side of the AI Agent node.
  • Search for Code and select the Code node to add it to the canvas.
  • In the node settings, set Language to JavaScript.
  • Rename the node to Parse Classification by double-clicking its title on the canvas.
  • Delete the placeholder code in the editor and paste the following:
const output = $input.first().json.output;

let parsed;
try {
  let jsonString = output;
  // Strip markdown code fences if the model wrapped its response
  const fenceMatch = output.match(/```(?:json)?\s*([\s\S]*?)```/);
  if (fenceMatch) {
    jsonString = fenceMatch[1].trim();
  }
  parsed = JSON.parse(jsonString);
} catch (e) {
  // Fallback if parsing fails entirely
  parsed = {
    category: "general",
    priority: "medium",
    escalate: false,
    draft_response: "I will connect you with a support agent."
  };
}

// Carry forward metadata from upstream nodes
parsed.timestamp = $input.first().json.timestamp || new Date().toISOString();
parsed.customerEmail = $input.first().json.customerEmail || "unknown";

return [{ json: parsed }];

What does this code do?

The code reads the AI Agent's raw text output from $input.first().json.output. This is how n8n's Code node accesses data from the previous node.

  • It checks for markdown code fences (a common quirk with local LLMs) and strips them before parsing.
  • If parsing fails for any reason, it falls back to a safe default that routes the message to a human agent.
  • It appends timestamp and customerEmail from the upstream Edit Fields node so downstream nodes can reference them.
  • Close the Code node to return to the canvas.
  • Click Listen for test event on the Webhook node, then open your terminal and run this test message:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I need help with a refund\", \"email\": \"customer@example.com\"}"
  • Click on the Parse Classification node in the canvas to view its output.
  • You should see structured JSON with category, priority, escalate, and draft_response fields populated by the AI.

Seeing an error in the Code node?

  • Make sure you ran the AI Agent node at least once before testing. The Code node needs the AI's output data to parse.
  • If you see "Cannot read properties of undefined", check that the AI Agent node's output connector is wired to the Code node's input.
  • Why is my Parse Classification node failing?

✔️ Awesome, I've got everything!

Great. Double check you saved the Code node before closing it.

ⓧ I'd like to double check the full code

Here is the complete code that should be in your Parse Classification node:

const output = $input.first().json.output;

let parsed;
try {
  let jsonString = output;
  const fenceMatch = output.match(/```(?:json)?\s*([\s\S]*?)```/);
  if (fenceMatch) {
    jsonString = fenceMatch[1].trim();
  }
  parsed = JSON.parse(jsonString);
} catch (e) {
  parsed = {
    category: "general",
    priority: "medium",
    escalate: false,
    draft_response: "I will connect you with a support agent."
  };
}

parsed.timestamp = $input.first().json.timestamp || new Date().toISOString();
parsed.customerEmail = $input.first().json.customerEmail || "unknown";

return [{ json: parsed }];
Route escalations with an IF node

Now that the data is structured, you can branch the workflow. High-risk messages (account deletion, legal threats, security issues) need to skip the auto-response and go straight to a human. The IF node splits the flow based on the escalate flag.

  • Click the + connector on the right side of the Parse Classification node.
  • Search for If and select the IF node.
  • Rename the node to Check Escalation by double-clicking its title.
  • In the IF node's condition settings, click Add condition.
  • Select Boolean from the data type dropdown.
  • In the Value field, click the expression toggle and enter {{ $json.escalate }}.
  • Set the comparison to is equal to and set the right value to true.

What is conditional routing?

The IF node evaluates a condition and sends data down one of two paths. The True output fires when the condition is met. The False output fires when it is not.

This is how you turn a single AI classification into different customer experiences. Escalations get priority handling. Everything else gets an immediate auto-response.

  • Close the IF node and return to the canvas.
  • Click the True output connector on the Check Escalation node.
  • Search for Respond to Webhook and add it.
  • In the Respond to Webhook node, set Respond With to Text.
  • Click the expression toggle on the Response Body field and paste this expression:
{{ JSON.stringify({ status: "escalated", category: $json.category, message: "Your request has been flagged for priority human review. A support agent will contact you within 1 hour.", requestId: $json.timestamp }) }}
  • Scroll down to Options and click Add Option.
  • Select Response Headers and add a header with name Content-Type and value application/json.
  • Close this node.
  • Test the escalation path by clicking Listen for test event on the Webhook node, then send this message from your terminal:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"Delete my account immediately\", \"email\": \"angry@example.com\"}"

You should see a JSON response with "status": "escalated" in your terminal output.

Getting a timeout or no response?

  • Make sure you clicked Listen for test event on the Webhook node before sending the curl command. The webhook only listens when activated.
  • If the False branch has no node connected yet, non-escalated messages will hang. That is expected at this stage. Only test with an escalation-triggering message for now.
  • Why is my n8n webhook not responding?
Handle resolved messages and test the full flow

Most support messages will not need escalation. They follow the False branch and receive an immediate auto-response with the AI's draft reply.

  • Back on the canvas, click the False output connector on the Check Escalation node.
  • Search for Respond to Webhook and add a second one.
  • Set Respond With to Text.
  • Click the expression toggle on the Response Body field and paste this expression:
{{ JSON.stringify({ status: "resolved", category: $json.category, priority: $json.priority, response: $json.draft_response, requestId: $json.timestamp }) }}
  • Add the same Content-Type response header: click Options > Add Option > Response Headers, set name to Content-Type and value to application/json.
  • Close the node.

Before you test the full flow, predict: if you send "I want a refund", will it be escalated or resolved? What about "Delete my account immediately"?

  • Click Listen for test event on the Webhook node and send the first test message:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I want a refund\", \"email\": \"customer@example.com\"}"

You should see a response with "status": "resolved" and "category": "billing".

  • Click Listen for test event again and send the second test:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"My app keeps crashing\", \"email\": \"user@example.com\"}"

You should see "status": "resolved" and "category": "technical".

  • Click Listen for test event one more time and send the escalation test:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"Delete my account immediately\", \"email\": \"angry@example.com\"}"

This time you should see "status": "escalated" with the human-review message. The account deletion language triggered the escalation flag.

What did you just prove?

Three different customer messages took three different paths through your workflow. Billing went to the resolved branch. Technical went to the resolved branch. Account deletion went to the escalated branch. This is conditional routing in action.

Category not matching what you expected?

  • Local LLMs can occasionally misclassify. If you see the wrong category, that is normal at this stage. Step 5 specifically addresses this with prompt refinement.
  • If you see "category": "general" for every message, the Parse Classification fallback fired. Check the AI Agent node's output to see if it returned valid JSON.
  • Why is my AI classifier returning the wrong category?

Your support router now classifies, routes, and responds to customer messages through different paths. Next up, you will stress-test it with tricky, ambiguous messages to see where the AI breaks down.

Stress-Test and Refine the Router

Your router handled three straightforward test messages in the last step. Real customer messages are messier. They combine emotions with requests, use vague language, and sometimes touch multiple categories at once.

In this step, you'll throw realistic support scenarios at the router and watch where it stumbles. The misclassifications you find reveal the limits of prompt-driven classification and teach you the iterate-and-improve cycle that production AI systems rely on.

In this step, get ready to:
  • Send 5 varied test messages that push classification boundaries.
  • Review execution history to spot misclassifications.
  • Refine the system prompt and re-run problem messages.
Send stress-test messages that push the router's limits

You need to test with messages that are emotionally charged, ambiguous, or combine multiple intents. These expose weaknesses a simple test would miss.

  • Return to your n8n workflow canvas at http://localhost:5678.
  • Click on the Webhook node and click Listen for test event to activate the test URL.

How long does the test webhook stay active?

The test webhook stays active for 120 seconds after you click Listen for test event. You can send multiple curl requests within that window. If it times out before you finish, click Listen for test event again to re-register.

  • Send the first test message (an angry billing complaint) by running this in your terminal:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I was charged twice last month and I'm furious\", \"email\": \"customer@example.com\"}"

You should see a JSON response with "category": "billing" and "priority": "high". The emotional tone should push it to high priority.

  • Send the second test message (a browser-specific bug report) by running:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"The export button doesn't work on Firefox\", \"email\": \"customer@example.com\"}"

You should see "category": "technical" and "priority": "medium" in the response.

  • Send the third test message (a simple account request) by running:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"Can you help me update my email address?\", \"email\": \"customer@example.com\"}"

You should see "category": "account" with "priority": "low" and "escalate": false.

Now for the tricky ones. These messages are deliberately ambiguous or contain escalation triggers that the model might miss.

Before you run the next message, what do you think the router will do with a message about closing an account? Will it escalate or treat it as a routine request?

  • Send the fourth test message (account closure inquiry) by running:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"I'm thinking about closing my account, what happens to my data?\", \"email\": \"customer@example.com\"}"

The expected result here is "category": "account" with "escalate": true because the message mentions account deletion. Check whether your router got this right.

  • Send the fifth test message (a legal threat combined with a refund demand) by running:
curl -X POST http://localhost:5678/webhook-test/support-intake -H "Content-Type: application/json" -d "{\"message\": \"Your service is terrible and I want my money back or I'll contact my lawyer\", \"email\": \"customer@example.com\"}"

The expected result is "category": "billing" with "priority": "high" and "escalate": true because it contains a legal threat.

Getting a timeout or no response?

If your curl hangs without a response, the test webhook may have expired (it only stays active for 120 seconds). Return to n8n and click Listen for test event again, then re-run the curl within the new window.

If you see a response with "category": "general", the AI's output could not be parsed as valid JSON. That is expected at this stage and you will address it in the prompt refinement substep below.

Why is my webhook timing out or returning 'general' for everything?

Review execution results in n8n

Each test run is recorded in n8n's execution history. This lets you inspect exactly what the AI produced at every node.

  • Click Executions in the left sidebar of n8n.
  • You should see a list of your recent test runs, one per curl request you sent.
  • Click on any execution to open its details.
  • Click on the AI Agent node in the execution view to see the raw text output the model produced.

Look for two common issues in the AI Agent's raw output:

  • The model wrapped its JSON in markdown code fences (```json ... ```). Your Parse Classification node handles this, but it adds fragility.
  • The model added explanatory text before or after the JSON. If the extra text fell outside the code fences or there were no fences at all, the parser falls back to the default "general" category.
  • The model misclassified a message (wrong category, wrong priority, or missed an escalation trigger).

Why does the model sometimes ignore the 'JSON only' instruction?

Small local models like Phi-3 Mini are less reliable at following strict format instructions than larger cloud models. They tend to "help" by adding explanations or wrapping output in formatting. This is normal and exactly why production AI systems include a parsing layer (which you already built).

The fix is to make the prompt more explicit. Adding reinforcing instructions and examples reduces the chance of format violations.

Refine the system prompt based on your findings

Now you'll update the AI Agent's system prompt to fix the issues you observed. This iterate-and-improve cycle is exactly how production AI systems are tuned.

  • Return to the workflow canvas and double-click the AI Agent node to open its settings.
  • Find the System Message (Instructions) field where your classification prompt lives.
  • Add the following line at the end of the prompt, just before the JSON format example:
Do not wrap your response in code fences or add any text outside the JSON object. Output ONLY the raw JSON.

What does this instruction do?

This reinforces the output format constraint. Small models respond well to repetition of format rules. By stating the constraint both at the start ("Respond ONLY with valid JSON") and again at the end, the model is less likely to add markdown fences or explanatory text.

  • If any of your test messages were misclassified, add 2-3 example classifications to the prompt to guide the model. Add them after the category definitions and before the format instructions, like this:
Examples:
- "I want my money back or I'll contact my lawyer" -> billing, high, escalate: true
- "I'm thinking about closing my account" -> account, medium, escalate: true
- "The export button doesn't work" -> technical, medium, escalate: false

Why do examples help more than detailed rules?

Small language models learn patterns from examples more reliably than from abstract rules. Adding 2-3 concrete input-output pairs to your prompt is a technique called few-shot prompting. It anchors the model's behavior to specific patterns you care about.

  • Click Back to canvas to close the node editor.
  • Save your workflow by pressing Ctrl+S.

Now re-test the messages that were previously misclassified. Before you run them, do you think the updated prompt will fix the classification?

  • Click Listen for test event on the Webhook node again.
  • Re-send the problem message(s) from your terminal using the same curl commands from earlier.
  • Check that the response now shows the correct category, priority, and escalation value.

You should see at least 4 out of 5 messages now correctly classified. If a message is still wrong, repeat the cycle: check the AI Agent's raw output, identify the pattern it missed, add another example, and re-test.

Still getting 'general' category on re-test?

Open the execution in the Executions panel and inspect the AI Agent node's output. If the raw text contains text like "Here is my classification:" followed by JSON, the extra text is preventing parsing. Make sure your added instruction says "Do not wrap your response in code fences or add any text outside the JSON object" and that it appears at the very end of the system prompt.

Help me fix my AI Agent prompt so it outputs clean JSON without extra text.

Your router now handles a range of realistic support messages with consistent accuracy. Next up, you'll tackle the Secret Mission and give your agent memory across a conversation thread.

Secret mission

Add Conversation Memory

Your support router handles single messages well. But real customers send follow-up messages. Can you add memory so the agent remembers earlier context in the same support thread?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • n8n workflow data stored in your user folder at C:\Users\<username>\.n8n.
  • Phi-3 Mini model stored locally by Ollama (2.2GB on disk).

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your Smart Support Router workflow.

  • Leave the terminal running npx n8n open. Your workflows persist in the local SQLite database.
  • Ollama stays running in the background and serves the Phi-3 Mini model on demand.
  • Your workflow, credentials, and execution history remain at http://localhost:5678 whenever n8n is running.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your files and workflows are saved and ready when you return.

  • In the terminal running n8n, press Ctrl+C to stop the process. Your workflows are saved automatically.
  • Ollama can be left running (minimal idle resource usage) or stopped if you prefer.
  • Next time you want to resume, open a terminal and run npx n8n again. Your Smart Support Router workflow will be right where you left it.

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes your workflows, credentials, and the downloaded model.

  • Stop n8n by pressing Ctrl+C in its terminal.
  • Delete your n8n data folder by running this command in PowerShell:
rmdir /s /q "%USERPROFILE%\.n8n"
  • Remove the Phi-3 Mini model from Ollama by running:
ollama rm phi3:mini

What stays on your machine?

Node.js and Ollama were prerequisites you installed before this project. The steps above only remove the project-specific data (your workflows and the Phi-3 model). Node.js and Ollama remain installed for future use.

Nice Work!

Nice Work!

You just built a fully local AI-powered support routing system using n8n and Ollama. Customer messages go in, intelligent classifications and tailored responses come out.

You've learned how to:

  • Build a webhook-triggered workflow in n8n that accepts customer messages and returns structured JSON responses, simulating a real support automation pipeline.
  • Connect a local LLM (Phi-3 Mini via Ollama) to n8n's AI Agent node for real-time intent classification and response generation.
  • Implement conditional routing with escalation detection that flags high-risk requests for human review, then stress-test and refine the system prompt through iterative improvement.
  • Secret Mission: Added conversation memory so the agent maintains context across a multi-turn support thread.

Ready to quiz yourself?