MabudAlam/NomAI

WIP

โ˜… 9Forks 2PythonGitHub โ†—Compare
calainomainutrition-app

README

NomAI Logo

NomAI

๐Ÿง  AI-Powered Nutrition Intelligence Platform

Analyze food, chat with AI, plan weekly diets, and receive real-time nutrition insights.

๐Ÿ“ฑ Mobile App: Download / View Repository

FastAPI Python Gemini Firebase Exa DuckDuckGo Deploy on Railway


โšก Overview

NomAI is a powerful AI Agent that brings nutrition and food intelligence to life. Whether you're analyzing meals through images, chatting with an AI nutrition assistant, or generating personalized weekly diet plans โ€” NomAI handles the heavy lifting with a sophisticated multi-step LLM pipeline backed by real-time web research.


โœจ Features

Feature Description
๐Ÿง  AI Nutrition Analysis Analyze food from images or text descriptions with a 3-step pipeline: food extraction โ†’ web search โ†’ LLM synthesis
๐Ÿ’ฌ Conversational AI Chatbot LangChain-powered agent that understands dietary preferences, allergies, and health goals
๐Ÿฝ๏ธ Weekly Diet Planner Generate 7-day personalized meal plans with carb cycling, variety tracking, and macro targets
๐Ÿ”„ Meal Alternatives Get 5 AI-suggested alternative meals respecting your dietary profile
๐Ÿ“Š Nutrition Tracking Mark meals as eaten, update plans on the fly, and track diet history
๐Ÿ”— Dual LLM Support Seamlessly switch between Google Gemini and OpenRouter (Claude) providers
๐ŸŒ Web-Grounded Analysis Nutrition data enriched with web search results from Exa or DuckDuckGo
๐Ÿ›ข๏ธ Firestore Persistence Chat history and diet plans stored in Google Firestore

Deploy on Railway


๐Ÿ—๏ธ Architecture

graph TD
    Client["๐Ÿ–ฅ๏ธ Client (Mobile / Web)"]
    Main["main.py โ€” FastAPI App"]
    
    Client --> Main

    Main --> NutritionRouter["/api/v1/nutrition"]
    Main --> ChatRouter["/api/v1/users"]
    Main --> AgentRouter["/api/v1/chat"]
    Main --> DietRouter["/api/v1/diet"]

    NutritionRouter --> NutritionServiceV2
    AgentRouter --> LangChainAgent["๐Ÿค– LangChain Agent"]
    LangChainAgent --> AgentTools["Tools: analyse_image\nanalyse_food_description"]
    AgentTools --> NutritionServiceV2
    ChatRouter --> ChatFirestore
    DietRouter --> DietService

    NutritionServiceV2 --> FoodExtractor["FoodExtractorService"]
    NutritionServiceV2 --> SearchService
    NutritionServiceV2 --> LLMProvider["LLM Provider\n(Gemini / OpenRouter)"]
    DietService --> LLMProvider
    DietService --> DietFirestoreDB["DietFirestore"]

    FoodExtractor --> LLMProvider
    SearchService --> ExaAPI["๐Ÿ” Exa / DuckDuckGo"]

    ChatFirestore --> Firestore["๐Ÿ”ฅ Firestore DB"]
    DietFirestoreDB --> Firestore
Loading

๐Ÿ”Œ API Endpoints

๐Ÿฅ— Nutrition โ€” /api/v1/nutrition

Method Path Description
POST /analyze Analyze nutrition from a food image (URL required)
POST /analyze-by-description Analyze nutrition from a text description

๐Ÿค– AI Agent Chat โ€” /api/v1/chat

Method Path Description
POST /messages Send a message and receive AI nutrition analysis with tool calls

๐Ÿ’ฌ Chat History โ€” /api/v1/users

Method Path Description
GET / Get chat messages for a user (paginated)
PATCH /log-status Update the isAddedToLogs flag for a message

๐Ÿฝ๏ธ Diet Plans โ€” /api/v1/diet

Method Path Description
POST / Generate a new weekly diet plan
GET /{user_id} Get the active weekly diet
GET /{user_id}/history Get diet history (paginated)
POST /{user_id}/suggest-alternatives Suggest 5 alternative meals
PUT /{user_id}/{day_index}/meals/{meal_type} Update a specific meal
PATCH /{user_id}/meals/eaten Mark a meal as eaten / not eaten
GET /{user_id}/diet/{diet_id} Get a specific diet by ID
POST /{user_id}/diet/{diet_id}/copy Copy a past diet as new active diet

๐Ÿง  AI Agent Decision Flow

NomAI's conversational brain operates as a ReAct Agent (Reasoning + Acting). It doesn't just respond; it thinks, selects tools, and iterates until it has the best answer.

