> Product data is structured, so chunking it like PDFs fails. How to serialize PIM attributes, route spec questions to filters and keep answers exact.
>
> Web page: https://balazscsorba.com/blog/rag-product-catalog-pim-data · Language: English · Also available in: [Deutsch](https://balazscsorba.com/de/blog/rag-product-catalog-pim-data.md) · [Magyar](https://balazscsorba.com/hu/blog/rag-product-catalog-pim-data.md)
> Author: Balázs Csorba · Published: 2026-10-17 · Keywords: rag product catalog, rag pim data, llm product data, product catalog chatbot, akeneo rag, pimcore ai search, rag structured data, metadata filtering rag, product attributes embeddings, b2b product assistant, etim classification ai

[Blog](https://balazscsorba.com/blog)/Retrieval & search

# RAG over product catalogues: turning PIM data into answers that hold

Product data is structured, so chunking it like PDFs fails. How to serialize PIM attributes, route spec questions to filters and keep answers exact.

[Balázs Csorba](https://balazscsorba.com/about)·October 17, 2026·10 min read

-   RAG
-   PIM
-   Product data
-   B2B e-commerce
-   Metadata filtering

![A frame of nine squares with tiny symbols, funnelling down to one card.](https://balazscsorba.com/images/blog/rag-product-catalog-pim-data/cover.webp?v=22f9b0ebf9)

## Key takeaways

-   Catalogue questions come in kinds: part numbers need exact lookup, specifications need structured filters, and only descriptive questions are a good fit for vector search.
-   PIM values are typed and carry locale, channel and unit, as in Akeneo's locale, scope and data triples; serialize them with labels and units, never with raw attribute codes.
-   Index one text per product model and keep variant attributes as structured payload, so a question about a size or a thread does not depend on an embedding.
-   Let the LLM extract filters into a schema you validate against the attribute model, then run filtered hybrid search; prices and stock come live from the shop, not from the index.
-   Evaluate with expected SKUs per query and the Exact, Substitute, Complement and Irrelevant vocabulary from Amazon's ESCI dataset.

On this page

1.  [What does PIM data actually look like?](https://balazscsorba.com/#what-pim-data-looks-like)
2.  [Which catalogue questions need which retrieval?](https://balazscsorba.com/#question-types)
3.  [One document per what: product, model or variant?](https://balazscsorba.com/#document-granularity)
4.  [How should attributes become text?](https://balazscsorba.com/#serializing-attributes)
5.  [Why do specifications belong in filters, not embeddings?](https://balazscsorba.com/#filters-not-embeddings)
6.  [What should never be in the index?](https://balazscsorba.com/#prices-and-freshness)
7.  [How do locales and classifications help?](https://balazscsorba.com/#multilingual-catalogues)
8.  [How do you test a catalogue assistant?](https://balazscsorba.com/#evaluating-catalogue-rag)
9.  [Where should you start?](https://balazscsorba.com/#where-to-start)
10.  [Sources](https://balazscsorba.com/#sources)

Listen to this article

0:000:00

Most RAG tutorials start from documents: PDFs, wiki pages, manuals. A product catalogue is a different animal. In a PIM the knowledge is already structured: typed attributes, units, variants, classifications, translations per locale and values per sales channel. If you flatten that into text and chunk it like a manual, you throw away exactly the structure that makes catalogue questions answerable.

This post is the data side of a catalogue assistant: what PIM data looks like, how to serialize it for retrieval, which questions belong to filters instead of embeddings, and how to test that the answers name the right SKU. It builds on [semantic product search for B2B shops](https://balazscsorba.com/blog/semantic-product-search-b2b), which covers ranking, and assumes the sync problems from [Pimcore ERP delta sync](https://balazscsorba.com/blog/pimcore-erp-delta-sync) are solved upstream.

## What does PIM data actually look like?

Take Akeneo as an example. A product's `values` are keyed by attribute code, and each code maps to an array of value objects with `locale`, `scope` and `data`. A localizable attribute has one value per locale, a scopable one has one value per channel, and an attribute that is both has one per locale and channel pair. A metric value is not a number but an object with `amount`, `unit` and `symbol`, where the symbol comes from the measurement family.

```
"values": {
  "name":        [{ "locale": "de_DE", "scope": null, "data": "Sechskantschraube DIN 933" }],
  "length":      [{ "locale": null, "scope": null,
                    "data": { "amount": "50", "unit": "MILLIMETER", "symbol": "mm" } }],
  "material":    [{ "locale": null, "scope": null, "data": "stainless_a2" }],
  "description": [{ "locale": "de_DE", "scope": "ecommerce", "data": "..." },
                  { "locale": "de_DE", "scope": "print",     "data": "..." }]
}
```

Variants add a second layer. In Akeneo a family variant defines one or two variant levels, the axes per level and which attributes are common and which vary, for up to three levels of enrichment: a root product model, a sub product model and the variant product. Only the variant product has a SKU; the product model has a code. Pimcore takes another route with the classification store: keys with data types grouped into groups, groups attached to objects, optional localization, and quantity values that pair a number with a unit.

Many technical B2B catalogues are additionally classified with ETIM, which its publisher describes as the international classification standard for technical products: classes with a uniform list of features and predefined values, available in 17 languages. ETIM 10.0 was released in December 2024 and ships as IXF, an XML format. If your suppliers deliver ETIM data, you already have a shared attribute vocabulary across brands, which is gold for filters.

## Which catalogue questions need which retrieval?

The single most useful design step is to sort the questions your assistant will get by the kind of retrieval they need. For a B2B catalogue, expect a very different mix from a support wiki: many questions are lookups or constraints, and fewer are open-ended.

Question

Example

Route

Identifier

Do you have 4711-050?

Exact lookup on SKU, EAN, supplier number

Specification

M8 stainless screws, at least 50 mm

Structured filter, then rank

Comparison

Difference between X200 and X300?

Fetch both records, compare attributes

Descriptive

Something for outdoor use near salt water

Hybrid search over text

Relation

Which nut fits this screw?

PIM associations, then lookup

Commercial

What does it cost me, is it in stock?

Live shop or ERP call, never the index

Amazon's Shopping Queries Dataset is a useful reference for the vocabulary. Its about 130,000 queries and 2.6 million human relevance judgements label each query and product pair as Exact (satisfies all query specifications), Substitute (fails some aspects but could stand in), Complement (does not fulfil the query but works with an exact item) or Irrelevant. A catalogue assistant should know which of the four it is offering, and say so: suggesting a substitute as if it were exact is the classic way to lose a B2B buyer's trust.

## One document per what: product, model or variant?

The first instinct is one document per SKU. For catalogues with variants this works badly in the text index. Forty lengths of the same screw share their name, description and most attributes, so a descriptive query fills the top results with near-duplicates of one product and pushes every alternative out. Embedding forty near-identical texts also costs storage and indexing time for no gain.

What works better is to split the record. The text index gets one document per product model, built from the common attributes and a compact summary of the variant axes, for example lengths 20 to 120 mm, stainless A2 and A4. The variant attributes live as structured payload, either on the same point in the vector store or in a separate table keyed by model code. Retrieval finds the model by meaning, and a filter or lookup picks the variant. Simple products without variants are their own model.

Text answers what a product is; structure answers which variant it is.

## How should attributes become text?

The serializer is the most underrated piece of a catalogue RAG pipeline. Embedding models and LLMs read labels, not codes: `stainless_a2` and `MILLIMETER` mean little, Material: stainless steel A2 and Length: 50 mm mean a lot. A few rules I apply:

-   **Resolve every code to its label** in the target locale: attribute names, select options, units via their symbol.
-   **Pick one channel.** Scopable values differ per channel; index the e-commerce scope, not the print catalogue text.
-   **One text per locale,** with an explicit fallback rule, so a German query never retrieves a half-English record by accident.
-   **Drop empty and internal attributes:** cost prices, workflow flags, import timestamps. They add noise and sometimes leak.
-   **Put identifiers in the text too:** SKU, EAN and supplier numbers belong in the BM25 field, because users paste them.

```
def serialize(model, variants, locale, labels, channel="ecommerce"):
    def val(values, code):
        for v in values.get(code, []):
            if v["locale"] in (locale, None) and v["scope"] in (channel, None):
                d = v["data"]
                if isinstance(d, dict) and "amount" in d:      # metric value
                    return f'{d["amount"]} {d["symbol"]}'
                return labels.option(code, d, locale) if isinstance(d, str) else d
        return None

    lines = [f'# {val(model["values"], "name")}']
    for code in labels.common_attributes(model["family"]):
        v = val(model["values"], code)
        if v not in (None, "", []):
            lines.append(f'{labels.attribute(code, locale)}: {v}')
    # summarise the variant axes instead of repeating every variant
    for axis in labels.axes(model["family_variant"]):
        seen = sorted({str(val(x["values"], axis)) for x in variants})
        lines.append(f'{labels.attribute(axis, locale)} (variants): {", ".join(seen)}')
    lines.append("SKUs: " + " ".join(x["identifier"] for x in variants))
    return "\n".join(lines)
```

Whether you render the attributes as key-value lines, a Markdown table or JSON is worth a test rather than a belief. The Table Meets LLM study found that LLM performance on table tasks varied with input choices such as format and content order; your embedding model and your queries decide which format retrieves best. I start with label: value lines because they work for BM25, embeddings and the LLM alike.

## Why do specifications belong in filters, not embeddings?

A dense embedding compresses a text into a direction that captures what it is about. It is not built to tell 48 mm from 50 mm, or to know that at least 50 excludes 48. BM25 matches the token 50 but has no idea of ranges or units either. So constraints should be extracted from the question and applied as filters on typed fields. Vector databases support this directly; in Qdrant a `must` clause with a `range` condition and `gte` or `lte` bounds does it, and the docs recommend payload indexes on every field you filter on.

```
{
  "query": "<embedding of: stainless hex screw for outdoor use>",
  "filter": {
    "must": [
      { "key": "thread",        "match": { "value": "M8" } },
      { "key": "material",      "match": { "any": ["stainless_a2", "stainless_a4"] } },
      { "key": "length_mm",     "range": { "gte": 50 } },
      { "key": "locale",        "match": { "value": "de_DE" } }
    ]
  },
  "limit": 20
}
```

The filter extraction is a small LLM call with structured output: give the model the list of filterable attributes with their types, allowed options and canonical units, and have it return a JSON object. Then validate it in code. Unknown attribute codes are dropped, option values must exist, and units are normalised before comparison, so 5 cm becomes 50 mm. This is the same discipline as in [text-to-SQL with guardrails](https://balazscsorba.com/blog/text-to-sql-llm-guardrails): the model proposes, a schema decides. If a constraint cannot be mapped, keep it in the text query rather than silently dropping it, and tell the user which filters were applied.

**An empty attribute is not a no**

Catalogue data is incomplete, often badly so for long-tail articles. If the assistant gets a record without a corrosion class and is asked whether the screw is suitable for salt water, the honest answer is that the data does not say. Pass missing values explicitly as not specified, instruct the model never to infer a technical value from the product name or from similar products, and test it with questions about attributes you know are empty. A wrong load rating in a B2B context is a liability question, not a UX glitch.

## What should never be in the index?

Prices, stock and delivery dates. In B2B shops prices are frequently customer-specific, with contract prices, scales and discounts, and stock moves every minute. An index snapshot of those values is wrong by design and, worse, may show one customer's conditions to another. Keep them out of the text and the payload. When the answer needs them, the assistant calls the shop's price and availability endpoint with the logged-in customer's session, the same way the storefront does. I covered the tool side of that in [an AI assistant for B2B shop support](https://balazscsorba.com/blog/ai-assistant-b2b-shop-customer-support).

For the rest, freshness is a sync problem. Re-serialize and re-embed a product model when any of its common attributes or labels change, and update only payload when a variant attribute changes, which is cheap because no embedding is involved. A changed option label, say a material renamed in German, touches every model that uses it, so key your change detection on the serialized text hash, not on the PIM's updated timestamp alone.

## How do locales and classifications help?

Catalogue data is often better translated than any wiki, because the PIM forces translations per attribute. Use that: one serialized text per locale, filtered by locale at query time, with a documented fallback when a translation is missing. Options and units are language-neutral in the payload, so filters work identically in German, English and Hungarian, and only the text index is per language. The general trade-offs of cross-lingual retrieval are in [multilingual RAG for German and Hungarian](https://balazscsorba.com/blog/multilingual-rag-german-hungarian).

Classifications such as ETIM add a second benefit. Because a class has a uniform feature list, the filter extraction prompt can be built per class: once the question is mapped to, say, a screw class, only that class's features are offered as filters. That keeps the prompt short and the extracted filters valid, even across suppliers whose own attribute names differ.

## How do you test a catalogue assistant?

Catalogue RAG has an advantage over document RAG: the ground truth is crisp. For each test query you can write down the SKUs or product models that are correct, which turns retrieval evaluation into hit rate and recall at k on IDs instead of judging passages. I build the set from search logs and sales questions, grouped by the question types in the table above, and label the expected results with the ESCI categories so a substitute is not counted as a hit. The metrics themselves are covered in [evaluating RAG](https://balazscsorba.com/blog/rag-evaluation-metrics).

-   **Identifier queries:** expect 100% exact hits; anything less is a tokenizer or field-mapping bug, not a model problem.
-   **Specification queries:** check the extracted filter against a hand-written expected filter before looking at results.
-   **Missing-data queries:** the correct answer is not specified; count invented values as failures.
-   **Commercial queries:** verify that the price comes from the live call, by testing with two customers who have different conditions.

## Where should you start?

Start with the serializer and the identifier path, because they are cheap and remove the most embarrassing failures. Then add filter extraction for the five to ten attributes that appear most in real questions, not for all of them. Only after that tune the text index, chunk sizes and rerankers. If your catalogue is also published with structured data, the same attribute mapping can feed both, which I described in [Schema.org structured data for B2B shops](https://balazscsorba.com/blog/schema-org-structured-data-b2b-shop). The pattern underneath is simple: the PIM already did the hard work of structuring product knowledge, and a good catalogue assistant keeps that structure all the way to the answer.

## Sources

1.  [Akeneo API: Products, product values, metric values and product models](https://api.akeneo.com/concepts/products.html)
2.  [Akeneo Help Center: What about products with variants?](https://help.akeneo.com/serenity-take-the-power-over-your-products/serenity-what-about-products-with-variants)
3.  [Pimcore documentation: Classification Store](https://docs.pimcore.com/platform/Pimcore/Objects/Object_Classes/Data_Types/Classification_Store/)
4.  [ETIM International: Classification](https://www.etim-international.com/classification/)
5.  [ETIM International: ETIM releases and the IXF format](https://etim-international.com/?p=754)
6.  [Reddy et al.: Shopping Queries Dataset, a large-scale ESCI benchmark for improving product search (2022)](https://arxiv.org/abs/2206.06588)
7.  [Amazon Science: esci-data repository](https://github.com/amazon-science/esci-data)
8.  [Qdrant documentation: Filtering](https://qdrant.tech/documentation/concepts/filtering/)
9.  [Sui et al.: Table Meets LLM, can large language models understand structured table data? (WSDM 2024)](https://arxiv.org/abs/2305.13062)

## Frequently asked questions

Can I just embed my product export and use RAG?

You can, and it will answer descriptive questions passably. It fails on part numbers, numeric specifications and variant differences, because embeddings do not compare numbers or exact codes reliably. Combine a text index with structured filters on the attributes.

Should each variant be its own document?

Usually not for the text index. Variants of one product model share most of their text, so they crowd each other out of the top results. Index the product model, store variant attributes as payload, and resolve the exact variant with a filter or a lookup.

Where do prices and stock come from?

From the shop or ERP at answer time. In B2B, prices are often customer-specific and stock changes constantly, so an index snapshot is wrong by design. The assistant should call a price and availability endpoint with the logged-in customer's context.

Does ETIM help with RAG?

Yes, where your data is classified with it. ETIM defines classes with a uniform list of features and predefined values, which gives you consistent attribute names for filters across suppliers and a multilingual vocabulary.

How do I stop the assistant from inventing specifications?

Pass the attribute values to the model as structured data with units, instruct it to answer only from them, and make an empty attribute an explicit not specified. Test it with questions about attributes you know are missing.

Written by Balázs Csorba

Senior fullstack & AI engineer in Styria, Austria – 10+ years of Vue, Nuxt, Node.js and PHP, now building tooling for AI agents.

[B2B e-commerce & PIM →](https://balazscsorba.com/expertise/b2b-ecommerce-developer)[About me →](https://balazscsorba.com/about)

## More articles

-   [Choosing an embedding model in 2026: leaderboards, costs and a bake-off on your own data](https://balazscsorba.com/blog/embedding-model-selection-2026)
-   [Contextual retrieval and late chunking: giving RAG chunks back their context](https://balazscsorba.com/blog/contextual-retrieval-late-chunking)
-   [Access control for RAG: permission-aware retrieval without leaking documents](https://balazscsorba.com/blog/rag-access-control-permissions)
-   [Query rewriting for conversational search: context, HyDE and filters](https://balazscsorba.com/blog/conversational-search-query-rewriting)

## Sounds like what you need?

Tell me about your project or role – I’d love to hear from you.

[Get in touch](mailto:contact@balazscsorba.com) [Connect on LinkedIn](https://www.linkedin.com/in/balazs-csorba)
