Skip to content

System Architecture

This document describes the architecture, design decisions, and data flow of the Semantic Recipe Finder application.

๐Ÿ—๏ธ High-Level Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Streamlit  โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚   FastAPI    โ”‚
โ”‚   Frontend   โ”‚  HTTP   โ”‚   Backend    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ–ผ           โ–ผ           โ–ผ
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚ ChromaDBโ”‚ โ”‚DataFrameโ”‚ โ”‚  Model  โ”‚
              โ”‚ Vector  โ”‚ โ”‚ Recipe  โ”‚ โ”‚sentence โ”‚
              โ”‚  Store  โ”‚ โ”‚  Data   โ”‚ โ”‚transformโ”‚
              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ฆ Component Overview

Frontend (Streamlit)

Purpose

User-facing interface for recipe search and browsing

Technology Stack

Streamlit 1.41.1 with component-based architecture

Key Components:

search_bar.py - Query input with real-time validation

recipe_card.py - Compact recipe display for search results

recipe_detail.py - Full recipe view with ingredients and instructions

header.py - App navigation and branding

State Management: - Uses Streamlit session state for search results caching - Maintains navigation state between search and detail views - API client handles backend communication with error handling

Communication: - HTTP client (httpx) for API requests - Configurable backend URL via API_BASE_URL environment variable - Error handling with user-friendly messages

Backend (FastAPI)

Purpose

RESTful API providing semantic search and recipe data

Technology Stack

FastAPI 0.115.6 with async support

Architecture Layers:

Location: app/api/

  • health.py - Health check endpoint
  • search.py - Semantic search endpoint with pagination
  • routes.py - Recipe detail retrieval

Location: app/services/

  • search_service.py - Search orchestration and result building
  • detail_service.py - Recipe detail retrieval from DataFrame
  • vectorstore.py - ChromaDB operations and vector search
  • loading_service.py - Startup data and model loading

Location: app/models/

  • Pydantic models for request/response validation
  • Type safety and automatic API documentation
  • Models: RecipeCard, RecipeDetail, SearchQuery, SearchResponse

Location: app/utils/

  • data_preprocessor.py - Text cleaning (lowercase, remove digits/punctuation)
  • vectorizer.py - Text-to-vector conversion with normalization

Vector Database (ChromaDB)

Purpose

Efficient similarity search on recipe embeddings

Technology Stack

ChromaDB 0.5.23 with DuckDB+Parquet backend

Configuration Details
  • Dimension: 384 (from all-MiniLM-L6-v2 model)
  • Distance Metric: Cosine similarity (default)
  • Persistence: data/processed/persist/ directory
  • Collection: Single recipes collection with 100 embeddings

Key Operations:

load_or_build_collection() - Initialize or load existing collection

search_collection(query_vector, top_k) - Vector similarity search

Automatic normalization of query vectors for consistent results

Machine Learning Model

Model

sentence-transformers/all-MiniLM-L6-v2

Specifications
  • Architecture: MiniLM (distilled BERT)
  • Output: 384-dimensional dense vectors
  • Training: Trained on 1B+ sentence pairs
  • Performance: ~120M parameters, fast inference (~50ms per query)
  • Use Case: Optimized for semantic similarity tasks

Why This Model?

โœ… Small size (90MB) suitable for CPU inference
โœ… Good balance between speed and quality
โœ… Widely used and well-documented
โœ… No GPU required for production deployment

๐Ÿ”„ Data Flow

Search Request Flow

User enters: "quick vegetarian pasta"
  • Streamlit validates input (non-empty)
  • Sends POST request to /search endpoint
  • Includes pagination params (offset, limit)
  • FastAPI receives request
  • Validates with SearchQuery Pydantic model
  • Passes to SearchService
# search_service.py
1. Clean query text (remove special chars, lowercase)
2. Vectorize with sentence-transformers model
3. Normalize vector (L2 norm)
4. Call ChromaDB for similarity search (top 100)
5. Retrieve recipe data from DataFrame
6. Build RecipeCard objects
7. Apply pagination (offset/limit)
8. Return SearchResponse
# vectorstore.py
1. Receive 384-dim query vector
2. Query ChromaDB collection
3. ChromaDB computes cosine similarity with all 100 vectors
4. Return top_k recipe IDs and similarity scores
# search_service.py -> get_recipe_cards()
1. Receive list of recipe IDs from ChromaDB
2. Lookup each ID in pandas DataFrame
3. Extract required fields for RecipeCard
4. Handle missing/invalid IDs gracefully
5. Return list of RecipeCard objects
  • Receive paginated results
  • Display recipe cards in grid layout
  • Show similarity scores
  • Enable click-through to detail view

Recipe Detail Flow

User clicks recipe card

Frontend navigates to detail page with recipe_id

GET request to /recipe/{recipe_id}

DetailService retrieves full recipe from DataFrame

