Helping Davis students with housing search using a multi-agent framework.
The repo has two halves:
backend/— a FastAPI server plus the AI agents (supervisor / search / filter) that operate overdata/listings.json.frontend/— a React + Vite single-page app ("hone").
The frontend talks to the backend through /api/.... In local dev, Vite
proxies /api to the backend on port 5050 (see frontend/vite.config.js).
You can run both services in one command, or use two terminals.
# from repo root
npm run dev:allThis starts backend (5050) and frontend (5173) together.
If this is your first run (no .venv yet), run setup once:
npm run dev:setup# from the repo root
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cd backend
uvicorn main:app --reload --port 5050Verify it's up:
curl http://localhost:5050/api/health # -> {"status":"ok"}cd frontend
npm install
npm run devOpen http://localhost:5173. The Vite proxy forwards /api/* to the backend,
so no extra config is needed for local dev.
If you see
http proxy error: /api/listings ... ECONNREFUSED, the backend in Terminal 1 isn't running. Start it and refresh. (The frontend also falls back to built-in sample listings and shows a banner, so the UI never hard-crashes.)
The backend currently exposes the routes the frontend search flow needs:
| Method & path | Returns |
|---|---|
GET /api/health |
{ "status": "ok" } |
GET /api/listings |
{ "listings": [...], "total": N } (see filters) |
GET /api/listings/{id} |
{ "listing": {...} }, or 404 { "error": ... } |
POST /api/chat/search-intake |
One chat turn; see backend/docs/AGENT_FLOW.md |
POST /api/chat/new |
Wipe chat and start over (MVP: one thread only) |
GET /api/chat/session |
Current chat: messages, collected, complete |
These mirror what the frontend sends. Params the dataset can satisfy are applied; params it has no fields for are ignored (so a search returns real results instead of an empty list):
| Param | Applied? | Notes |
|---|---|---|
q |
✅ | substring match on address / city / property type |
priceMin |
✅ | |
priceMax |
✅ | |
bedrooms |
✅ | treated as a minimum (e.g. 2 → 2+ bedrooms) |
sublease |
✅ | dataset has none → returns empty |
longTerm |
✅ | all records qualify |
rating |
⬜ ignored | source data has no reviews |
petFriendly |
⬜ ignored | source data has no pet policy |
maxBusMinutes |
⬜ ignored | no commute data yet |
data/listings.json stores RentCast-shaped records. backend/adapter.py is a
small, pure layer that translates them into the shape the frontend's
ListingCard expects (_id, name, address, priceMin/Max,
bedroomsMin/Max, etc.) and applies the filters above. The agents' logic is
left untouched; the listing filter mirrors the concept in
backend/agents/filter_agent.py.
The multi-agent intake flow is documented in backend/docs/AGENT_FLOW.md
(beginner-friendly diagram, API contract, and file map).
The FastAPI server exposes POST /api/chat/search-intake, which runs the
supervisor agent (Ollama) and returns complete: true when budget, bedrooms,
and distance are collected — then the filter agent fills listings.
Agents need extra setup:
- Ollama running locally with the
llama3.1model (supervisor_agent.py,search_agent*.pypoint athttp://localhost:11434). - A
TAVILY_API_KEYforsearch_agentv2.py(web enrichment). - A
GROQ_API_KEYforgroq_client.py.
Put any keys in a local .env file (never commit it):
# .env (repo root) — DO NOT COMMIT
TAVILY_API_KEY=...
GROQ_API_KEY=...
These keys are not required to run the listing search flow above.
Frontend (frontend/.env, see frontend/.env.example) — all optional for
local dev:
VITE_API_BASE_URL— absolute backend URL for production builds. In local dev leave it unset; the code defaults to/apiand the proxy handles it.
Never commit .env, node_modules, or secrets — they're in .gitignore.