graph TD
    User["๐Ÿ‘ค User Input\n(Chat/Image)"] --> Context["๐Ÿ“‹ Context Builder\n(Preferences + Allergies + Goals)"]
    Context --> Brain["๐Ÿง  LLM Controller\n(ReAct State Graph)"]
    
    Brain --> Decision{"Is this food-related?"}
    
    Decision -- "No / Simple Q&A" --> Direct["๐Ÿ’ฌ Direct Friendly Answer"]
    Decision -- "Yes / Needs Analysis" --> ToolSelection["๐Ÿ› ๏ธ Tool Selection"]
    
    ToolSelection -- "Image Provided" --> ToolA["๐Ÿ“ธ analyse_image"]
    ToolSelection -- "Text Description" --> ToolB["๐Ÿ“ analyse_food_description"]
    
    ToolA --> Pipe["๐Ÿงช Nutrition Pipeline"]
    ToolB --> Pipe
    
    Pipe --> Observation["๐Ÿ” Tool Observation\n(Structured Nutrition Data)"]
    Observation --> Brain
    
    Brain --> Final["๐ŸŽ Final Personalized Response\n(Friendly Answer + Data)"]
    Final --> Firestore["๐Ÿ”ฅ Sync to Firestore"]
Loading

The State Graph (ReAct Pattern)

NomAI maintains a Message State that evolves during a single request:

  1. State Init: User message + System prompt (Personalized Profile).
  2. Reasoning: The LLM analyzes the intent. If it sees food, it pauses and emits a tool_call.
  3. Acting: The system executes the analyse_image or analyse_food_description tool.
  4. Observation: The tool's output (nutrients, calories, fiber) is appended to the message list.
  5. Finalization: The LLM reads the tool's findings and crafts a warm, personalized message (e.g., "This burger looks delicious, Pavel! It has 650 calories, but keep an eye on the sodium...").

๐Ÿš€ Deployment

NomAI supports two easy deployment options: Google Cloud Platform (GCP) or Railway.

โ˜๏ธ Option 1: GCP (Cloud Run)

NomAI is architected for the cloud, utilizing a fully automated CI/CD pipeline on Google Cloud Platform (GCP).

graph LR
    Dev["๐Ÿ’ป Developer\nPush to GitHub"] --> CB["โš™๏ธ Google Cloud Build"]

    subgraph "CI/CD Pipeline"
        CB --> Build["๐Ÿณ Docker Build\n(Dockerfile.api)"]
        Build --> AR["๐Ÿ“ฆ Artifact Registry\n(Docker Image)"]
        AR --> Deploy["๐Ÿš€ Cloud Run\n(Managed Serverless)"]
    end

    Deploy --> Public["๐ŸŒ Public API Endpoints\n(https://nomai-service-...)"]

    subgraph "Infrastructure"
        Firebase["๐Ÿ”ฅ Firestore\n(NoSQL DB)"]
        Secret["๐Ÿ”’ Secret Manager\n(API Keys)"]
    end

    Deploy -.-> Firebase
    Deploy -.-> Secret
Loading

๐Ÿš‚ Option 2: Railway (One-Click Template)

One-click deployment to Railway with built-in environment variable management and template setup.

Deploy on Railway

๐Ÿš‚ One-Click Railway Template Deployment

  1. Click the button above โ€” This opens Railway with the NomAI template pre-loaded
  2. Connect your GitHub repository to enable automatic deployments
  3. Configure Environment Variables in the Railway dashboard:
Variable Description Required
FIREBASE_CREDENTIALS_JSON Full Firebase service account JSON string โฌœ
GOOGLE_API_KEY Google Gemini API key (If openrouter is used then can skip this ) โœ…
EXA_API_KEY Exa search API key (optional if not using DuckDuckGo) โฌœ
SEARCH_PROVIDER exa or duckduckgo โœ…
PROVIDER_TYPE gemini or openrouter โœ…
GEMINI_MODEL Gemini model name (auto-switches when PROVIDER_TYPE=gemini) โฌœ
OPENROUTER_MODEL OpenRouter model name (auto-switches when PROVIDER_TYPE=openrouter) โฌœ
OPENROUTER_API_KEY OpenRouter API key (if using openrouter) โฌœ
AGENT_MODEL Agent model for LangChain if provider is gemini then put gemini model else openrouter model (default: openai/gpt-4o-mini) โœ…

Model Auto-Switching: When PROVIDER_TYPE=gemini, the system uses GEMINI_MODEL (default: gemini-2.0-flash). When PROVIDER_TYPE=openrouter, it uses OPENROUTER_MODEL (default: google/gemini-3.1-flash-lite-preview).

The Firebase Credentials are loaded 3 ways , on the local machine via direct service.json file , on the GCP it does n't require service.json file , on non Google provider like railway , we load the service.json file via key FIREBASE_CREDENTIALS_JSON.

  1. Deploy โ€” Railway auto-detects Python, installs dependencies via uv, and starts uvicorn main:app
  2. Custom Domain (optional) โ€” Add a custom domain in Railway service settings

