How to Build a customer support Agent Using CrewAI in TypeScript for healthcare
A healthcare customer support agent handles patient-facing questions like appointment status, billing explanations, prior authorization updates, and basic policy guidance. It matters because the difference between a good answer and a bad one is not just user experience; it can affect privacy, compliance, and whether a patient gets the right next step without exposing protected health information.
Architecture
- •
Support intake layer
- •Receives chat or email messages from the patient portal, contact center, or SMS gateway.
- •Normalizes the request into a structured task.
- •
Triage agent
- •Classifies intent: scheduling, billing, claims, benefits, prescription refill, escalation.
- •Detects urgency and routes anything clinical or high-risk to a human.
- •
Policy retrieval tool
- •Pulls approved answers from internal knowledge bases.
- •Restricts responses to organization-approved content only.
- •
Compliance guardrail
- •Redacts PHI where possible.
- •Blocks unsupported medical advice and enforces escalation rules.
- •
Human handoff path
- •Sends uncertain cases to support staff with full audit context.
- •Preserves conversation history and decision rationale.
- •
Audit and logging layer
- •Stores prompts, tool calls, outputs, and routing decisions.
- •Supports compliance review and incident investigation.
Implementation
1) Install CrewAI for TypeScript and define your support task
Use the TypeScript SDK and keep your agent narrow. In healthcare, the first version should do triage and policy lookup, not diagnosis.
npm install @crewai/crewai zod dotenv
import "dotenv/config";
import { Agent, Task, Crew } from "@crewai/crewai";
const supportAgent = new Agent({
role: "Healthcare Support Agent",
goal: "Answer patient support questions using approved policy content only",
backstory:
"You work for a healthcare provider support desk. You must never provide medical advice or expose sensitive data.",
verbose: true,
});
const triageTask = new Task({
description: `
Classify the incoming message into one of:
billing | scheduling | claims | benefits | prescription | escalation
If the message contains symptoms, medication changes, self-harm risk,
or urgent clinical issues, mark it as escalation.
Return JSON only.
`,
expectedOutput:
'{"category":"billing|scheduling|claims|benefits|prescription|escalation","reason":"string","needs_human":true|false}',
agent: supportAgent,
});
const crew = new Crew({
agents: [supportAgent],
tasks: [triageTask],
});
2) Add a policy lookup tool with hard constraints
For healthcare support, don’t let the model invent answers. Give it a tool that queries an approved knowledge base or CMS-backed policy store.
import { z } from "zod";
const PolicyLookupInput = z.object({
topic: z.enum(["billing", "scheduling", "claims", "benefits", "prescription"]),
});
async function lookupPolicy(topic: string): Promise<string> {
const policies: Record<string, string> = {
billing: "Patients can request itemized statements through the portal.",
scheduling: "Appointments can be rescheduled up to 24 hours before visit time.",
claims: "Claims status is available after provider submission through member services.",
benefits: "Benefit eligibility is confirmed by plan year and member ID.",
prescription: "Refill requests are routed to the pharmacy queue; no medication changes by support.",
};
return policies[topic] ?? "No approved policy found.";
}
If your CrewAI TypeScript version exposes Tool in your installed package, register this function as a tool on a dedicated answer agent. If not, call it in your application layer after triage and pass only the approved text into the next task.
3) Run triage first, then generate a safe response
This pattern keeps the LLM inside a controlled workflow. The agent decides whether to answer or escalate; your code decides what data it is allowed to see.
async function handleIncomingMessage(message: string) {
const triageResult = await crew.kickoff({
inputs: { message },
});
const raw = String(triageResult);
if (raw.includes('"needs_human":true') || raw.includes('"category":"escalation"')) {
return {
route: "human_handoff",
note: "Escalated due to clinical or high-risk content.",
audit: { triageResult },
};
}
const categoryMatch = raw.match(/"category":"([^"]+)"/);
const category = categoryMatch?.[1] as
| "billing"
| "scheduling"
| "claims"
| "benefits"
| "prescription"
| undefined;
if (!category || category === "escalation") {
return {
route: "human_handoff",
note: "Unable to classify safely.",
audit: { triageResult },
};
}
const policyText = await lookupPolicy(category);
return {
route: "auto_reply",
category,
reply: `Approved response for ${category}: ${policyText}`,
audit: { triageResult },
};
}
handleIncomingMessage("Can I change my insulin dose?").then(console.log);
4) Add audit fields before you ship
Healthcare teams need traceability. Log what was asked, what was classified, what policy was used, and why escalation happened.
type AuditRecord = {
conversationId: string;
userIdHash: string;
timestamp: string;
inputRedacted: string;
category?: string;
actionTaken: "auto_reply" | "human_handoff";
};
function redactPhi(text: string): string {
return text
.replace(/\b\d{3}-\d{2}-\d{4}\b/g, "[SSN]")
.replace(/\b\d{10}\b/g, "[PHONE]")
.replace(/\b[A-Z][a-z]+ [A-Z][a-z]+\b/g, "[NAME]");
}
Production Considerations
- •
Deploy in-region
- •Keep inference and logs in your required data residency boundary.
- •For healthcare workloads tied to regulated records, avoid sending PHI across unapproved regions.
- •
Store full audit trails
- •Persist prompt inputs, model outputs, tool calls, and final routing decisions.
- •
Add hard guardrails
`` Wait this isn't right—let's fix formatting?
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit