Technology

How We Built an AI-Powered Meal Planning & Nutrition Assistant for a Food Delivery Company (RAG + LangChain + NestJS)

How Sofyrus Technologies built an AI nutrition assistant using RAG, LangChain, NestJS & OpenAI — with add-to-cart chat, meal plans, and real ecommerce integration.

Last updated 20 min readBy Waseem Ahmad
How We Built an AI-Powered Meal Planning & Nutrition Assistant for a Food Delivery Company (RAG + LangChain + NestJS)

A deep technical breakdown of architecture decisions, retrieval strategy, hallucination prevention, and real business outcomes from a production RAG-powered food delivery chatbot.


Introduction

About fourteen months ago, a mid-sized food delivery company came to us with a problem that sounded deceptively simple: "We want a chatbot that helps users find healthy meals."

What followed was one of the most technically interesting projects we've shipped at Sofyrus Technologies — and one that quietly redefined how their customers interact with the product.

The end result wasn't a FAQ bot with a few canned responses. It was a fully conversational AI assistant that understands nutritional goals, reads live product catalogs, builds personalized weekly meal plans, answers "is this meal low-carb?" in the context of a user's dietary history, and lets people add items directly to their cart from inside the chat — all without leaving the conversation.

This article is a detailed account of how we built it, what broke along the way, and what we learned. If you're a CTO evaluating AI for your product, a founder thinking about conversational commerce, or a developer trying to figure out where RAG actually fits — this is written for you.


The Business Problem

The client ran a food delivery platform with over 4,000 SKUs across dozens of restaurant and meal-kit partners. Their catalog was rich — detailed nutritional data, allergen labels, meal categories, calorie counts, macros — but almost none of it was surfaced to users in a useful way.

Search was keyword-based. Filters were rigid. And when users had questions like "what's a good high-protein lunch under 600 calories?" or "I'm diabetic — which meals should I avoid?" — there was no answer. Not even a static FAQ covered it.

The result: high drop-off rates in the browse and checkout flow, a flood of nutrition-related support tickets, and a significant percentage of users who'd arrive looking for something specific, fail to find it, and leave.

The ask was clear. The solution was not.


Why Traditional Chatbots Were Not Enough

Before we get into architecture, it's worth being honest about what we ruled out and why.

Rule-based chatbots were off the table immediately. With 4,000+ products and infinite combinations of dietary preferences, building decision trees for every possible user intent would have taken months and still wouldn't have handled edge cases gracefully.

Fine-tuned LLMs were tempting but wrong for this use case. Fine-tuning makes sense when you're trying to change the model's style or embed specific domain knowledge permanently. But our product catalog changes — new meals get added, nutritional data gets updated, items go out of stock. A fine-tuned model would be stale within weeks of training. And retraining every time the catalog updates is not a viable operational plan.

Off-the-shelf AI chat widgets couldn't connect to live inventory, couldn't pull real calorie data, and couldn't integrate with the order flow. We evaluated two third-party platforms and both hit the same wall: no real ecommerce hooks, no live retrieval, no ability to reason over fresh product data.

What we needed was something that could combine the reasoning ability of a large language model with live, structured retrieval from the client's actual data. That's where RAG entered the picture.


The Vision: An AI Nutrition & Meal Planning Assistant

Before writing a single line of code, we spent a week with the client's team mapping out what the assistant needed to actually do. Not in vague product terms — in precise interaction terms.

The final capability list looked like this:

  • Answer nutritional questions about specific meals ("How many grams of protein in the Thai basil bowl?")
  • Make personalized recommendations based on stated goals ("I want to lose weight and I'm vegetarian")
  • Build full weekly meal plans with calorie targets
  • Compare meal options side by side
  • Remember context within a conversation ("Which of those two is lower in sodium?")
  • Handle FAQs about the platform, delivery times, account issues
  • Add recommended meals to cart from the chat UI
  • Support WhatsApp as an alternative channel
  • Escalate gracefully when it didn't know something

That last point mattered a lot. We'd seen too many AI demos that confidently made things up. For a nutrition use case, hallucinated calorie counts or incorrect allergen information could actually harm users. Accuracy wasn't a nice-to-have.


Solution Architecture

Here's how the system is structured at a high level:

User → React Chat UI → NestJS Backend → LangChain Orchestration Layer
     → RAG Pipeline (Query Embedding + Vector Search)
     → Vector Database (pgvector / Pinecone)
     → Retrieved Context + User History
     → OpenAI LLM (GPT-4o)
     → Response + Tool Calls
     → Ecommerce API (Add to Cart / Order Status)
     → Back to React Chat UI

