Arag / ARCHITECTURE.md
AuthorBot
feat: proper visitor analytics system - New Visitor model (visitor_uid + fingerprint fallback, first_seen/last_seen/page_views, geo, device) - VisitorRepository with upsert + analytics queries - visitor_tracker.py: record_visitor() upserts on every session/init - session/init accepts visitor_uid in request body (HF-Spaces iframe-safe, no cookies) - 4 new analytics endpoints: /visitors /geo /devices /sessions/stats - geo.py: region extraction + X-Real-IP support - DB migration: visitor_uid column on chat_sessions - ARCHITECTURE.md updated
3ae7860
|
Raw
History Blame Contribute Delete
28.6 kB
# Author RAG β€” Architecture Reference
---
## πŸ€– AI AGENT MANDATORY PROTOCOL
> **THIS BLOCK IS FOR AI AGENTS (including Antigravity / Gemini). FOLLOW ON EVERY PROMPT.**
### On EVERY new request you must:
1. **READ this file first** before reading any source file or writing any code.
2. **CHECK Β§9 Invariants** β€” verify your planned change does not violate any rule.
3. **CHECK Β§8 Where to Add Features** β€” confirm you are editing the correct file for the task.
4. **After completing any change** that adds a file, route, model column, config value, or service β€” **UPDATE this document** to reflect the new state.
5. **Push the updated `ARCHITECTURE.md`** in the same commit as the code change. They must never go out of sync.
### What counts as a required update:
| Change made | Section to update |
|---|---|
| New file created | Β§4 Full File Tree |
| New route added | Β§4 (correct sub-section) |
| New DB column added | Β§4 Models table + Β§8 |
| New config value added | Β§7 Configuration |
| New invariant established | Β§9 Invariants |
| New feature area added | Β§8 Where to Add Features |
| Architecture restructured | Β§3 Layer diagram + Β§4 |
### NEVER do this:
- Edit source files without checking Β§9 first.
- Add a new file without adding it to Β§4.
- Define a new guard/detector outside `guards.py`.
- Add prompt text outside `prompter.py`.
- Skip updating this document after a structural change.
---
> **Purpose:** This document is the single source of truth for any AI agent or human
> developer joining this codebase. Read this file first before opening any source file.
> It answers: *What does each file do? How does a request flow? Where do I add X?*
---
## 1. What This System Does
**Author RAG** is a multi-tenant SaaS platform. Each customer (an *author*) gets an
AI-powered chatbot that lives on their website and answers questions about their books.
The chatbot is a persuasive sales tool β€” it understands reader intent, retrieves precise
passages from the book, and surfaces purchase links at the right moment.
**Key numbers:**
- Runtime: Python 3.11, FastAPI async
- Database: SQLite (dev) / PostgreSQL (prod) via SQLAlchemy async
- Vector store: ChromaDB (local persistent)
- LLM: OpenAI GPT-4o-mini (configurable)
- Embeddings: `text-embedding-3-small`
---
## 2. Top-Level Directory Layout
```
Author RAG/
β”œβ”€β”€ app/ ← All application code (see Β§4)
β”œβ”€β”€ static/ ← Compiled frontend (admin SPA, widget JS)
β”œβ”€β”€ requirements.txt ← Python dependencies
β”œβ”€β”€ Dockerfile ← HuggingFace Spaces deployment
β”œβ”€β”€ ARCHITECTURE.md ← THIS FILE
└── .env ← Secrets (never committed)
```
---
## 3. Layer Architecture
The codebase follows a strict 5-layer architecture. **Data only flows downward.**
A layer may never import from a layer above it.
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Layer 1 β€” API / Routers β”‚
β”‚ app/api/, app/admin/routers/, app/superadmin/ β”‚
β”‚ β€’ Accepts HTTP requests β”‚
β”‚ β€’ Validates input (Pydantic schemas) β”‚
β”‚ β€’ Delegates to services β€” no business logic here β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 2 β€” Services / Pipeline β”‚
β”‚ app/services/ β”‚
β”‚ β€’ All business logic lives here β”‚
β”‚ β€’ The RAG pipeline is the core of this layer β”‚
β”‚ β€’ May call repositories and other services β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 3 β€” Repositories β”‚
β”‚ app/repositories/ β”‚
β”‚ β€’ All database queries live here β”‚
β”‚ β€’ Returns model objects β€” never raw SQL β”‚
β”‚ β€’ No business logic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 4 β€” Models β”‚
β”‚ app/models/ β”‚
β”‚ β€’ SQLAlchemy ORM table definitions only β”‚
β”‚ β€’ No methods with logic β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 5 β€” Core / Infrastructure β”‚
β”‚ app/core/, app/config.py, app/dependencies.py β”‚
β”‚ β€’ DB engine, Redis, ChromaDB clients β”‚
β”‚ β€’ JWT / token crypto β”‚
β”‚ β€’ No domain logic β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
---
## 4. Full File Tree with Descriptions
### `app/` β€” Root
| File | Role |
|------|------|
| `main.py` | FastAPI app factory. Registers middleware, exception handlers, routers. Startup delegates to `core/startup/`. |
| `config.py` | All env-var settings via Pydantic `BaseSettings`. **One source of truth for every config value.** |
| `dependencies.py` | FastAPI `Depends()` providers: DB session, Redis, auth guards (`get_current_author_scoped`, `get_subscription_author`, `get_current_superadmin`). |
---
### `app/core/` β€” Infrastructure
| File | Role |
|------|------|
| `core/startup/db.py` | `init_db()` β€” creates all tables + runs incremental ALTER TABLE migrations on startup. |
| `core/startup/seeder.py` | `seed_superadmin()` β€” bootstraps the superadmin account from env vars on first run. |
| `core/access/token_crypto.py` | Creates and verifies signed subscription tokens (widget embed). |
| `core/access/subscription.py` | Subscription validation logic (expiry, budget, revocation). |
| `core/access/jwt_blacklist.py` | Redis-backed JWT revocation list. |
| `core/access/totp.py` | TOTP 2FA for SuperAdmin login. |
| `core/chroma_client.py` | Singleton ChromaDB client. |
| `core/security.py` | `hash_password()`, `verify_password()`. |
---
### `app/api/` β€” Public Widget API
These routes are called by the embedded widget on the author's website.
All require `X-Subscription-Token` header (validated via `get_subscription_author`).
| File | Routes | Role |
|------|--------|------|
| `api/chat.py` | `POST /api/chat/{slug}` | Main chat endpoint β€” calls `run_pipeline()`. |
| | `POST /api/chat/{slug}/session/init` | Creates a new visitor session. |
| | `GET /api/chat/{slug}/session/history/{id}` | Returns chat history. |
| | `POST /api/chat/{slug}/session/farewell` | Graceful session close + summarization. |
| | `POST /api/chat/{slug}/session/rate` | Star rating. |
| | `POST /api/chat/{slug}/track-click` | Records buy-link click. |
| | `POST /api/chat/{slug}/feedback` | πŸ‘/πŸ‘Ž message feedback. |
| | `GET /api/chat/{slug}/events` | SSE stream for ingestion progress. |
| `api/ingest.py` | `POST /api/admin/{slug}/books/upload` | Book PDF upload + async embedding pipeline. |
| `api/widget.py` | `POST /api/widget/token` | Returns a signed subscription token for the embed script. |
| `api/schemas_router.py` | `/api/auth/*` | Login, register, token refresh. |
---
### `app/admin/` β€” Author Admin API
Prefix: `/api/admin/{author_slug}/`
Auth: Bearer JWT (`get_current_author_scoped`)
| File | Routes | Role |
|------|--------|------|
| `admin/router.py` | β€” | 20-line aggregator. Mounts all 7 sub-routers. |
| `admin/routers/dashboard.py` | `/sessions`, `/sessions/search`, `/sessions/{id}/transcript`, `/sessions/{id}/block`, `/sessions/{id}/messages/{mid}/annotate` | Session management and message review. |
| `admin/routers/books.py` | `/books`, `/books/{id}`, `/books/{id}/cover` | Book list, delete, cover upload. |
| `admin/routers/analytics.py` | `/analytics`, `/analytics/funnel`, `/analytics/intents`, `/analytics/visitors`, `/analytics/geo`, `/analytics/devices`, `/analytics/sessions/stats` | Dashboard charts, funnel data, visitor analytics. |
| `admin/routers/settings.py` | `/password`, `/widget-config`, `/profile`, `/personality`, `/notifications`, `/embed-token`, `/token-usage` | All author account settings. |
| `admin/routers/links.py` | `/smart-links/{book_id}` | Buy/preview URL management. |
| `admin/routers/qa.py` | `/qa`, `/qa/import`, `/qa/export`, `/qa/{id}` | Custom Q&A training data (CRUD + CSV). |
| `admin/routers/exports.py` | `/export/sessions`, `/export/analytics`, `/export/conversations` | CSV data exports. |
---
### `app/superadmin/` β€” SuperAdmin API
Prefix: `/api/super/`
Auth: Bearer JWT + TOTP (`get_current_superadmin`)
| File | Routes | Role |
|------|--------|------|
| `superadmin/router.py` | β€” | 15-line aggregator. Mounts 3 sub-routers. |
| `superadmin/routers/platform.py` | `/`, `/diag`, `/health`, `/audit`, `/announce`, `/backup` | Platform diagnostics, audit log, announcements. |
| `superadmin/routers/authors.py` | `/authors`, `/authors/{id}`, `/authors/{id}/suspend`, `/authors/{id}/grant`, `/authors/{id}/embed-token` | Full author account lifecycle. |
| `superadmin/routers/grants.py` | `/grants`, `/grants/{id}/revoke`, `/grants/{id}/bonus-tokens`, `/grants/{id}/extend`, `/grants/{id}/reset-tokens`, `/grant` | Subscription token budget management. |
| `superadmin/routers/_utils.py` | β€” | Shared `_err()` error handler (no routes). |
---
### `app/services/pipeline/` β€” The RAG Pipeline ⭐
This is the core of the product. Every visitor message flows through `run_pipeline()`.
```
app/services/pipeline/
β”œβ”€β”€ __init__.py ← Public API: run_pipeline, PipelineResult, invalidate_book_cache
β”œβ”€β”€ core.py ← 12-step orchestrator (the conductor, ~200 lines)
β”œβ”€β”€ generation.py ← Steps 8–10: LLM call + faithfulness retry + safety scrub
β”œβ”€β”€ handlers.py ← Short-circuit response functions (greeting, catalog, piracy...)
β”œβ”€β”€ helpers.py ← Pure stateless utilities (call_llm, format_history, get_book_links...)
β”œβ”€β”€ guards.py ← Boolean detectors (is_greeting, is_full_story_request, ...) β€” SINGLE SOURCE OF TRUTH
β”œβ”€β”€ cache.py ← LRU answer cache (256 slots, MD5 key)
└── dedup.py ← Chunk deduplication by Jaccard overlap
```
#### The 12-Step Pipeline (`core.py`)
```
Step 0 sanitize_user_input() Strip dangerous/empty input
Step 1 check_boundary() Jailbreak / piracy / off-topic guard
Step 1.5 check_custom_qa() Short-circuit: exact Q&A match (no LLM)
Step 2 classify_intent() 3-tier: rules β†’ cache β†’ LLM-as-fallback
Step 3 Book resolution Greeting / catalog / book-select short-circuits
LRU cache lookup Skip steps 4-12 on cache hit
Step 4 rewrite_query() Generate 3 query variations for multi-pass retrieval
Step 5 retrieve_chunks() ChromaDB vector search (top_k=15 default)
Step 6 rerank_chunks() Cross-encoder re-ranking (top_n=5 default)
Step 6.5 deduplicate_chunks() Remove near-duplicate overlapping windows
Step 7 build_context() Token-aware context assembly (max 2000 tokens)
Step 8 MASTER_SYSTEM_PROMPT.format Assemble full prompt with history + interest + context
call_llm() OpenAI chat completions
Step 9 check_faithfulness() NLI guardrail β€” retry with stricter prompt if hallucinating
Step 10 scrub_unsafe_response() Output safety scrub + is_response_safe() check
Step 11 UpsellEngine.select_strategy() Choose upsell strategy based on intent + session state
Step 12 ResponseFormatter.format() Assemble final JSON response with optional buy button
cache_set() Store result for future identical questions
```
**Where to look for what:**
| Task | File |
|------|------|
| Change the LLM model or temperature | `config.py` β†’ `OPENAI_CHAT_MODEL`, `RAG_TEMPERATURE` |
| Edit the system prompt | `services/prompter.py` |
| Change what counts as a greeting | `services/pipeline/guards.py` β†’ `_GREETINGS` |
| Change upsell logic | `services/upsell_engine.py` |
| Change faithfulness threshold | `services/faithfulness.py` |
| Change how queries are rewritten | `services/rewriter.py` |
| Change intent labels / rules | `services/intent.py` |
| Change chunking strategy | `services/chunker.py` |
| Change re-ranking | `services/reranker.py` |
| Change context token limit | `config.py` β†’ `RAG_CONTEXT_MAX_TOKENS` |
---
### `app/services/` β€” All Other Services
| File | Role |
|------|------|
| `rag_pipeline.py` | **Compatibility shim only.** Imports from `pipeline/` and re-exports. |
| `intent.py` | 3-tier intent classifier: keyword rules β†’ session cache β†’ LLM fallback. |
| `rewriter.py` | Query rewriter β€” generates 3 variations using conversation context. |
| `guardrails.py` | Input sanitization, boundary checks, output safety, response scrubbing. |
| `prompter.py` | All prompt templates: `MASTER_SYSTEM_PROMPT`, boundary responses, upsell hooks. |
| `faithfulness.py` | NLI-based faithfulness scoring against retrieved context. |
| `formatter.py` | `ResponseFormatter` β€” assembles the final JSON `{text, links, has_links}` dict. |
| `upsell_engine.py` | `UpsellEngine` β€” selects upsell strategy, decides if link should show. |
| `context_builder.py` | Token-aware context string builder from ranked chunks. |
| `vector_store.py` | ChromaDB retrieval β€” multi-query search + score filtering. |
| `reranker.py` | Cross-encoder re-ranking of retrieved chunks. |
| `embeddings.py` | Generates and stores OpenAI embeddings into ChromaDB. |
| `chunker.py` | Splits book text into overlapping sliding-window chunks. |
| `parser.py` | Extracts clean text from PDF uploads. |
| `auth_service.py` | Register, login, token refresh logic. |
| `superadmin_service.py` | Business logic for author lifecycle (suspend, delete, grant). |
| `token_budget.py` | Token usage tracking and budget enforcement. |
| `email_service.py` | Transactional email sending. |
| `rate_limiter.py` | Per-IP / per-author rate limiting. |
| `file_validator.py` | PDF upload validation (size, MIME type, header). |
| `session_store.py` | Redis-backed session state persistence. |
| `summarizer.py` | End-of-session conversation summarizer. |
| `notifications.py` | In-app notification helpers. |
| `analytics.py` | Analytics event recording facade. |
#### `services/analytics_core/`
| File | Role |
|------||------|
| `tracker.py` | Records `AnalyticsEvent` rows after each chat turn. |
| `aggregator.py` | Pre-aggregates daily analytics for dashboard charts. |
| `geo.py` | IP β†’ country/region/city lookup (MaxMind GeoLite2). Exposes `get_real_ip()` (proxy-aware). |
| `visitor_tracker.py` | `record_visitor()` β€” upserts `Visitor` table on every `session/init`. Deduplicates via localStorage UUID then fingerprint fallback. |
#### `services/session_core/`
| File | Role |
|------|------|
| `manager.py` | `SessionManager` + `SessionContext` β€” the session state object passed through the pipeline. |
| `context.py` | Interest score and tag tracking across turns. |
| `fingerprint.py` | Visitor fingerprint generation (SHA-256, no PII). |
---
### `app/models/` β€” Database Tables
| File | Table | Key columns |
|------|-------|-------------|
| `user.py` | `users` | `id`, `email`, `role` (`author`/`superadmin`), `bot_name`, `chatbot_is_active` |
| `book.py` | `books` | `id`, `author_id`, `title`, `status`, `chroma_collection_id`, `chunk_count`, `buy_url` |
| `chat_session.py` | `chat_sessions`, `chat_messages` | Session: `visitor_fingerprint`, `visitor_uid`, `turn_count`, `rating`, `blocked`. Message: `role`, `intent`, `faithfulness_score`, `hallucination_detected` |
| `client_access.py` | `client_access` | `author_id`, `plan`, `token_budget`, `tokens_used`, `expires_at`, `is_revoked` |
| `custom_qa.py` | `custom_qa` | `author_id`, `question`, `answer`, `match_threshold`, `priority`, `match_count` |
| `analytics.py` | `analytics_events` | `author_id`, `session_id`, `intent`, `link_clicked`, `prompt_tokens`, `response_ms` |
| `link.py` | `links` | `author_id`, `book_id`, `purchase_url`, `preview_url` |
| `document.py` | `documents` | `book_id`, `filename`, `status`, `pages` |
| `visitor.py` | `visitors` | `author_id`, `visitor_uid` (UUID, unique per author), `fingerprint`, `first_seen`, `last_seen`, `page_views`, `country_code`, `region`, `city`, `device_type`, `browser`, `os` |
---
### `app/repositories/` β€” Database Access Layer
| File | Role |
|------||------|
| `base.py` | `BaseRepository` β€” shared `get_by_id`, `save`, `delete` methods. |
| `user_repo.py` | `UserRepository` β€” lookup by email/slug, list all authors. |
| `book_repo.py` | `BookRepository` β€” `list_active_for_author()`, `list_for_author()`. |
| `access_repo.py` | `AccessRepository` β€” `get_active_for_author()` β€” used everywhere for budget checks. |
| `link_repo.py` | `LinkRepository` β€” `get_for_book()`, `upsert_for_book()`. |
| `audit_repo.py` | `AuditRepository` β€” append-only `log()`, `list_recent()`. |
| `document_repo.py` | `DocumentRepository` β€” book document tracking. |
| `visitor_repo.py` | `VisitorRepository` β€” `get_by_uid()`, `get_by_fingerprint()`, `create()`, `touch()`, distribution queries for analytics. |
---
### `app/schemas/` β€” Pydantic Request/Response Schemas
| File | Role |
|------|------|
| `chatbot.py` | `ChatRequest`, `ChatResponse`, `SessionInitResponse`, `FarewellRequest` |
| `admin.py` | `AnnotateRequest`, `FlagRequest`, `PasswordChangeRequest`, `ProfileUpdate`, `WidgetConfigUpdate` |
| `superadmin.py` | `CreateAuthorRequest`, `GrantAccessRequest`, `RevokeAccessRequest`, `AddBonusTokensRequest` |
| `auth.py` | `LoginRequest`, `RegisterRequest`, `TokenResponse` |
---
### `app/middleware/` β€” Request Middleware
| File | Role |
|------|------|
| `logging_middleware.py` | Structured request/response logging with `structlog`. |
| `rate_limit_middleware.py` | Sliding-window rate limiting (configurable per route type). |
| `security_headers.py` | CSP, HSTS, X-Frame-Options headers. |
| `metrics.py` | Prometheus metrics collection (optional). |
---
### `app/tasks/` β€” Background Tasks
| File | Role |
|------|------|
| `ingestion_task.py` | Async book processing: parse β†’ chunk β†’ embed β†’ store in ChromaDB. |
| `analytics_task.py` | Periodic analytics aggregation. |
| `email_task.py` | Queued email sending. |
| `backup_task.py` | SQLite + ChromaDB backup. |
| `expiry_check_task.py` | Subscription expiry warnings. |
| `geo_update_task.py` | Retroactive geo enrichment for sessions. |
| `link_health_task.py` | Checks buy links for 404s. |
| `celery_app.py` | Celery app instance (optional β€” tasks can run inline). |
---
## 5. A Complete Request Trace
**Scenario:** A reader on an author's website asks *"Is the ending happy?"*
```
Browser (widget JS)
β”‚ POST /api/chat/{author_slug}
β”‚ Headers: X-Subscription-Token: <signed JWT>
β”‚ Body: { "message": "Is the ending happy?", "session_id": "..." }
β–Ό
app/api/chat.py β†’ chat()
β”‚ 1. get_subscription_author() Verify token, check budget, load author
β”‚ 2. SessionManager.load() Load session from Redis (history, book selection)
β”‚ 3. run_pipeline(query, author, session_context, db)
β–Ό
app/services/pipeline/core.py β†’ run_pipeline()
β”‚ Step 0: sanitize_user_input() β†’ "Is the ending happy?"
β”‚ Step 1: check_boundary() β†’ no violation
β”‚ Step 1.5: check_custom_qa() β†’ no match
β”‚ Step 2: classify_intent() β†’ intent="question", confidence=0.92
β”‚ Step 3: BookRepository.list_active_for_author() β†’ [Book("The Last Signal")]
β”‚ is_greeting() β†’ False
β”‚ is_catalog_question() β†’ False
β”‚ LRU cache lookup β†’ miss
β”‚ Step 4: rewrite_query() β†’ ["Is the ending happy?", "How does it end?", "resolution of the story"]
β”‚ Step 5: retrieve_chunks() β†’ 15 chunks from ChromaDB
β”‚ Step 6: rerank_chunks() β†’ top 5 chunks scored by cross-encoder
β”‚ Step 6.5: deduplicate_chunks() β†’ 4 unique chunks
β”‚ Step 7: build_context() β†’ 1,840 tokens of context
β–Ό
app/services/pipeline/generation.py β†’ generate_response()
β”‚ Step 8: MASTER_SYSTEM_PROMPT.format(...)
β”‚ call_llm() β†’ "Without giving anything away, I can say..."
β”‚ Step 9: check_faithfulness() β†’ faithful=True, score=0.91
β”‚ Step 10: is_response_safe() β†’ True
β”‚ return (response, 0.91, False, 312, 87)
β–Ό
core.py (continues)
β”‚ Step 11: UpsellEngine.select_strategy() β†’ "SOFT_MENTION"
β”‚ should_include_link() β†’ True (turn 4, high interest)
β”‚ get_book_links() β†’ purchase_url="https://amazon.com/..."
β”‚ Step 12: ResponseFormatter.format() β†’ {text, links:[{label:"Get the book", url:...}], has_links:True}
β”‚ cache_set() β†’ stored for next identical query
β–Ό
app/api/chat.py (continues)
β”‚ SessionManager.save() Update session: turn_count++, history appended
β”‚ analytics.record_event() Log intent, tokens, latency, link_shown
β”‚ token_budget.deduct() Deduct prompt+completion tokens from grant
β–Ό
HTTP 200 { "message": "Without giving anything away...", "links": [...] }
β–Ό
Browser (widget JS)
Renders response + "Get the book" button
```
---
## 6. Authentication Model
Three distinct auth tiers with separate dependencies:
```
Tier 1: Visitor (Widget)
β†’ Header: X-Subscription-Token (signed JWT, no login needed)
β†’ Dependency: get_subscription_author()
β†’ Checks: token signature, expiry, token budget, author active
Tier 2: Author (Admin)
β†’ Header: Authorization: Bearer <JWT>
β†’ Dependency: get_current_author_scoped()
β†’ Checks: JWT validity, author role, slug matches token
Tier 3: SuperAdmin
β†’ Header: Authorization: Bearer <JWT>
β†’ Dependency: get_current_superadmin()
β†’ Checks: JWT validity, superadmin role, TOTP verified
```
---
## 7. Key Configuration Values
All in `app/config.py` (loaded from `.env`):
| Variable | Default | Effect |
|----------|---------|--------|
| `OPENAI_CHAT_MODEL` | `gpt-4o-mini` | LLM used for response generation |
| `OPENAI_EMBED_MODEL` | `text-embedding-3-small` | Embedding model |
| `RAG_RETRIEVAL_TOP_K` | `15` | Chunks fetched from ChromaDB |
| `RAG_RERANK_TOP_N` | `5` | Chunks kept after re-ranking |
| `RAG_RERANK_MIN_SCORE` | `0.3` | Min cross-encoder score to keep |
| `RAG_CONTEXT_MAX_TOKENS` | `2000` | Max tokens in assembled context |
| `RAG_MAX_RESPONSE_TOKENS` | `300` | Max tokens in LLM response |
| `RAG_TEMPERATURE` | `0.4` | LLM temperature |
| `RAG_BOOK_CONFIDENCE_THRESHOLD` | `0.7` | Min score to route to a specific book |
| `SUPERADMIN_EMAIL` | β€” | Seeded on first startup |
| `SUPERADMIN_PASSWORD` | β€” | Seeded on first startup |
---
## 8. Where to Add New Features
| Task | Where to add it | Notes |
|------|-----------------|-------|
| New author admin route | `app/admin/routers/<closest>.py` | Or create a new file + mount in `admin/router.py` |
| New superadmin route | `app/superadmin/routers/<platform|authors|grants>.py` | |
| New intent label | `app/services/intent.py` β†’ `_RULE_MAP` | Add keyword rules first; LLM only as fallback |
| New guard/detector | `app/services/pipeline/guards.py` | **Never define is_greeting elsewhere** |
| New short-circuit response | `app/services/pipeline/handlers.py` | Must return `PipelineResult` |
| New prompt template | `app/services/prompter.py` | Keep ALL prompt text in one file |
| New DB column | `app/models/<model>.py` + `app/core/startup/db.py` β†’ `_NEW_COLUMNS` | No Alembic needed |
| New background task | `app/tasks/<name>_task.py` | Register in `tasks/celery_app.py` if async |
| New config value | `app/config.py` | Always typed, always has a default |
---
## 9. Invariants β€” Rules That Must Never Be Broken
1. **Services never import from routers.** Data flows down only.
2. **`guards.py` is the single source of truth** for all boolean detectors. Never re-define `is_greeting()` elsewhere.
3. **All prompt text lives in `prompter.py`.** No inline f-strings with user-facing text in other files.
4. **All pipeline results return `PipelineResult`.** Never return a raw dict from `run_pipeline()`.
5. **Repositories never contain business logic.** If it's a decision, it belongs in a service.
6. **Never skip a pipeline step.** Steps 0–12 run for every message; short-circuits return early from `core.py`, not by skipping steps.
7. **Cache only cacheable intents.** `purchase_intent`, `complaint`, `greeting` are never cached (time/person-sensitive).
8. **Token usage must always be recorded.** Every LLM call goes through `call_llm()` in `helpers.py` which returns token counts.
9. **All new DB columns go into `core/startup/db.py β†’ _NEW_COLUMNS`.** This keeps migrations consistent across environments.
10. **All exceptions have typed exception classes** in `app/exceptions/`. Never `raise HTTPException` directly in service layer.
11. **This document must be updated on every structural change.** See Β§AI AGENT MANDATORY PROTOCOL above.
---
## 10. Change Log
> AI agents: append an entry here after every significant change. Format: `YYYY-MM-DD | What changed | Files affected`.
| Date | Change | Files Affected |
|------|--------|----------------|
| 2026-06-19 | Initial project build β€” auth, chat API, admin panel, ingestion pipeline | All |
| 2026-06-22 | Intelligence overhaul β€” Python-first intent classifier, hybrid rewriter, LRU cache, chunk dedup, natural upsell | `intent.py`, `rewriter.py`, `upsell_engine.py`, `rag_pipeline.py` |
| 2026-06-23 | Modularity refactor Phase 1 β€” `admin/router.py` split into 7 sub-routers, `rag_pipeline.py` split into `pipeline/` package | `admin/router.py`, `admin/routers/*`, `services/pipeline/*`, `services/rag_pipeline.py` |
| 2026-06-23 | Modularity refactor Phase 2 β€” `superadmin/router.py` split into 3 sub-routers, `main.py` startup extracted to `core/startup/`, `pipeline/core.py` generation extracted to `pipeline/generation.py` | `superadmin/router.py`, `superadmin/routers/*`, `core/startup/*`, `services/pipeline/generation.py`, `main.py` |
| 2026-06-23 | Added `ARCHITECTURE.md` with AI Agent Mandatory Protocol | `ARCHITECTURE.md` |
| 2026-06-23 | **Production Improvements Phases 1–5:** 8 bugs fixed, 7 features added. B1 fingerprint null crash, B2 silent truncation, B3 OpenAI client per-call, B4 no LLM retry, B5 piracyβ†’jailbreak wrong intent, B6 history only 3 turns, B8 no budget warning, B9 sync ChromaDB in async loop. New: per-visitor rate limit, input cap, tenacity retry, intent rule expansion (+30% coverage), history fix, budget email warning, async health check. | `config.py`, `middleware/rate_limit_middleware.py`, `api/chat.py`, `services/pipeline/core.py`, `services/pipeline/helpers.py`, `services/intent.py`, `dependencies.py`, `services/token_budget.py`, `requirements.txt` |
| 2026-06-23 | **Visitor Analytics System:** Replaced session-based visitor counting with proper deduplication. New `Visitor` model (one row per browser per author), `VisitorRepository`, `visitor_tracker.py` service. `session/init` now accepts `visitor_uid` (localStorage UUID) in request body β€” HF-Spaces-safe (no cookies). 4 new analytics endpoints: `/visitors`, `/geo`, `/devices`, `/sessions/stats`. `geo.py` updated with region extraction and X-Real-IP header support. | `models/visitor.py`, `repositories/visitor_repo.py`, `services/analytics_core/visitor_tracker.py`, `services/analytics_core/geo.py`, `api/chat.py`, `admin/routers/analytics.py`, `schemas/chatbot.py`, `models/chat_session.py`, `core/startup/db.py`, `models/__init__.py` |