platepilot

Free AI providers for PlatePilot’s two model seams

PlatePilot needs a model in two places, both already isolated behind provider seams in packages/provider-contracts (ADR-0003):

Seam What it must do Modality Call shape
MealAnalysisProvider Look at a sanitized plate photo and return the meal’s items with estimated nutrition (the ProviderJobResult shape). Image + text in Asynchronous: submit → job id → getStatus.
MealPlanProvider Produce a Monday–Sunday week of meals (title, method, per-serving nutrition, ingredients with store sections, allergens) from preferences; localize shopping wording. Text in Synchronous: generateWeek, localizeShoppingItems.

The deterministic mocks (DeterministicMealAnalysisProvider, DeterministicMealPlanProvider) implement both today.

Candidates

Provider Free offer (as far as verified) Vision Structured JSON output Card needed Notes
Google Gemini API (AI Studio) Verified: the pricing page lists the Flash and Flash-Lite models (Gemini 3.8 Flash, 3.7/3.6/3.5 Flash, 3.5 and 3.1 Flash-Lite) as “Free of charge” on the free tier; Pro models are paid only. Per-model RPM/RPD limits are shown in AI Studio and on the rate-limits page (rendered client-side, so not captured here). Yes Yes: responseMimeType: application/json plus a responseSchema. No One key covers both seams. Google states free-tier prompts and responses may be used to improve its products, so only synthetic data may go through it (ADR-0005).
OpenRouter :free models Verified via GET /api/v1/models: 21 models with the :free suffix, several accepting image input (e.g. google/gemma-4-31b-it:free, nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free, qwen/qwen3.8-27b:free). Some OpenAI-compatible response_format, model-dependent No Aggregator; free models rotate, have low request-per-minute and per-day caps, and route to third-party hosts with their own data terms. Good as a fallback seam target because the API is OpenAI-shaped.
Groq Verified: a “Free Plan” with per-model RPM/RPD/TPM limits exists on the rate-limits page. Model list on the free plan not captured; Groq has historically served Llama 4 Scout/Maverick (vision) and Llama 3.x there. Yes (Llama 4) OpenAI-compatible response_format: json_schema No Very fast inference; best for the plan generator if Gemini limits bite. Vision quality is below Gemini Flash.
Cerebras Verified: “New accounts receive $5 in free credits after adding a verified payment method”; a Free Trial tier with small RPM limits. No Yes Yes Text-only, so plan generation only. Card requirement rules it out for “free without commitment”.
Mistral (La Plateforme) Not verified today: the public pricing page only shows the consumer chat plans. Mistral has had a free “Experiment” API tier with rate limits; Pixtral models support images. Yes (Pixtral) JSON mode Historically no Re-check the console before planning on it.
Hugging Face Inference Providers Not verified today: free accounts have had a small monthly credit usable across hosted providers. Some Model-dependent No Credits are small; fine for experiments, not for a Sunday batch job across users.

Recommendation

Use the Google Gemini API free tier for both seams, with Groq (plan generation) or an OpenRouter :free vision model (photo analysis) as the fallback adapter. Reasons:

  1. One provider, one key, both modalities, and native JSON-schema output, which maps directly onto the zod schemas we already have in @platepilot/contracts (GeneratedWeekPlan, NutritionMetrics, PlanIngredient).
  2. No payment method, so the account is genuinely free and cannot be surprised by a bill.
  3. Flash-Lite is the right fit for the Sunday batch (PLAN_AUTO_GENERATE_HOUR): cheap per request and the largest free daily quota; Flash for the interactive photo estimate.

Trade-offs to accept:

Integration plan (next milestone)

  1. Decide the policy. ADR-0003 allows real adapters selected by validated configuration; ADR-0005 forbids “production integrations” in M0 environments and provider.module.ts currently throws when MOCK_PROVIDERS=false. Record an ADR-0011 that allows a personal free-tier key for local exploration under three conditions: synthetic data only, key from the environment (never committed, never in .env.example with a value), and the mock remains the default.
  2. Configuration. Add MEAL_PLAN_PROVIDER=mock|gemini, MEAL_ANALYSIS_PROVIDER=mock|gemini, GEMINI_API_KEY, GEMINI_PLAN_MODEL (default a Flash-Lite id), GEMINI_ANALYSIS_MODEL (default a Flash id) to apps/api/src/config/environment.ts with a refinement that requires the key when either selector is gemini, and keep the MOCK_PROVIDERS production guard.
  3. Adapters in a new packages/provider-gemini (so the API and worker share them):
    • GeminiMealPlanProvider.generateWeek: one generateContent call per week with a system instruction built from the request (allergens as hard exclusions, dislikes, focus, dietary pattern, servings, unit system, location for product names), responseSchema generated from the contract zod schema, temperature low, and a post-validation pass that rejects any meal containing an excluded allergen or a disliked ingredient and re-asks for that slot only. localizeShoppingItems is a second, smaller call and can be skipped when location is absent.
    • GeminiMealAnalysisProvider: submit records the job and returns queued; the worker’s processing step reads the sanitized object from LocalStack/S3, sends it inline (JPEG, already downscaled by the app) with a prompt asking for items, portions and the eight nutrition values, validates against ProviderJobResult, and maps refusals or malformed output to a retryable provider_failed. This keeps the existing outbox, lease and retry machinery unchanged.
    • Use the REST endpoint directly with fetch rather than the SDK to keep the dependency surface small; both calls are a single POST.
  4. Resilience. Map HTTP 429 and 5xx to the seam’s retryable failure; add a per-process token bucket sized from the free-tier RPM so the Sunday job cannot trip the limit; fall back to the deterministic provider after N failures and record which provider produced a week alongside PlanWeek.generatedAt so the UI can say “sample plan”.
  5. Tests. Contract tests with recorded responses (no network in CI), a golden test that the allergen post-check rejects a planted violation, and an opt-in live smoke test gated on GEMINI_API_KEY being present.
  6. Docs. README provider section, docs/runbooks/provider-outage.md (quota exhaustion), and the threat model (third-party data flow, key handling).

Getting a key (for when the ADR is agreed)

Google AI Studio → “Get API key” → create a key in a new Google Cloud project with billing left off. Keep it in the desktop’s shell environment or the repository .env (gitignored), never in apps/api/.env.example or a commit.