| import json |
| import pandas as pd |
| import gradio as gr |
| import torch |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
|
|
| |
| MODEL_ID = "Qwen/Qwen2.5-1.5B-Instruct" |
|
|
| ORDER_COLUMNS = [ |
| "customer", |
| "item", |
| "quantity", |
| "flavor", |
| "pickup_time", |
| "delivery_address", |
| "payment_status", |
| "notes", |
| "missing_fields", |
| ] |
|
|
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) |
| model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=torch.float32) |
| model.eval() |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
|
|
| SYSTEM_PROMPT = """ |
| You are a careful order extraction engine for tiny sellers. |
| |
| Extract customer orders from messy DMs. Return only valid JSON with this exact shape: |
| { |
| "orders": [ |
| { |
| "customer": "", |
| "item": "", |
| "quantity": "", |
| "flavor": "", |
| "pickup_time": "", |
| "delivery_address": "", |
| "payment_status": "", |
| "notes": "", |
| "missing_fields": [] |
| } |
| ], |
| "prep_list": [], |
| "reply_drafts": [ |
| {"customer": "", "reply": ""} |
| ] |
| } |
| |
| Critical rules: |
| - Do not invent customer names. |
| - Do not change customer names. |
| - Do not merge different customers. |
| - Include every customer message that looks like an order or possible order. |
| - Use only facts explicitly present in the messages. |
| - If a value is unknown, use an empty string. |
| - Do not add order_id or total_cost unless the message mentions them. |
| - For pickup orders, put the pickup time in pickup_time and leave delivery_address empty unless a real address is given. |
| - If the customer is unsure, still include the order and put uncertainty in notes. |
| - missing_fields should only include practical fields the seller needs to ask for, such as quantity, flavor, pickup_time, delivery_address, or payment_status. |
| - Reply drafts should be short, friendly, and ask only for missing information. |
| """ |
|
|
| def extract_json(text): |
| start = text.find("{") |
| end = text.rfind("}") |
| if start == -1 or end == -1: |
| raise ValueError("No JSON object found") |
| return json.loads(text[start:end + 1]) |
|
|
| def normalize_orders(data): |
| rows = [] |
| for order in data.get("orders", []): |
| row = {} |
| for col in ORDER_COLUMNS: |
| value = order.get(col, "") |
| if isinstance(value, list): |
| value = ", ".join(str(v) for v in value) |
| row[col] = value |
| rows.append(row) |
| return pd.DataFrame(rows, columns=ORDER_COLUMNS) |
|
|
| def format_list(title, items): |
| if not items: |
| return f"### {title}\nNothing found." |
| lines = [] |
| for item in items: |
| if isinstance(item, dict): |
| lines.append("- " + json.dumps(item, ensure_ascii=False)) |
| else: |
| lines.append(f"- {item}") |
| return f"### {title}\n" + "\n".join(lines) |
|
|
| def format_replies(replies): |
| if not replies: |
| return "### Reply drafts\nNothing found." |
| lines = [] |
| for reply in replies: |
| customer = reply.get("customer", "Customer") |
| text = reply.get("reply", "") |
| lines.append(f"**{customer}**\n\n{text}") |
| return "### Reply drafts\n\n" + "\n\n---\n\n".join(lines) |
|
|
| def analyze_messages(messages): |
| if not messages.strip(): |
| return pd.DataFrame(columns=ORDER_COLUMNS), "Paste some DMs first.", "", "" |
|
|
| prompt = tokenizer.apply_chat_template( |
| [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": messages}, |
| ], |
| tokenize=False, |
| add_generation_prompt=True, |
| ) |
|
|
| inputs = tokenizer(prompt, return_tensors="pt") |
| with torch.no_grad(): |
| output = model.generate( |
| **inputs, |
| max_new_tokens=900, |
| do_sample=False, |
| pad_token_id=tokenizer.eos_token_id, |
| ) |
|
|
| generated = tokenizer.decode( |
| output[0][inputs["input_ids"].shape[1]:], |
| skip_special_tokens=True, |
| ) |
|
|
| try: |
| data = extract_json(generated) |
| except Exception as exc: |
| return ( |
| pd.DataFrame(columns=ORDER_COLUMNS), |
| f"### Needs review\nThe model did not return valid JSON: {exc}", |
| "", |
| generated, |
| ) |
|
|
| orders_df = normalize_orders(data) |
| prep = format_list("Prep list", data.get("prep_list", [])) |
| replies = format_replies(data.get("reply_drafts", [])) |
| raw = json.dumps(data, indent=2, ensure_ascii=False) |
| return orders_df, prep, replies, raw |
|
|
| with gr.Blocks(title="DM Order Desk") as demo: |
| gr.Markdown("# DM Order Desk") |
| gr.Markdown("Turn messy customer DMs into clean orders, prep lists, and reply drafts using a small model.") |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| messages = gr.Textbox( |
| label="Messy customer DMs", |
| value=EXAMPLE_INPUT, |
| lines=14, |
| ) |
| run = gr.Button("Organize orders", variant="primary") |
|
|
| with gr.Column(scale=2): |
| orders = gr.Dataframe(label="Order sheet", headers=ORDER_COLUMNS) |
| prep = gr.Markdown(label="Prep list") |
| replies = gr.Markdown(label="Reply drafts") |
| raw = gr.Code(label="Raw JSON", language="json") |
|
|
| run.click(analyze_messages, inputs=messages, outputs=[orders, prep, replies, raw]) |
|
|
| demo.launch() |