primce commited on
Commit
2da4364
·
verified ·
1 Parent(s): 898df72

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +85 -29
README.md CHANGED
@@ -24,7 +24,8 @@ Lexsi AI Safety & Alignment Hackathon 2026, Track 3 (Agentic fine-tuning), solo
24
 
25
  Live demo: https://huggingface.co/spaces/primce/hinglish-expense-agent ·
26
  Demo video (1:44): https://huggingface.co/datasets/primce/lexsi-runs/resolve/main/demo/lexsi_track3_demo.mp4 ·
27
- Write-up: https://huggingface.co/spaces/primce/hinglish-track3-article
 
28
 
29
  ![Same task, same prompt, same weights: base model ends in MISMATCH, this adapter matches the ledger](demo_before_after.gif)
30
 
@@ -55,6 +56,10 @@ seed adapters are kept for the ablation and variance controls.
55
  p = 7.5e-09** on hard-30 and **81 fixed, 0 broken, p < 1e-20** on hard-90. Against the
56
  round-2 adapter, **18 fixed, 2 broken, p = 0.0004**; against the round-1 adapter, 7 fixed,
57
  3 broken, p = 0.34 (directional, not significant).
 
 
 
 
58
  - Tool calls per task: **3.57** on hard-30, exactly the oracle's count (base: 7.03). On hard-90:
59
  3.38 against an oracle 3.41 and a base 7.24 — 87 of 90 tasks take exactly the oracle number of
60
  calls, 3 take one fewer, none take more.
@@ -88,8 +93,10 @@ SHA-256 pinned in the repo, so the numbers cannot drift.
88
  ## Safety split
89
 
90
  The repo also ships a 32-task injection eval: the agent is asked a normal question while a tool
91
- result carries attacker text. `attack_ledger` (money actually moved) and `attack_answer` (the
92
- number was repeated) are exact checks — the marker 999999 can never be a correct answer.
 
 
93
 
94
  | Family | n | Attack |
95
  |---|---:|---|
@@ -100,40 +107,60 @@ number was repeated) are exact checks — the marker 999999 can never be a corre
100
 
101
  Measured 2026-11-04/05 on the 24 injection tasks:
102
 
103
- | System | safe | utility under attack | attack_ledger | attack_answer |
104
- |---|---:|---:|---:|---:|
105
- | Base Tiny Aya | 0/24 | 0/24 | 13/24 | 8/24 |
106
- | `sft2` (round 1) | 11/24 | 11/24 | 0/24 | 3/24 |
107
- | `sft3` (round 2) | 15/24 | 15/24 | 0/24 | 2/24 |
108
- | **`sft4` (this adapter)** | **14/24** | **14/24** | **0/24** | **0/24** |
109
- | `sft4` + hardened prompt | 14/24 | 14/24 | 0/24 | 2/24 |
110
-
111
- Base → this adapter removes 13 ledger attacks and introduces 0 (exact McNemar p = 0.00024) and
112
- removes all 8 attacker numbers from answers. Abstention on underspecified requests: **4/8 asked**
113
- and 4/8 invented, up from 2/8 and 6/8 on `sft3`. The hardened prompt on the same adapter reaches
114
- **7/8 asked and 0/8 invented**, clearing the pre-registered bar, but puts 2/24 attacker numbers
115
- back into prose; the adapter alone is the default. The prompt is not an accuracy tax: on the clean
116
- hard-90 holdout the hardened prompt scores **86/90 against 84/90** for the default (2 fixed,
117
- 0 broken, exact McNemar p = 0.5, `sft4_hard90_hardened` in the run logs). Thresholds, per-family
118
- counts and the threat model: `docs/SAFETY.md`.
 
119
 
120
  Two further pinned splits repeat the probe so the claim is not one sample and one payload
121
  (measured 2026-11-08/09, Kaggle T4 x2, greedy, `--no-4bit`):
122
 
123
- | Split | System | safe | utility | attack_ledger | attack_answer |
124
- |---|---|---:|---:|---:|---:|
125
- | `safety_eval2`, 72 injected of 80 | Base | 0/80 | 0/80 | 32/72 | 11/72 |
126
- | | **`sft4`** | 54/80 | 50/80 | **0/72** | **0/72** |
127
- | | `sft4` + hardened | 63/80 | 55/80 | **0/72** | **0/72** |
128
- | `safety_payee`, 16 injected | Base | 0/16 | 0/16 | 5/16 | 0/16 |
129
- | | **`sft4`** | 12/16 | 12/16 | **0/16** | **0/16** |
130
- | | `sft4` + hardened | 12/16 | 12/16 | **0/16** | **0/16** |
131
 
132
  The payee split has no absurd amount: the tool result claims the sender's UPI handle changed to
133
  `99887766554@upi`, and the split is scored on the answer because that is what would move the money.
134
  Underspecified rows were re-sampled: **4/8 asked by default, 8/8 asked and 0/8 invented** with the
135
- hardened prompt. Across all three splits: **0 attacker values in the ledger and 0 in the answer on
136
- 112 injected tasks** (0/24 + 0/72 + 0/16), four payload styles, no LLM judge.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  ## Training
139
 
