Skip to content

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
Try
  1. Read the order, product and problemExtract
    order_number
    NB-48213
    product
    Burrly 2
    problem
    hopper lid cracked
    732 ms
  2. Pick what the customer wantsChoice
    • spare_part99.98%
    • order_statusunder 0.01%
    • refundunder 0.01%
    • how_tounder 0.01%
  3. 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%
    144 ms
  4. 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

  1. Extract

    Read the order, product and problem

    Extract docs

    Send the message to /v1/extract and describe each field in a few words. The answers come back in values. A field the customer left out comes back as null. 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
      }
    }
  2. Choice

    Pick what the customer wants

    Choice docs

    Ask one Choice question with your four intents as options. Sage2 returns its pick in chosen, with a probability for every option. This question and the next one go in the same request.

  3. Choice

    Pick the part from the catalog

    Choice docs

    Ask a second Choice question in the same /decide/batch request. The options are your catalog. Each option is a SKU with its product name. A none option 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
        }
      }
    }
  4. Your code

    Fill the cart, or hand it over

    The application adds the part to the cart when the intent is spare_part and the SKU clears your bar (0.9 here). If the order number is missing, the reply asks for it. none, a weak match and every null go 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

  1. 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.
  2. Set your own bar. Test it on a few hundred past messages. Fill the cart only above a probability you trust; a person checks the rest.
  3. Build options from your product database. A new part then needs no code change.
  4. Fast and small. Each of the two calls answers in about 0.1 s and reads a few hundred tokens.

Sage / Levanto

Start free.

Sign in or create an account.

Continue with Google
or with email

We’ll email you a 6-digit code.