File size: 4,974 Bytes
cd02163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Ask Decider 2B questions from the terminal. Runs on a GPU, or on a CPU (slower).

  # A whole Jev/SystemOne request from a file (or - for stdin)
  decider --request request.json

  # Quick questions about a text or JSON file
  decider --state ticket.txt \
      --choice "team=Which team should handle this?:billing|Payments, charges and refunds,technical|App bugs and outages,account|Login and profile" \
      --yes-no "urgent=Is this urgent?" \
      --score "upset=How upset is the customer?:calm,frustrated,angry"

  # Many states at once (one per line, or a JSON list) -> one JSON answer per line
  decider --states tickets.txt --yes-no "urgent=Is this urgent?"

Describe choice options as key|description: the model is much more accurate when options say what
they mean (6-ticket routing check: 3/6 with bare keys, 5/6 with descriptions).

Options: --model (repo id or folder, default Jeetu456/decider-2b), --device (cuda, mps or cpu; default:
cuda if available), --full (print the whole response, not just the picks).
"""
import argparse
import json
import os
import sys


def read_state(path):
    text = sys.stdin.read() if path == "-" else open(path, encoding="utf-8").read()
    try:
        return json.loads(text)
    except ValueError:
        return text.strip()


def parse_questions(args):
    questions = {}
    for spec in args.choice or []:
        qid, rest = spec.split("=", 1)
        instructions, options = rest.rsplit(":", 1)
        criteria = {}
        for option in (o.strip() for o in options.split(",")):
            if option:
                key, _, description = option.partition("|")
                criteria[key.strip()] = description.strip() or None
        questions[qid] = {"type": "choice", "instructions": instructions, "criteria": criteria}
    for spec in args.yes_no or []:
        qid, instructions = spec.split("=", 1)
        questions[qid] = {"type": "noul", "instructions": instructions}
    for spec in args.score or []:
        qid, rest = spec.split("=", 1)
        instructions, levels = rest.rsplit(":", 1)
        questions[qid] = {"type": "score", "instructions": instructions,
                          "criteria": [level.strip() for level in levels.split(",") if level.strip()]}
    return questions


def picks(response):
    out = {}
    for qid, answer in response["answers"].items():
        if answer["type"] == "choice":
            out[qid] = {"choice": answer["choice"], "confidence": round(answer["confidence"], 3)}
        elif answer["type"] == "score":
            out[qid] = {"score": round(answer["score"], 2), "confidence": round(answer["confidence"], 3)}
        else:
            out[qid] = {"yes": answer["noul"] >= 0.5, "p_yes": round(answer["noul"], 3)}
    return out


def main():
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--model", default=os.environ.get("DECIDER_MODEL", "Jeetu456/decider-2b"))
    parser.add_argument("--device", default=None)
    parser.add_argument("--request", help="a Jev/SystemOne request JSON file, or - for stdin")
    parser.add_argument("--state", help="a text or JSON file, or - for stdin")
    parser.add_argument("--states", help="many states: one per line, or a JSON list")
    parser.add_argument("--choice", action="append", help='"id=question:key|description,key|description,..."')
    parser.add_argument("--yes-no", action="append", help='"id=question"')
    parser.add_argument("--score", action="append", help='"id=question:lowest,...,highest"')
    parser.add_argument("--full", action="store_true", help="print the full Jev response")
    args = parser.parse_args()

    import torch
    path = args.model
    if not os.path.isdir(path):
        from huggingface_hub import snapshot_download
        path = snapshot_download(path)
    from decider._core import load_release_model, systemone

    device = args.device or ("cuda" if torch.cuda.is_available() else "cpu")
    model = load_release_model(path, device=device)
    show = (lambda r: r) if args.full else picks

    if args.request:
        print(json.dumps(show(systemone(model, read_state(args.request))), indent=2))
        return
    questions = parse_questions(args)
    if not questions:
        parser.error("give --request, or at least one --choice, --yes-no or --score")
    if args.states:
        text = open(args.states, encoding="utf-8").read()
        try:
            states = json.loads(text)
        except ValueError:
            states = [line for line in text.splitlines() if line.strip()]
        for state in states:
            print(json.dumps(show(systemone(model, {"state": state, "questions": questions}))))
        return
    if not args.state:
        parser.error("give --state (or --states, or --request)")
    print(json.dumps(show(systemone(model, {"state": read_state(args.state), "questions": questions})), indent=2))


if __name__ == "__main__":
    main()