Skip to content

Repository files navigation

OptiFood AI 🥗🤖

An AI-powered Telegram bot for intelligent food logging — text, voice, photo, and barcode — with a five-layer recognition pipeline, anti-hallucination validation, and an async-first architecture.

PythonaiogramFastAPIPostgreSQLRedisTestsCoverageLicense: MIT


📋 Table of Contents


🧭 Overview

OptiFood AI is a production-grade Telegram bot that lets users log meals by simply sending a text description, voice message, photo, or barcode scan. The bot automatically identifies food items, calculates macros (calories, protein, fat, carbs, fiber, alcohol), and tracks daily nutrition goals.

The backend runs as two independent Docker services: a FastAPI + aiogram webhook API and a TaskIQ async worker. Communication between them uses Redis Pub/Sub — no polling, no blocking the event loop.



OptiFood AI Demo


✨ Features

Text food logging

Voice inputPhoto recognitionBarcode scan
VoicePhotoBarcode
  • Five-layer AI recognition pipeline (L1–L5) with explicit fallback conditions, from Redis cache to LLM
  • Anti-hallucination validator — dual-layer check via physical nutrient limits and the Atwater formula
  • Intelligent AI Mentor — daily macro analysis with contextual recommendations powered by GPT
  • Daily Statistics
  • Premium subscription — paywall for analytics and photo-log, managed via premium_until field
  • Language & Settings
  • Async-first — PostgreSQL via asyncpg, Redis async client, aiogram 3.x, TaskIQ worker
  • Strict layered architecture — Port/Adapter (DIP), Unit of Work, Repository, Orchestrator Facade
  • Full observability — Sentry initialized independently in both api and worker processes; structured logging

🏗 Architecture

Architecture Diagram

Layer Rules

LayerLocationCan importForbidden
Presentationapp/bot/handlers/, middlewares/core/services/, core/ports/, schemas/infrastructure/database/models directly
Applicationapp/core/services/, orchestrator/core/skills/, core/ports/, schemas/Any infrastructure/ import
Domain Skillsapp/core/skills/core/services/ai_service.py, schemas/infrastructure/
Infrastructureapp/infrastructure/, app/worker/schemas/Business logic

🧠 AI Pipeline (L1→L5)

Each level is activated only if the previous one returned no result:

LevelMethodLatencyCondition
L1Redis Cache (MD5 key, TTL 24h)~0 msSimple queries only
L2Fuzzy Local DB (Rapidfuzz, in-memory)~2–5 msCache miss
L3Vector Semantic Search (Pinecone + OpenAI Embeddings, cached 30d)~50–100 msFuzzy miss
L4LLM Fallback — GPT Structured JSON Output~1–3 sVector miss orcomplex_query
L5Background indexing (asyncio.gather)asyncAfter L4 hit — indexes result into Redis + Pinecone

Pre-Parser (FoodParserPipeline): normalizes input, extracts quantity + units, detects complex_query (multiple numbers or conjunctions). Rules live in core/constants.py.

Anti-hallucination (NutritionCalculator.sanitize_dto):

  1. Physical limits — protein + fat + carbs ≤ 100 g/100 g; calories ≤ 950 kcal/100 g
  2. Atwater formula — if deviation from protein×4 + fat×9 + carbs×4 exceeds 30%, recalculates automatically

🛠 Tech Stack

CategoryTechnology
Bot frameworkaiogram 3.x
Web frameworkFastAPI + Uvicorn
DatabasePostgreSQL 15 via asyncpg + SQLAlchemy 2.0 (async)
MigrationsAlembic
Cache / FSMRedis 7 (FSM storage, Pub/Sub, LLM cache, embeddings)
Task queueTaskIQ + taskiq-redis
AI / LLMOpenAI (GPT-4o, Whisper, Embeddings) via litellm
Vector searchPinecone via httpx (async)
Fuzzy searchRapidfuzz (in-memory index)
Barcodezxing-cpp (asyncio.to_thread)
External food dataOpen Food Facts via aiohttp
NLP / Lemmatizationpymorphy3 + pymorphy3-dicts-uk, NLTK
Localizationaiogram-i18n + Fluent (.ftl files)
Configpydantic-settings + SecretStr
MonitoringSentry SDK (independent in api + worker)
Image processingPillow
Testingpytest, pytest-asyncio, pytest-mock, pytest-cov

📁 Project Structure

