Blog · Guide
How AI agents choose between products, and what your product data has to give them
Agents shortlist in code and pick with decision models. Which product fields decide whether you are a candidate at all, with a JSON-LD example.
By Sascha Hoffmann · Published · Markdown

An AI agent can only choose your product if your product made it onto its list of candidates, and that list is built from data it could extract without guessing.
That sounds obvious. It stopped being abstract for me in September, when I started building with a new class of model that does nothing but choose.
The short answer
Publish every field that decides a purchase as plain text in the server-rendered HTML, and again as Product and Offer JSON-LD: name, price as a number, currency, availability, shipping terms, return terms and a stable identifier.
Anything an agent has to infer from a sentence, an image or a script is a field it may drop. A dropped field usually means a dropped candidate.
How an agent actually picks a product
Most people still picture an agent as a chatbot reading your page and writing an opinion. Increasingly, the pick itself is made by something much narrower.
In September 2026, TypeSafe AI released Jev, a model that does not generate text at all. You hand it a state and a set of predefined questions, and it returns typed answers with probabilities. One question type, Choice, selects one option from a list of up to 255, with a probability for every option and a confidence value.
That design changes where the decision happens. The agent's code builds the menu first, then the model picks from it. TypeSafe's own guidance for large candidate sets follows the same order: filter obvious mismatches in code, score what is left, then choose among the shortlist.
Open-source agents already work this way. Browser Use published an agent in which every page observation becomes a numbered table of visible elements, and Jev picks the operation and the target from that table. The fastbrowse project describes the same idea in one line: the model picks one of the candidates on the page, so it cannot click something that is not there.
The consequence for a shop is simple. If your price is missing from the data the agent extracted, a filter like "under 150 EUR" does not rank you lower. It removes you before any model ever sees your name.
The fields that decide whether you are a candidate
A human skims past a missing return policy. An agent filtering candidates cannot, because it needs a value to compare. These are the fields I would treat as mandatory:
- Name and a stable identifier. A GTIN, MPN or at least a SKU lets an agent recognise the same product across sources instead of treating your listing as a stranger.
- Price as a number with a currency.
"price": 129.00and"priceCurrency": "EUR", not"price": "ab 129 €*". The footnote asterisk is a sentence, and sentences need interpretation. - Availability. A schema.org value such as
InStock, not "usually ships soon". - Shipping terms. Cost and delivery time, in structured form where you can.
- Return terms. Our Check flags missing return and shipping information because these are the questions agents refuse to answer without a source.
- An image URL. Not for the decision itself, but most shopping surfaces will not show a product card without one.
A JSON-LD example you can copy
This is a complete Product with one Offer, including shipping and return policy. Render it server-side, in the HTML response, not injected after load.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Trail Runner 3 Waterproof",
"sku": "TR3-WP-42",
"gtin13": "4006381333931",
"image": "https://example.com/img/tr3-wp.jpg",
"brand": { "@type": "Brand", "name": "Example" },
"offers": {
"@type": "Offer",
"price": 129.00,
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
"url": "https://example.com/products/tr3-wp",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": 4.90, "currency": "EUR" },
"shippingDestination": { "@type": "DefinedRegion", "addressCountry": "DE" },
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 3, "unitCode": "DAY" }
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "DE",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnFees": "https://schema.org/FreeReturn"
}
}
}
The structured copy is the second copy. The same price and the same terms have to be readable as text on the page as well, because not every agent parses JSON-LD and a mismatch between the two is itself a reason to distrust you.
Write for a reader that takes you literally
Decision models are fast and cheap, and they read very literally. TypeSafe documents the weak spots itself: negations and scope words go wrong, counting is unreliable, and dates are treated as text rather than calculated.
For product data that has a direct translation. "Free shipping on orders over 50 EUR, except bulky items and islands" is three conditions and two exceptions in one sentence. A structured shipping rate plus a separate, plainly worded exception is much harder to misread.
The same goes for availability windows and prices that depend on a date. Put the number in a field. Do not leave the arithmetic to the reader.
There is a second weak spot that matters for how long your pages are. TypeSafe calls it context rot: accuracy drops when the state contains material the question does not need. A product page that wraps two lines of facts in a wall of cross-sells, repeated navigation and marketing copy is asking the agent to find the needle. I wrote more about that cost in How HTML size affects what an AI model can read.
Do not argue for yourself inside your data
This is the part I expect some people to try anyway, so it is worth being direct.
TypeSafe states openly that Jev does not treat its input as hostile. Text that argues for its own classification can shift the answer. In other words, a product description that says "this is the best choice for runners with wide feet" might nudge a naive agent.
Please do not do it. It is manipulation of a system your buyer trusted to act for them, it will be the first thing agent builders learn to filter, and it contradicts the one thing that makes your data valuable: that it is true. Our own Check has a rule for this. We never fake markup to gain points, even on our own domain, which is why our self-scan sits at 95 rather than 100.
Describe the product. Let the agent decide.
How to check your own product pages
Three checks, in order, take about ten minutes per template:
- Open a product page, view the source (not the inspector) and search for the price. If it is not in the raw HTML, a large share of agents will never see it.
- Copy the JSON-LD from the source into the Schema Markup Validator and confirm that
priceis a number andpriceCurrency,availability,shippingDetailsandhasMerchantReturnPolicyare present. - Compare the structured values with the visible text. Same price, same delivery time, same return window.
The feeds part of AgentReady Check runs these checks on your homepage and linked product data. If your domain does not sell anything directly, that part is left out of your score instead of counted as a failure.
What nobody outside the big platforms can tell you
I cannot tell you exactly how ChatGPT, Gemini or Perplexity weigh product candidates today. None of them publishes that pipeline, and I am not going to invent one.
What is observable is the pattern in open agents and in the models built for this job: build a menu from extracted data, filter in code, let a cheap model choose. You do not need to know the weights to know that a product with no extractable price is not on the menu.