How to Build a customer support Agent Using LangGraph in Python for healthcare

By Cyprian AaronsUpdated 2026-04-21
customer-supportlanggraphpythonhealthcare

A healthcare customer support agent handles patient questions, routes sensitive requests, pulls policy-safe answers from approved sources, and escalates anything clinical or risky to a human. That matters because in healthcare, a bad answer is not just a bad experience; it can create compliance issues, expose PHI, or delay care.

Architecture

  • Input router

    • Classifies the message into billing, appointment, insurance, medication, or escalation.
    • Keeps clinical questions out of automated resolution paths.
  • Policy-aware retrieval layer

    • Pulls answers from approved documents only: FAQs, benefits guides, clinic hours, claims policies.
    • Blocks raw model guessing when the source is missing.
  • State machine orchestrator

    • Uses StateGraph to move between classify → retrieve → respond → escalate.
    • Makes the agent deterministic enough for audit and review.
  • PHI guardrail layer

    • Detects sensitive data like MRNs, diagnosis details, or medication names.
    • Redacts where needed and routes high-risk content to humans.
  • Human handoff node

    • Creates a ticket or sends context to a support queue.
    • Preserves conversation state for continuity.
  • Audit logging

    • Stores prompts, decisions, retrieval results, and final responses.
    • Needed for compliance review and incident investigation.

Implementation

1) Define the graph state

Use a typed state object so every node knows what it can read and write. In healthcare support flows, keep the state small and explicit.

from typing import TypedDict, Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_core.messages import HumanMessage, AIMessage

class SupportState(TypedDict):
    messages: Annotated[list, add_messages]
    route: str
    retrieved_context: str
    response: str
    needs_handoff: bool

2) Add routing, retrieval, response, and escalation nodes

This is the core pattern. The classifier decides whether the request is safe to answer automatically or must be escalated.

def classify_intent(state: SupportState):
    text = state["messages"][-1].content.lower()
    if any(term in text for term in ["diagnosis", "symptom", "pain", "dose", "medication"]):
        return {"route": "clinical", "needs_handoff": True}
    if any(term in text for term in ["bill", "claim", "copay", "invoice"]):
        return {"route": "billing", "needs_handoff": False}
    if any(term in text for term in ["appointment", "schedule", "reschedule"]):
        return {"route": "appointments", "needs_handoff": False}
    return {"route": "general", "needs_handoff": False}

def retrieve_approved_context(state: SupportState):
    route = state["route"]
    knowledge_base = {
        "billing": "Billing policy: claims are processed within 10 business days. Copays vary by plan.",
        "appointments": "Appointments can be rescheduled up to 24 hours before the visit.",
        "general": "Support hours are Monday-Friday 8am-6pm local time."
    }
    return {"retrieved_context": knowledge_base.get(route, "")}

def generate_response(state: SupportState):
    user_text = state["messages"][-1].content
    context = state["retrieved_context"]

    if state["needs_handoff"]:
        return {
            "response": (
                "I can’t help with clinical guidance here. "
                "I’m transferring this to a licensed support specialist."
            )
        }

    if not context:
        return {
            "response": (
                "I couldn’t find an approved policy answer. "
                "I’m escalating this to a human agent."
            ),
            "needs_handoff": True,
        }

    return {
        "response": f"Based on our policy: {context} Your request was: {user_text}"
    }

def create_handoff_ticket(state: SupportState):
    # Replace with Zendesk/ServiceNow/EPIC queue integration.
    ticket_payload = {
        "category": state["route"],
        "message": state["messages"][-1].content,
        "context": state.get("retrieved_context", ""),
    }
    print("Creating handoff ticket:", ticket_payload)
    return {}

3) Wire the graph with conditional routing

LangGraph’s add_conditional_edges is the right tool here. It keeps clinical escalation explicit instead of hiding it inside prompt logic.

def route_after_classification(state: SupportState) -> str:
    if state["needs_handoff"]:
        return "handoff"
    return state["route"]

graph = StateGraph(SupportState)

graph.add_node("classify_intent", classify_intent)
graph.add_node("retrieve_approved_context", retrieve_approved_context)
graph.add_node("generate_response", generate_response)
graph.add_node("create_handoff_ticket", create_handoff_ticket)

graph.add_edge(START, "classify_intent")
graph.add_conditional_edges(
    "classify_intent",
    route_after_classification,
    {
        "billing": "retrieve_approved_context",
        "appointments": "retrieve_approved_context",
        "general": "retrieve_approved_context",
        "handoff": "create_handoff_ticket",
    },
)

graph.add_edge("retrieve_approved_context", "generate_response")
graph.add_edge("generate_response", END)
graph.add_edge("create_handoff_ticket", END)

app = graph.compile()

4) Run it with healthcare-safe inputs

In production you’d add PHI redaction before this call and log every transition. For now this shows how the compiled app behaves.

initial_state = {
    "messages": [HumanMessage(content="Can I reschedule my appointment for next week?")],
    "route": "",
    "retrieved_context": "",
    "response": "",
    "needs_handoff": False,
}

result = app.invoke(initial_state)
print(result["response"])

If you want richer control flow later, LangGraph also supports ToolNode, MemorySaver, and checkpointing via compile(checkpointer=...). For healthcare workflows that need traceability across sessions, checkpointing is not optional.

Production Considerations

  • Compliance

    • Treat all conversation data as potentially sensitive.
    • Minimize PHI in prompts and logs; store only what you need for support operations.
    • Align retention with HIPAA policies and internal legal review.
  • Auditability

    • Log node transitions, retrieved documents, final responses, and handoff reasons.
    • Keep immutable traces for incident response and quality review.
    • Version your prompts and policy docs so you can reproduce outcomes.
  • Data residency

    • Keep patient data inside approved regions and cloud accounts.
    • If your model provider crosses borders or stores telemetry elsewhere, block it.
    • Use regional vector stores and region-pinned object storage for retrieved content.
  • Guardrails

    • Add PHI detection before LLM calls.
    • Force escalation on anything clinical, urgent, self-harm related, or ambiguous.
    • Use allowlisted sources only; never let the model browse free-form web content.

Common Pitfalls

  • Letting the model answer clinical questions

    • Fix by routing symptom/medication/diagnosis content straight to human handoff.
    • The agent should support operations, not practice medicine.
  • Stuffing PHI into prompts and logs

    • Fix by redacting identifiers before classification and generation.
    • Keep audit logs structured and minimal.
  • Using one giant prompt instead of explicit graph nodes

    • Fix by separating classification, retrieval, response generation, and escalation into distinct nodes.
    • You get better observability and safer failure modes.

If you build this as a graph first and an LLM second, you get something healthcare teams can actually approve. That’s the difference between a demo bot and a support agent that survives compliance review.


Keep learning

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

Related Guides