Match a message to a spare part
Get the order, the intent and the exact part from one support message.
A customer writes in about a broken part. To help, you need three things: the order, what they want, and the exact part number your shop sells. This guide gets all three with two Sage2 calls: extract the facts, then choose the intent and the matching SKU from your own catalog.
When nothing fits, you get null or none, and a person takes the message.
- Extract
- Choice
- Read the order, product and problemExtract
- order_number
- NB-48213
- product
- Burrly 2
- problem
- hopper lid cracked
- Pick what the customer wantsChoice
- spare_part99.98%
- order_statusunder 0.01%
- refundunder 0.01%
- how_tounder 0.01%
- Pick the part from the catalogChoice
- CP3-STEAMunder 0.01%
- CP3-TRAYunder 0.01%
- CM-JUGunder 0.01%
- BR2-LID99.99%
- MM-SEALunder 0.01%
- noneunder 0.01%
- Your codeHopper lid added to the cart
The application adds BR2-LID to the cart and replies to the customer.
Extract took 732 ms. Both Choice questions ran in one request: 144 ms.
Get the code
Set SAGE_API_KEY (get a free key), then run it. Python: pip install requests. TypeScript: Node 22+. Save as recipe.mts and run npx tsx recipe.mts.
A null answer is not an error. Sage2 isn't sure, and each step below says what to do with it. Limits, errors and retries: Limits and errors.
import os
import requests
def sage(path, body):
r = requests.post(
"https://sage.levanto.ai" + path,
headers={"Authorization": f"Bearer {os.environ['SAGE_API_KEY']}"},
json=body,
timeout=30,
)
r.raise_for_status()
return r.json()
message = "Hi, order NB-48213. I dropped my Burrly 2 and the hopper lid cracked. Can I buy just the lid? Please don't make me buy another grinder."
found = sage("/v1/extract", {
"content": message,
"extract": {
"order_number": "the order number",
"product": "the product the customer owns",
"problem": "what went wrong",
},
})["values"]
def answers(out, group=0):
# Each answer is a full decision. Failed ones (ok is false) are left out: treat a missing id as "not sure".
return {a["result"]["id"]: a["result"]["result"] for a in out["results"][group]["answers"] if a["ok"]}
a = answers(sage("/decide/batch", {
"requests": [
{
"content": message,
"questions": [
{
"id": "intent",
"kind": "choice",
"instructions": "What does the customer want?",
"options": [
{"option": "spare_part", "description": "A replacement part"},
{
"option": "order_status",
"description": "To know where an order is",
},
{"option": "refund", "description": "Their money back"},
{"option": "how_to", "description": "Help using a product"},
],
},
{
"id": "sku",
"kind": "choice",
"instructions": "Which of these spare parts does the message need? Choose none if no part on the list fits.",
"options": [
{"option": "CP3-STEAM", "description": "Steam wand for Crema Pro 3"},
{"option": "CP3-TRAY", "description": "Drip tray for Crema Pro 3"},
{"option": "CM-JUG", "description": "Milk jug for Crema Mini"},
{"option": "BR2-LID", "description": "Hopper lid for Burrly 2"},
{"option": "MM-SEAL", "description": "Lid seal for MilkMate"},
{"option": "none", "description": "None of these parts"},
],
},
],
},
],
}))
BAR = 0.9 # your bar: test it on past messages
intent = a.get("intent", {}).get("chosen")
part = a.get("sku", {})
sku = part.get("chosen") if (part.get("probability") or 0) >= BAR else None
if intent == "spare_part" and sku not in (None, "none"):
if found["order_number"]:
print(f"Order {found['order_number']}: add {sku} to the cart and reply")
else:
print(f"Add {sku} to the cart and ask for the order number")
elif intent == "spare_part" and sku == "none":
print("Hand it to a person: the part isn't in the catalog")
else:
print("Hand it to a person:", intent or "intent unclear")
How it works
Send the message to
/v1/extractand describe each field in a few words. The answers come back invalues. A field the customer left out comes back asnull. Ask the customer for it.Runs with the helper from Get the code.
message = "Hi, order NB-48213. I dropped my Burrly 2 and the hopper lid cracked. Can I buy just the lid? Please don't make me buy another grinder." found = sage("/v1/extract", { "content": message, "extract": { "order_number": "the order number", "product": "the product the customer owns", "problem": "what went wrong", }, })["values"]Example Sage2 output
{ "values": { "order_number": "NB-48213", "product": "Burrly 2", "problem": "hopper lid cracked" }, "results": { "order_number": { "value": "NB-48213", "text": "NB-48213", "start": 10, "end": 18, "probability": 0.9815, "probabilities": [ { "value": "NB-48213", "text": "NB-48213", "start": 10, "end": 18, "probability": 0.9815 }, { "value": null, "probability": 0.0185 } ] }, "product": { "value": "Burrly 2", "text": "Burrly 2", "start": 33, "end": 41, "probability": 0.9988, "probabilities": [ { "value": "Burrly 2", "text": "Burrly 2", "start": 33, "end": 41, "probability": 0.9988 }, { "value": null, "probability": 0.0012 } ] }, "…": "one entry per field" }, "meta": { "model": "levanto-sage-2", "usage": { "input_tokens": 218, "output_tokens": 314 }, "latency_ms": 731.6 } }Ask one Choice question with your four intents as
options. Sage2 returns its pick inchosen, with a probability for every option. This question and the next one go in the same request.Ask a second Choice question in the same
/decide/batchrequest. Theoptionsare your catalog. Each option is a SKU with its product name. Anoneoption catches a part that isn't in the catalog, or no part at all.Runs with the helper from Get the code.
def answers(out, group=0): # Each answer is a full decision. Failed ones (ok is false) are left out: treat a missing id as "not sure". return {a["result"]["id"]: a["result"]["result"] for a in out["results"][group]["answers"] if a["ok"]} a = answers(sage("/decide/batch", { "requests": [ { "content": message, "questions": [ { "id": "intent", "kind": "choice", "instructions": "What does the customer want?", "options": [ {"option": "spare_part", "description": "A replacement part"}, { "option": "order_status", "description": "To know where an order is", }, {"option": "refund", "description": "Their money back"}, {"option": "how_to", "description": "Help using a product"}, ], }, { "id": "sku", "kind": "choice", "instructions": "Which of these spare parts does the message need? Choose none if no part on the list fits.", "options": [ {"option": "CP3-STEAM", "description": "Steam wand for Crema Pro 3"}, {"option": "CP3-TRAY", "description": "Drip tray for Crema Pro 3"}, {"option": "CM-JUG", "description": "Milk jug for Crema Mini"}, {"option": "BR2-LID", "description": "Hopper lid for Burrly 2"}, {"option": "MM-SEAL", "description": "Lid seal for MilkMate"}, {"option": "none", "description": "None of these parts"}, ], }, ], }, ], }))Example Sage2 output
{ "results": [ { "answers": [ { "ok": true, "result": { "id": "intent", "kind": "choice", "result": { "chosen": "spare_part", "probability": 0.9998, "probabilities": [ { "option": "spare_part", "probability": 0.9998 }, { "option": "order_status", "probability": 0.0001 }, { "option": "refund", "probability": 0 }, { "option": "how_to", "probability": 0 } ] }, "meta": { "model": "levanto-sage-2" } }, "error": null }, { "ok": true, "result": { "id": "sku", "kind": "choice", "result": { "chosen": "BR2-LID", "probability": 0.9999, "probabilities": [ { "option": "CP3-STEAM", "probability": 0 }, { "option": "CP3-TRAY", "probability": 0 }, { "option": "CM-JUG", "probability": 0 }, { "option": "BR2-LID", "probability": 0.9999 }, { "option": "MM-SEAL", "probability": 0 }, { "option": "none", "probability": 0 } ] }, "meta": { "model": "levanto-sage-2" } }, "error": null } ] } ], "meta": { "model": "levanto-sage-2", "request_count": 1, "question_count": 2, "latency_ms": 144.4, "usage": { "billed_input_tokens": 263, "image_count": 0, "image_tokens": 0, "input_tokens": 263, "output_tokens": 0 } } }- Your code
Fill the cart, or hand it over
The application adds the part to the cart when the intent is
spare_partand the SKU clears your bar (0.9 here). If the order number is missing, the reply asks for it.none, a weak match and everynullgo to a person.Runs with the helper from Get the code.
BAR = 0.9 # your bar: test it on past messages intent = a.get("intent", {}).get("chosen") part = a.get("sku", {}) sku = part.get("chosen") if (part.get("probability") or 0) >= BAR else None if intent == "spare_part" and sku not in (None, "none"): if found["order_number"]: print(f"Order {found['order_number']}: add {sku} to the cart and reply") else: print(f"Add {sku} to the cart and ask for the order number") elif intent == "spare_part" and sku == "none": print("Hand it to a person: the part isn't in the catalog") else: print("Hand it to a person:", intent or "intent unclear")
Before you ship
- Confirm the part fits. A vague message, such as "my machine is broken", can still match a part with a high
probability. Check in code that the part fits the customer's product and the order exists before you send a payment link. - Set your own bar. Test it on a few hundred past messages. Fill the cart only above a
probabilityyou trust; a person checks the rest. - Build
optionsfrom your product database. A new part then needs no code change. - Fast and small. Each of the two calls answers in about 0.1 s and reads a few hundred tokens.