尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A voice-first rental assistant that collects a tenant's spoken preferences, shortlists real Bengaluru listings, explains every choice with citations, and books a site visit on Google Calendar for both tenant and owner.
The problem it addresses isn't finding listings — it's judging whether one fits your life. Is the commute realistic? What's the area actually like? Is the extra room worth the extra rent? Every answer this system gives is traceable to a source, and where it has no source it says so.檢視完整 README ↗A voice-first rental assistant that collects a tenant's spoken preferences, shortlists real Bengaluru listings, explains every choice with citations, and books a site visit on Google Calendar for both tenant and owner.
The problem it addresses isn't finding listings — it's judging whether one fits your life. Is the commute realistic? What's the area actually like? Is the extra room worth the extra rent? Every answer this system gives is traceable to a source, and where it has no source it says so.
Status: Phases 0–3 built (2026-09-10), evals in sign-off (2026-09-15) — see
Docs/Implementation_Plan.md. Booking, cancel and reschedule work by voice and over HTTP against the real Google calendars; the confirmation PDF is emailed and discarded. All 60 eval cases (Suites A, B, C) passed on three consecutive local passes on one build on 2026-09-15 (Docs/JOB2_SCORES.md). Not yet done: Phase 4 — the three CI runs, the production latency report, the §6 walkthrough and the sign-off record.
Problem_Statement_Detailed.md (v3.10) is the single source of truth. Scope, data schema, latency budget, all 58 error cases, and the sign-off contract live there — and it governs wherever this README, the architecture, or any summary disagrees with it.
Architecture.md is how the system is structured to meet it — components, data model, turn lifecycles, error taxonomy, and the decisions taken with their alternatives.
Implementation_Plan.md is the build plan as a decision document — plain language: the 39 tasks in five phases, why that order, what "done" means for each, and a table of every decision the owner will be asked to make (the two gates, the model pins, the region). Its companion, Implementation_Plan_Addendum.md, is the technical reference builders work from: for every task, the files, interfaces, tests and step-by-step instructions.
| You are about to… | Read |
|---|---|
| Orient, or set up | this README |
| Decide what correct behaviour is | the specification — §6 for failure behaviour, §7.3 for what "done" means |
| Decide where code goes, or how something is shaped | the architecture — §5 for components, §4 for the data model, §12 for decisions already taken |
| Decide what to build next, or take a gate decision | the implementation plan — §4 for the order, §5 for the decisions |
| Build a task | the addendum, at that task's number |
The architecture is derived from the specification, and the plan from both; none is independent. If they disagree, the specification wins and the others are wrong.
Architecture.md §11.2)flowchart LR
B["Browser<br/>(Vercel)"] -- "WebSocket: mic audio" --> S["Backend<br/>(Railway)"]
S -- "shortlist + citations" --> B
S <--> D["Deepgram<br/>STT"]
S <--> G["Groq · Job 1<br/>extraction"]
S <--> A["Anthropic · Job 2<br/>grounded explanation"]
S <--> T["Smallest.ai<br/>TTS"]
S <--> C["Google Calendar<br/>+ Gmail"]
S --- L["Static dataset ·<br/>guide index ·<br/>precomputed OSM"]
Every provider call originates on the backend. The browser talks to exactly one origin and holds no keys. The dataset, the closed guide index and all OpenStreetMap values are resolved at build time and served from local storage — no source fetching, no retrieval fetching and no OSM lookups happen inside a tenant's turn.
Three structures carry most of the correctness, and are worth knowing before reading any code — all detailed in Architecture.md:
Provenanced<T> — every fact travels with its source, method, timing and as-of date. A distance without its method label is unrepresentable, not merely discouragedTurnOutcome — "nothing found" and "couldn't ask" are different variants of a union, so they cannot accidentally render alikeThe full set — fifteen guardrails, each against the section that actually enforces it — is indexed in Architecture.md §17.3.
The pipeline uses two models because the two jobs have opposite requirements:
| Job | Model | Why |
|---|---|---|
| 1 — Extraction & edit routing | Groq openai/gpt-oss-120b | Latency-critical, sits inside the 700 ms acknowledgment budget. Needs schema conformance, not reasoning |
| 2 — Grounded explanation | Anthropic claude-sonnet-5 | Decides whether the zero-hallucination bar is met. Off the critical path — capability beats speed |
Both are pinned by exact model ID, never a latest alias — the CI guarantee is void if the model can change underneath the suite.
| Layer | Choice |
|---|---|
| Speech-to-text | Deepgram (keyterm boosting, endpointing 400 ms with a content-aware hold) |
| LLM | Groq + Anthropic (see above) |
| Text-to-speech | Smallest.ai, streaming |
| Maps / transit | OpenStreetMap MCP — jagan-shanmugam/open-streetmap-mcp |
| Calendar & email | Google Calendar + Gmail, single OAuth |
| Listings source | data/Bangalore_Properties_List.xlsx — a supplied spreadsheet, imported once |
| Frontend host | Vercel |
| Backend host | Railway |
.
├── .github/workflows/ci.yml # unit, contract-drift, frontend build, evals ×3
├── .env.example # every secret name, empty
├── Docs/
│ ├── Problem_Statement_Detailed.md # the specification
│ ├── Problem_Statement_Summary.md # condensed working summary
│ ├── Architecture.md # how it is structured
│ ├── Implementation_Plan.md # the build plan — decisions, order, done-when
│ └── Implementation_Plan_Addendum.md # per-task technical reference for builders
├── data/
│ ├── raw/ # ignored: raw HTML, guide pages, MCP responses
│ ├── SOURCE_NOTES.md # what the supplied spreadsheet publishes
│ ├── GATE_D.md · GATE_L.md # the two written gate decisions
│ ├── guides/sources.json # 1–3 guide URLs per locality
│ └── bundle/ # committed, versioned, read-only at runtime
│ ├── manifest.json # DatasetManifest — the sign-off record
│ ├── listings.json # curated ListingRecord[]
│ ├── osm_facts.json # OsmFactRecord[] — every listing × every query
│ ├── chunks.json # GuideChunk[] (the citable text; Chroma holds vectors)
│ └── chroma/ # persisted ChromaDB, one collection per locality
├── contract/v1.schema.json # exported JSON Schema of the wire contract
├── scripts/
│ ├── google_auth.py # one-time OAuth: prints the refresh token
│ └── latency_spike.py # Gate L driver against the deployed skeleton
├── backend/ # Railway — pipeline, both LLM jobs, calendar, PDF
│ ├── pyproject.toml · Dockerfile · railway.json
│ ├── scout/
│ │ ├── main.py # create_app(); boot check runs before the port opens
│ │ ├── config.py # Settings (pydantic-settings) — all secrets, model ids
│ │ ├── domain/ # provenance, listing, osm, guides, manifest, constraints, shortlist, booking
│ │ ├── contract/ # the versioned wire contract: outcome, view-models, messages, http, export
│ │ ├── platform/ # artefacts (bundle loader), boot checks, telemetry
│ │ ├── providers/ # thin wrappers: deepgram, groq, anthropic, smallest, google calendar, gmail
│ │ ├── engines/ # amounts, reducer, shortlist, commute, availability, slots
│ │ ├── conversation/ # session, hold, router, persona, state, orchestrator
│ │ ├── grounding/ # retrieval, resolvers, opener, assembler
│ │ ├── booking/ # service, reconcile, pdf
│ │ ├── presentation/viewmodel.py # the only thing the browser receives
│ │ ├── api/ # ws (mic gateway), http (bookings, health, contract, admin)
│ │ └── pipeline/ # offline build: import_sheet, curate, gap_report,
│ │ # chunking, build_index, precompute_osm, manifest
│ └── tests/unit/ · tests/integration/
├── evals/
│ ├── fixtures/ # frozen slice: listings, chunks, OSM facts
│ ├── harness/driver.py # text in → TurnOutcome out, no audio
│ ├── assertions/ # view-model, provenance, order matchers
│ ├── cases/a/ · cases/b/ · cases/c/ # 20 JSON cases each
│ ├── suites/test_suite_a.py · test_suite_b.py · test_suite_c.py
│ └── latency/score.py # p99 per stage per turn type; 2× rule
└── frontend/ # Vercel — Next.js draws view-models, holds no keys
├── src/app/ # routes (page.tsx only; NO api/ directory)
├── src/lib/audio/ # capture worklet, player, unlock, barge-in
├── src/lib/transport/ # WebSocket + HTTP clients, contract check
├── src/lib/viewmodels/contract.ts # generated from contract/v1.schema.json
├── src/lib/state/session.ts # mirrors the backend session
└── src/components/ # cards, snapshot, sources, mic, booking, failures
Every secret is set on the backend; none belongs in the frontend or in the repo. The
boot check refuses to start, naming every missing value at once, if any is absent.
.env.example lists them all.
| Variable | Source |
|---|---|
DEEPGRAM_API_KEY | deepgram.com |
GEMINI_API_KEY (with JOB1_PROVIDER=gemini, the default) or GROQ_API_KEY (with JOB1_PROVIDER=groq) | Job 1; only the selected provider's key is required |
ANTHROPIC_API_KEY | console.anthropic.com — Job 2 |
SMALLEST_API_KEY, SMALLEST_VOICE_ID | app.smallest.ai/dashboard |
GOOGLE_OAUTH_CREDENTIALS | printed once by python scripts/google_auth.py <client_secret.json> (Calendar + Gmail send scopes) |
GOOGLE_TENANT_CALENDAR_ID, GOOGLE_OWNER_CALENDAR_ID, GOOGLE_SENDER_EMAIL | the demo account's two secondary calendars and its address |
OPERATOR_TOKEN | any secret string; guards POST /admin/availability |
CORS_ALLOWED_ORIGINS | comma-separated explicit allowlist; * is refused |
The frontend takes exactly one backend-related variable, and it is public, not a secret:
| Variable | Value |
|---|---|
NEXT_PUBLIC_API_URL | The Railway backend URL |
The build is ordered by what can invalidate what, not by what is satisfying to build. Two things can still prove the design wrong, so both are settled first — in parallel, each behind a gate. The rationale is §9 of the specification; the task-by-task plan is Implementation_Plan.md.
Phase 0 — de-risk (both tracks at once)
Then, in order
# Backend (Python 3.12). The lock file carries the dev tools too (pytest, ruff).
cd backend
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.lock
pip install -e .
copy ..\.env.example .env # then fill every value
python -m pytest tests/unit -q # unit suite; the committed bundle is loaded from a copy
python -m scout.main # boot checks first; exits 2 naming any missing secret
# Frontend (Next.js). NEXT_PUBLIC_API_URL is baked in at build time.
cd frontend
npm install
npm run contract # regenerates the types from contract/v1.schema.json
npm test
echo NEXT_PUBLIC_API_URL=http://localhost:8000 > .env.local
npm run dev
# Eval suites (need the Job 1 and Job 2 keys; each run costs model quota)
python -m pytest evals -q
Booking without the microphone: POST /bookings/slots, POST /bookings,
POST /bookings/{code}/cancel, POST /bookings/{code}/reschedule — bodies in
contract/v1.schema.json; the 6-character code is the only credential, and an unknown
code and a cancelled one get the same answer.
Deployment happens twice: a walking skeleton in Phase 0, so the latency budget can be measured on real infrastructure, and the full application later — the same two targets, promoted rather than replaced.
Backend first, frontend second — the two hosts deploy independently, so version skew is possible in production even when CI is green. The backend exposes a contract version and the frontend pins the one it expects.
Railway (backend)
Vercel (frontend)
CORS is an explicit allowlist, never *. The mic WebSocket goes browser → backend directly, never proxied.
Three suites, 20 cases each, 60 total at 100% pass, run three times — locally, or in CI by gh workflow run ci.yml (the evals job is manual-only and serial: Job 1's free-tier Gemini quota is 500 calls a day for the whole project and a pass is ~146 of them). A run pings both providers first and aborts before spending a call if either refuses.
| Suite | Checks |
|---|---|
| A — Feasibility | Shortlist respects budget, bedrooms and must-haves; ≥1 case per schema field; null handling |
| B — Edit correctness | Untouched listings and their order are byte-identical before and after a voice edit |
| C — Grounding | Citations resolve to the correct listing-scoped source; gaps declared; injection and cross-locality contamination probes |
Latency is instrumented per component and scored at p99, separately per turn type. See §7.3 of the full statement for the complete sign-off contract.
If you contribute one thing to this codebase, know these:
null is a real value, and it never satisfies a must-have. Unstated fields read "not stated" — never blank, never zero, never inferred. Unknowns surface as their own group rather than being silently dropped or silently counted.Asia/Kolkata, never server-local time. The backend is deliberately hosted outside India.Login and accounts · post-visit feedback · cross-session preference history · mobile app · languages beyond English · production-grade privacy compliance.
Owner contact is the labelled placeholder 999999999 throughout — deliberately not a valid Indian mobile number, so nothing in a demo can dial a real person.