Returns RecipeDetail with all fields

Frontend renders detailed view

๐Ÿ—„๏ธ Data Storage

Format

Pandas DataFrame loaded from recipes.csv

Index

recipe_id (integer)

Columns (subset used)
  • name, description, recipe_category
  • keywords (list), ingredients (list), instructions (list)
  • n_ingredients, total_time_minutes
  • calories, fat_content, protein_content, carbohydrate_content
  • aggregated_rating

Access Pattern

โœ… Loaded once at startup
โœ… Stored in config.df for global access
โœ… Indexed access by recipe_id (O(1) lookup)

Files

  • ids_embs.npy - Recipe IDs (1D array)
  • metadata_embs.npy - Embeddings (2D array, shape: [100, 384])
Loading Process
  1. Read at startup by loading_service.py
  2. Inserted into ChromaDB collection
  3. Used for vector similarity search
Pre-computation Details
  • Embeddings pre-computed offline (see notebooks/vectorize.py)
  • Combines: name + description + category + keywords
  • Normalized to unit length

๐Ÿ”ง Configuration

Location: app/core/config.py

Singleton pattern for application state

class Config:
    ready: bool = False           # App initialization status
    model: SentenceTransformer    # Loaded ML model
    df: pd.DataFrame              # Recipe data
    search_service: SearchService # Initialized service
    detail_service: DetailService # Initialized service

Location: app/main.py

Application initialization workflow

@app.on_event("startup")
async def startup_event():
    1. Load sentence-transformer model
    2. Load recipe DataFrame from CSV
    3. Initialize ChromaDB collection
    4. Load pre-computed embeddings
    5. Initialize services
    6. Set config.ready = True

๐Ÿงช Testing Architecture

Test Structure
tests/
โ”œโ”€โ”€ integration/
โ”‚   โ””โ”€โ”€ test_smoke_api.py        # Full API testing (15 tests)
โ””โ”€โ”€ unit/
    โ”œโ”€โ”€ services/
    โ”‚   โ”œโ”€โ”€ test_search_service.py    # SearchService (21 tests)
    โ”‚   โ””โ”€โ”€ test_detail_service.py    # DetailService (7 tests)
    โ””โ”€โ”€ utils/
        โ”œโ”€โ”€ test_preprocessor_new.py  # Text cleaning (13 tests)
        โ””โ”€โ”€ test_vectorizer_new.py    # Vectorization (7 tests)
  • Mock search_collection() to avoid ChromaDB dependency
  • Use FastAPI.TestClient for HTTP request simulation
  • Mock model with 384-dim vectors
  • Mock external dependencies (model, ChromaDB)
  • Test edge cases (empty queries, missing data)
  • Verify error handling and fallback logic

Coverage Summary

  • Total Tests: 61
  • Pass Rate: 100% โœ…
  • Coverage Areas:
    • API endpoints (health, search, detail)
    • Service layer logic
    • Text preprocessing
    • Vector operations
    • Error handling

๐Ÿš€ Performance Considerations

Startup Loading

โœ… All data loaded once at startup
โœ… Models cached in memory
โœ… No per-request model loading

Vector Search

โœ… ChromaDB uses efficient indexing
โœ… Pre-normalized embeddings
โœ… Fast cosine similarity computation

DataFrame Access

โœ… Indexed by recipe_id for O(1) lookup
โœ… Minimal memory footprint (100 recipes)
โœ… No database queries needed

Current Scale

100 recipes (proof-of-concept)

To Scale Up
  • ๐Ÿ”ผ Increase ChromaDB collection size
  • ๐Ÿ“ฆ Add batch processing for embeddings
  • ๐Ÿ’พ Consider caching layer (Redis)
  • โš–๏ธ Deploy with load balancer
  • ๐ŸŽฎ Use GPU for model inference at scale

๐Ÿ”’ Security

Security Considerations

โš ๏ธ No authentication (demo application)
โœ… Input validation with Pydantic
โœ… CORS enabled for frontend communication
โœ… No user data storage
โœ… Static dataset (no user-generated content)

๐Ÿ“ Design Decisions

Why ChromaDB?

โœ… Easy setup with minimal configuration
โœ… Supports persistent storage out-of-the-box
โœ… Good performance for <10k vectors
โœ… Python-native with good documentation

Why Sentence-Transformers?

โœ… State-of-the-art semantic similarity
โœ… Easy integration with HuggingFace
โœ… No API keys or external services needed
โœ… Fast inference on CPU

Why Streamlit?

โœ… Rapid prototyping for ML/AI apps
โœ… Built-in state management
โœ… Easy deployment to Spaces
โœ… Component-based architecture

Why FastAPI?

โœ… Modern async Python framework
โœ… Automatic OpenAPI documentation
โœ… Type safety with Pydantic
โœ… High performance (comparable to Node.js)


Navigation: Home | Architecture | API