Railway Features

  • โœ… Automatic HTTPS/SSL
  • โœ… GitHub integration for auto-deploy on push
  • โœ… Environment variable management
  • โœ… Built-in logs and monitoring
  • โœ… Free tier available

Production Stack

  • Containerization: uv-optimized Python 3.13 slim image for fast builds and minimal footprint.
  • Orchestration: Managed via cloudbuild.yaml in infra/cloudbuild/ (GCP).
  • Hosting: Google Cloud Run for autoscaling serverless execution (GCP) or Railway for managed deployment.
  • Registry: Google Artifact Registry for secure container storage (GCP).

NomAI uses a sophisticated 3-step pipeline for accurate, web-grounded nutrition analysis:

graph LR
    A["๐Ÿ“ธ Multi-modal Input\n(Image + Prompt)"] --> B["Step 1\nFood Identification"]
    B --> C["Step 2\nWeb Grounding"]
    C --> D["Step 3\nMultimodal Synthesis"]
    D --> E["๐Ÿ“Š Structured\nNutrition Response"]
    
    B -.- B1["Lightweight LLM identifies\nfood items & generates\nenriched search queries"]
    C -.- C1["Exa / DuckDuckGo\nfinds USDA, FDA, or\nbrand-specific data"]
    D -.- D1["Powerful Multimodal LLM\ncombines Actual Image +\nWeb Data + User Prompt"]
Loading
Step Purpose Service
1. Food Identification Lightweight LLM detects food items and generates enriched queries for search FoodExtractorService
2. Web Grounding Executes targeted searches (Exa/DDG) to find authoritative nutritional facts SearchService
3. Multimodal Synthesis Final LLM combines Actual Image + Web Results + User Prompt for the result GeminiProvider / OpenRouterProvider
4. Client Delivery Returns highly accurate, fact-checked structured data to the user FastAPI Response

๐Ÿ“… Diet Plan Generation Flow

Generating a weekly diet plan is a multi-stage process that balances nutritional targets, metabolic variety (carb cycling), and food diversity.

graph TD
    Input["๐Ÿ“ฅ DietInput Payload\n(Macros + Preferences + Goals)"] --> Calc["โš–๏ธ Target Calculator"]
    
    Calc --> Patterns["๐Ÿ”„ Carb Cycling Logic\n(Mon-Sun Pattern: 0, -15, -10, -15, +10, -10, +15)"]
    
    Patterns --> Loop["๐Ÿ” 7-Day Generation Loop"]
    
    subgraph "Per-Day Iteration"
        DayPrompt["๐Ÿ“ Prompt Builder\n(Day Context + Used Foods)"] --> LLMCall["๐Ÿค– LLM Provider\n(Gemini / OpenRouter)"]
        LLMCall --> DailyClean["๐Ÿงน Data Cleaning\n(Remove concerns/alternatives)"]
        DailyClean --> Variety["๐Ÿฅ— Update Used Foods\n(Track variety for next days)"]
    end
    
    Loop --> DayPrompt
    Variety -- "Next Day" --> Loop
    
    Variety -- "End Loop" --> Aggregator["๐Ÿ“Š Weekly Aggregator\n(Sum Macros & Totals)"]
    Aggregator --> Firestore["๐Ÿ”ฅ Firestore Storage\n(users/{userId}/diet/{dietId})"]
    Firestore --> Final["โœ… WeeklyDietOutput\n(Sent to Client)"]

Loading

Key Stages

  1. Payload Intake: Receives targets (Calories, P/C/F), dietary restrictions (Vegan, Keto, etc.), and health goals.
  2. Carb Cycling Pattern: Instead of flat targets, the system applies a pattern (e.g., lower carbs on Mon/Thu, higher carbs on Fri/Sun) to prevent metabolic adaptation.
  3. Sequential Variety Loop: The system generates one day at a time, feeding the list of used_foods from previous days back into the next prompt to ensure you don't eat the same thing every day.
  4. Final Aggregation: Calculates the total weekly impact and persists the plan as "active" in Firestore.

๐ŸŒ Web Grounding Infrastructure

NomAI utilizes a modular search architecture to ground AI responses in real-world data. It supports multiple search backends that can be toggled via environment variables.

graph TD
    Pipeline["๐Ÿงช Nutrition Pipeline"] --> Router["๐Ÿ” SearchService (Router)"]
    
    Router --> Env{"SEARCH_PROVIDER\nEnv Var"}
    
    Env -- "exa" --> Exa["๐Ÿš€ ExaSearchProvider"]
    Env -- "duckduckgo" --> DDG["๐Ÿฆ† DuckDuckGoSearchProvider"]
    
    Exa --> ExaAPI["Exa AI API\n(Authoritative / Linked Data)"]
    DDG --> DDG_Library["DDGS Library\n(Global Web Search)"]
    
    ExaAPI --> Results["๐Ÿ“Š Normalized SearchResults\n(Title, URL, Snippet, Score)"]
    DDG_Library --> Results
    
    Results --> Pipeline
