import string, torchfrom peft import PeftModelfrom transformers import AutoModelForCausalLM, AutoTokenizer BASE = "Qwen/Qwen3-4B"tokenizer = AutoTokenizer.from_pretrained(BASE)model = PeftModel.from_pretrained( AutoModelForCausalLM.from_pretrained(BASE, dtype=torch.bfloat16, device_map="auto"), "mogita/jev-decider-qwen3-4b").eval() CATEGORIES = ["🏥 Healthcare", "✈️ Travel", "🍽️ Restaurants", "🎓 Education", "🎟️ Entertainment", "🏦 Fees", "🐾 Pets", "💡 Utilities", "💵 Income", "🔄 Internal Transfer", "🔑 Rent, Loan", "🔔 Subscriptions", "🚗 Transportation", "🛍️ Shopping", "🛒 Groceries", "🛡️ Insurance", "🧴 Personal Care"] PROMPT = """Below is a bank transaction. {state} Which budget category does it belong to? {options} Reply with the single letter only.""" def decide(record, categories=CATEGORIES): options = "\n".join(f"{c}. {label}" for c, label in zip(string.ascii_uppercase, categories)) text = tokenizer.apply_chat_template( [{"role": "user", "content": PROMPT.format(state=record, options=options)}], tokenize=False, add_generation_prompt=True, enable_thinking=False) ids = tokenizer(text, return_tensors="pt").to(model.device) letters = [tokenizer.encode(c, add_special_tokens=False)[0] for c in string.ascii_uppercase[:len(categories)]] with torch.no_grad(): logits = model(**ids).logits[0, -1, letters].float() probs = torch.softmax(logits, dim=-1) return categories[probs.argmax()], probs.max().item() print(decide("Payee: Albert Heijn\nAmount: -37.82 EUR\nDate: 2026-03-12"))# ('🛒 Groceries', 0.996)