# Project export: PETSOS

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Instantly connect panicked pet owners to life-saving guidance and nearby emergency vets—just by speaking.
- Devpost: https://devpost.com/software/petsos-0iqmaz
- GitHub: https://github.com/ashleyvarghesee/petsos
- Demo: https://www.canva.com/design/DAGrGmql6rI/JA0bI8kjSJowPQ0TGuKOjw/edit?utm_content=DAGrGmql6rI&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton
- Team: 1 GitHub contributor(s) — ashleyvarghese (1 commits)

## Devpost submission (written by the team)

### Inspiration

This project was inspired by my family friend's loss of their dog during a critical moment of crisis. It shed light on an overlooked issue: emergency pet health service. As a pet owner, it terrifies me that there is no 911 for our loved pets. So with the power of AI - I took on the challenge.

### What it does

PET911 is a voice-activated emergency assistant that guides pet owners through life-threatening situations — completely hands-free. It simulates a real 911-style call for pets and responds with calm, intelligent, and timely support. Here's what it does: 🗣️ Understands your voice in real-time Users can call a real phone number and speak naturally. PET911 uses voice streaming through Vapi to transcribe speech instantly and maintain a fluid, multi-turn conversation. 🤖 Provides life-saving guidance using AI Powered by Claude, the assistant triages emergencies by asking crucial questions and delivering context-specific, step-by-step instructions (e.g., performing the Heimlich maneuver for a choking dog based on its breed and weight). 🧠 Remembers what you said The assistant uses memory from the conversation so it doesn’t repeat itself or forget details like the pet’s condition or size — enabling natural, human-like dialogue even in high-stress moments. 🌐 Finds the closest emergency vet clinic PET911 uses your ZIP code or live coordinates to identify and link you to the closest open emergency clinic—ensuring fast, location-aware care when every second counts. 📍 Sends an SMS with directions PET911 identifies a nearby emergency clinic and sends a text message with a direct link to the location. This allows the user to begin navigation immediately — skipping the stress of searching online or fumbling with maps during a crisis. 🔄 Transfers to a live vet If needed, PET911 offers to transfer the call to a pre-set backup assistant, simulating the experience of speaking with a real vet receptionist in the area — for needed escalation. ❤️ Offers emotional support The AI is designed to be calm, empathetic, and reassuring — because pet emergencies are overwhelming, and users need to feel safe and heard.

### How we built it

PET911 runs on a modular multi-agent system built with a Flask backend. A voice interface powered by Vapi captures real-time input, which is routed through specialized agents: a Triage Agent for rapid emergency assessment, a Memory Agent for contextual continuity, and a Decision Agent to determine next steps. Claude serves as the core reasoning engine. For location support, we integrated OpenStreetMap, and emergency routing is handled via SMS through Textbelt.

### Challenges we ran into

We hit roadblocks integrating Fetch.ai’s Python SDK due to package conflicts, which led us to rethink how we handled real-time vet discovery. Vapi's real-time voice streaming also came with unexpected 404 webhook issues and delayed responses, requiring deep debugging. On top of that, prompting Claude for step-by-step, non-overwhelming instructions demanded careful prompt engineering to simulate a calm, conversational emergency assistant.

### Accomplishments we're proud of

Built a fully functional voice AI emergency system from scratch using Vapi and Claude, with natural, multi-turn memory and contextual awareness. Integrated a simulated agent ecosystem combining triage, decision-making, and location support under emergency pressure. Engineered real-time responses that adapt to user input instead of overwhelming them—just like a real 911 operator would. Successfully routed emergency info via SMS, with geolocation-based vet directions and calming follow-ups. Debugged complex streaming issues and webhooks under time pressure and got everything running seamlessly across tools.

### What we learned

Building PET911 taught me how to design for urgency, empathy, and clarity under pressure. I deepened my skills in real-time voice interaction, multi-agent system design, and external API integration. I also learned how to troubleshoot complex streaming and webhook errors, all while keeping the user experience as the top priority—especially in life-or-death scenarios.

## README (from the GitHub repository)

# 🐾 PET911 - AI-Powered Pet Emergency Assistant

A voice-activated AI assistant for pet emergencies with real-time SMS notifications and vet locator services.

## 🚀 Features