Loading

๐Ÿค– LLM Provider Strategy

NomAI supports multiple LLM backends via a strategy pattern:

classDiagram
    class LLMProvider {
        <<abstract>>
        +generate_from_text(prompt, schema)
        +generate_from_image(prompt, image_bytes, schema)
    }
    class GeminiProvider {
        +Google Gemini API
        +Structured output support
        +Native vision capabilities
    }
    class OpenRouterProvider {
        +OpenRouter API
        +Claude, GPT, etc.
        +JSON schema validation
    }
    LLMProvider <|-- GeminiProvider
    LLMProvider <|-- OpenRouterProvider
Loading

Switch providers with a single environment variable: PROVIDER_TYPE=gemini or PROVIDER_TYPE=openrouter.


๐Ÿ“‚ Project Structure

nomai-backend/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ agent/                  # LangChain AI agent
โ”‚   โ”œโ”€โ”€ config/                 # App settings
โ”‚   โ”œโ”€โ”€ endpoints/              # API route handlers
โ”‚   โ”œโ”€โ”€ exceptions/             # Custom exception hierarchy
โ”‚   โ”œโ”€โ”€ middleware/             # FastAPI middleware
โ”‚   โ”œโ”€โ”€ models/                 # Pydantic data models
โ”‚   โ”œโ”€โ”€ services/               # Business logic layer
โ”‚   โ””โ”€โ”€ utils/                  # Helpers and shared utilities
โ”œโ”€โ”€ infra/                      # Infrastructure as Code
โ”‚   โ”œโ”€โ”€ cloudbuild/             # GCP Cloud Build configs
โ”‚   โ””โ”€โ”€ docker/                 # Dockerfile.api
โ”œโ”€โ”€ main.py                     # App entrypoint
โ”œโ”€โ”€ pyproject.toml              # Dependencies (uv)
โ””โ”€โ”€ railway.json                # Railway deployment config

โš ๏ธ Error Handling

NomAI uses a comprehensive exception hierarchy with 18 standardized error codes:

graph TD
    Base["BaseNomAIException"]
    Base --> V["ValidationException (400)"]
    Base --> I["ImageProcessingException (400/413)"]
    Base --> N["NutritionAnalysisException (400/422)"]
    Base --> E["ExternalServiceException (502)"]
    Base --> C["ConfigurationException (503)"]
    Base --> R["RateLimitException (429)"]
    Base --> B["BusinessLogicException (400)"]
Loading

๐Ÿš€ Getting Started

1. Clone the Repository

git clone https://github.com/Pavel401/nomai-backend.git
cd nomai-backend

2. Install Dependencies

uv sync

3. Run the Server

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

๐Ÿ”ง Environment Variables

Variable Purpose Default
PROD Production mode toggle false
PROVIDER_TYPE LLM provider (gemini / openrouter) gemini
GOOGLE_API_KEY Google Gemini API key โ€”
GEMINI_MODEL Gemini model name (auto-used when PROVIDER_TYPE=gemini) gemini-2.0-flash
OPENROUTER_API_KEY OpenRouter API key โ€”
OPENROUTER_MODEL OpenRouter model (auto-used when PROVIDER_TYPE=openrouter) google/gemini-3.1-flash-lite-preview
AGENT_MODEL Agent model for LangChain openai/gpt-4o-mini
SEARCH_PROVIDER Search backend (exa / duckduckgo) exa
EXA_API_KEY Exa search API key โ€”
FIREBASE_CREDENTIALS_PATH Firebase service account JSON path โ€”
FIREBASE_CREDENTIALS_JSON Firebase service account JSON string (for Railway) โ€”
FIRESTORE_DATABASE_ID Firestore database ID mealai
DEBUG_MODE Enable pipeline debug logging false
HOST / PORT Server bind address 0.0.0.0 / 8000

๐Ÿ‘จโ€๐Ÿ’ป Tech Stack

Tech Use Case
FastAPI API framework
LangChain Agent orchestration & tool management
Google Gemini Primary LLM for analysis & generation
OpenRouter Alternative LLM provider (Claude, GPT, etc.)
Pydantic v2 Data validation & structured output
Firestore Chat & diet plan persistence
Exa / DuckDuckGo Web search for nutrition data grounding
Python 3.13+ Core backend language
uv Package management

Built with โค๏ธ

Contributors

MabudAlam

Issues

docker ?

#1 ยท open ยท 0 comments