n4nAI

Building a CrewAI crew for customer support triage

Step-by-step guide to building a CrewAI crew for customer support triage with runnable code, routing logic, and verification tips for engineers.

n4n Team3 min read577 words

Audio narration

Coming soon — every post will get a voice note here.

A practical crewai customer support triage example shows how to split incoming tickets into classification, urgency scoring, and routing without a single oversized prompt. This walkthrough builds that crew with CrewAI, connects it to a resilient model gateway, and gives you a verification path you can run in CI.

Step 1: Install dependencies and scaffold the project

The crewai customer support triage example below assumes Python 3.11+ and a virtual environment. Install the framework and a dotenv loader:

pip install crewai langchain-openai python-dotenv

Create a .env file to hold your gateway credentials. Keep model names out of source control.

OPENAI_API_KEY=sk-your-key
OPENAI_API_BASE=https://api.n4n.ai/v1
TRIAGE_MODEL=gpt-4o-mini

Load these in your entry script:

import os
from dotenv import load_dotenv

load_dotenv()
MODEL = os.getenv("TRIAGE_MODEL", "gpt-4o-mini")
BASE_URL = os.getenv("OPENAI_API_BASE")
API_KEY = os.getenv("OPENAI_API_KEY")

Step 2: Define the incoming ticket shape

Triage only works if you constrain the input. Use a plain dict or Pydantic model. A minimal ticket:

{
  "ticket_id": "SUP-1042",
  "subject": "Cannot export CSV from dashboard",
  "body": "The export button spins forever after the latest update. I have a quarterly report due.",
  "customer_tier": "enterprise"
}

In code, pass this as the inputs dict to the crew. Don’t embed the raw email thread as one string; separate subject and body so agents can reference fields explicitly.

Step 3: Build the triage agents

In this crewai customer support triage example, we define three narrow agents. Each has a single responsibility, which keeps prompts short and eval-able.

from crewai import Agent

classifier = Agent(
    role="Support Classifier",
    goal="Map the ticket to a product area: billing, dashboard, api, or account",
    backstory="You have tagged 50k support tickets. You output only the area name.",
    allow_delegation=False,
    verbose=False,
)

urgency = Agent(
    role="Urgency Scorer",
    goal="Score urgency 1-5 using customer tier and business impact language",
    backstory="You prioritize enterprise blockers over cosmetic issues.",
    allow_delegation=False,
    verbose=False,
)

router = Agent(
    role="Queue Router",
    goal="Pick the owning team and suggest a first-response template",
    backstory="You know the on-call map and standard macros.",
    allow_delegation=False,
    verbose=False,
)

Narrow roles beat a “general support bot” because you can unit-test each output independently.

Step 4: Create tasks with explicit outputs

Tasks bind agents to inputs and force structured output. Use output_json with a small schema so the next step can parse without regex.

from crewai import Task
from pydantic import BaseModel

class ClassOut(BaseModel):
    area: str

class UrgOut(BaseModel):
    score: int
    reason: str

class RouteOut(BaseModel):
    team: str
    macro: str

t_class = Task(
    description="Classify ticket {ticket_id}: {subject} | {body}",
    agent=classifier,
    expected_output="JSON with area",
    output_json=ClassOut,
)

t_urg = Task(
    description="Score urgency for tier {customer_tier}: {subject} | {body}",
    agent=urgency,
    expected_output="JSON with score and reason",
    output_json=UrgOut,
)

t_route = Task(
    description="Given area {area} and urgency {score}, pick team and macro",
    agent=router,
    expected_output="JSON with team and macro",
    output_json=RouteOut,
)

The {area} and {score} placeholders are filled from prior task outputs when you use a sequential process.

Step 5: Point the crew at a resilient LLM backend

CrewAI uses LangChain under the hood. Instantiate ChatOpenAI with your gateway base URL. Pointing the crew at n4n.ai’s OpenAI-compatible endpoint gives you access to 240+ models and automatic fallback when a provider is rate-limited, so a single model name doesn’t become a single point of failure.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model=MODEL,
    base_url=BASE_URL,
    api_key=API_KEY,
    temperature=0,
)

Attach the LLM to each agent by passing llm=llm in the Agent constructor, or set it globally via crewai.llm if your version supports it. Use temperature=0 for triage—determinism matters more than creativity.

Step 6: Assemble and run the crew

Running the crewai customer support triage example end to end takes under a second per ticket on a small model. Wire the agents and tasks:

from crewai import Crew, Process

crew = Crew(
    agents=[classifier, urgency, router],
    tasks=[t_class, t_urg, t_route],
    process=Process.sequential,
    verbose=True,
)

ticket = {
    "ticket_id": "SUP-1042",
    "subject": "Cannot export CSV from dashboard",
    "body": "The export button spins forever after the latest update. I have a quarterly report due.",
    "customer_tier": "enterprise",
}

result = crew.kickoff(inputs=ticket)
print(result)

The final task returns a RouteOut JSON. Intermediate outputs are accessible via crew.tasks[i].output.json_dict if you need to log them.

Step 7: Verify the triage output

Success means the router output is valid and the classification matches a held-out label. Write a smoke test:

def test_triage_runs():
    out = crew.kickoff(inputs=ticket)
    data = out.json_dict
    assert data["team"] in {"dashboard_oncall", "billing_oncall", "api_oncall", "account_team"}
    assert isinstance(data["macro"], str) and len(data["macro"]) > 0

Run it in CI against a fixed ticket to catch prompt drift. For manual verification, print crew.tasks[0].output.json_dict and confirm area == "dashboard" on the sample above. If the classifier flips to api, your prompt or model version changed.

Token usage should be small: three short completions, not one long chain. If you see thousands of input tokens per ticket, you’re leaking conversation history.

Step 8: Production considerations

Cache repeated triage patterns. Gateways like n4n.ai honor client routing directives and forward provider cache-control hints, which matters when you see the same “export broken” spike across hundreds of tickets. Set cache_control on the classifier prompt and let the gateway return cached completions.

Add a fallback rule outside the crew: if score >= 4 and customer_tier == "enterprise", page a human regardless of model output. The crew accelerates routing; it doesn’t replace your escalation policy.

Meter cost per route. Per-token usage metering lets you attribute spend to the router agent versus the urgency scorer, so you can swap the classifier to a cheaper model without guessing. Keep the crew’s task boundaries fixed even as you change models underneath—that’s the whole point of the decomposition.

Tagscrewaireal-world-examplescustomer-supportuse-case

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai real-world crew examples posts →