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