Skip to content

RAG Retrieval — Published Products & Chat History

How the chat pipeline selects knowledge chunks for retrieval: published storefront products only, conversation-aware embedding, and link follow-up handling.

Related: RAG & chat API · Product sync · Conversation history · Vector call stack · Storefront product cards


Env varDefaultUsed for
CHAT_HISTORY_MESSAGES50LLM reply — how many prior user/assistant turns are sent to OpenRouter in buildPrompt()
CHAT_RAG_HISTORY_MESSAGES8RAG retrieval only — how many recent turns are prepended when embedding the search query

They are independent on purpose:

  • 50 messages → better multi-turn answers (“what about that one?”, long threads).
  • 8 messages → enough context for follow-ups like “what is the link?” without embedding an entire long thread every turn (lower cost, sharper vector search).

Both are set in .env / .env.sample.


Module: app/lib/rag-search-query.server.jsbuildRagSearchQuery(message, historyForPrompt)

Before vector search, the app embeds text like:

Recent conversation:
Customer: what is the minimum price product you have?
Assistant: The minimum price product we have is Matching Separates, priced at $7,000.
Current customer message: and what is that product link?

Flow in app/lib/rag.server.js:

  1. Load up to CHAT_HISTORY_MESSAGES from the thread (newest first, reversed to chronological).
  2. ragSearchText = buildRagSearchQuery(message, historyForPrompt) — uses last CHAT_RAG_HISTORY_MESSAGES of that history.
  3. getEmbeddingsBatched([ragSearchText])searchSimilarChunks(...).

The current user message is not duplicated in DB history when embedding (history is loaded before saving the current turn).


When the customer asks for a link / URL / “that product” (isProductLinkFollowUp), and there is no focusProduct from a product card:

  1. Parse product titles from the last assistant reply (extractProductTitlesFromAssistantHistory).
  2. Resolve published catalog rows (findPublishedProductsByTitles).
  3. Merge that product’s chunks ahead of vector results (mergeChunksProductFirst).
  4. Inject Product from conversation context with Product page URL: … for the LLM.

Prompt rules (prompt-builder.server.js): when the customer explicitly asks for a link, include a markdown link from Store Context; do not fall back to the homepage only.


Draft, archived, and unpublished products must not appear in RAG answers or product cards. Zero inventory is OK if the product is published.

Module: app/lib/product-storefront.server.js

  • Shopify Admin search: status:active published_status:published (buildShopifyProductsSearchQuery).
  • GraphQL field publishedAt stored on products.published_at (migration 20260523150000_add_product_published_at).

ingestProduct: if !isProductPublishedOnStorefront(product)purge embeddings/chunks for that Shopify product ID and skip ingest.

purgeIneligibleProductsFromRag(shopId) runs at the start of each product sync to clean stale draft/unpublished embeddings.

vector-search.server.js SQL excludes product-linked chunks unless:

lower(p.status) = 'active' AND p.published_at IS NOT NULL

Non-product chunks (FAQ, website knowledge, policies) are unchanged (product_id IS NULL).

product-cards.server.js — catalog lookups use storefrontVisibleProductWhere() (active + publishedAt set).

chunker.server.js — product overview chunks include Product page: {url} when available.


Terminal window
npx prisma migrate deploy # includes products.published_at

Then run a full product sync in admin so:

  • published_at is populated for live catalog items
  • draft/unpublished products are purged from embeddings

FileRole
app/lib/product-storefront.server.jsEligibility, sync query, purge RAG
app/lib/rag-search-query.server.jsbuildRagSearchQuery, link follow-up helpers
app/lib/rag.server.jsOrchestration: embed search text, conversation product boost
app/lib/vector-search.server.jspgvector search with published-product filter
app/lib/ingestion.server.jsGate + purge on ingest
app/lib/product-sync.server.jsSync filter + publishedAt
app/lib/prompt-builder.server.jsLink-on-request + published-only rules