System design document for the PromptAds AI contextual advertising platform.
- System Overview
- Ad Matching Pipeline
- Data Models
- API Design
- Frontend Dashboard
- Infrastructure
- Security
- Scaling Strategy
PromptAds AI serves contextual ads to AI applications by matching user prompts to advertiser content using semantic vector similarity.
┌─────────────────────────────────────────────────────────────────┐
│ AI Application │
│ (Chatbot, Copilot, Search Agent, etc.) │
│ │
│ from promptads_ai import get_ad │
│ ad = get_ad(user_prompt) │
└──────────────────────┬──────────────────────────────────────────┘
│ POST /engine/match-ad
▼
┌─────────────────────────────────────────────────────────────────┐
│ PromptAds API (FastAPI) │
│ │
│ ┌──────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ Auth │ │ Engine │ │ Analytics │ │
│ │ Service │ │ (Match Pipeline) │ │ Service │ │
│ └──────────┘ └────────┬─────────┘ └───────────────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Embedding │ │ Qdrant │ │ Postgres │ │ Redis │ │
│ │ (sentence- │ │(vector)│ │ (RDBMS) │ │ (cache) │ │
│ │ transformers) │ │ │ │ │ │ │ │
│ └────────────────┘ └────────┘ └──────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Advertiser Dashboard (Next.js 15) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ Overview │ │ Ad Mgr │ │ Analytics │ │ Settings │ │
│ └──────────┘ └──────────┘ └───────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
When a developer calls getAd("best coding laptop"), the engine runs a
5-stage pipeline:
User Prompt
│
▼
┌─────────────────────┐
│ 1. Embedding │ Convert prompt to 384-dim vector
│ Service │ (all-MiniLM-L6-v2)
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ 2. Vector Search │ Find top-K nearest ad vectors
│ (Qdrant) │ in the vector store
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ 3. Ad Ranker │ Score each candidate:
│ │ final = relevance × 0.70 + bid × 0.30
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ 4. Ad Generator │ Optionally rewrite ad copy to
│ (optional) │ better fit the prompt context
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ 5. Engine Service │ Orchestrates the full pipeline,
│ (orchestrator) │ returns MatchResponse
└─────────────────────┘
| Module | File | Responsibility |
|---|---|---|
| Embedding Service | engine/embedding_service.py |
Encodes text to dense vectors using sentence-transformers |
| Vector Search | engine/vector_search.py |
Queries Qdrant for nearest-neighbor ad vectors |
| Ad Ranker | engine/ad_ranker.py |
Computes final score: relevance_weight * relevance + bid_weight * normalized_bid |
| Ad Generator | engine/ad_generator.py |
Optional LLM-based ad copy rewriting for context fit |
| Engine Service | engine/engine_service.py |
Orchestrates the pipeline end-to-end |
final_score = (relevance_weight × cosine_similarity) + (bid_weight × normalized_bid)
Default weights: relevance_weight = 0.70, bid_weight = 0.30.
The highest-scoring ad wins. Weights are configurable per request to let developers tune the relevance-vs-revenue trade-off.
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| String | Unique email | |
| hashed_password | String | bcrypt hash |
| created_at | DateTime | Registration time |
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| owner_id | UUID | FK to User |
| title | String | Ad headline |
| text | String | Ad body copy |
| target_keywords | Array[String] | Targeting keywords |
| bid_amount | Float | Cost-per-impression bid |
| daily_budget | Float | Daily spend cap |
| destination_url | String | Click-through URL |
| is_active | Boolean | Active/paused toggle |
| created_at | DateTime | Creation time |
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| ad_id | UUID | FK to Ad |
| event_type | String | impression or click |
| created_at | DateTime | Event time |
All routes are prefixed from the FastAPI root. Authentication uses JWT
Bearer tokens issued by /auth/login.
POST /auth/register { email, password } → { token }
POST /auth/login { email, password } → { token }
POST /ads/create { title, text, ... } → Ad
GET /ads/list → [ Ad, ... ]
PUT /ads/update/{id} { title?, text?, ... } → Ad
DELETE /ads/delete/{id} → { ok }
POST /engine/match-ad
Request: { user_prompt, top_k?, relevance_weight?, bid_weight? }
Response: { ad_id, title, text, relevance_score, bid_amount, final_score }
POST /engine/match-ads
Request: { user_prompt, n?, top_k?, relevance_weight?, bid_weight? }
Response: { ads: [...], total_candidates, pipeline_latency_ms }
POST /analytics/impression { ad_id } → { ok }
POST /analytics/click { ad_id } → { ok }
GET /health → { status: "ok" }
Built with Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS, shadcn/ui, and Recharts.
| Page | Route | Description |
|---|---|---|
| Landing | / |
Marketing page with CTA |
| Login | /login |
Email/password authentication |
| Register | /register |
New account creation |
| Dashboard Overview | /dashboard |
KPI cards + area chart |
| Ads Manager | /dashboard/ads |
CRUD table with search, edit, delete |
| Create Ad | /dashboard/ads/new |
Form with validation (react-hook-form + zod) |
| Analytics | /dashboard/analytics |
Tabbed charts (impressions, clicks, CTR) |
| Settings | /dashboard/settings |
Profile, API keys, danger zone |
| Service | Image | Port | Purpose |
|---|---|---|---|
| backend | Custom (Dockerfile) | 8000 | FastAPI API server |
| frontend | Custom (Dockerfile) | 3000 | Next.js dashboard |
| postgres | postgres:16-alpine | 5432 | Primary database |
| qdrant | qdrant/qdrant:latest | 6333, 6334 | Vector similarity search |
| redis | redis:7-alpine | 6379 | Response caching |
See .env.example for the full list. Key variables:
DATABASE_URL PostgreSQL connection string
QDRANT_HOST / PORT Qdrant vector DB connection
REDIS_URL Redis connection string
JWT_SECRET_KEY Secret for signing JWT tokens
EMBEDDING_MODEL sentence-transformers model name
- Authentication: JWT Bearer tokens (HS256), issued on register/login
- Password Hashing: bcrypt via passlib
- CORS: Configurable allowed origins
- Input Validation: Pydantic schemas on all endpoints
- SQL Injection: SQLAlchemy ORM (parameterized queries)
- Rate Limiting: Can be added via Redis-backed middleware
- Backend: Stateless FastAPI workers behind a load balancer
- Qdrant: Supports clustering and sharding for large ad catalogs
- PostgreSQL: Read replicas for analytics queries
- Redis: Cluster mode for distributed caching
| Metric | Target |
|---|---|
| Match latency (p95) | < 50ms |
| Embedding inference | < 20ms (MiniLM on CPU) |
| Vector search (10K ads) | < 5ms |
| Concurrent requests | 1,000+ per worker |
- Embedding cache — Cache prompt embeddings in Redis (TTL: 5 min)
- Match cache — Cache full match results for repeated prompts (TTL: 1 min)
- Ad vector cache — Qdrant keeps vectors in memory, no extra caching needed