Let's walk through each layer.

React + TypeScript frontend handled the chat UI. We built a custom component — not a third-party widget — because we needed tight control over cart integration, streaming responses, and WhatsApp parity. The UI renders markdown, supports structured meal cards with "Add to Cart" buttons inline in chat responses, and streams the LLM output token by token so it feels fast even on slower connections.

NestJS backend manages sessions, authentication, request routing, rate limiting, and orchestrates all downstream calls. We chose NestJS specifically because its module and dependency injection architecture meant we could keep the AI logic cleanly separated from ecommerce logic — more on that below.

LangChain sits at the orchestration layer, managing prompt construction, memory, chain execution, and tool calls. It gave us a structured way to compose the retrieval pipeline without reinventing routing logic from scratch.

RAG layer handles query embedding, vector similarity search, and context injection. When a user asks something, we embed the query and search the vector database for the most semantically relevant meal records, FAQ entries, and nutritional guidelines before passing anything to the LLM.

Vector database holds embeddings for the full product catalog, FAQ content, nutritional guidelines, and a curated set of diet plan templates. We initially used Pinecone for ease of setup, then migrated to pgvector (PostgreSQL extension) for cost control and to keep everything in one database.

OpenAI GPT-4o as the base LLM. We also experimented with GPT-3.5-turbo for lower-latency responses on simpler queries — more on that tradeoff later.

Ecommerce APIs are called via LangChain tools — structured function calls that let the model trigger cart additions, check order status, and query live inventory.


Why We Chose RAG Instead of Fine-Tuning

This comes up in almost every AI project conversation we have, so it's worth being direct.

Fine-tuning trains the model's weights on your data — which means the knowledge is baked in and static. For a product catalog that updates daily, static knowledge is a liability.

RAG is fundamentally different. Instead of baking knowledge into the model, you retrieve relevant information at query time and inject it into the prompt. The LLM never needs to "remember" what meals are in the catalog — it reads that information fresh on every query from your vector database.

For this project, RAG gave us three things fine-tuning couldn't:

Live accuracy. When a meal goes out of stock or its calorie count changes, we update the vector database. The model immediately reflects that change in responses. No retraining, no deployment cycle.

Traceable answers. Every response is grounded in retrieved documents. We can log exactly which product records the model used to construct an answer. That audit trail is important for a nutrition use case — if a user ever disputes a calorie figure, we can trace it back to the source document.

Lower cost at scale. Fine-tuning requires significant GPU compute for training and hosting. With RAG on a managed vector database and a per-token API, costs scale proportionally to usage — much more predictable for a startup client.


How LangChain Helped Orchestrate the AI Workflow

We evaluated a few orchestration options — including building a custom pipeline from scratch — before landing on LangChain. The deciding factor wasn't the library itself; it was the time cost of building equivalent abstractions ourselves.

LangChain gave us:

ConversationalRetrievalChain out of the box — which handles the retrieval step, injects retrieved documents into the prompt, and manages conversation history in a way that doesn't blow out the context window on long sessions.

Tool use / function calling with clean abstractions. When the LLM decides it needs to add an item to cart, it calls a structured tool. LangChain handles the routing and response parsing so we don't have to manually process every JSON response from OpenAI.

Memory management. We used a hybrid approach: short-term in-memory conversation history (last N turns) combined with a PostgreSQL-backed summary for longer sessions. LangChain's memory modules gave us a starting point we could customize rather than build from zero.

Prompt templating. Managing prompt construction across different query types — meal search vs. diet plan generation vs. FAQ vs. order inquiry — without prompt templates would have been chaos. LangChain's PromptTemplate system kept this maintainable.

One important callout: LangChain adds abstraction overhead. There were moments where debugging a chain failure required diving three layers deep into the library code. If your use case is extremely simple — one prompt, one retrieval step, one response — you might not need it. For our multi-tool, multi-chain architecture, it paid for itself in development time.


Building the Frontend Experience in React

The chat UI sounds simple until you're actually building it for an ecommerce context.

