bogdanraduta commited on
Commit
2f4ca07
Β·
verified Β·
1 Parent(s): 97d7a0c

Add inference contract: INFERENCE.md (scamguard_sys_v1)

Browse files
Files changed (1) hide show
  1. inference_contract/INFERENCE.md +193 -0
inference_contract/INFERENCE.md ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # scam-guard β€” Inference Contract (`scamguard_sys_v1`)
2
+
3
+ **For the iOS / ScamGuardMLX engine.** This is the frozen inference contract for the
4
+ published `scam-guard-qwen06b` (and `-qwen17b`) weights. It is the exact spec the
5
+ model was trained and evaluated against β€” the thing the M0 spike correctly
6
+ identified as missing. Wire the engine to *this* and the enum typos and the
7
+ wrong-verdict-on-the-reference-message go away.
8
+
9
+ Three files in this folder:
10
+ - `INFERENCE.md` β€” this document
11
+ - `prompt_scamguard_sys_v1.txt` β€” the system prompt, verbatim (source of truth)
12
+ - `schema_scamguard_v1.json` β€” the output JSON Schema, for constrained decoding
13
+
14
+ Great M0 work β€” feasibility (0.5–2 s) matches the card, and your schema correction
15
+ is **correct**: the contract is `tactics[] = {tactic, evidence, explanation}`.
16
+
17
+ ---
18
+
19
+ ## TL;DR β€” what fixes the blocker
20
+
21
+ The model is stochastic over an *exact* training-time contract. Three things must
22
+ match it byte-for-byte; miss any one and you get enum typos / wrong verdicts:
23
+
24
+ 1. **System prompt** = the frozen `scamguard_sys_v1` string below. Never edit it.
25
+ 2. **User turn** = `[channel: <tag>]\n<message text>` where `<tag>` ∈ `sms | email | chat`.
26
+ 3. **Constrained JSON decoding** against `schema_scamguard_v1.json`. This is what
27
+ kills the enum typos β€” the enums are decoded as a closed set, not free text.
28
+
29
+ Then a **post-decode evidence check** (verbatim-substring) is part of the contract,
30
+ not optional.
31
+
32
+ ---
33
+
34
+ ## 1. System prompt β€” `scamguard_sys_v1` (verbatim, do not edit)
35
+
36
+ Load `prompt_scamguard_sys_v1.txt` as the `system` turn exactly. Bump the version
37
+ id and re-coordinate with the model team if a single character changes β€” the model
38
+ is trained against this string.
39
+
40
+ ```
41
+ You are an on-device scam and fraud detector. You read ONE message (SMS, email,
42
+ or chat text, possibly containing visible URLs) and judge whether it is a scam.
43
+ You never fetch URLs or use any network; you reason ONLY over the visible text.
44
+
45
+ Return a single JSON object with these fields:
46
+
47
+ 1. verdict β€” exactly one of three levels (never a probability or percentage):
48
+ - scam_likely: clear scam mechanism present (a request for money, credentials,
49
+ card data, OTP relay, a fee to release a parcel/prize, payment redirection,
50
+ remote access, etc.) driven by manipulation tactics. Reserve this level for
51
+ messages that would cause real harm if acted on.
52
+ - suspicious: manipulation signals are present but the message could plausibly
53
+ be legitimate, OR the only indicators are weak (urgency alone, an unfamiliar
54
+ link alone, an authority claim with no money/data ask). The honest middle:
55
+ "verify through your own channel before acting".
56
+ - no_indicators: no scam mechanism; a genuine-looking OTP, transaction alert,
57
+ courier notice, receipt, promo, or ordinary message. Legitimate messages can
58
+ be urgent and can contain links β€” do NOT flag them for that alone.
59
+
60
+ 2. tactics β€” a list of the manipulation tactics you detected, each an object with:
61
+ - tactic: one of the fixed tactic ids below,
62
+ - evidence: a VERBATIM substring copied character-for-character from the input
63
+ message (it will be checked by exact substring match; any tactic whose
64
+ evidence is not found verbatim in the message is dropped and counted as
65
+ fabricated, so never paraphrase, truncate mid-word, or invent evidence),
66
+ - explanation: one calm, plain sentence a non-technical person (including an
67
+ elderly person) can understand β€” describe the manipulation pattern, never
68
+ instructions for constructing it, and never panic language.
69
+ When verdict is no_indicators, tactics is an empty list.
70
+
71
+ Fixed tactic ids (use these exact strings, nothing else):
72
+ - urgency_pressure
73
+ - authority_impersonation
74
+ - payment_redirect
75
+ - credential_phishing
76
+ - courier_customs_fee
77
+ - prize_lottery
78
+ - investment_too_good
79
+ - romance_advance_fee
80
+ - family_emergency_impersonation
81
+ - tech_support
82
+ - link_obfuscation
83
+ - refund_overpayment
84
+ - subscription_trap
85
+
86
+ 3. explanation β€” one or two calm, actionable sentences summarizing the verdict
87
+ for a frightened non-technical reader. No alarmist tone; state what is going on
88
+ and, implicitly, that they can verify safely.
89
+
90
+ 4. recommended_action β€” one safe action id from the fixed schema enum. Prefer the
91
+ action that routes the reader to THEIR OWN trusted channel (their bank's
92
+ official number, the courier's own app) rather than any contact in the message.
93
+ Use no_action_needed only for no_indicators.
94
+
95
+ Rules:
96
+ - Evidence must be a verbatim substring of the message. This is non-negotiable.
97
+ - A tactic label means "this manipulation pattern is present", not "this is a
98
+ scam" β€” the verdict is a separate judgment. Multiple tactics per message are
99
+ expected.
100
+ - Urgency alone, a shortener/unfamiliar link alone, or an authority claim with no
101
+ money/data ask should rarely exceed suspicious.
102
+ ```
103
+
104
+ ## 2. User-turn format (exact)
105
+
106
+ ```
107
+ [channel: sms]
108
+ Contul tău a fost blocat. Confirmă datele aici: http://example-bank.invalid/verify
109
+ ```
110
+
111
+ - The prefix is literally `[channel: ` + tag + `]` then a newline then the raw
112
+ message text. Tag is one of `sms`, `email`, `chat` (lowercase).
113
+ - Apply the model's chat template around system+user as usual (mlx-swift
114
+ `apply_chat_template` with `add_generation_prompt=true`). No few-shot, no extra
115
+ preamble β€” the system prompt is the whole instruction.
116
+
117
+ ## 3. Constrained JSON decoding (the enum-typo fix)
118
+
119
+ The reference implementation decodes with the output constrained to
120
+ `schema_scamguard_v1.json` (a JSON-schema grammar). Do the same on device β€” this is
121
+ non-negotiable for a shippable engine:
122
+
123
+ - Minimum bar: constrain the three enum fields to their closed sets β€” `verdict`,
124
+ each `tactics[].tactic`, and `recommended_action`. That alone removes the enum
125
+ typos you saw.
126
+ - Better: constrain the whole object shape (a GBNF grammar compiled from the JSON
127
+ schema; llama.cpp/`mlx`-side grammar or a logit mask). `additionalProperties` is
128
+ false β€” reject unknown keys.
129
+ - Free-text fields (`evidence`, `explanation`) stay unconstrained strings.
130
+
131
+ If mlx-swift lacks grammar decoding today: implement a logit mask over the enum
132
+ tokens at minimum, and keep your strict validator as the backstop (re-sample on
133
+ invalid). But plan for real grammar-constrained decode β€” the validity gate depends
134
+ on it.
135
+
136
+ ## 4. Output contract (schema summary)
137
+
138
+ Machine-readable: `schema_scamguard_v1.json`. Shape:
139
+
140
+ ```json
141
+ {
142
+ "verdict": "scam_likely | suspicious | no_indicators",
143
+ "tactics": [
144
+ { "tactic": "<one of the 13 ids>", "evidence": "<verbatim substring>", "explanation": "<plain sentence>" }
145
+ ],
146
+ "explanation": "<overall plain-language explanation>",
147
+ "recommended_action": "<one of the 10 safe-action ids>"
148
+ }
149
+ ```
150
+
151
+ - `verdict` (required, enum, 3 values above)
152
+ - `tactics` (required list; **empty** when `verdict == no_indicators`). Each item is
153
+ `{tactic, evidence, explanation}` β€” all three required, `evidence`/`explanation`
154
+ min length 1.
155
+ - `explanation` (required, non-empty)
156
+ - `recommended_action` (required, enum, 10 values):
157
+ `call_bank_official_number`, `do_not_click_link`, `verify_via_official_app_or_site`,
158
+ `call_family_member_known_number`, `do_not_share_codes_or_credentials`,
159
+ `do_not_send_money`, `ignore_and_delete`, `report_to_authorities`,
160
+ `check_sender_address`, `no_action_needed`.
161
+ - `additionalProperties: false` at every level.
162
+
163
+ The 13 tactic ids are the exact strings in the prompt above (taxonomy v1).
164
+
165
+ ## 5. Post-decode evidence kill-switch (part of the contract)
166
+
167
+ After decode, for each detected tactic, verify `evidence` is a **verbatim
168
+ substring** of the input message. If it is not found character-for-character, DROP
169
+ that tactic (do not display it) and count it as fabricated. This mirrors the
170
+ training/eval verifier (`scamguard.verify`); the model is trained expecting it.
171
+ Keep your strict validator β€” just add this substring check to it.
172
+
173
+ ## 6. The reference Romanian message / int4 note
174
+
175
+ With the exact prompt (Β§1) + user format (Β§2) + constrained decode (Β§3), the model
176
+ card's reference RO message should classify correctly. If it still misfires **after
177
+ all three are matched**, it's most likely quantization sensitivity β€” try the **int8**
178
+ weights instead of int4 for that case and tell us; we'll co-debug (it may be a known
179
+ int4 edge we document, not an integration bug). Don't treat a single reference-case
180
+ miss as a contract failure until Β§1–§3 are byte-exact.
181
+
182
+ ## 7. Versioning
183
+
184
+ `PROMPT_VERSION = scamguard_sys_v1`. The prompt + schema are frozen together and the
185
+ weights are trained against them. Any change = a new version id + a retrain; never
186
+ silently edit the prompt on the client. Pin the version string in the app and log it
187
+ with each verdict so a future model swap is traceable.
188
+
189
+ ---
190
+
191
+ *Delivered by the scam-guard model team to unblock the M0 spike. Questions on the
192
+ prompt/schema/decode β†’ model team; this contract is the source of truth over any
193
+ value inferred from the PRD.*