A production-grade agentic AI system for aggregating ride fares across multiple cab booking platforms (Uber, Ola, Rapido, InDrive) using LangGraph orchestration and Playwright web scraping.
User Input
│
▼
┌─────────────────┐
│ NLP Parser │ (LangChain + GPT-4o-mini or regex fallback)
│ Extract intent │
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LangGraph Workflow │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Geolocation │───▶│ Parallel │───▶│ Negotiation │ │
│ │ Node │ │ Scrape Node │ │ Node │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ │ ┌──────┴──────┐ │ │
│ │ │ Scrapers │ │ │
│ │ │ ┌────────┐ │ │ │
│ │ │ │ Uber │ │ │ │
│ │ │ │ Ola │ │ │ │
│ │ │ │ Rapido │ │ │ │
│ │ │ │InDrive │ │ │ │
│ │ │ └────────┘ │ │ │
│ │ └─────────────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Decision │◀─────────────────────│ Ranking │ │
│ │ Node │ │ Matrix │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Notification │ (Deep link + Push notification) │
│ │ Node │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Natural language ride request parsing
- Parallel fare scraping from 4 platforms
- Geolocation resolution (Nominatim/Google Maps)
- Intelligent ranking based on user priorities (speed/cost/balanced)
- InDrive automated price negotiation
- Deep link generation for one-tap booking
- Local/webhook notification support
# Clone and install
cd ride-aggregator
pip install -e ".[dev]"
# Install Playwright browsers
playwright install chromium
# Configure environment
cp .env.example .env
# Edit .env with your API keysCreate a .env file:
# LLM Provider: "regex" (default, no API needed), "ollama" (free, local), or "openai"
LLM_PROVIDER=regex
# For Ollama (free, runs locally)
# Install: https://ollama.ai then run: ollama pull llama3.2
OLLAMA_MODEL=llama3.2
OLLAMA_BASE_URL=http://localhost:11434
# For OpenAI (paid, optional)
OPENAI_API_KEY=optional_if_using_ollama_or_regex
# Other settings
GOOGLE_MAPS_API_KEY=optional_for_better_geocoding
HEADLESS_BROWSER=true
REQUEST_TIMEOUT=30
MAX_RETRIES=3
LOG_LEVEL=INFO-
Regex (default, free): Works out of the box, no setup needed
LLM_PROVIDER=regex
-
Ollama (free, local): Better NL understanding, runs on your machine
# Install Ollama from https://ollama.ai ollama pull llama3.2 # or mistral, phi3, etc. pip install langchain-ollama
LLM_PROVIDER=ollama OLLAMA_MODEL=llama3.2
-
OpenAI (paid): Best accuracy
pip install langchain-openai
LLM_PROVIDER=openai OPENAI_API_KEY=your_key
ride-agent "Get me a ride from Connaught Place to the airport. Prioritize speed, keep it under 400"ride-agent --pickup "Connaught Place, Delhi" --dropoff "IGI Airport Terminal 3" --max-price 400 --priority speedride-agent --interactivefrom ride_aggregator import RideAggregatorAgent, RideRequest, Location, UserPreferences, Priority
request = RideRequest(
pickup=Location(address="Connaught Place, Delhi"),
dropoff=Location(address="Indira Gandhi International Airport"),
preferences=UserPreferences(
priority=Priority.SPEED,
max_price=400,
),
)
agent = RideAggregatorAgent()
result = agent.run(request)
if result.get("decision"):
print(result["decision"].explanation)
print(f"Book here: {result['decision'].deep_link}")src/ride_aggregator/
├── __init__.py
├── cli.py # Command-line interface
├── core/
│ ├── __init__.py
│ ├── agent.py # LangGraph workflow orchestration
│ ├── config.py # Configuration management
│ ├── logging.py # Structured logging
│ └── models.py # Pydantic data models
├── nlp/
│ ├── __init__.py
│ └── parser.py # Natural language parsing
├── nodes/
│ ├── __init__.py
│ ├── geolocation.py # Location resolution node
│ ├── scraper.py # Parallel scraping node
│ ├── negotiation.py # InDrive negotiation node
│ ├── decision.py # Ranking and decision node
│ └── notification.py # Notification node
├── scrapers/
│ ├── __init__.py
│ ├── base.py # Abstract scraper base class
│ ├── manager.py # Scraper orchestration
│ ├── uber.py # Uber fare scraper
│ ├── ola.py # Ola fare scraper
│ ├── rapido.py # Rapido fare scraper
│ └── indrive.py # InDrive fare scraper + negotiation
└── services/
├── __init__.py
├── geolocation.py # Geocoding service
├── decision.py # Ranking/decision logic
└── notification.py # Notification handling
The NLP parser extracts ride booking intent from natural language using a dual-mode approach:
User Input: "Get me a ride from Connaught Place to the airport. Prioritize speed, under 400"
│
▼
┌───────────────────────────────┐
│ RequestParser │
│ ┌─────────────────────────┐ │
│ │ LLM Mode (Ollama/OpenAI)│ │ ◄── Primary (if configured)
│ └───────────┬─────────────┘ │
│ │ fallback │
│ ┌───────────▼─────────────┐ │
│ │ Regex Mode │ │ ◄── Fallback (always works)
│ └─────────────────────────┘ │
└───────────────┬───────────────┘
│
▼
ParsedRideRequest:
- pickup: "Connaught Place"
- dropoff: "airport"
- priority: "speed"
- max_price: 400
1. Location Extraction
# Pattern: "from X to Y"
r"from\s+(.+?)\s+to\s+(.+?)(?:\.|,|$|prioritize|keep|under)"
# Input: "from Connaught Place to the airport"
# Output: pickup="Connaught Place", dropoff="airport"2. Priority Detection
# Speed keywords: ["prioritize speed", "fastest", "quick", "asap", "urgent"]
# Cost keywords: ["cheapest", "lowest price", "budget", "cheap"]
# "Prioritize speed" → priority="speed"
# "cheapest option" → priority="cost"
# Default → priority="balanced"3. Price Constraint Extraction
# Patterns:
r"under\s*(?:rs\.?|inr|₹)?\s*(\d+)" # "under 400"
r"keep\s+it\s+under\s*(\d+)" # "keep it under 400"
r"budget\s*(?:of|is)?\s*(\d+)" # "budget 500"
# "keep it under 400" → max_price=4004. Provider Exclusion
# Pattern:
r"(?:no|exclude|avoid|skip)\s+(uber|ola|rapido|indrive)"
# "no rapido" → excluded_providers=["rapido"]When Ollama or OpenAI is configured, uses structured prompting:
SYSTEM_PROMPT = """Extract from natural language:
- pickup_address: starting location
- dropoff_address: destination
- priority: "speed", "cost", or "balanced"
- max_price: budget limit in INR
- excluded_providers: list of providers to avoid
Return valid JSON."""| Mode | Pros | Cons |
|---|---|---|
| Regex | No API needed, fast, deterministic | Limited to known patterns |
| LLM | Handles typos, variations, complex sentences | Requires setup, slower |
The system tries LLM first, automatically falls back to regex if unavailable.
-
NLP Parsing: User input is parsed using the dual-mode parser to extract pickup, dropoff, priority, and constraints.
-
Geolocation: Addresses are converted to lat/lng coordinates using Nominatim (free) or Google Maps API.
-
Parallel Scraping: Playwright launches headless browsers to scrape fare estimates from all providers simultaneously.
-
Negotiation: For InDrive, calculates the average price from other providers and submits a counter-offer (85% of average).
-
Decision Matrix: Ranks options using weighted scoring based on user priority:
- Speed priority: 70% ETA weight, 20% price weight
- Cost priority: 20% ETA weight, 70% price weight
- Balanced: 50/50 split
-
Notification: Generates a deep link for the recommended option and displays/sends the recommendation.
Note on Architecture: This project utilizes local Playwright browser automation to circumvent restricted production APIs. While functional in a local residential environment, production deployment would transition to a dedicated API gateway using rotated residential proxies to bypass data-center IP blocking.
The scrapers use Playwright to simulate real browser interactions:
- Launches headless Chromium with mobile user agent
- Types addresses with human-like delays
- Waits for DOM elements to load
- Extracts prices from rendered HTML
- Handles dynamic content and popups
Website selectors may need updates as platforms change their UI.
- Scraping depends on website structure (selectors may break)
- Rate limiting may apply from provider websites
- Actual booking requires manual confirmation via deep link
- Surge pricing detection is approximate
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Type checking
mypy src/ride_aggregator
# Linting
ruff check src/MIT