Skip to content

Commit b5f55d7

Browse files
committed
feat: updated readme
1 parent cd8b139 commit b5f55d7

6 files changed

Lines changed: 231 additions & 8 deletions

File tree

Readme.md

Lines changed: 231 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,234 @@ uvicorn app.main:app --reload
3232

3333
## Tech Stack
3434

35-
- Framework/Server: FastAPI, Uvicorn
36-
- Database & Vectors: PostgreSQL (Neon), pgvector
37-
- AI/ML: OpenAI (chat), CLIP/OpenCLIP (image embeddings)
38-
- Text-to-Speech: ElevenLabs
39-
- Auth: Clerk (JWT)
40-
- Cloud/Storage(Image): AWS S3 (+ CDN/presigned)
41-
- DevOps: Docker, Docker Compose
42-
- Config/Validation: Pydantic
35+
- FastAPI + Uvicorn: Python web framework and ASGI server
36+
- PostgreSQL (Neon) + pgvector: relational data + vector search
37+
- OpenAI + (CLIP/OpenCLIP): chat + image/text embeddings
38+
- ElevenLabs — TTS / voice replies
39+
- Clerk — JWT auth for API protection
40+
- AWS S3 — object storage + presigned uploads
41+
- Pydantic — config & schema validation
42+
- SQLAlchemy — ORM & sessions
43+
44+
## Routes: Chat (`app/routers/chat.py`)
45+
46+
**Endpoint:** `POST /api/chat`
47+
**Auth:** `Authorization: Bearer <Clerk JWT>` (required)
48+
**Does:** Runs the RAG pipeline and returns an answer plus supporting sources. Persists the turn as `channel="text"`.
49+
50+
## Routes: Chat History (`app/routers/chat_history.py`)
51+
52+
**Base prefix:** `/api/chat`
53+
**Auth:** `Authorization: Bearer <Clerk JWT>` (required)
54+
55+
### `GET /api/chat/history`
56+
57+
Returns a paginated list of the user’s **scan chats** with:
58+
59+
- `scan_id`, `scan_title`
60+
- `artwork_id`, `artwork_title`
61+
- `artwork_image_url` (auto-resolved to a usable URL)
62+
- `created_at`, `last_message`, `last_message_at`
63+
64+
## Route: Image Embedding (`app/routers/embed.py`)
65+
66+
**Endpoint:** `POST /api/embed-image`
67+
**Does:** Fetches an image from a URL, computes its embedding, and returns the vector + dimension.
68+
69+
**Implementation Notes**
70+
71+
- Loads the image via `app.helpers.common.load_image_from_url(...)`.
72+
- Embeds with `app.embeddings.embed_pil_image(...)`.
73+
- Embedding model/dimension come from your embeddings module (see `IMAGE_EMBEDDING_MODEL` in `app/config.py`).
74+
75+
## Route: Image Search (`app/routers/search.py`)
76+
77+
**Endpoint:** `POST /api/search-image`
78+
**Auth:** `Authorization: Bearer <Clerk JWT>` (required)
79+
**Uploads:** `multipart/form-data` with an image file field named **`file`**
80+
**Does:** Embeds the uploaded image, searches nearest artworks (pgvector), applies confidence/margin logic, persists a scan, and returns top-K neighbors + a `scan_id`.
81+
82+
**Query Params**
83+
84+
- `top_k` : number of results to return (default from `TOPK_DEFAULT`, 1–100)
85+
- `metric` : similarity metric: `cosine` | `l2` | `ip` (default `METRIC_DEFAULT`)
86+
- `sim_threshold` : min similarity for a match (default `IMAGE_MATCH_SIM_THRESHOLD`)
87+
- `margin_threshold` : min absolute margin `(top1 - top2)` when `require_margin=true`
88+
- `require_margin` : enforce margin checks (`true/false`, default `IMAGE_MATCH_REQUIRE_MARGIN`)
89+
- `solo_threshold` : min similarity to accept when only one candidate is present
90+
- `high_conf_threshold` : auto-accept if `top1 >= high_conf_threshold`
91+
- `margin_ratio_threshold` : min ratio `(top1 / top2)` when margin is enforced
92+
93+
## Route: Voice Chat (`app/routers/voice.py`)
94+
95+
**Endpoint:** `POST /api/voice/chat`
96+
**Auth:** `Authorization: Bearer <Clerk JWT>` (required)
97+
**Does:** Converts speech to text (ASR), runs RAG chat, then returns **TTS audio (base64)** + transcript + text answer. Persists the turn as `channel="voice"`.
98+
99+
### Form Fields
100+
101+
- **artwork_id** _(string, required)_ : target artwork context
102+
- **artist_id** _(string, optional)_
103+
- **scan_id** _(string, optional)_ : tie to existing scan/session
104+
- **prompt** _(string, optional)_ : text prompt when no audio is sent
105+
- **audio_file** _(file, optional)_ : audio if present, ASR will produce `transcript`
106+
- **voice_id** _(string, optional)_ : TTS voice (provider-specific)
107+
- **metric** _(string, optional)_ : `cosine` | `l2` | `ip` (default: `METRIC_DEFAULT`)
108+
- **sim_threshold** _(float, optional)_ : `[0,1]` (default: `TEXT_MATCH_SIM_THRESHOLD`)
109+
- **top_k** _(int, optional)_ : retrieval candidates (default: `6`)
110+
- **language_code** _(string, optional)_ : ASR language hint (e.g., `en`, `en-US`)
111+
112+
## Auth: Clerk JWT (`app/auth_clerk.py`)
113+
114+
**What it does:**
115+
Validates Clerk-issued JWTs on incoming requests and returns the current user (`{"user_id": sub, "claims": ...}`) for protected routes.
116+
117+
**Headers supported**
118+
119+
- `Authorization: Bearer <JWT>`
120+
- `X-Client-Auth: Bearer <JWT>` (fallback for mobile/web clients)
121+
122+
**Env vars**
123+
124+
- `CLERK_ISSUER` **(required)** : `https://art-connect.org.clerk.accounts.dev`
125+
126+
**How it works**
127+
128+
- Caches Clerk **JWKS** for 5 minutes to verify signatures.
129+
- Verifies `iss` (issuer) and, if provided, `aud` (audience).
130+
- Extracts `sub` : returned as `user_id` for downstream usage.
131+
- Raises `401` on missing/bad bearer or invalid token.
132+
133+
## Config (`app/config.py`)
134+
135+
Central place for **env-driven settings** (loaded via `python-dotenv`).
136+
These control retrieval metrics, table/column names, thresholds, and model IDs.
137+
138+
### What it does
139+
140+
- Reads `.env` and exposes constants used across routers/helpers.
141+
- Sets **defaults** so the API can run locally without a huge .env.
142+
- Groups knobs for **image search**, **text RAG**, and **DB schema**.
143+
144+
### Key Env Vars (with defaults)
145+
146+
**Models**
147+
148+
- `MODEL_ID` — image model id for local usage (default: `openai/clip-vit-base-patch32`)
149+
- `TEXT_EMBED_MODEL` — text embedding model (default: `text-embedding-3-small`)
150+
- `GEN_MODEL` — chat/generation model (default: `gpt-4o-mini`)
151+
- `OPENAI_API_KEY` — required for OpenAI models
152+
153+
**Database / Vectors**
154+
155+
- `DATABASE_URL` — Postgres connection string
156+
- `PGVECTOR_PROBES` — index probe count for ANN searches (default: `10`)
157+
158+
**Image Embeddings (table/cols)**
159+
160+
- `IMAGE_EMBED_TABLE` (default: `artwork_embeddings_image`)
161+
- `IMAGE_EMBED_VECTOR_COL` (default: `embedding`)
162+
- `IMAGE_EMBED_ARTWORK_ID_COL` (default: `artwork_id`)
163+
164+
**Retrieval Controls (shared)**
165+
166+
- `METRIC_DEFAULT``cosine` | `l2` | `ip` (default: `cosine`)
167+
- `TOPK_DEFAULT` — default top-K (default: `5`)
168+
169+
**Image Match Thresholds**
170+
171+
- `IMAGE_MATCH_SIM_THRESHOLD` (default: `0.70`)
172+
- `IMAGE_MATCH_MARGIN_THRESHOLD` (default: `0.10`)
173+
- `IMAGE_MATCH_REQUIRE_MARGIN` (`true|false`, default: `true`)
174+
- `IMAGE_MATCH_SOLO_THRESHOLD` (default: `0.80`)
175+
- `IMAGE_MATCH_HIGH_CONF_THRESHOLD` (default: `0.90`)
176+
- `IMAGE_MATCH_MARGIN_RATIO_THRESHOLD` (default: `1.05`)
177+
178+
**Domain Tables (read-joins)**
179+
180+
- `ARTWORKS_TABLE` (default: `Artwork`)
181+
- `ARTWORKS_ID_COL` (default: `id`)
182+
- `ARTWORKS_ARTIST_ID_COL` (default: `artistId`)
183+
- `ARTISTS_TABLE` (default: `Artist`)
184+
- `ARTISTS_ID_COL` (default: `id`)
185+
- `ARTISTS_NAME_COL` (default: `name`)
186+
- `ARTWORKS_TITLE_COL` (default: `title`)
187+
- `ARTWORKS_DESC_COL` (default: `description`)
188+
- `ARTISTS_BIO_COL` (default: `bio`)
189+
190+
**Text Embeddings (RAG)**
191+
192+
- `TEXT_EMBED_TABLE_ARTWORK` (default: `artwork_embeddings_text`)
193+
- `TEXT_EMBED_TABLE_ARTIST` (default: `artist_embeddings_text`)
194+
- `TEXT_EMBED_VECTOR_COL` (default: `embedding`)
195+
- `TEXT_EMBED_TEXT_COL` (default: `content`)
196+
- `TEXT_MATCH_SIM_THRESHOLD` (default: `0.60`)
197+
198+
> **Operator mapping (pgvector):**
199+
> `cosine → <=>`, `l2 → <->`, `ip → <#>`; code converts distance : similarity for thresholds.
200+
201+
## Chat DB Pool (`app/db_chat.py`)
202+
203+
**What it does:**
204+
Provides a **psycopg connection pool** and a `get_chat_cursor()` context manager for chat-related queries (scans/messages).
205+
206+
**Env var**
207+
208+
- `CHAT_DATABASE_URL` **(required)** : Postgres connection string for the **chat** database.
209+
210+
## App DB Pool (`app/db.py`)
211+
212+
**What it does:**
213+
Creates a **psycopg connection pool** for the primary app database and exposes:
214+
215+
- `get_cursor()` : pooled cursor with `pgvector` registered and `ivfflat.probes` set
216+
- `get_conn()` : direct connection context (bypasses pool)
217+
218+
**Env vars**
219+
220+
- `DATABASE_URL` **(required)** : Postgres connection string
221+
- `PGVECTOR_PROBES` : ANN probe count for `ivfflat` (default from `app/config.py`)
222+
223+
## Embeddings (`app/embeddings.py`)
224+
225+
**What it does**
226+
227+
- **Image embeddings (CLIP):** Loads a CLIP model once, embeds a PIL image, **L2-normalizes** features, and returns a Python list of floats.
228+
- **Text embeddings (OpenAI):** Calls OpenAI Embeddings. Returns a list of floats.
229+
230+
## LLM Wrapper (`app/llm.py`)
231+
232+
**What it does**
233+
234+
- Small helper around OpenAI Chat Completions to turn **question + RAG context (+ optional history)** into an answer that stays grounded.
235+
- System Prompts
236+
237+
## App Entry (`app/main.py`)
238+
239+
**What it does**
240+
241+
- Creates the FastAPI app, configures **CORS**, exposes health & identity endpoints.
242+
- Registers feature routers: **embed**, **search**, **chat**, **voice**, **chat history**.
243+
244+
**Endpoints (no global `/api` prefix)**
245+
246+
- `GET /` — status payload (`name`, `version`, `python`, `server_time_utc`, `uptime`, `git_sha?`)
247+
- `GET /healthz``{ "ok": true }`
248+
- `GET /whoami` — returns `{ "user_id": ... }` (requires Clerk auth)
249+
- **Routers included**
250+
- `POST /embed-image` — image to embedding
251+
- `POST /search-image` — image search (pgvector)
252+
- `POST /chat` — text chat (RAG)
253+
- `POST /voice/chat` — voice chat (ASR to RAG to TTS)
254+
- `GET /chat/history` — chat list
255+
- `GET /chat/{scan_id}/messages` — messages for a scan
256+
257+
**CORS**
258+
259+
- Reads `ALLOW_ORIGINS`, splits by comma, and enables:
260+
- `allow_credentials=True`, `allow_methods="*"`, `allow_headers="*"`
261+
262+
## Schemas (`app/schemas.py`)
263+
264+
Pydantic models that define request/response shapes for the API.
265+
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
465 Bytes
Binary file not shown.
3.59 KB
Binary file not shown.

0 commit comments

Comments
 (0)