By Sagar Shankaran, Founder of CallSphere
Build an AI agent that performs initial crisis assessments, matches callers with appropriate resources, and executes warm handoffs to trained counselors — with safety-first design principles.
Key takeaways
Crisis helplines handle deeply sensitive interactions. An AI agent in this context must follow one unwavering principle: the agent triages and routes, it never counsels. This tutorial builds an agent that performs structured assessments, matches callers with resources, and executes warm handoffs to trained counselors. Safety always takes priority over efficiency.
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
from enum import Enum
from uuid import uuid4
class RiskLevel(Enum):
LOW = "low"
MODERATE = "moderate"
HIGH = "high"
CRITICAL = "critical"
class CrisisCategory(Enum):
MENTAL_HEALTH = "mental_health"
SUBSTANCE_USE = "substance_use"
DOMESTIC_VIOLENCE = "domestic_violence"
HOMELESSNESS = "homelessness"
FOOD_INSECURITY = "food_insecurity"
GENERAL_DISTRESS = "general_distress"
@dataclass
class CrisisAssessment:
assessment_id: str = field(default_factory=lambda: str(uuid4()))
caller_name: str = "Anonymous"
crisis_category: Optional[CrisisCategory] = None
risk_level: RiskLevel = RiskLevel.LOW
is_immediate_danger: bool = False
has_suicidal_ideation: bool = False
assessment_notes: str = ""
created_at: datetime = field(default_factory=datetime.utcnow)
handoff_to: Optional[str] = None
@dataclass
class CrisisResource:
resource_id: str = field(default_factory=lambda: str(uuid4()))
name: str = ""
category: CrisisCategory = CrisisCategory.GENERAL_DISTRESS
phone: str = ""
serves_area: list[str] = field(default_factory=list)
accepts_walk_ins: bool = False
The assessment tool uses structured questions and keyword detection to classify risk level.
flowchart LR
CALLER(["Donor or Volunteer"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Nonprofit AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Donation pledge captured"])
O2(["Volunteer slot booked"])
O3(["Program lead handoff"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from agents import function_tool
assessments_log: list[CrisisAssessment] = []
CRITICAL_INDICATORS = [
"suicide", "kill myself", "end my life", "want to die",
"self-harm", "cutting", "overdose", "gun",
"being beaten", "he is hitting me", "she is hitting me",
"child is being hurt", "locked in",
]
HIGH_RISK_INDICATORS = [
"not safe", "afraid", "threatened", "stalking",
"nowhere to go", "sleeping outside", "evicted today",
"relapse", "withdrawal", "haven't eaten",
]
@function_tool
async def perform_assessment(
caller_description: str,
caller_name: str = "Anonymous",
caller_city: str = "",
caller_state: str = "",
) -> dict:
"""Perform an initial crisis assessment based on the caller's
description. Classifies risk level and identifies needs."""
text_lower = caller_description.lower()
risk_level = RiskLevel.LOW
is_danger = False
has_si = False
if any(ind in text_lower for ind in CRITICAL_INDICATORS):
risk_level = RiskLevel.CRITICAL
is_danger = True
has_si = any(w in text_lower for w in [
"suicide", "kill myself", "end my life", "want to die"])
elif any(ind in text_lower for ind in HIGH_RISK_INDICATORS):
risk_level = RiskLevel.HIGH
category = CrisisCategory.GENERAL_DISTRESS
for cat, kws in {
CrisisCategory.MENTAL_HEALTH: ["depressed", "anxiety", "panic"],
CrisisCategory.SUBSTANCE_USE: ["drinking", "drugs", "relapse"],
CrisisCategory.DOMESTIC_VIOLENCE: ["hitting", "abuse", "violent"],
CrisisCategory.HOMELESSNESS: ["homeless", "shelter", "evicted"],
}.items():
if any(kw in text_lower for kw in kws):
category = cat
break
assessment = CrisisAssessment(
caller_name=caller_name, crisis_category=category,
risk_level=risk_level, is_immediate_danger=is_danger,
has_suicidal_ideation=has_si, assessment_notes=caller_description,
)
assessments_log.append(assessment)
response = {"assessment_id": assessment.assessment_id,
"risk_level": risk_level.value, "category": category.value,
"is_immediate_danger": is_danger}
if risk_level == RiskLevel.CRITICAL:
response["immediate_action"] = (
"CRITICAL: Initiate warm handoff. Advise calling 911 if "
"in physical danger. Provide 988 for mental health crisis.")
return response
After assessment, the agent matches the caller with relevant local resources.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for behavioral health in your browser — 60 seconds, no signup.
resources_db: list[CrisisResource] = []
NATIONAL_RESOURCES = [
{"name": "988 Suicide & Crisis Lifeline", "phone": "988",
"category": "mental_health"},
{"name": "National Domestic Violence Hotline",
"phone": "1-800-799-7233", "category": "domestic_violence"},
{"name": "SAMHSA National Helpline", "phone": "1-800-662-4357",
"category": "substance_use"},
]
@function_tool
async def find_resources(
crisis_category: str,
state: str = "",
) -> dict:
"""Find crisis resources matching the caller's category."""
category = CrisisCategory(crisis_category)
matches = []
for nr in NATIONAL_RESOURCES:
if nr["category"] == crisis_category:
matches.append({"name": nr["name"], "phone": nr["phone"],
"type": "national"})
for resource in resources_db:
if resource.category != category:
continue
if state and resource.serves_area and state not in resource.serves_area:
continue
matches.append({"name": resource.name, "phone": resource.phone,
"type": "local", "walk_ins": resource.accepts_walk_ins})
return {"resources": matches, "total_found": len(matches)}
The warm handoff ensures the caller is connected to a human, not just given a phone number.
@function_tool
async def initiate_warm_handoff(
assessment_id: str,
counselor_type: str,
urgency: str = "standard",
) -> dict:
"""Initiate a warm handoff to a trained counselor."""
assessment = None
for a in assessments_log:
if a.assessment_id == assessment_id:
assessment = a
break
if not assessment:
return {"error": "Assessment not found"}
counselor_queue = {
"mental_health": "Licensed Mental Health Counselor",
"substance_use": "Substance Abuse Counselor",
"domestic_violence": "DV Advocate",
"general": "Crisis Counselor",
}
counselor = counselor_queue.get(counselor_type, "Crisis Counselor")
assessment.handoff_to = counselor
return {
"status": "handoff_initiated",
"counselor_type": counselor,
"estimated_wait": "under 2 minutes" if urgency == "critical"
else "5-10 minutes",
"risk_level": assessment.risk_level.value,
}
from agents import Agent, Runner
crisis_agent = Agent(
name="Crisis Helpline Agent",
instructions="""You are a crisis helpline triage agent.
Your role: assess, connect, and support — NEVER counsel.
RULES:
1. Suicidal ideation detected -> provide 988 + warm handoff
2. Physical danger -> advise calling 911 first
3. NEVER attempt therapy or clinical advice
4. Stay with the caller until a counselor connects
5. For low-risk, provide resources and offer to check back
TONE: Warm, calm, empathetic, non-judgmental.""",
tools=[
perform_assessment,
find_resources,
initiate_warm_handoff,
],
)
result = Runner.run_sync(
crisis_agent,
"I just lost my apartment and I have nowhere to go tonight. "
"I have two kids with me. It is getting cold outside.",
)
print(result.final_output)
AI should never replace trained crisis counselors. This agent serves as a triage layer that reduces wait times by gathering initial information and routing callers to the right specialist. The agent always defers to human counselors for anything beyond basic assessment. Every interaction is logged for supervisory review.
Still reading? Stop comparing — try CallSphere live.
See the behavioral health AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
In production, the warm handoff integrates with a call center platform like Twilio. When initiated, the caller enters a priority queue, the assessment summary appears on the counselor's screen, and the connection is maintained until the counselor confirms they are on the line.
The agent provides direct phone numbers for national crisis lines (988, NDVH, SAMHSA) and stays engaged with the caller using grounding techniques developed by clinical staff until a counselor becomes available. For critical-risk cases, the agent advises calling 911.
#CrisisSupport #AISafety #NonprofitAI #AgenticAI #Python #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
How to build a safety eval pipeline that runs known jailbreak corpora, prompt-injection attacks, and tool-misuse scenarios on every release — and gates merges on it.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Stop the agent BEFORE it does the wrong thing. How to wire input and output guardrails in the OpenAI Agents SDK with cheap classifiers and an eval suite that proves they work.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco