File size: 9,269 Bytes
580cc4b | 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 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | """Decision console for the trained model. Paste a state, get typed answers.
Not a chat. The model has no decoder and cannot generate text - for each
question it picks one of the labels YOU defined and reports how confident it
is. Ask it a question and it will not answer; it will fill in your form about
the sentence you typed.
python decide.py # agent schema by default
python decide.py --schema risk # start on another one
Inside the prompt:
:schemas list the built-in question sets
:use <name> switch schema
:show print the current questions
:q quit
Paste multi-line states and finish with a blank line. Anything that parses as
JSON is treated as JSON, everything else as plain text - the model was trained
on JSON states but the encoder does not care.
"""
from __future__ import annotations
import argparse
import json
import os
import time
os.environ.setdefault("TRANSFORMERS_VERBOSITY", "error")
os.environ.setdefault("HF_HUB_DISABLE_PROGRESS_BARS", "1")
from typed_schema import Question
SCHEMAS = {
"agent": {
"action": Question.choice(
"What should the observability system do with this trace?",
{"continue": "Let the agent proceed without interruption.",
"human_review": "Queue this trace for a human to review.",
"observe": "Keep running, but flag the trace for later sampling.",
"stop": "Halt the agent now."}),
"outcome": Question.choice(
"How did this agent run turn out?",
{"success": "The agent completed the task correctly.",
"partial": "The agent made progress but did not finish.",
"failure": "The agent did not accomplish the task.",
"harmful": "The agent caused damage or violated a constraint."}),
"needs_review": Question.noul(
"This trace requires human review.",
true_desc="A human should inspect this run.",
false_desc="No human attention is warranted."),
"risk": Question.score(
"How risky was the agent's behaviour in this trace?",
["Benign: read-only or clearly safe actions.",
"Low: routine writes within scope.",
"Moderate: irreversible or out-of-scope actions.",
"High: destructive, security-relevant or policy-violating."]),
},
"support": {
"category": Question.choice(
"What kind of support request is this?",
{"billing": "Charges, invoices, refunds or payment methods.",
"technical": "Something is broken or not behaving as documented.",
"account": "Access, login, permissions or profile changes.",
"other": "Anything that does not fit the categories above."}),
"action": Question.choice(
"What should happen to this ticket next?",
{"auto_reply": "A templated answer resolves it.",
"escalate": "Send it to a specialist queue.",
"refund": "Issue a refund or credit.",
"close": "No action needed, close it."}),
"needs_human": Question.noul(
"This ticket needs a human agent.",
true_desc="A person should handle this.",
false_desc="Automation can resolve it."),
"urgency": Question.score(
"How quickly does this need attention?",
["No time pressure.", "Routine, normal queue.",
"Elevated, within the week.", "Critical, same day."]),
},
"risk": {
"credential_compromise": Question.noul(
"The evidence indicates a credential or account has been compromised."),
"action": Question.choice(
"What should the security team do?",
{"monitor": "Keep watching, no action yet.",
"investigate": "Open an investigation.",
"contain": "Isolate the affected system or account now.",
"dismiss": "This is benign, close it."}),
"severity": Question.score(
"How severe is this incident?",
["Informational.", "Low, routine noise.",
"Moderate, needs a response.", "High, active compromise."]),
},
}
EXAMPLE = {
"agent": {"agent": {"autonomy": "checkpointed", "model": "internal-agent-v4"},
"constraints": ["Do not touch customer data outside named accounts"],
"task": "Delete personal data for accounts in the erasure queue.",
"trace_summary": {"duration_s": 128.5, "steps": 7, "tool_errors": 1,
"irreversible_actions": 2, "constraint_violations": 1}},
"support": {"message": "i was charged twice this month and the second charge "
"is still pending. can you refund it today please",
"customer": {"plan": "pro", "tenure_months": 14}},
"risk": {"alert": "impossible travel: login from two countries 8 minutes apart",
"user": {"role": "admin", "mfa": False},
"signals": {"failed_logins": 0, "new_device": True}},
}
def render(out, elapsed_ms):
width = max(len(k) for k in out)
for name, r in out.items():
bar = "#" * int(round(r["confidence"] * 24))
print(f" {name:<{width}} {r['label']:<16} {r['confidence']:.3f} {bar}")
others = {k: v for k, v in r["probabilities"].items() if k != r["label"]}
rest = " ".join(f"{k} {v:.2f}" for k, v in
sorted(others.items(), key=lambda kv: -kv[1])[:3])
if rest:
print(f" {'':<{width}} {rest}")
print(f"\n {elapsed_ms:.0f} ms, one forward pass, $0\n")
def main():
p = argparse.ArgumentParser()
p.add_argument("--ckpt", default="jevlite.pt")
p.add_argument("--schema", default="agent", choices=list(SCHEMAS))
p.add_argument("--backend", default="torch", choices=["torch", "coreml"])
p.add_argument("--compute-units", default="cpu_and_ne",
choices=["cpu_only", "cpu_and_ne", "cpu_and_gpu", "all"],
help="coreml backend only; cpu_and_ne keeps the GPU off")
p.add_argument("--coreml-mode", default="worker",
choices=["worker", "inprocess"],
help="worker isolates ANE in a subprocess (default); "
"inprocess is diagnostic only")
p.add_argument("--pkg-dir", default="artifacts/coreml")
a = p.parse_args()
# 05_serve.py starts with a digit so it cannot be imported by name
import importlib.util
spec = importlib.util.spec_from_file_location("serve", "05_serve.py")
S = importlib.util.module_from_spec(spec)
spec.loader.exec_module(S)
print("loading the model...", flush=True)
e = S.load(a.ckpt, backend=a.backend, compute_units=a.compute_units,
pkg_dir=a.pkg_dir, coreml_mode=a.coreml_mode)
name = a.schema
print(f"ready on {e['dev']} schema: {name} "
f"temperatures {[round(t, 3) for t in e['temps']]}")
print("type or paste a state, press enter. plain english works too.")
print(" :ex run the built-in example")
print(" :use <name> switch schema (" + " ".join(SCHEMAS) + ")")
print(" :schemas :show :q\n")
def send(raw):
try:
state = json.loads(raw)
except json.JSONDecodeError:
state = raw
t0 = time.perf_counter()
out = S.decide(state, SCHEMAS[name])
render(out, (time.perf_counter() - t0) * 1000)
def unbalanced(t):
"""True while a JSON-looking paste is still missing closers."""
if t.lstrip()[:1] not in "{[":
return False
q = t.count('"') % 2
return q or t.count("{") > t.count("}") or t.count("[") > t.count("]")
while True:
try:
line = input("> ").strip()
except (EOFError, KeyboardInterrupt):
print()
return
if not line:
continue
if line in (":q", ":quit", ":exit"):
return
if line == ":schemas":
print(" " + " ".join(SCHEMAS))
continue
if line == ":show":
print(json.dumps(SCHEMAS[name], indent=1))
continue
if line in (":ex", ":example"):
print(f" {json.dumps(EXAMPLE[name])}")
send(json.dumps(EXAMPLE[name]))
continue
if line.startswith(":use"):
want = line.split(None, 1)[1].strip() if " " in line else ""
if want in SCHEMAS:
name = want
print(f" schema: {name} questions: "
f"{', '.join(SCHEMAS[name])}")
else:
print(f" no such schema. have: {', '.join(SCHEMAS)}")
continue
# Enter sends. Only a half-finished JSON paste keeps reading, so a
# pasted one-liner works and prose works, which is what people expect.
while unbalanced(line):
try:
more = input("… ")
except (EOFError, KeyboardInterrupt):
break
if not more.strip():
break
line += "\n" + more
send(line)
if __name__ == "__main__":
main()
|