Where was this photo taken? See the answer — and how we got there.
A multi-agent forensic photo analyzer. Upload one or more photos of the same place; specialists look for tiny clues — shadows, sun angle, weather, vegetation, signage, license plates, architecture, infrastructure — and a deterministic verifier cross-checks the top guess with pvlib (sun position), Open-Meteo (weather), and Nominatim (does the address exist?).
flowchart TD
UI["Next.js + MapLibre<br/><i>upload · SSE viewer · map</i>"]
API[FastAPI + LangGraph]
UI <-->|"multipart → SSE"| API
API --> P[ingest → preprocess → EXIF]
P --> R{"EXIF GPS<br/>present?"}
R -->|yes, short-circuit| V
R -->|no| T["triage<br/><i>indoor / outdoor · low_info</i>"]
T --> EX[extract_regions]
EX --> SPECIALISTS
subgraph SPECIALISTS [Specialist agents · parallel fan-out via LangGraph Send]
direction LR
sun[shadow_sun]:::vis
env[environment]:::vis
wea[weather_visible]:::vis
sig[signage_commerce]:::tools
lan[landmark]:::tools
arc[architecture_culture]:::tools
veh[vehicle]:::tools
end
SPECIALISTS --> H["hypothesizer<br/><i>selected · considered · rejected</i>"]
H --> V{{"verifier · deterministic Python<br/>pvlib · Open-Meteo · Nominatim"}}
V -->|contradiction · retry ≤ 2| H
V -->|ok| RP[reporter → compute_confidence_status]
RP --> OUT([Report])
classDef vis fill:#dbeafe,stroke:#3b82f6,stroke-width:1.5px,color:#1e3a8a
classDef tools fill:#fef3c7,stroke:#f59e0b,stroke-width:1.5px,color:#78350f
Blue specialists are pure-vision (no external lookups). Amber specialists are ReAct agents — they can call free, no-key tools to verify hunches:
landmark— Wikipedia, DuckDuckGo, Jina Readersignage_commerce— Wikipedia, DuckDuckGo, country_infoarchitecture_culture— Wikipedia, DuckDuckGo, country_info, plate_formatvehicle— plate_format, country_info, DuckDuckGo
A step budget (≈5 tool-calling rounds, hard kill at 20 graph steps) prevents runaway loops. A budget-exhausted specialist emits honest "couldn't determine" evidence at confidence 0 instead of crashing the pipeline.
Backend — Python 3.11+, FastAPI, LangGraph + LangChain, OpenRouter (one API key for all LLMs). Per-role model routing: Gemini Flash for coarse buckets, Claude Haiku for lighter ReAct, Claude Sonnet for richer reasoning, Gemini Pro for landmark geography.
Frontend — Next.js 16 (App Router), Tailwind 4, MapLibre GL JS + OpenFreeMap vector tiles, Zustand.
cp .env.example .env
# add OPENROUTER_API_KEY (the only required key)
just install
just dev
# open http://localhost:3000Or with Docker:
cp .env.example .env
just upCLI without the web UI:
just analyze path/to/photo.jpgEstimates are probabilistic and can be wrong. Intended for journalism, education, and research. Not for tracking private individuals.
MIT — see LICENSE.