Standard chat interfaces display text. Our interface needed to:

  • Stream LLM responses token by token
  • Render structured meal cards mid-conversation (with image, name, calories, macros, price)
  • Show an "Add to Cart" button inside a chat bubble
  • Handle loading states for retrieval (there's a ~1–2 second delay before streaming starts)
  • Maintain a persistent cart state synced with the main platform
  • Work on mobile without breaking the layout

We used a custom React hook to manage the streaming connection and a small set of structured response types that the backend returned alongside prose. When the LLM recommended specific meals, the backend enriched those recommendations with live product data — image URLs, current pricing, stock status — before sending them to the frontend. The UI then rendered these as interactive cards, not just text.

The "Add to Cart" button wired directly to the platform's existing cart API. No redirect, no page reload. A user could discover a meal, check its macros, ask follow-up questions, add it, and keep chatting — all in one place.

WhatsApp integration was handled separately via a webhook that translated the WhatsApp message format into our standard API contract. The AI layer was identical; only the channel adapter changed.


Designing a Context-Aware Meal Recommendation System

Context-awareness was the hardest thing to get right, and also the thing users noticed most when it worked.

The failure mode we kept hitting early on: a user would say "I want something low-carb" and the assistant would recommend a meal. Then the user would say "something a bit spicier?" and the assistant would forget the low-carb constraint and start recommending random dishes.

The fix was multi-part. First, we structured the conversation history so that stated user preferences were explicitly extracted and maintained in a session profile — not just left implicit in the raw chat log. When a user says "I'm vegetarian" or "I'm trying to stay under 1800 calories," that gets parsed and stored in a structured preference object.

Second, that preference object is injected into every subsequent prompt as a system-level constraint: "The user has stated: vegetarian diet, calorie target 1800/day, dislikes spicy food." The LLM doesn't have to infer this from scrolling through chat history — it's handed to it explicitly.

Third, we built a query expansion step before vector search. Instead of embedding the raw user message ("something a bit spicier"), we first expanded it using a lightweight LLM call that incorporated current session context: "User wants a vegetarian, low-carb meal with moderate spice, under 500 calories." That expanded query retrieved far more relevant results from the vector database than the original three-word message would have.


Nutrition Intelligence and Personalized Meal Plans

Meal plan generation was a separate workstream — and the feature that required the most prompt engineering work.

When a user asks for a weekly meal plan, they're giving you an underconstrained problem. To generate something useful, the system needs to ask the right clarifying questions (or make smart defaults), know the product catalog well enough to construct realistic combinations, and produce output that's actually practical — not just nutritionally sound on paper.

Our approach:

We built a multi-turn meal plan flow. The first response to a meal plan request is always a set of clarifying questions: How many days? What's your daily calorie target? Any dietary restrictions? Any meals you already like from our catalog? After collecting this, we run a structured generation prompt that asks the LLM to build a plan using only meals that exist in the retrieved catalog context.

That last part is key. We don't let the model free-generate meal ideas — we retrieve 30–40 candidate meals that match the user's stated constraints, inject those into the prompt, and ask the model to compose a plan from that set. This prevents the model from inventing meals that don't exist on the platform.

The output is a structured JSON object — day by day, meal by meal — that the frontend renders as a visual plan with individual "Add to Cart" options for each day.


Connecting AI Recommendations to Ecommerce Workflows

The ecommerce integration was where rubber met road. A beautiful recommendation means nothing if it doesn't convert.

We implemented two LangChain tools that the LLM could call:

add_to_cart(item_id, quantity) — triggers the platform's cart API, returns confirmation or an error message. The LLM calls this tool when it detects clear intent ("yes, add that") and the user has a valid session.

check_order_status(order_id) — pulls live order data and returns it to the LLM as context, which then rephrases it in natural language ("Your order is currently being prepared at the restaurant and should be picked up in about 20 minutes").

We also added a live inventory check step before any recommendation. Before surfacing a meal to a user, the system checks its stock status. Nothing erodes trust faster than recommending something that's unavailable.


Add-to-Cart Directly from Chat

This feature deserves its own section because it was the one the client was most skeptical about and ultimately most excited by.

The implementation: when the LLM recommends meals, the backend intercepts the response, extracts any mentioned product IDs, fetches their live data, and returns a hybrid response — prose from the LLM, structured product cards from the backend. Each card has a native "Add to Cart" button.

The UX decision we debated: should the AI add to cart automatically when intent is clear, or always ask for confirmation? We landed on always asking. For a nutrition context, adding items without explicit confirmation felt like overreach. One extra "Shall I add that?" message is a small cost for user trust.


Challenges We Faced

Hallucinations

This was our number one concern going in, and it turned out to be manageable — but not trivially.

The main guard was retrieval-grounding: the model only answers nutrition questions about meals that appear in the retrieved context. If a user asks about a meal we have no record of, the model says it doesn't have information rather than fabricating data.

We also used a custom system prompt instruction: "Only state nutritional facts if they appear in the provided product context. If a fact is not in the context, say so explicitly." This reduced confabulation significantly in testing.

For allergen questions specifically, we added a secondary validation layer — a template-based check that cross-referenced any allergen claim in the response against the product database directly. Allergen information was too important to trust to probabilistic generation alone.

Retrieval Quality

Early retrieval results were noisy. A user asking "something light for dinner" would pull in unrelated items because the embedding similarity was too broad.

Fixes: we added metadata filtering alongside vector search (filter by category, calorie range, meal type before doing similarity search), improved the chunking strategy for product descriptions, and introduced the query expansion step mentioned earlier.

Context Window Management

Long conversations caused problems. By turn 20 of a conversation, the raw history was consuming a significant portion of the context window and leaving less room for retrieved content.

Solution: after every 10 turns, we ran a summarization step that compressed prior conversation history into a structured preference profile. Old raw turns were dropped; the summary persisted. This kept the effective context lean without losing important user preferences.

Performance

First-token latency on complex queries was around 4–5 seconds — too slow for a chat experience. We attacked this on several fronts: moved retrieval and embedding to run in parallel with session loading, cached frequently accessed product embeddings in Redis, and introduced a lightweight intent classifier that routed simple FAQ queries to GPT-3.5-turbo (faster, cheaper) while complex planning queries went to GPT-4o.

The result: median first-token latency dropped to under 2 seconds for the majority of queries.

Cost Optimization

OpenAI API costs were the most variable part of the bill. Meal plan generation prompts were expensive — large context windows, complex outputs. We implemented token budget caps per conversation, cached vector search results for identical queries (with a 15-minute TTL), and batched embedding generation for catalog updates rather than running them inline.


Security and Data Privacy Considerations

A few things we locked down from day one:

User data isolation. Conversation history and preference profiles are scoped strictly to user sessions. One user's data never bleeds into another's context.

No PII in prompts. We never included the user's name, email, or order history in LLM prompts. The system knows dietary preferences; it doesn't know who you are.

Rate limiting at the NestJS gateway. Aggressive use of the chat API (intentional or bot-driven) is capped per user per minute. This controls cost and prevents abuse.

Prompt injection defense. We stripped and sanitized user input before embedding it in prompts. A user trying to inject instructions via their message ("Ignore all previous instructions and...") would have their input treated as a plain query, not an instruction.

Output filtering. Responses were filtered for any content that referenced the system prompt or exposed internal logic.


Results and Business Impact

We measured outcomes at 30, 60, and 90 days post-launch. Across those checkpoints:

  • Chat-to-cart conversion rate: 34% of users who engaged with the meal assistant added at least one item to cart — compared to a baseline of 11% for browse-and-search users
  • Average order value: 18% higher for chatbot-assisted orders, driven by multi-item meal plans
  • Support ticket volume: Nutrition-related support tickets dropped by 62% in the first 60 days
  • Session depth: Average chat session length was 7.4 turns, suggesting users were genuinely engaging — not bouncing after one answer
  • WhatsApp channel: Accounted for 23% of total chat sessions within the first month, mostly from repeat users

The client's internal NPS for the AI feature (measured via an in-chat prompt) averaged 4.2 out of 5. The most common complaint was response time on complex queries — which is a reasonable tradeoff for the capabilities offered.


Lessons Learned

RAG quality lives or dies with retrieval quality, not the LLM. We spent more time tuning the retrieval pipeline than the prompts. A better retriever with a decent LLM outperforms a great LLM with poor retrieval, every time.

Structured preferences beat implicit context. Don't rely on the model to infer dietary constraints from raw chat history. Extract and store them explicitly, then inject them as hard constraints.

Never recommend what you can't deliver. Inventory checks before recommendations aren't optional in ecommerce. Real-time stock validation is table stakes.

User trust in AI nutrition tools is fragile. One wrong allergen claim and you've lost the user. Invest in correctness guardrails disproportionately for health-adjacent features.

Streaming matters for perceived performance. Even when the time-to-complete-response didn't change, showing the first token within 1.5 seconds made the experience feel dramatically faster.

LangChain is a good scaffold, not a permanent solution. As the codebase matured, we started replacing some LangChain abstractions with custom implementations for better observability. Don't treat it as immovable infrastructure.


Future Enhancements

A few things on the roadmap that we didn't ship in v1:

  • Multi-modal input: allowing users to photograph a meal and ask about its nutritional content
  • Longitudinal preference learning: using past orders and ratings to improve recommendations over time without explicit user input
  • Calorie tracking integration: syncing with fitness apps so the assistant can give recommendations in the context of a user's actual daily intake
  • Proactive nudges: notifying users via WhatsApp when a meal that matches their stated preferences comes back in stock or goes on promotion

Final Thoughts

Building an AI assistant that people actually use for something as personal as food and nutrition is harder than it looks from the outside. The gap between "the demo worked" and "this works reliably in production for 10,000 users" is where real engineering happens.

What we've taken from this project is a clearer sense of where AI genuinely adds value in commerce: not replacing the browse experience, but extending it into a medium — conversation — where users can express intent more naturally than they can through filters and search bars.

If you're evaluating a similar build — whether for food delivery, healthcare, retail, or any domain where product knowledge meets user intent — we're happy to talk through the architecture choices and what we'd do differently. Reach out to the Sofyrus team directly.


Frequently Asked Questions (FAQs)

Q1: What is a RAG-based AI chatbot, and why is it better than a regular chatbot for food delivery?

RAG (Retrieval-Augmented Generation) combines a large language model with a live knowledge retrieval system. Instead of relying on static, pre-trained knowledge, a RAG chatbot fetches relevant information from your actual database before generating a response. For food delivery, this means the assistant can answer questions about your specific menu, current stock, and nutritional data accurately — without hallucinating information that doesn't exist in your catalog.

Q2: How does the AI meal planning assistant avoid giving incorrect nutritional information?

We use a retrieval-grounding strategy: the model is only permitted to state nutritional facts that appear in the retrieved product context. If a fact isn't in the retrieved documents, the model is instructed to say so explicitly. For allergen information, we also added a secondary template-based validation layer that cross-references AI responses against the live product database.

Q3: Why did you choose LangChain for this project?

LangChain provided structured abstractions for retrieval chain management, tool calling (for cart and order APIs), memory management, and prompt templating — all of which we needed. Building equivalent infrastructure from scratch would have added 4–6 weeks of development time. That said, as the product matured, we replaced some LangChain modules with custom implementations for better observability and control.

Q4: Why NestJS instead of other Node.js frameworks for the backend?

NestJS's module-based, dependency-injection architecture gave us clean separation between AI orchestration logic, ecommerce integration logic, and API routing. It's also TypeScript-native, which reduced integration friction with the React frontend. For teams building multi-concern backends (as any AI+ecommerce system will be), NestJS's structure enforces discipline that bare Express does not.

Q5: Can this type of AI assistant be built for platforms other than food delivery?

Yes. The core architecture — React chat UI, NestJS backend, LangChain orchestration, RAG over a product catalog, ecommerce API integration — is applicable to any domain where users need to discover products through conversation. Healthcare supplement platforms, fitness equipment retailers, pharmacy systems, and grocery delivery platforms all have similar requirements.

Q6: How much does it cost to run an AI assistant like this at scale?

Costs are primarily driven by LLM API usage (OpenAI), vector database hosting, and backend compute. At moderate scale (50,000 conversations per month), a well-optimized system with model routing (cheaper models for simple queries, powerful models for complex ones) and caching can run in the range of $0.03–0.10 per conversation. Costs rise significantly without caching and model routing strategies.

Q7: How do you handle user data privacy in an AI nutrition chatbot?

We follow a strict policy of no PII in LLM prompts. Dietary preferences and conversation history are session-scoped and never shared across users. Input is sanitized before prompt injection. Rate limiting prevents abuse. All conversation data is stored in a PostgreSQL database under the client's own infrastructure with standard encryption at rest and in transit.

Q8: What is the difference between fine-tuning an LLM and using RAG for a product catalog?

Fine-tuning bakes knowledge into the model's weights at training time — making it static. If your catalog updates, the model is immediately out of date. RAG retrieves knowledge at query time from a live database, so updates to the catalog are reflected immediately without any retraining. For dynamic product data, RAG is almost always the correct choice.

Q9: How long does it take to build an AI assistant like this?

For our team, v1 — covering meal search, nutrition Q&A, meal plan generation, and cart integration — took approximately 14 weeks from discovery to production launch. A simpler FAQ-only assistant might take 4–6 weeks. A full multi-channel deployment with WhatsApp integration, multi-LLM routing, and advanced preference learning is closer to 5–6 months.

Q10: How does the AI handle a user asking about a meal that doesn't exist in the catalog?

The retrieval step returns only real meals from the vector database. If no relevant meal is found, the model is instructed to inform the user that it doesn't have information on that item — and can suggest alternatives from the retrieved catalog. This is a deliberate design choice: a graceful "I don't know" is always preferable to a confident wrong answer.

Keep reading

Explore more insights from Sofyrus on product engineering, AI delivery, and enterprise technology.