Build a shopping assistant
This guide builds a shopping assistant for a fictional outdoor-gear shop, Ridgeway. Customers describe what they need (“a tent for two for spring hikes in Scotland, under 250 pounds”); the assistant searches the live catalog, compares a few products using real specifications and prices, and adds an item to the basket only after the customer confirms. It covers the parts that make commerce different from support: grounding every price and claim in catalog data, keeping the customer in control of the basket, and measuring whether recommendations help.
You will need: an API key in SYNTIC_API_KEY, Python 3.9 or later, pip install openai, and a product search you can call from code.
Ground rules for a commerce assistant
| Rule | Why |
|---|---|
| Prices, stock and specifications come only from tool results | A model’s memory of a product is out of date or wrong; an invented price is a legal problem. |
| The basket changes only after explicit confirmation | Customers must stay in control of what they buy. |
| Comparisons cite the specification they rely on | ”Lighter” is checkable when it says “1.6 kg against 2.1 kg”. |
| Out-of-stock items are said to be out of stock | Recommending what cannot be bought wastes the customer’s time. |
The tools
TOOLS = [
{"type": "function", "function": {
"name": "search_products",
"description": (
"Search the Ridgeway catalog. Returns up to 8 products with id, name, price_gbp, in_stock and key specs. "
"Use for every product question; never describe a product you have not retrieved."
),
"parameters": {"type": "object", "properties": {
"query": {"type": "string", "description": "What the customer wants, in catalog terms, e.g. '2 person tent 3 season'"},
"max_price_gbp": {"type": "number"},
"category": {"type": "string", "enum": ["tents", "sleeping", "packs", "footwear", "clothing", "cooking"]},
}, "required": ["query"]}}},
{"type": "function", "function": {
"name": "get_product",
"description": "Full specification, price and stock for one product id from search results.",
"parameters": {"type": "object", "properties": {"product_id": {"type": "string"}}, "required": ["product_id"]}}},
{"type": "function", "function": {
"name": "add_to_basket",
"description": (
"Add a product to the customer's basket. Only call this after the customer has clearly said yes to "
"a specific product and quantity in their latest message."
),
"parameters": {"type": "object", "properties": {
"product_id": {"type": "string"}, "quantity": {"type": "integer", "minimum": 1, "maximum": 5},
}, "required": ["product_id", "quantity"]}}},
]The add_to_basket description states the confirmation rule, and the code below enforces it again, because a description is guidance, not a guarantee.
The instructions
SYSTEM = """
You are Ridgeway's shopping assistant. Help customers choose outdoor gear.
- Use search_products for every product question. Quote prices, stock and specifications only from tool results.
- Recommend at most three products. For each, give the name, price, and the one or two specs that matter
for the customer's need. Say plainly if something is out of stock.
- Ask one short question when the need is unclear (number of people, season, budget), then search.
- Never add to the basket unless the customer's latest message clearly confirms a product and quantity.
If unsure, ask: "Shall I add the <name> to your basket?"
- If nothing in the catalog fits, say so and suggest the closest alternative or relaxing one requirement.
- Do not discuss competitors' products or prices.
""".strip()The loop, with a confirmation check
import json
import os
import re
from openai import OpenAI
client = OpenAI(base_url="https://app.syntic.ai/v1", api_key=os.environ["SYNTIC_API_KEY"])
YES = re.compile(r"\b(yes|yeah|yep|please do|add it|go ahead|sure)\b", re.I)
def run_tool(name, args, session):
if name == "search_products":
return catalog.search(args["query"], args.get("max_price_gbp"), args.get("category")) # your search
if name == "get_product":
return catalog.get(args["product_id"])
if name == "add_to_basket":
last_user = next(m["content"] for m in reversed(session["messages"]) if m["role"] == "user")
if not YES.search(last_user):
return {"error": "Not confirmed. Ask the customer to confirm the product and quantity first."}
product = catalog.get(args["product_id"])
if not product or not product["in_stock"]:
return {"error": "That product is not in stock."}
session["basket"].append({"product_id": product["id"], "quantity": args["quantity"]})
return {"ok": True, "basket_items": len(session["basket"])}
return {"error": f"unknown tool {name}"}
def reply(session, text):
msgs = session["messages"]
msgs.append({"role": "user", "content": text})
for _ in range(6):
r = client.chat.completions.create(model="Amara S1.2", stream=False, max_tokens=700,
tools=TOOLS, messages=[{"role": "system", "content": SYSTEM}] + msgs)
m = r.choices[0].message
msgs.append(m.model_dump(exclude_none=True))
if not m.tool_calls:
return m.content
for call in m.tool_calls:
try:
result = run_tool(call.function.name, json.loads(call.function.arguments or "{}"), session)
except Exception as err:
result = {"error": str(err)}
msgs.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
return "Sorry, I couldn't finish that. Could you rephrase?"
session = {"messages": [], "basket": []}
print(reply(session, "I need a tent for two for spring hikes in Scotland, under 250 pounds."))A simple keyword check is enough to block the most common failure (adding to the basket while still comparing). For production, show an Add to basket button next to each recommendation and let the click, not the model, change the basket; the assistant then only suggests.
Make search results model-friendly
The assistant is only as good as what search_products returns:
- Return 5 to 8 products, not 50. Ranking is your search engine’s job.
- Include the specs that drive decisions in your category (weight, packed size, season rating for tents) as named fields, with units.
- Include
in_stockand the current price in every result; never let the model fill them in. - Normalise names: the model will repeat them to customers.
Measure whether it helps
| Metric | How |
|---|---|
| Grounding | Sample 100 conversations; check every price and spec mentioned against the catalog. Target: no mismatches. |
| Confirmation | Count add_to_basket calls rejected by the check; each one is a near miss to investigate. |
| Usefulness | Conversion rate of sessions with the assistant against those without, and basket returns. |
| Coverage | Share of conversations ending with “nothing fits”; these show catalog gaps or search problems. |
Prices quoted by an assistant are part of your shop’s offer in many jurisdictions. Keep the grounding rule strict, log what the assistant showed, and have someone review the setup with whoever owns consumer law compliance for your shop.
Related pages
- Build a support assistant: after-sales questions and hand-over.
- Design good tools
- Reduce hallucinations