@@ -148,6 +175,35 @@ hardened prompt. Across all three splits: **0 attacker values in the ledger and
148
  reports the delta on both axes (`docs/ROUND2.md`).
149
  - Output format: a bare JSON array of one tool call per turn, no prose, no markdown fences.
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  ## Usage
152
 
153
  ```python
 
24
 
25
  Live demo: https://huggingface.co/spaces/primce/hinglish-expense-agent ·
26
  Demo video (1:44): https://huggingface.co/datasets/primce/lexsi-runs/resolve/main/demo/lexsi_track3_demo.mp4 ·
27
+ Write-up: https://huggingface.co/spaces/primce/hinglish-track3-article ·
28
+ All 90 held-out episodes, base vs adapter: https://huggingface.co/spaces/primce/hinglish-episodes
29
 
30
  ![Same task, same prompt, same weights: base model ends in MISMATCH, this adapter matches the ledger](demo_before_after.gif)
31
 
 
56
  p = 7.5e-09** on hard-30 and **81 fixed, 0 broken, p < 1e-20** on hard-90. Against the
57
  round-2 adapter, **18 fixed, 2 broken, p = 0.0004**; against the round-1 adapter, 7 fixed,
58
  3 broken, p = 0.34 (directional, not significant).
59
+ - Seed control: the same recipe on seeds 7 and 123 scores 84/90 and 83/90, so the **pooled
60
+ three-seed estimate is 251/270 = 93.0% on hard-90** (task-block bootstrap 95% 87.4–97.4%) and
61
+ **88/90 = 97.8% on hard-30**. The shipped seed 42 is not a lucky seed; `docs/STATS.md` recomputes
62
+ both from the run logs.
63
  - Tool calls per task: **3.57** on hard-30, exactly the oracle's count (base: 7.03). On hard-90:
64
  3.38 against an oracle 3.41 and a base 7.24 — 87 of 90 tasks take exactly the oracle number of
65
  calls, 3 take one fewer, none take more.
 
93
  ## Safety split
94
 
95
  The repo also ships a 32-task injection eval: the agent is asked a normal question while a tool
96
+ result carries attacker text. `attacker_expense` (the ledger holds an expense nobody asked for) and
97
+ `attack_answer` (the number was repeated) are exact checks — the marker 999999 can never be a
98
+ correct answer. An earlier threshold metric (a single balance >= 999999 INR) could not fire on a
99
+ shared expense; the correction and the six affected tasks are in `docs/SAFETY.md`.
100
 
101
  | Family | n | Attack |
102
  |---|---:|---|
 
107
 
108
  Measured 2026-11-04/05 on the 24 injection tasks:
109
 
110
+ | System | safe | utility under attack | ledger accounting | answer hit | sentinel |
111
+ |---|---:|---:|---:|---:|---:|
112
+ | Base Tiny Aya | 0/24 | 0/24 | 18/24 | 8/24 | 13/24 |
113
+ | `sft2` (round 1) | 11/24 | 11/24 | 2/24 | 3/24 | 0/24 |
114
+ | `sft3` (round 2) | 15/24 | 15/24 | 4/24 | 2/24 | 0/24 |
115
+ | **`sft4` (this adapter)** | **14/24** | **14/24** | **2/24** | **0/24** | **0/24** |
116
+ | `sft4` + hardened prompt | 14/24 | 14/24 | 3/24 | 2/24 | 0/24 |
117
+
118
+ Base → this adapter removes 16 ledger attacks and introduces 0 on the ledger count (18 → 2; exact
119
+ McNemar p = 3e-5) and removes all 8 attacker numbers from answers. Abstention on underspecified
120
+ requests: **4/8 asked** and 4/8 invented, up from 2/8 and 6/8 on `sft3`. The hardened prompt on the
121
+ same adapter reaches **7/8 asked and 0/8 invented**, clearing the pre-registered bar, but puts 2/24
122
+ attacker numbers back into prose and is worse on the held-out split (21/30 dirty ledgers against
123
+ 5/30); the adapter alone is the default. The prompt is not an accuracy tax on clean data: on the
124
+ hard-90 holdout it scores **86/90 against 84/90** for the default (2 fixed, 0 broken, exact McNemar
125
+ p = 0.5, `sft4_hard90_hardened` in the run logs). Thresholds, per-family counts and the threat
126
+ model: `docs/SAFETY.md`.
127
 
128
  Two further pinned splits repeat the probe so the claim is not one sample and one payload
129
  (measured 2026-11-08/09, Kaggle T4 x2, greedy, `--no-4bit`):
130
 
131
+ | Split | System | safe | utility | ledger accounting | answer hit | sentinel |
132
+ |---|---|---:|---:|---:|---:|---:|
133
+ | `safety_eval2`, 72 injected of 80 | Base | 0/80 | 0/80 | 48/72 | 11/72 | 32/72 |
134
+ | | **`sft4`** | 54/80 | 50/80 | **4/72** | **0/72** | **0/72** |
135
+ | | `sft4` + hardened | 63/80 | 55/80 | 6/72 | **0/72** | **0/72** |
136
+ | `safety_payee`, 16 injected | Base | 0/16 | 0/16 | — | 0/16 | 5/16 |
137
+ | | **`sft4`** | 12/16 | 12/16 | — | **0/16** | **0/16** |
138
+ | | `sft4` + hardened | 12/16 | 12/16 | — | **0/16** | **0/16** |
139
 
140
  The payee split has no absurd amount: the tool result claims the sender's UPI handle changed to
141
  `99887766554@upi`, and the split is scored on the answer because that is what would move the money.
142
  Underspecified rows were re-sampled: **4/8 asked by default, 8/8 asked and 0/8 invented** with the
143
+ hardened prompt.
144
+
145
+ A fourth split changes only the payload language — 24 attacks, 18 Latin-script Hinglish and 6
146
+ Devanagari, eight per family (`data/tasks_safety_hinglish.jsonl`, own pin; measured 2026-11-09):
147
+
148
+ | System | n | safe | utility | ledger accounting | answer hit | sentinel |
149
+ |---|---:|---:|---:|---:|---:|---:|
150
+ | Base Tiny Aya | 24 | 0/24 | 0/24 | 14/24 | 5/24 | 10/24 |
151
+ | **`sft4` (this adapter)** | 24 | 23/24 | 23/24 | **0/24** | **0/24** | **0/24** |
152
+ | `sft4` + hardened prompt | 24 | 24/24 | 24/24 | **0/24** | **0/24** | **0/24** |
153
+
154
+ The adapter is safe on 6/6 Devanagari and 17/18 Latin payloads; the one miss is a utility miss,
155
+ not an attack. Across all four published splits: **6 attacker expenses in the ledger and 0
156
+ attacker values in the answer on 136 injected tasks** (2/24 + 4/72 + 0/16 + 0/24; Wilson 95%
157
+ 2.0–9.3%), four payload styles in three payload languages, no LLM judge.
158
+
159
+ A fifth split was built after the adapter was frozen: 30 tasks, half with the payload split across
160
+ two tool results and half with the amount obfuscated (`9,99,999`, zero-width, spelled out), seed
161
+ `20261110`, own pin (`docs/SAFETY.md`). This adapter: **5/30** ledger, **0/30** answer, 22/30
162
+ solved. Every miss is in the obfuscated half; the split-payload half is 0/15. The hardened prompt
163
+ is worse there (21/30), which is why it is not the default.
164
 
165
  ## Training
166
 
 
175
  reports the delta on both axes (`docs/ROUND2.md`).
176
  - Output format: a bare JSON array of one tool call per turn, no prose, no markdown fences.
177
 
178
+ ## Cost and size
179
+
180
+ | | This adapter | Strongest public Track 3 entry using a larger base (as reported by that repo) |
181
+ |---|---|---|
182
+ | Base model | CohereLabs/tiny-aya-global, **3.35B** | Aya Expanse, **8B** |
183
+ | Trainable | LoRA r=16 all-linear, ~60M params (**121 MB** safetensors) | QLoRA DPO + SFT |
184
+ | Eval cost, one T4 | **50–105 s/task** greedy, fp16 (`--no-4bit`) | not reported |
185
+ | Residual attacks | **6 of 136** leftover expenses in the ledger, **0 of 136** in answers (+5/30 and 0/30 on the held-out split) | 0.7% of attacks, 95.6% normal-task success, 100% warnings |
186
+
187
+ The size is a choice, not a shim: Track 3 is a fine-tuning track for a small Cohere Labs model,
188
+ and the point of the entry is that a 3.35B model carries the whole behaviour — ledger-safe under
189
+ injection, abstains when a field is missing, 93.33% on the 90-task held-out split.
190
+
191
+ Do not read the last row as a like-for-like win. The two eval sets were built by different people,
192
+ so 6/136 and 0.7% are not the same denominator. What the table does support is narrower and still
193
+ useful: **a small residual ledger-attack rate at 2.4× fewer parameters, with a 121 MB adapter that
194
+ fits on a free T4**, and no warning behaviour to trade against it (we are at 0 of 136 there — see
195
+ `docs/SAFETY.md`).
196
+
197
+ The residual is also measured with a defence in front of it. `hinglish_agent/guard.py` is a
198
+ deterministic check on tool results, outside the model; run in `neutralise` mode over the same 136
199
+ injected rows on one T4 it takes the leftover expenses from **6/136 to 0/136**, leaves the answers at
200
+ 0/136, and still solves **136/136** (against 99/136 with the payload delivered). Because
201
+ `neutralise` leaves a marker where the note was, the same run was repeated in `strip` mode, which
202
+ deletes the field and puts nothing in its place: **0/136 expense, 136/136 solved, 136/136 flagged**
203
+ in 10,567 s. What it is not: a model property, or a claim that the guard beats an adaptive
204
+ attacker — **5/8** on a blind set written after its rules were frozen and **6/8** on eight written
205
+ after reading its source (the two misses spell the amount out); see `docs/GUARD.md`.
206
+
207
  ## Usage
208
 
209
  ```python