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 endpointsearch.py- Semantic search endpoint with paginationroutes.py- Recipe detail retrieval
Location: app/services/
search_service.py- Search orchestration and result buildingdetail_service.py- Recipe detail retrieval from DataFramevectorstore.py- ChromaDB operations and vector searchloading_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-v2model) - Distance Metric: Cosine similarity (default)
- Persistence:
data/processed/persist/directory - Collection: Single
recipescollection 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
- Streamlit validates input (non-empty)
- Sends POST request to
/searchendpoint - Includes pagination params (offset, limit)
- FastAPI receives request
- Validates with
SearchQueryPydantic 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
- 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_categorykeywords(list),ingredients(list),instructions(list)n_ingredients,total_time_minutescalories,fat_content,protein_content,carbohydrate_contentaggregated_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
- Read at startup by
loading_service.py - Inserted into ChromaDB collection
- 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
๐งช 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.TestClientfor 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