Stayquery is a Python 3.12 hybrid RAG evaluator for hospitality data.
- Postgres is the authoritative write-side store for source records, normalized tables, query runs, retrieval hits, and
documentschunks. - Qdrant is a derived read-side vector index. It must be rebuildable from Postgres documents.
- OpenAI is used for structured query planning, embeddings, and grounded answer generation.
- Typer exposes the CLI in
stayquery.cli;stayquery.rag.answer_queryis the stable app boundary for ask flows. - SQLAlchemy models define the database shape, and Alembic applies migrations.
- Every user query produces one hybrid answer. SQL and vector retrieval are internal inputs to hybrid fusion, not standalone answer modes.
src/stayquery/: application package.service.py: orchestrates planning, SQL retrieval, vector retrieval, hybrid fusion, answer generation, and run persistence.sql_retrieval.py: compiles a constrainedQuerySpecinto safe SQLAlchemy filters.vector.py: maintains the Qdrant index and rehydrates vector hits from Postgres documents.hybrid.py: merges SQL and vector rankings with reciprocal rank fusion.ingest.py: validates seed JSON, upserts relational rows, chunks documents, and optionally indexes vectors.source_api.py: syncs external Aminess API records into local seed JSON files before ingest.chunking.py: converts source JSON records into stable multilingual document chunks.schemas.py: Pydantic contracts for query specs, retrieval hits, answers, and ingest results.openai_client.py: OpenAI wrapper for strict structured planning, embeddings, and grounded answers.models.py,db.py,config.py,cli.py: persistence, runtime settings, and command-line entrypoints.
tests/: unit and contract tests for CLI behavior, schema validation, chunking, OpenAI schema strictness, retrieval safety, and eval fixtures.tests/fixtures/questions.yaml: evaluation questions used bystayquery eval.data/raw/: tracked fake/reference records and validation schemas used by tests; may be ingested explicitly for test scenarios.data/novigrad/: current private real-data source; ignored by Git and never committed.data/korcula/: private local output for Korcula records synced from the external Aminess API; ignored by Git and never committed.migrations/: Alembic environment and schema revision.docker-compose.yml: local Postgres and Qdrant services.pyproject.toml: package metadata, dependencies, CLI script, pytest, and Ruff settings.README.md: user-facing setup and usage guide.uv.lock: locked dependency graph; keep it in sync when dependencies change.
Use uv for Python environment and command execution.
docker compose up -d postgres qdrant
uv sync --all-extras
uv run stayquery db upgrade
uv run stayquery ingest --data path/to/data --skip-vector
uv run stayquery sync-api --destination korcula --out data/korcula --skip-vectorThe --skip-vector ingest path validates schemas and writes Postgres rows without requiring OpenAI credentials.
Set OPENAI_API_KEY only when running embeddings or answer generation. Full RAG flows are:
uv run stayquery ingest --data path/to/data
uv run stayquery ask "Which options are good for a family that needs beach access?"
uv run stayquery eval --questions tests/fixtures/questions.yamlWhen changing dependencies, update pyproject.toml and refresh uv.lock with uv lock.
Run the test suite before handing off changes:
uv run pytestTargeted expectations:
- Retrieval changes must preserve bound SQL parameters and the hybrid fallback to vector hits when SQL returns zero hits.
- Schema and chunking changes must preserve seed validation, multilingual text flattening, stable
doc_id, and stablecontent_hash. - CLI changes must preserve the hybrid answer output, fallback display, warnings, and timing summary.
- OpenAI planning schema changes must preserve strict structured output compatibility.
- Ingest or migration changes should also be smoke-tested with
uv run stayquery ingest --data path/to/data --skip-vectoragainst a deliberately selected local dataset when Postgres is available. - Source API sync changes should preserve configurable Aminess API settings, ignored private output under
data/korcula, and reuse the normal ingest validation path.
- Inspect the existing code and tests before editing.
- Keep changes scoped to the requested behavior; avoid unrelated refactors and metadata churn.
- Do not overwrite user changes or revert files unless explicitly asked.
- Never commit secrets,
.env, API keys, local cache directories, or service data. - Prefer
rgandrg --filesfor search. - Prefer existing project patterns over new abstractions.
- Use
uv run ...for project commands. - Avoid raw SQL unless there is a clear need; prefer SQLAlchemy expressions and models.
- Add or update tests when behavior changes.
- Keep comments and docstrings useful: explain non-obvious intent, not line-by-line mechanics.
- LLM planning may produce only a
QuerySpec; it must never produce executable SQL. - SQL retrieval must compile
QuerySpecinto SQLAlchemy expressions with bound parameters. - Postgres
documentsare authoritative for retrieval evidence. Qdrant is a derived index only. - Vector retrieval must rehydrate Qdrant hits from Postgres before returning evidence.
- Skip stale vector hits when the Postgres document is missing or the Qdrant
content_hashno longer matches. - Hybrid retrieval uses reciprocal rank fusion over SQL and vector hits.
- If SQL returns zero hits, hybrid retrieval falls back to vector hits and records
sql_zero_hits. - Generated answers must use only the supplied evidence and cite source ids in the form
source_file#section_path. - Keep SQL and vector retrieval internal to hybrid evidence selection so evaluation measures the app's final hybrid answer.
- At the end of each run, explicitly update every relevant file affected by the final diff, including code, tests, fixtures, docs, schemas, migrations, config, and changelog entries when they apply.
- Update
README.mdwhen setup commands, CLI usage, environment variables, service dependencies, or workflow expectations change. - Update fixtures or evaluation notes when adding, removing, or materially changing evaluation questions.
- Document data shape changes when source JSON schemas, chunk metadata, or ingest behavior changes.
- Document migration implications when database tables, columns, indexes, or persistence behavior change.
- Update docs cross-links when adding, removing, renaming, or materially changing documentation pages.
- Keep
AGENTS.mdcurrent when agent workflow, testing expectations, or RAG invariants change.
After making the code change:
- Review the git diff.
- Identify every relevant file implied by the diff, not only the file that was directly requested.
- Update all affected code, tests, fixtures, docs, schemas, migrations, config, and changelog entries needed to keep the repository coherent.
- Update all affected documentation according to Documentation requirements.
- If architecture changed, update docs/architecture and create or update an ADR.
- If API behavior changed, update docs/api.
- If config/env/deployment changed, update docs/operations.
- If auth/security/data handling changed, update docs/security.
- If developer commands changed, update README.md or docs/development.
- Update
PROGRESS.mdby adding new follow-up tasks discovered during the run and changing existing task statuses when work has started or completed. - If no related files need updates, state the reason explicitly.
Do not finish until documentation has been checked against the final diff.