diff --git a/examples/deep-research-apartment-finder/.env.example b/examples/deep-research-apartment-finder/.env.example new file mode 100644 index 00000000..7f34bdb7 --- /dev/null +++ b/examples/deep-research-apartment-finder/.env.example @@ -0,0 +1,5 @@ +# Firecrawl API key (get from https://firecrawl.dev) +FIRECRAWL_API_KEY=your_firecrawl_api_key_here + +# Anthropic API key (get from https://console.anthropic.com) +ANTHROPIC_API_KEY=your_anthropic_api_key_here \ No newline at end of file diff --git a/examples/deep-research-apartment-finder/README.md b/examples/deep-research-apartment-finder/README.md new file mode 100644 index 00000000..ccae9706 --- /dev/null +++ b/examples/deep-research-apartment-finder/README.md @@ -0,0 +1,55 @@ +# Apartment Finder CLI + +A command-line tool that uses Firecrawl's Deep Research API and Anthropic's Claude 3.7 to find and analyze apartment listings based on your preferences. + +## Features + +- Interactive input for apartment search preferences +- Searches apartments by location, budget, bedrooms, and amenities +- Automatically researches apartment listings across multiple websites +- Uses AI to analyze and extract the top 3 options +- Provides detailed information including price, location, features, and pros/cons +- Option to save results as JSON + +## Installation + +1. Clone this repository: + + ``` + git clone + cd apartment-finder + ``` + +2. Install dependencies: + + ``` + pip install -r requirements.txt + ``` + +3. Set up API keys: + - Copy `.env.example` to `.env` + - Fill in your Firecrawl API key from [firecrawl.dev](https://firecrawl.dev) + - Fill in your Anthropic API key from [console.anthropic.com](https://console.anthropic.com) + +## Usage + +Run the script and follow the interactive prompts: + +```bash +python apartment_finder.py +``` + +The script will prompt you for: + +- Location (city or neighborhood) +- Budget (maximum monthly rent) +- Number of bedrooms +- Desired amenities + +After searching and analyzing, the tool will display the top apartment options and offer to save the results to a JSON file. + +## Notes + +- The search process may take a few minutes due to the deep research API. +- Results will vary based on available apartment listings at the time of search. +- API usage may incur costs depending on your Firecrawl and Anthropic subscription plans. diff --git a/examples/deep-research-apartment-finder/apartment_finder.py b/examples/deep-research-apartment-finder/apartment_finder.py new file mode 100644 index 00000000..3a599a0d --- /dev/null +++ b/examples/deep-research-apartment-finder/apartment_finder.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 + +import os +import sys +import json +from typing import Dict, List, Any +import anthropic +from firecrawl import FirecrawlApp +from dotenv import load_dotenv + +# Define colors for terminal output +class Colors: + CYAN = '\033[96m' + YELLOW = '\033[93m' + GREEN = '\033[92m' + RED = '\033[91m' + MAGENTA = '\033[95m' + BLUE = '\033[94m' + BOLD = '\033[1m' + RESET = '\033[0m' + +# Load environment variables +load_dotenv() + +FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY") +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") + +if not FIRECRAWL_API_KEY or not ANTHROPIC_API_KEY: + print(f"{Colors.RED}Error: API keys not found. Please set FIRECRAWL_API_KEY and ANTHROPIC_API_KEY environment variables.{Colors.RESET}") + print(f"{Colors.YELLOW}You can create a .env file with these variables or set them in your shell.{Colors.RESET}") + sys.exit(1) + +# Initialize clients +firecrawl = FirecrawlApp(api_key=FIRECRAWL_API_KEY) +claude = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) + +def get_user_preferences(): + """Get apartment search preferences from user input""" + print(f"\n{Colors.BOLD}{Colors.CYAN}=== Apartment Finder ==={Colors.RESET}") + print(f"{Colors.CYAN}Please enter your apartment search preferences:{Colors.RESET}") + + # Get required inputs + location = input(f"\n{Colors.YELLOW}Enter location (city or neighborhood): {Colors.RESET}") + while not location.strip(): + location = input(f"{Colors.RED}Location cannot be empty. Please enter a location: {Colors.RESET}") + + budget = input(f"{Colors.YELLOW}Enter your maximum budget (e.g., $2000): {Colors.RESET}") + while not budget.strip(): + budget = input(f"{Colors.RED}Budget cannot be empty. Please enter your maximum budget: {Colors.RESET}") + if not budget.startswith('$'): + budget = f"${budget}" + + # Get optional inputs with defaults + bedrooms = input(f"{Colors.YELLOW}Enter number of bedrooms (default: 1): {Colors.RESET}") or "1" + + amenities = input(f"{Colors.YELLOW}Enter desired amenities, separated by commas (e.g., gym,pool,parking): {Colors.RESET}") or "" + + return { + "location": location.strip(), + "budget": budget.strip(), + "bedrooms": bedrooms.strip(), + "amenities": amenities.strip() + } + +def build_search_query(user_prefs): + amenities_str = f" with {user_prefs['amenities'].replace(',', ', ')}" if user_prefs['amenities'] else "" + return f"{user_prefs['bedrooms']} bedroom apartments for rent in {user_prefs['location']} under {user_prefs['budget']}{amenities_str}" + +def research_apartments(query: str) -> Dict[str, Any]: + """Use Firecrawl's deep research to find apartment listings""" + print(f"\n{Colors.BOLD}{Colors.CYAN}🔍 INITIATING DEEP RESEARCH 🔍{Colors.RESET}") + print(f"{Colors.BLUE}Researching apartments with query: '{query}'{Colors.RESET}") + print(f"{Colors.BLUE}This may take a few minutes...{Colors.RESET}\n") + + # Define research parameters + params = { + "maxDepth": 3, # Number of research iterations + "timeLimit": 180, # Time limit in seconds + "maxUrls": 20 # Maximum URLs to analyze + } + + # Start research with real-time updates + def on_activity(activity): + activity_type = activity['type'] + message = activity['message'] + + if activity_type == 'info': + color = Colors.CYAN + elif activity_type == 'search': + color = Colors.BLUE + elif activity_type == 'scrape': + color = Colors.MAGENTA + elif activity_type == 'analyze': + color = Colors.GREEN + else: + color = Colors.RESET + + print(f"[{color}{activity_type}{Colors.RESET}] {message}") + + # Run deep research + results = firecrawl.deep_research( + query=query, + params=params, + on_activity=on_activity + ) + + return results + +def analyze_with_claude(research_results: Dict[str, Any], user_prefs: Dict[str, str]) -> List[Dict[str, Any]]: + """Use Claude to analyze apartment data and extract top options""" + print(f"\n{Colors.BOLD}{Colors.MAGENTA}🧠 ANALYZING RESULTS WITH CLAUDE 3.7 🧠{Colors.RESET}") + + # Extract relevant information from sources + sources_text = "\n\n".join([ + f"Source {i+1}:\n{source.get('content', '')}" + for i, source in enumerate(research_results['data']['sources'][:15]) # Limit to first 15 sources + ]) + + # Add the final analysis as an additional source + final_analysis = research_results['data'].get('finalAnalysis', '') + if final_analysis: + sources_text += f"\n\nFinal Analysis:\n{final_analysis}" + + # Prepare system prompt with better handling for limited data + system_prompt = """ + You are an expert apartment finder assistant. Your task is to analyze text about apartments and find the top apartment options that best match the user's preferences. + + If you find specific apartment listings with details, extract and organize them into exactly 3 options. + + For each listing you can identify, extract: + 1. Price (monthly rent) + 2. Location (specific neighborhood, address if available) + 3. Key features (bedrooms, bathrooms, square footage, type of building) + 4. Amenities (both in-unit and building amenities) + 5. Pros and cons (at least 3 of each) + + If you cannot find 3 complete listings with all details, do your best with the information available. You can: + - Create fewer than 3 listings if that's all you can find + - Extrapolate missing information based on similar listings or market trends + - For missing specific details, use general information about the area + + You MUST format your response as a JSON array of objects. Each object should have these exact fields: + - title (string) + - price (string) + - location (string) + - features (array of strings) + - amenities (array of strings) + - pros (array of strings) + - cons (array of strings) + + If you absolutely cannot find any apartment listings with enough details, return an array with a single object containing general information about apartments in the area, with "No specific listings found" as the title. + + Example JSON structure: + [ + { + "title": "Luxury 2BR in Downtown", + "price": "$2,500/month", + "location": "123 Main St, Downtown", + "features": ["2 bedrooms", "2 bathrooms", "950 sq ft"], + "amenities": ["In-unit laundry", "Parking garage", "Fitness center"], + "pros": ["Great location", "Modern appliances", "Pet friendly"], + "cons": ["Street noise", "Small kitchen", "Limited storage"] + } + ] + + Return ONLY the JSON array, nothing else. + """ + + # Create the user message + user_message = f""" + I'm looking for {user_prefs['bedrooms']} bedroom apartments in {user_prefs['location']} with a budget of {user_prefs['budget']}. + + Additional preferences: {user_prefs.get('amenities', 'None specified')} + + Please analyze the following information and find apartment options that match my criteria: + + {sources_text} + """ + + # Call Claude API + response = claude.messages.create( + model="claude-3-7-sonnet-20250219", + max_tokens=4000, + temperature=0, + system=system_prompt, + messages=[{"role": "user", "content": user_message}] + ) + + # Extract and parse JSON from response with better error handling + try: + content = response.content[0].text + + # Clean the content - strip markdown formatting or text before/after JSON + content = content.strip() + if content.startswith('```json'): + content = content[7:] + if content.endswith('```'): + content = content[:-3] + content = content.strip() + + # Look for JSON array in the response + if content.startswith('[') and content.endswith(']'): + return json.loads(content) + + # Try to find JSON brackets if not properly formatted + json_start = content.find('[') + json_end = content.rfind(']') + 1 + if json_start >= 0 and json_end > json_start: + json_str = content[json_start:json_end] + return json.loads(json_str) + + # If we can't find JSON, create a fallback response + print(f"{Colors.YELLOW}Could not find valid JSON in Claude's response, creating fallback response{Colors.RESET}") + return [{ + "title": "No specific listings found", + "price": f"Target: {user_prefs['budget']}", + "location": user_prefs['location'], + "features": [f"{user_prefs['bedrooms']} bedroom(s)"], + "amenities": user_prefs['amenities'].split(',') if user_prefs['amenities'] else ["Not specified"], + "pros": ["Information is based on general market research", "Consider visiting apartment listing websites directly", "Contact local real estate agents for current availability"], + "cons": ["No specific listings were found in the research", "Prices and availability may vary", "Additional research recommended"] + }] + except Exception as e: + print(f"{Colors.RED}Error parsing Claude's response: {e}{Colors.RESET}") + print(f"{Colors.YELLOW}Creating fallback response{Colors.RESET}") + return [{ + "title": "Error analyzing apartment listings", + "price": f"Target: {user_prefs['budget']}", + "location": user_prefs['location'], + "features": [f"{user_prefs['bedrooms']} bedroom(s)"], + "amenities": user_prefs['amenities'].split(',') if user_prefs['amenities'] else ["Not specified"], + "pros": ["Try refining your search criteria", "Consider searching specific apartment websites", "Contact local real estate agents"], + "cons": ["Search encountered technical difficulties", "Results may not be accurate", "Consider trying again later"] + }] + +def display_results(apartments: List[Dict[str, Any]]): + """Display the top apartment options in a readable format""" + if not apartments: + print(f"{Colors.RED}No suitable apartments found that match your criteria.{Colors.RESET}") + return + + print(f"\n{Colors.BOLD}{'=' * 80}{Colors.RESET}") + print(f"{Colors.BOLD}{Colors.GREEN}🏠 TOP {len(apartments)} APARTMENT OPTIONS 🏠{Colors.RESET}".center(80)) + print(f"{Colors.BOLD}{'=' * 80}{Colors.RESET}") + + for i, apt in enumerate(apartments): + print(f"\n{Colors.BOLD}{Colors.CYAN}🔑 OPTION {i+1}: {apt.get('title', 'Apartment')}{Colors.RESET}") + print(f"{Colors.YELLOW}💰 Price: {apt.get('price', 'N/A')}{Colors.RESET}") + print(f"{Colors.YELLOW}📍 Location: {apt.get('location', 'N/A')}{Colors.RESET}") + + print(f"\n{Colors.MAGENTA}📋 Features:{Colors.RESET}") + for feature in apt.get('features', []): + print(f" {Colors.BLUE}•{Colors.RESET} {feature}") + + print(f"\n{Colors.MAGENTA}✨ Amenities:{Colors.RESET}") + for amenity in apt.get('amenities', []): + print(f" {Colors.BLUE}•{Colors.RESET} {amenity}") + + print(f"\n{Colors.GREEN}👍 Pros:{Colors.RESET}") + for pro in apt.get('pros', []): + print(f" {Colors.BLUE}•{Colors.RESET} {pro}") + + print(f"\n{Colors.RED}👎 Cons:{Colors.RESET}") + for con in apt.get('cons', []): + print(f" {Colors.BLUE}•{Colors.RESET} {con}") + + print(f"\n{Colors.CYAN}{'-' * 80}{Colors.RESET}") + +def main(): + # Get user preferences through interactive input + user_prefs = get_user_preferences() + + # Print summary of search criteria + print(f"\n{Colors.BOLD}{Colors.CYAN}=== Search Criteria ==={Colors.RESET}") + print(f"{Colors.BLUE}Location: {Colors.YELLOW}{user_prefs['location']}{Colors.RESET}") + print(f"{Colors.BLUE}Budget: {Colors.YELLOW}{user_prefs['budget']}{Colors.RESET}") + print(f"{Colors.BLUE}Bedrooms: {Colors.YELLOW}{user_prefs['bedrooms']}{Colors.RESET}") + print(f"{Colors.BLUE}Amenities: {Colors.YELLOW}{user_prefs['amenities'] or 'None specified'}{Colors.RESET}") + + # Build search query + query = build_search_query(user_prefs) + + # Run research + research_results = research_apartments(query) + + # Analyze with Claude + top_apartments = analyze_with_claude(research_results, user_prefs) + + # Display results + display_results(top_apartments) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/deep-research-apartment-finder/requirements.txt b/examples/deep-research-apartment-finder/requirements.txt new file mode 100644 index 00000000..d7961953 --- /dev/null +++ b/examples/deep-research-apartment-finder/requirements.txt @@ -0,0 +1,3 @@ +anthropic==0.18.0 +firecrawl==0.2.0 +python-dotenv==1.0.0 \ No newline at end of file