OptiFood AI/
├── .github/workflows/ # CI/CD — GitHub Actions
├── alembic/ # Database migrations
│ └── versions/
├── app/
│ ├── main.py # Entry point: FastAPI + aiogram webhook
│ ├── config.py # Pydantic Settings, SecretStr for all API keys
│ ├── bot/
│ │ ├── pubsub.py # Redis Pub/Sub: worker result listener
│ │ ├── handlers/ # common, food, admin, errors
│ │ ├── middlewares/ # user_context, throttling, state_clear, i18n, onboarding
│ │ ├── keyboards/ # inline, reply
│ │ └── states/ # FoodLogState, EditWeightState, SettingsState
│ ├── core/ # Domain layer — never imports infrastructure directly
│ │ ├── constants.py # LLM prompts, regex patterns, Atwater coefficients
│ │ ├── exceptions.py # DomainError, RecognitionFailedError, BarcodeNotFoundError
│ │ ├── orchestrator/ # FoodOrchestrator — single stable entry point for worker tasks
│ │ ├── ports/ # ABC interfaces: ITaskQueue, IFoodCache, IVectorStore, IFuzzyIndex
│ │ ├── services/ # ai, analytics, media, nutrition_calculator, nutrition, recognition, user
│ │ └── skills/ # Atomic pipeline components: pre_parser, text_normalizer, barcode,
│ │ # dto_builder, nutrition_calc, ocr, voice, mentor
│ ├── infrastructure/ # Concrete port implementations + external integrations
│ │ ├── cache/ # FuzzyProductIndex (Rapidfuzz), redis_client (FoodCache)
│ │ ├── database/ # SQLAlchemy models, UnitOfWork, repositories, VectorStore (Pinecone)
│ │ ├── external_api/ # openfoodfacts_client (aiohttp)
│ │ ├── storage/ # local_media.py (S3-ready interface)
│ │ └── task_queue/ # taskiq_queue.py — ITaskQueue implementation
│ ├── locales/ # Fluent .ftl files (uk, en)
│ ├── schemas/ # Pydantic DTOs: FoodItemDTO, MealCreateDTO, OrchestratorResult, UserDTO
│ ├── utils/ # TelegramFormatter, UIBuilder
│ └── worker/
│ ├── broker.py # TaskIQ broker + SentryMiddleware
│ └── tasks.py # Task entry points, TaskNotifier, FoodTaskCoordinator
├── scripts/ # i18n audit and auto-fix utilities
├── tests/
│ ├── bot/ # [INSERT TESTS — middleware, handlers]
│ ├── core/ # [INSERT TESTS — services, dto_builder, nutrition_calc, pre_parser, recognition pipeline]
│ └── infrastructure/ # [INSERT TESTS — repositories, fuzzy index, openfoodfacts client]
├── docker-compose.yml
├── Dockerfile # Multi-stage build (builder + runtime), non-root user
├── requirements.txt
├── pytest.ini
└── .pre-commit-config.yaml # ruff (lint + format), trailing whitespace, secret detection

🚀 Quick Start

Prerequisites

  • Docker & Docker Compose
  • A Telegram Bot Token (@BotFather)
  • OpenAI API Key
  • Pinecone API Key + Index name

1. Clone and configure

git clone https://github.com/PyDevDeep/optifood-ai.git
cd optifood-ai
cp .env.example .env # fill in all required values

2. Start services

docker compose up --build

This starts three containers:

ServiceRole
redisFSM storage, Pub/Sub, cache
optifood_apiFastAPI + aiogram webhook; runs alembic upgrade head on startup
optifood_workerTaskIQ worker; shares /tmp/optifood_media volume with api

3. Register webhook

curl "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://<YOUR_DOMAIN>/webhook"

⚙️ Configuration

All sensitive values are managed through app/config.py (Pydantic SecretStr). Set them via .env:

# TelegramTELEGRAM_BOT_TOKEN=...WEBHOOK_URL=https://yourdomain.com/webhookWEBHOOK_SECRET=...# OpenAIOPENAI_API_KEY=...# PineconePINECONE_API_KEY=...PINECONE_INDEX_NAME=...# PostgreSQLDATABASE_URL=postgresql+asyncpg://user:pass@db:5432/optifood# RedisREDIS_HOST=redisREDIS_PORT=6379# SentrySENTRY_DSN_API=...SENTRY_DSN_WORKER=...# PremiumADMIN_IDS=[INSERT_TELEGRAM_ID]

⚠️Never commit .env to the repository. It is listed in .gitignore.


🧪 Testing

# Run all tests
pytest
# With coverage report
pytest --cov=app --cov-report=term-missing
# Run a specific layer
pytest tests/core/
pytest tests/infrastructure/
pytest tests/bot/

Test structure mirrors the application layers:

The project is thoroughly tested with 248 asynchronous tests covering all architectural layers:

DirectoryFocus
tests/bot/Presentation layer: Middlewares (AntiSpam, Quota, UserContext) and FSM state routing.
tests/core/Domain logic: NutritionCalculator (Atwater limits), NLP parsing, AI Service fallbacks, and isolated business services.
tests/infrastructure/Data access: FuzzyProductIndex, SQLAlchemy repositories, and integrations (OpenFoodFacts).

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/your-feature
  3. Follow the layer rules — no cross-layer imports
  4. All UI strings go to .ftl files under app/locales/; hardcoding in handlers fails pre-commit
  5. Run pre-commit run --all-files before pushing (ruff lint + format, secret detection)
  6. Open a Pull Request with a clear description of the change

📄 License

🛡 License

This project is licensed under the MIT License. See the LICENSE file for details.

About

An AI-powered Telegram bot for intelligent food logging — text, voice, photo, and barcode — with a five-layer recognition pipeline, anti-hallucination validation, and an async-first architecture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages