n4nAI

A product Q&A chatbot from your catalog with LangChain

Build a product Q&A chatbot from your catalog with LangChain using RAG. Step-by-step tutorial with runnable code for ecommerce retrieval and chat.

n4n Team2 min read495 words

Audio narration

Coming soon — every post will get a voice note here.

A product qa chatbot langchain catalog implementation turns static product data into a conversational interface that answers spec questions without a human in the loop. This tutorial builds a retrieval-augmented generation (RAG) pipeline from a sample ecommerce catalog, using LangChain and an OpenAI-compatible chat model.

Prerequisites

  • Python 3.10 or newer
  • langchain, langchain-openai, langchain-community, chromadb
  • An OpenAI API key (or any OpenAI-compatible endpoint)
  • A product catalog as JSON lines

Install the dependencies:

pip install langchain langchain-openai langchain-community chromadb openai

Sample catalog

We use a toy catalog.jsonl with three products. In production this would be exported from your PIM or database.

{"sku":"A1","name":"TrailRunner Shoe","category":"footwear","specs":{"weight_g":280,"waterproof":true,"size_range":[38,46]},"description":"Lightweight trail shoe with Gore-Tex membrane."}
{"sku":"B2","name":"UrbanCommute Jacket","category":"apparel","specs":{"weight_g":420,"waterproof":false,"size_range":["S","M","L","XL"]},"description":"Breathable city jacket with reflective trim."}
{"sku":"C3","name":"PeakPower Backpack","category":"bags","specs":{"volume_l":30,"laptop_sleeve":true,"weight_g":900},"description":"30L hiking backpack with padded 16-inch laptop sleeve."}

Load and chunk the catalog

LangChain’s JSONLoader reads JSON lines. We flatten each record into a single text block and preserve SKU and category as metadata for later filtering.

from langchain_community.document_loaders import JSONLoader
import json

loader = JSONLoader(
    file_path="catalog.jsonl",
    jq_schema=".",
    text_content=False
)
raw_docs = loader.load()

docs = []
for d in raw_docs:
    obj = json.loads(d.page_content)
    content = f"{obj['name']} ({obj['sku']}): {obj['description']} Specs: {obj['specs']}"
    docs.append({
        "page_content": content,
        "metadata": {"sku": obj["sku"], "category": obj["category"]}
    })

# Rebuild as LangChain Document objects
from langchain_core.documents import Document
documents = [Document(page_content=d["page_content"], metadata=d["metadata"]) for d in docs]

Checkpoint — inspect the first document:

print(documents[0].page_content)
print(documents[0].metadata)

Expected output:

TrailRunner Shoe (A1): Lightweight trail shoe with Gore-Tex membrane. Specs: {'weight_g': 280, 'waterproof': True, 'size_range': [38, 46]}
{'sku': 'A1', 'category': 'footwear'}

Embed and store in Chroma

We embed with text-embedding-3-small and index in an in-memory Chroma collection. For an OpenAI-compatible endpoint that honors provider cache-control hints, set base_url on the embeddings client.

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
    documents,
    embeddings,
    collection_name="catalog"
)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})

Build the retrieval chain

Define the LLM. For resilience against provider outages, an OpenAI-compatible endpoint that provides automatic fallback across providers—such as n4n.ai—keeps the chatbot online when one model is rate-limited. Set base_url if you use such a gateway.

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    # base_url="https://api.n4n.ai/v1"  # optional OpenAI-compatible gateway
)

Now the prompt and the stuff-documents chain. The system prompt restricts answers to retrieved context.

from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate

system_prompt = (
    "You are a product support assistant. Answer questions using only the provided "
    "catalog excerpts. If the answer is not in the context, state that you don't know. "
    "Context: {context}"
)
prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("human", "{input}")
])

qa_chain = create_stuff_documents_chain(llm, prompt)
rag_chain = create_retrieval_chain(retriever, qa_chain)

Add conversational memory

Without memory, follow-up questions like “Does it come in size 42?” fail because the model forgot the product. Wrap the chain with RunnableWithMessageHistory.

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}
def get_history(session_id):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

conversational_rag = RunnableWithMessageHistory(
    rag_chain,
    get_history,
    input_messages_key="input",
    history_messages_key="chat_history",
    output_messages_key="answer"
)

Run the product qa chatbot langchain catalog

Invoke with a stable session_id per user:

resp = conversational_rag.invoke(
    {"input": "Which shoes are waterproof and under 300 grams?"},
    config={"configurable": {"session_id": "u1"}}
)
print(resp["answer"])

Expected output:

The TrailRunner Shoe (A1) is waterproof (Gore-Tex membrane) and weighs 280 grams, which is under 300 grams.

Follow-up turn in the same session:

resp2 = conversational_rag.invoke(
    {"input": "Does it come in size 42?"},
    config={"configurable": {"session_id": "u1"}}
)
print(resp2["answer"])

Expected output:

Yes, the TrailRunner Shoe has a size range of 38 to 46, so size 42 is available.

Inspect retrieved context

Before shipping, verify the retriever returns relevant chunks. The rag_chain exposes retriever output if you call the underlying retriever directly:

hits = retriever.invoke("waterproof shoes under 300g")
for h in hits:
    print(h.metadata["sku"], "->", h.page_content[:60])

Expected output:

A1 -> TrailRunner Shoe (A1): Lightweight trail shoe with Gore-Tex mem

If precision is low, increase k or add a metadata filter (e.g., category="footwear").

Handling catalog updates

Chroma persists to disk with persist_directory. For incremental updates, upsert by SKU to avoid duplicates:

vectorstore = Chroma.from_documents(
    documents,
    embeddings,
    collection_name="catalog",
    persist_directory="./chroma_db"
)

# Later: add or replace a product
new_doc = Document(
    page_content="TrailRunner Shoe (A1): Updated 2025 edition with Vibram sole. Specs: {'weight_g': 275, 'waterproof': True, 'size_range': [38, 47]}",
    metadata={"sku": "A1", "category": "footwear"}
)
vectorstore.add_documents([new_doc])

Because Chroma keys by internal ID, you should delete the old SKU first in production using a metadata filter.

Production considerations

  • Latency: Embedding the catalog dominates cold start. Embed once, persist, and reuse. Query-time embedding is a single vector per question.
  • Routing: If you serve multiple regions, honor client routing directives to keep data local and reduce round-trips.
  • Metering: Per-token usage metering lets you attribute cost to each customer session. OpenAI-compatible responses include a usage field; log it per session_id.
  • Guardrails: The prompt restricts answers to context, but add a post-check that extracted SKUs exist in your catalog to catch model drift.

Extending the pipeline

The same pattern works for multilingual catalogs (use text-embedding-3-large or a multilingual model), image-attached products (store image URLs in metadata and return them in the answer), and faceted search (pass filter={"category": "bags"} to the retriever).

A product qa chatbot langchain catalog deployment is only as good as retrieval precision. Tune k, add hybrid search with BM25 for keyword-heavy queries like SKU lookups, and log failed questions to improve chunk size and metadata.

Tagslangchainecommercechatbotrag

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All framework tutorials: e-commerce search & recommendations posts →