### Core Functionality
- **Voice-Activated Emergency Response** - Natural conversation with pet owners during emergencies
- **Real-Time SMS Notifications** - Automatic vet locator links sent via SMS when calls start
- **Intelligent Triage System** - Step-by-step first-aid guidance for common pet emergencies
- **Live Vet Transfer** - Seamless transfer to emergency veterinarians when needed
- **Bilingual Support** - English and Spanish language support
- **Vet Locator Service** - Web-based emergency clinic finder with directions

### Technical Features
- **Real-Time Decision Logic** - Dynamic webhook processing with intelligent event routing
- **Robust Phone Number Detection** - Recursive payload analysis for reliable SMS delivery
- **Quota Management** - Smart SMS limiting to prevent API abuse
- **Error Handling** - Graceful fallbacks and comprehensive logging
- **Memory-Free Design** - Consistent, reliable responses every time

## 🛠️ Tech Stack

- **Backend**: Flask (Python)
- **Voice Platform**: Vapi.ai
- **LLM**: Anthropic Claude 3 Haiku
- **SMS Service**: Textbelt API
- **Geolocation**: OpenStreetMap Nominatim API
- **Voice Synthesis**: ElevenLabs
- **Speech Recognition**: Deepgram Nova-2

## 📋 Prerequisites

- Python 3.8+
- ngrok (for webhook tunneling)
- Vapi.ai account
- Anthropic API key
- Textbelt API key
- ElevenLabs API key

## 🚀 Quick Start

### 1. Clone the Repository
```bash
git clone https://github.com/yourusername/pet911.git
cd pet911
```

### 2. Install Dependencies
```bash
pip install -r requirements.txt
```

### 3. Environment Setup
Create a `.env` file in the root directory:
```env
ANTHROPIC_API_KEY=your_anthropic_key_here
TEXTBELT_API_KEY=your_textbelt_key_here
ELEVENLABS_API_KEY=your_elevenlabs_key_here
```

### 4. Start ngrok Tunnel
```bash
ngrok http 5000
```

