Add example/Deep-research Apartment finder

This commit is contained in:
Aparup Ganguly 2025-03-24 20:06:08 +05:30
parent 6a6199eb4b
commit 87539aaf16
4 changed files with 356 additions and 0 deletions

View File

@ -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

View File

@ -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 <repository-url>
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.

View File

@ -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()

View File

@ -0,0 +1,3 @@
anthropic==0.18.0
firecrawl==0.2.0
python-dotenv==1.0.0