### 5. Configure Vapi Assistant
1. Go to [Vapi.ai Dashboard](https://dashboard.vapi.ai)
2. Create a new assistant
3. Set the webhook URL to your ngrok URL + `/vapi-webhook`
4. Configure voice settings (ElevenLabs recommended)
5. Set up phone number routing

### 6. Run the Application
```bash
python app.py
```

## 📁 Project Structure

```
petSOS/
├── agents/
│   ├── __init__.py
│   ├── llm_agent.py          # Claude LLM integration
│   └── triage_agent.py       # Emergency triage logic
├── vet_locator/
│   ├── main_agent.py         # Vet locator service
│   ├── sms_service.py        # SMS integration
│   └── templates/
│       └── index.html        # Vet locator web interface
├── app.py                    # Main Flask application
├── requirements.txt          # Python dependencies
├── test_sms.py              # SMS testing utility
└── README.md                # This file
```

## 🔧 Configuration

### SMS Settings
- **Target Numbers**: Configure in `app.py` line 264
- **SMS Quota**: Adjust `MAX_SMS_PER_SESSION` in `app.py` line 30
- **Vet Locator URL**: Update `VET_LOCATOR_URL` in `app.py` line 28

### Emergency Response
- **Transfer Number**: Set in Vapi assistant configuration
- **First Aid Protocols**: Customize in `agents/llm_agent.py`
- **Language Support**: Configure in system prompts

## 🧪 Testing

### Test SMS Functionality
```bash
python test_sms.py
```

### Test Vet Locator
1. Start the Flask server
2. Visit `http://localhost:5000/vet-locator`
3. Enter a location to find nearby clinics

### Test Voice Assistant
1. Call your configured Vapi phone number
2. Describe a pet emergency
3. Verify SMS is sent automatically
4. Test vet transfer functionality

## 📊 API Endpoints

- `POST /vapi-webhook/chat/completions` - LLM chat completions
- `POST /vapi-webhook` - Vapi event webhooks
- `GET /vet-locator` - Vet locator web interface
- `POST /vet-locator/api/clinics` - Clinic search API
- `GET /vet-locator/api/health` - Health check

## 🔒 Security Considerations

- API keys stored in environment variables
- SMS quota limiting to prevent abuse
- Input validation on all endpoints
- Error handling without sensitive data exposure

## 🤝 Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## 📝 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 🙏 Acknowledgments

- **Vapi.ai** for voice platform infrastructure
- **Anthropic** for Claude LLM capabilities
- **OpenStreetMap** for geolocation services
- **Textbelt** for SMS delivery
- **ElevenLabs** for voice synthesis

## 📞 Support

For support, email support@pet911.com or create an issue in this repository.

---

**Built with ❤️ for pet safety and emergency response** 

## Detected evidence (automated analysis)

Indexed codebase: 10 recognized source files, 53 KB.
- Anthropic (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- JavaScript (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (15 of 15)

```
.gitignore
agents/__init__.py
agents/llm_agent.py
agents/triage_agent.py
app.py
env.example
LICENSE
README.md
requirements.txt
test_sms.py
vet_locator/main_agent.py
vet_locator/README.md
vet_locator/requirements.txt
vet_locator/sms_service.py
vet_locator/templates/index.html
```

### Dependencies

- requirements.txt: anthropic, Flask@==3.0.0, python-dotenv@==1.0.0, requests@==2.31.0
- vet_locator/requirements.txt: flask@==3.0.0, python-dotenv@==1.0.0, requests@==2.31.0, twilio@==9.6.3

### Recent commits (newest first)

- Initial commit: PET911 AI-powered pet emergency assistant

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
Flask==3.0.0
requests==2.31.0
python-dotenv==1.0.0
anthropic 
```

### vet_locator/requirements.txt

```
flask==3.0.0
requests==2.31.0
python-dotenv==1.0.0
twilio==9.6.3 
```

### app.py

```python
import json
import time
import random
import string
import requests
import os
from flask import Flask, request, Response, send_from_directory
from dotenv import load_dotenv

# Import the new LLM agent
from agents.llm_agent import get_llm_response

# Import vet locator SMS service
try:
    from vet_locator.sms_service import sms_service
    VET_LOCATOR_AVAILABLE = True
    print("✅ Vet locator SMS service loaded")
except ImportError:
    VET_LOCATOR_AVAILABLE = False
    print("⚠️  Vet locator SMS service not available")

# Load environment variables
load_dotenv()

app = Flask(__name__)

# Textbelt SMS configuration
TEXTBELT_API_KEY = os.getenv('TEXTBELT_API_KEY', 'textbelt')  # Use env var or fallback to free testing key
VET_LOCATOR_URL = "https://f93a-2600-1012-b225-308f-61e3-f4c0-8bcb-c74b.ngrok-free.app/vet-locator"

# Track which call IDs have already triggered an SMS (in-memory, resets on server restart)
sms_sent_call_ids = set()
MAX_SMS_PER_SESSION = 1  # Safety limit to prevent using up all quota - only 1 SMS per session

def send_textbelt_sms(phone_number, message):
    """
    Send SMS using Textbelt API
    """
    # Safety check - don't send if we've already sent too many
    if len(sms_sent_call_ids) >= MAX_SMS_PER_SESSION:
        print(f"⚠️  SMS quota limit reached ({MAX_SMS_PER_SESSION}). Not sending SMS to {phone_number}")
        return False
    
    try:
        # Format phone number (remove + if present)
        formatted_phone = phone_number.replace('+', '') if phone_number.startswith('+') else phone_number
        
        # Prepare the SMS data
        sms_data = {
            'phone': formatted_phone,
            'message': message,
            'key': TEXTBELT_API_KEY
        }
        
        # Send SMS via Textbelt
        response = requests.post('https://textbelt.com/text', data=sms_data)
        result = response.json()
        
        if result.get('success'):
            print(f"✅ Textbelt SMS sent successfully to {phone_number}")
            print(f"📊 Quota remaining: {result.get('quotaRemaining', 'Unknown')}")
            return True
        else:
            print(f"❌ Textbelt SMS failed: {result.get('error', 'Unknown error')}")
            return False
            
    except Exception as e:
        print(f"❌ Error sending Textbelt SMS: {e}")
        return False

def send_vet_locator_sms(phone_number):
    """
    Send vet locator link via Textbelt SMS (obfuscated with [dot] and [slash])
    """
    message = (
        "🐾 PET911 Emergency Vet Locator\n\n"
        "Find nearby emergency vet clinics at: http[colon][slash][slash]f93a-2600-1012-b225-308f-61e3-f4c0-8bcb-c74b[dot]ngrok-free[dot]app[slash]vet-locator\n\n"
        "Stay safe with your pet! 🐕🐱"
    )
    return send_textbelt_sms(phone_number, message)

def detect_poor_transcription(text):
    """
    Detect if Vapi's transcription is likely poor quality.
    Returns True if the transcription seems garbled or unclear.
    """
    if not text:
        return True
    
    # Check for very short responses that might be incomplete
    if len(text.strip()) < 3:
        return True
    
    # Check for repeated characters (common in poor transcription)
    for char in text:
        if text.count(char) > len(text) * 0.4:  # If any character is >40% of the text
            return True
    
    # Check for common transcription artifacts
    artifacts = ['...', 'um', 'uh', 'ah', 'er', 'mm', 'hmm']
    if any(artifact in text.lower() for artifact in artifacts):
        return True
    
    return False

def send_vet_locator_link(phone_number, location=None, pet_info=None):
    """
    Send vet locator link via SMS to caller
    
    Args:
        phone_number (str): Caller's phone number
        location (str): Optional ZIP code or coordinates
        pet_info (dict): Optional pet information
    """
    if not VET_LOCATOR_AVAILABLE:
        print("❌ Vet locator SMS service not available")
        return False
    
    try:
        if pet_info:
            success = sms_service.send_emergency_vet_sms(phone_number, pet_info)
        else:
            success = sms_service.send_vet_locator_sms(phone_number, location)
        
        if success:
            print(f"✅ Vet locator SMS sent to {phone_number}")
        else:
            print(f"❌ Failed to send vet locator SMS to {phone_number}")
        
        return success
        
    except Exception as e:
        print(f"❌ Error sending vet locator SMS: {e}")
        return False

# MAIN LLM ENDPOINT - Now supports streaming
@app.route('/vapi-webhook/chat/completions', methods=['POST'])
def vapi_chat_completions():
    data = request.json
    print("🟢 Received /chat/completions request")

    conversation_history = data.get("messages", [])
    phone_number = None
    if 'customer' in data and 'number' in data['customer']:
        phone_number = data['customer']['number']
        print(f"📱 User phone number: {phone_number}")

    # Remove memory-based greeting: do NOT use memory to personalize greeting
    # Only use standard greeting every time

    # Check the last user message for poor transcription
    if conversation_history:
        last_user_message = None
        for msg in reversed(conversation_history):
            if msg.get("role") == "user":
                last_user_message = msg.get("content", "")
                break
        if last_user_message and detect_poor_transcription(last_user_message):
            print(f"⚠️  Detected poor transcription: '{last_user_message}'")
            conversation_history.append({
                "role": "system",
                "content": "The user's last message may have been poorly transcribed by the speech recognition system. If their response seems unclear or incomplete, ask them to repeat or speak more clearly."
            })

    try:
        print(f"🟢 Calling get_llm_response with {len(conversation_history)} messages")
        llm_stream = get_llm_response(conversation_history, None)  # R
[truncated — 14398 more characters]
```

### test_sms.py

```python
import requests
import json
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

def test_textbelt_sms():
    """Test Textbelt SMS functionality"""
    
    # Your phone number
    phone_number = "2093629161"  # Remove the + for Textbelt
    
    # Test message
    message = "🐾 PET911 Test SMS\n\nThis is a test message from PET911 Emergency Vet Locator.\n\nFind nearby emergency vet clinics at: http[colon][slash][slash]f93a-2600-1012-b225-308f-61e3-f4c0-8bcb-c74b[dot]ngrok-free[dot]app[slash]vet-locator\n\nStay safe with your pet! 🐕🐱"
    
    # Get API key from environment variable
    api_key = os.getenv('TEXTBELT_API_KEY', 'textbelt')
    
    # Textbelt API data
    sms_data = {
        'phone': phone_number,
        'message': message,
        'key': api_key
    }
    
    print(f"📤 Testing SMS to: {phone_number}")
    print(f"📝 Message: {message[:50]}...")
    print(f"🔑 Using key: {api_key}")
    print("\n🚀 Sending SMS...")
    
    try:
        # Send SMS via Textbelt
        response = requests.post('https://textbelt.com/text', data=sms_data)
        result = response.json()
        
        print(f"\n📊 Response Status: {response.status_code}")
        print(f"📊 Response: {json.dumps(result, indent=2)}")
        
        if result.get('success'):
            print(f"\n✅ SMS sent successfully!")
            print(f"📊 Quota remaining: {result.get('quotaRemaining', 'Unknown')}")
            print(f"🆔 Text ID: {result.get('textId', 'N/A')}")
            return True
        else:
            print(f"\n❌ SMS failed: {result.get('error', 'Unknown error')}")
            return False
            
    except Exception as e:
        print(f"\n❌ Error sending SMS: {e}")
        return False

if __name__ == "__main__":
    print("🧪 PET911 SMS Test")
    print("=" * 50)
    
    success = test_textbelt_sms()
    
    if success:
        print("\n🎉 Test completed successfully! Check your phone for the SMS.")
    else:
        print("\n💥 Test failed. Check the error message above.") 
```

### agents/__init__.py

```python
# PET911 Agents Package 
```

### agents/triage_agent.py

```python
def handle_triage(user_input):
    """
    Basic triage agent for PET911 emergency responses.
    This is a temporary mock implementation that will be replaced with AI logic.
    """
    user_input_lower = user_input.lower()
    
    # Emergency keywords detection
    if "choking" in user_input_lower:
        return "What breed and size is your dog?"
    elif "swallowed" in user_input_lower:
        return "What did your pet swallow and when? This is important for determining the urgency."
    elif "bleeding" in user_input_lower:
        return "Where is the bleeding located? Is it severe or minor?"
    elif "poison" in user_input_lower or "toxic" in user_input_lower:
        return "What substance did your pet ingest? Do you have the packaging?"
    elif "seizure" in user_input_lower:
        return "How long has the seizure been going on? Is this the first time?"
    elif "hit by car" in user_input_lower or "accident" in user_input_lower:
        return "Is your pet conscious? Can they move all their limbs?"
    else:
        return "Please describe the emergency in detail so I can provide the best guidance." 
```

### vet_locator/main_agent.py

```python
from flask import Flask, request, jsonify, send_from_directory
import requests
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

app = Flask(__name__)

@app.route('/vet-locator')
def index():
    return send_from_directory('templates', 'index.html')

@app.route('/vet-locator/api/clinics', methods=['POST'])
def get_clinics():
    """Get nearby emergency vet clinics based on location"""
    data = request.json
    location = data.get('zip') or data.get('location')
    
    if not location:
        return jsonify({'error': 'ZIP or location required'}), 400

    try:
        # Use OpenStreetMap Nominatim API to find emergency vet clinics
        search_query = f"emergency+vet+clinic+{location}"
        resp = requests.get(
            f'https://nominatim.openstreetmap.org/search',
            params={
                'q': search_query,
                'format': 'json',
                'limit': 5,
                'addressdetails': 1
            },
            headers={'User-Agent': 'PET911-VetLocator/1.0'}
        )
        
        if resp.status_code != 200:
            return jsonify({'error': 'Failed to fetch clinic data'}), 500
            
        data = resp.json()
        
        # Process and format clinic data
        clinics = []
        for clinic in data[:5]:  # Top 5 results
            display_name = clinic.get('display_name', 'Unknown Clinic')
            
            # Clean up the display name for better readability
            name_parts = display_name.split(',')
            clinic_name = name_parts[0] if name_parts else 'Emergency Vet Clinic'
            
            clinics.append({
                'name': clinic_name,
                'address': display_name,
                'lat': clinic.get('lat'),
                'lon': clinic.get('lon'),
                'distance': clinic.get('distance', 'Unknown')
            })
        
        return jsonify({
            'clinics': clinics,
            'location': location,
            'count': len(clinics)
        })
        
    except Exception as e:
        print(f"Error fetching clinics: {e}")
        return jsonify({'error': 'Failed to fetch clinic data'}), 500

@app.route('/vet-locator/api/health')
def health_check():
    """Health check endpoint"""
    return jsonify({'status': 'healthy', 'service': 'vet-locator'})

if __name__ == '__main__':
    port = int(os.getenv('VET_LOCATOR_PORT', 5000))  # Changed to 5000 to match main app
    app.run(host='0.0.0.0', port=port, debug=True) 
```

### vet_locator/sms_service.py

```python
import os
from twilio.rest import Client
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

class VetLocatorSMS:
    def __init__(self):
        """Initialize Twilio client for SMS service"""
        self.account_sid = os.getenv('TWILIO_ACCOUNT_SID')
        self.auth_token = os.getenv('TWILIO_AUTH_TOKEN')
        self.from_number = os.getenv('TWILIO_FROM_NUMBER')  # Your Vapi phone number
        self.vet_locator_url = os.getenv('VET_LOCATOR_URL')  # Your ngrok URL
        
        if not all([self.account_sid, self.auth_token, self.from_number]):
            print("⚠️  Warning: Twilio credentials not found. SMS service will be disabled.")
            self.client = None
        else:
            self.client = Client(self.account_sid, self.auth_token)
    
    def send_vet_locator_sms(self, to_phone, location=None):
        """
        Send SMS with vet locator link to caller
        
        Args:
            to_phone (str): Phone number to send SMS to
            location (str): Optional ZIP code or coordinates to pre-fill
        """
        if not self.client:
            print("❌ SMS service not available - Twilio credentials missing")
            return False
        
        try:
            # Build the URL with optional location parameter
            url = self.vet_locator_url
            if location:
                url += f"?zip={location}"
            
            # Create the SMS message
            message_body = (
                f"🚨 PET911 Emergency Vet Locator\n\n"
                f"Find nearby emergency veterinary clinics:\n"
                f"{url}\n\n"
                f"Click the link to find the closest emergency vet to your location. "
                f"Stay calm and get your pet help quickly!"
            )
            
            # Send the SMS
            message = self.client.messages.create(
                body=message_body,
                from_=self.from_number,
                to=to_phone
            )
            
            print(f"✅ SMS sent successfully to {to_phone}")
            print(f"📱 Message SID: {message.sid}")
            return True
            
        except Exception as e:
            print(f"❌ Failed to send SMS to {to_phone}: {e}")
            return False
    
    def send_emergency_vet_sms(self, to_phone, pet_info=None):
        """
        Send emergency vet SMS with additional context
        
        Args:
            to_phone (str): Phone number to send SMS to
            pet_info (dict): Optional pet information for context
        """
        if not self.client:
            print("❌ SMS service not available - Twilio credentials missing")
            return False
        
        try:
            # Build the URL
            url = self.vet_locator_url
            
            # Create personalized message
            if pet_info:
                pet_name = pet_info.get('name', 'your pet')
                pet_type = pet_info.get('type', 'pet')
                message_body = (
                    f"🚨 PET911 Emergency Alert\n\n"
                    f"Emergency vet locator for {pet_name} ({pet_type}):\n"
                    f"{url}\n\n"
                    f"Find the nearest emergency veterinary clinic. "
                    f"Drive safely and call ahead if possible."
                )
            else:
                message_body = (
                    f"🚨 PET911 Emergency Vet Locator\n\n"
                    f"Find nearby emergency veterinary clinics:\n"
                    f"{url}\n\n"
                    f"Get your pet help quickly. Drive safely!"
                )
            
            # Send the SMS
            message = self.client.messages.create(
                body=message_body,
                from_=self.from_number,
                to=to_phone
            )
            
            print(f"✅ Emergency vet SMS sent to {to_phone}")
            print(f"📱 Message SID: {message.sid}")
            return True
            
        except Exception as e:
            print(f"❌ Failed to send emergency vet SMS to {to_phone}: {e}")
            return False

# Global instance for easy access
sms_service = VetLocatorSMS() 
```

### agents/llm_agent.py

```python
import os
import json
import anthropic
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

# Initialize the Anthropic client
try:
    # Note: Ensure you have ANTHROPIC_API_KEY in your .env file
    api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        raise ValueError("ANTHROPIC_API_KEY not found in .env file.")
    client = anthropic.Anthropic(api_key=api_key)
except ValueError as e:
    print(f"Error initializing Anthropic client: {e}")
    client = None

# System prompt to define the AI's role and behavior
SYSTEM_PROMPT = """
You are PET911, a caring emergency assistant for pet owners. Talk like a real person - warm, calm, and helpful.

TRANSFER RULES:
- USE the transferCall tool ONLY when someone EXPLICITLY asks to speak to a vet
- Examples: "Can I speak to a vet?", "I need to talk to a vet", "Transfer me to a vet", "I want to speak to a veterinarian"
- DO NOT transfer for:
  * Medical emergency descriptions like "my dog is choking"
  * Medical questions like "What do I do when she's coughing?"
  * Follow-up care questions like "How do I help her?"
  * General pet health questions
- When someone asks to speak to a vet, use the transferCall tool and say nothing at all
- DO NOT say "transferring you" or "let me transfer you" or anything about transferring
- DO NOT make up stories about medical emergencies when someone asks to speak to a vet
- DO NOT use names unless they tell you their name

TALK LIKE A HUMAN:
- Be conversational and warm
- Don't say "here are the steps" or "first, second, third"
- Don't count out loud or be robotic
- Say things like "I want you to..." or "Can you do that for me?"
- Check in with them: "How's that going?" or "Are you okay?"
- Be encouraging: "Good job" or "You're doing great"
- Keep responses SHORT - 1-2 sentences max
- Don't give long speeches or pep talks
- NEVER give multiple instructions in one response
- Don't repeat information they already know
- For thrusts: just say "Okay, give one thrust now" - no counting

EMERGENCY RESPONSE:
When someone describes a pet emergency, gather essential information naturally:
1. If they haven't mentioned pet type/breed/size, ask briefly
2. Ask about age if not mentioned  
3. Get details about what happened and symptoms
4. Then provide step-by-step help

STEP-BY-STEP GUIDANCE:
- Give ONE small action at a time
- Wait for their response before the next step
- Be encouraging and supportive
- Don't overwhelm with multiple instructions
- Keep it simple and direct
- For Heimlich: guide them through ONE thrust at a time, not multiple thrusts
- Don't repeat pet details they already told you
- Just say "Okay, give one thrust now" - no counting needed

LANGUAGE: Match the user's language (English/Spanish)

EXAMPLES:
User: "My dog is choking"
You: "I can help with that. What breed and size is your dog?"

User: "Golden retriever, about 50 pounds"
You: "How old is your dog?"

User: "3 years old"
You: "What exactly is happening? What symptoms are you seeing?"

User: "He's coughing and pawing at his mouth"
You: "I'm here to help. I want you to gently open your dog's mouth and look inside. Can you do that for me?"

User: "I don't see anything"
You: "That's okay. Now place one hand on his chest, just behind the front legs. Can you do that?"

User: "Yes, I'm doing that"
You: "Good. Now with your other hand, make a fist and place it just below the rib cage. Are you ready?"

User: "Yes"
You: "Perfect. Okay, give one thrust now."

User: "I did it"
You: "Great job. How's he doing? Is he coughing or breathing better?"

User: "She's starting to cough up"
You: "Good. Keep encouraging her to cough. Let me know if she's still struggling."

User: "What do I do when she's coughing?"
You: "That's actually a good sign - it means the object is coming up. Keep encouraging her to cough and let her work it out naturally. Just stay calm and supportive."

BE NATURAL - talk like you're really there helping them, not like a robot giving instructions. Keep responses short and helpful. NEVER give multiple steps or long speeches.
"""

# Define the tool for call transfers, matching Vapi's expected format
TRANSFER_TOOL = {
    "name": "transferCall",
    "description": "Transfer the call to a veterinarian",
    "input_schema": {
        "type": "object",
        "properties": {},
        "required": []
    }
}

def get_llm_response(messages, phone_number=None):
    """
    Get response from Claude (no memory system)
    """
    # Check if client is properly initialized
    if not client:
        raise ValueError("Anthropic client not initialized. Please check your ANTHROPIC_API_KEY.")
    
    # Prepare messages for Claude (filter out system messages)
    claude_messages = []
    for msg in messages:
        if msg.get("role") == "user":
            claude_messages.append({"role": "user", "content": msg.get("content", "")})
        elif msg.get("role") == "assistant":
            claude_messages.append({"role": "assistant", "content": msg.get("content", "")})
    
    # Get response from Claude
    try:
        response = client.messages.create(
            model="claude-3-haiku-20240307",
            max_tokens=1000,
            system=SYSTEM_PROMPT,
            messages=claude_messages,
            tools=[TRANSFER_TOOL],
            stream=True
        )
        
        return response
        
    except Exception as e:
        print(f"Error getting LLM response: {e}")
        raise 
```

### vet_locator/templates/index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Emergency Vets Near You - PET911</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .header {
            background: linear-gradient(135deg, #ff6b6b, #ee5a24);
            color: white;
            padding: 20px;
            border-radius: 10px;
            text-align: center;
            margin-bottom: 20px;
        }
        .search-section {
            background: white;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        .button {
            background: #ff6b6b;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin: 5px;
        }
        .button:hover {
            background: #ee5a24;
        }
        .button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
        input[type="text"] {
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
            width: 200px;
            margin: 5px;
        }
        .results {
            background: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }
        .clinic-item {
            border: 1px solid #eee;
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
            background: #fafafa;
        }
        .clinic-name {
            font-weight: bold;
            color: #333;
            font-size: 18px;
            margin-bottom: 5px;
        }
        .clinic-address {
            color: #666;
            font-size: 14px;
            margin-bottom: 10px;
        }
        .map-link {
            background: #4CAF50;
            color: white;
            text-decoration: none;
            padding: 8px 16px;
            border-radius: 3px;
            display: inline-block;
            font-size: 14px;
        }
        .map-link:hover {
            background: #45a049;
        }
        .loading {
            text-align: center;
            color: #666;
            font-style: italic;
        }
        .error {
            color: #d32f2f;
            background: #ffebee;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
        }
        .location-info {
            background: #e3f2fd;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
            color: #1976d2;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>🚨 Emergency Vets Near You</h1>
        <p>PET911 - Find the nearest emergency veterinary clinic</p>
    </div>

    <div class="search-section">
        <h3>Find Emergency Vet Clinics</h3>
        <button class="button" onclick="useGeo()" id="geoBtn">
            📍 Use My Location
        </button>
        <br><br>
        <input type="text" id="location" placeholder="Or enter ZIP code or city name" />
        <button class="button" onclick="search()" id="searchBtn">🔍 Search</button>
        
        <div id="locationInfo" class="location-info" style="display: none;"></div>
    </div>

    <div class="results">
        <h3>Nearby Emergency Vet Clinics</h3>
        <div id="loading" class="loading" style="display: none;">
            Searching for emergency vet clinics...
        </div>
        <div id="error" class="error" style="display: none;"></div>
        <ul id="results"></ul>
    </div>

    <script>
        async function useGeo() {
            const geoBtn = document.getElementById('geoBtn');
            const locationInfo = document.getElementById('locationInfo');
            
            geoBtn.disabled = true;
            geoBtn.textContent = '📍 Getting location...';
            
            try {
                const position = await new Promise((resolve, reject) => {
                    navigator.geolocation.getCurrentPosition(resolve, reject, {
                        timeout: 10000,
                        enableHighAccuracy: true
                    });
                });
                
                const {latitude, longitude} = position.coords;
                const locationParam = `${latitude},${longitude}`;
                
                document.getElementById('location').value = locationParam;
                locationInfo.textContent = `📍 Location found: ${latitude.toFixed(4)}, ${longitude.toFixed(4)}`;
                locationInfo.style.display = 'block';
                
                await search();
                
            } catch (error) {
                console.error('Geolocation error:', error);
                locationInfo.textContent = '❌ Could not get your location. Please enter your ZIP code or city manually.';
                locationInfo.style.display = 'block';
            } finally {
                geoBtn.disabled = false;
                geoBtn.textContent = '📍 Use My Location';
            }
        }

        async function search() {
            const location = document.getElementById('location').value.trim();
            const loading = document.getElementById('loading');
            const error = document.getElementById('error');
            const results = document.getElementById('results');
            const searchBtn = document.getElementById('searchBtn');
            
            if (!location) {
                error.textContent = 'Please enter a location or use your current location.';
                error.style.display = 'block';
                return;
            }
            
 
[truncated — 2600 more characters]
```