Daisuke675 commited on
Commit
16e1120
·
verified ·
1 Parent(s): 62ba200

Upload 76_generate_scugnizz_repair_dataset_v2.py with huggingface_hub

Browse files
76_generate_scugnizz_repair_dataset_v2.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # /// script
4
+ # dependencies = ["huggingface_hub"]
5
+ # ///
6
+
7
+ """
8
+ 76_generate_scugnizz_repair_dataset.py
9
+
10
+ Dataset sintetico mirato per correggere i problemi emersi nel benchmark:
11
+
12
+ 1) TOOL_RESULT -> risposta naturale senza inventare dati
13
+ 2) Copia esatta di hash, IP, IPv6, porte, domini, URL, numeri
14
+ 3) TOOL_CALL con nome tool e argomenti corretti
15
+
16
+ Default:
17
+ - 30.000 renderer
18
+ - 10.000 exact-copy tecnico
19
+ - 10.000 tool-calling
20
+ Totale train circa 50.000 + validation.
21
+
22
+ Uso:
23
+ uv run 76_generate_scugnizz_repair_dataset.py --upload --repo-id ProjectScugnizz/scugnizz-agentic-repair-50k
24
+
25
+ HF Job:
26
+ hf jobs uv run --namespace ProjectScugnizz --flavor h200 --timeout 1h 76_generate_scugnizz_repair_dataset.py --upload --repo-id ProjectScugnizz/scugnizz-agentic-repair-50k
27
+ """
28
+
29
+ import argparse
30
+ import hashlib
31
+ import json
32
+ import os
33
+ import random
34
+ from pathlib import Path
35
+
36
+ from huggingface_hub import HfApi, upload_folder
37
+
38
+
39
+ CITIES = ["Napoli","Trento","Udine","Genova","Bologna","Roma","Milano","Verona","Palermo","Cagliari","Aosta","Ancona","Lecce","Bari","Trieste","Pordenone"]
40
+ CONDITIONS = ["sereno","pioggia","neve","vento","nebbia","caldo","nuvoloso","temporale"]
41
+ STOCKS = ["AAPL","NVDA","TSLA","VWCE","MSFT","AMD","META","GOOGL","AMZN","SPY","ENI","ISP"]
42
+ CURRENCIES = {"VWCE":"EUR","ENI":"EUR","ISP":"EUR"}
43
+ ARTISTS = ["Metallica","Muse","Coldplay","Queen","Daft Punk","Radiohead","Battiato","Subsonica"]
44
+ SONGS = ["One","Uprising","Yellow","Bohemian Rhapsody","One More Time","No Surprises","La cura","Nuvole rapide"]
45
+ ALBUMS = ["...And Justice for All","The Resistance","Parachutes","A Night at the Opera","Discovery","OK Computer","L'imboscata","Microchip emozionale"]
46
+ SENDERS = ["Marco","Anna","Giulia","Davide","Carla","Sergio","Luca","Elena"]
47
+ EVENTS = ["Riunione","Audit","Backup","Manutenzione","Briefing","Formazione","Sopralluogo","Dentista"]
48
+ DATES = ["oggi","domani","lunedì","martedì","mercoledì","giovedì","venerdì"]
49
+ TIMES = ["08:30","09:00","09:30","10:15","14:00","15:45","16:30","18:00"]
50
+ DOMAINS = ["redhotcyber.com","example.org","openai.com","scugnizz.ai","trenitaly.help","agenzia-login.info"]
51
+ URLS = ["https://example.org/login","https://redhotcyber.com/article","https://trenitaly.help/pay","http://test.local/a.zip"]
52
+ IPV4S = ["010.000.001.255","192.168.001.010","104.21.10.12","008.008.008.008","001.001.001.001","043.157.095.055"]
53
+ IPV6S = [
54
+ "2001:0db8:0000:0000:0000:ff00:0042:8329",
55
+ "2001:db8:0000:0000:0000:ff00:0042:8329",
56
+ "fe80:0000:0000:0000:0202:b3ff:fe1e:8329",
57
+ ]
58
+ HASHES_SHA256 = [
59
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
60
+ "a3f5c9019b2d44e7aa00ff11cc22dd33ee44ff556677889900aabbccddeeff00",
61
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
62
+ "0000000000000000000000000000000000000000000000000000000000000000",
63
+ ]
64
+ HASHES_MD5 = [
65
+ "00112233445566778899aabbccddeeff",
66
+ "44d88612fea8a8f36de82e1278abb02f",
67
+ "ffffffffffffffffffffffffffffffff",
68
+ "00000000000000000000000000000000",
69
+ ]
70
+
71
+
72
+ def jd(x):
73
+ return json.dumps(x, ensure_ascii=False, separators=(",", ":"))
74
+
75
+
76
+ def row(messages, category):
77
+ return {
78
+ "messages": messages,
79
+ "source": "scugnizz-agentic-repair-v1",
80
+ "category": category,
81
+ }
82
+
83
+
84
+ def one(system, user, assistant, category):
85
+ return row([
86
+ {"role": "system", "content": system},
87
+ {"role": "user", "content": user},
88
+ {"role": "assistant", "content": assistant},
89
+ ], category)
90
+
91
+
92
+ def sys_renderer():
93
+ return (
94
+ "Se ricevi TOOL_RESULT o PAYLOAD, NON chiamare strumenti.\n"
95
+ "Rispondi solo usando i dati presenti.\n"
96
+ "Non inventare email, eventi, dispositivi, nomi, tool o valori.\n"
97
+ "Copia numeri, hash, IP, porte, domini e percentuali esattamente."
98
+ )
99
+
100
+
101
+ def sys_tool():
102
+ return (
103
+ "You can call tools when needed.\n"
104
+ "Use only the available tool names and copy arguments exactly.\n"
105
+ "If a tool is required, answer only with TOOL_CALL JSON."
106
+ )
107
+
108
+
109
+ def system_with_tools(lines):
110
+ return "\n".join([
111
+ "You can call tools when needed.",
112
+ "Use only the available tool names and copy arguments exactly.",
113
+ "If the user request requires a tool, answer only with TOOL_CALL.",
114
+ "",
115
+ "Available tools:",
116
+ *lines
117
+ ])
118
+
119
+
120
+ def tool_line(name, required):
121
+ return f"- {name} | required: {', '.join(required)}" if required else f"- {name}"
122
+
123
+
124
+ def call(name, args):
125
+ return "TOOL_CALL:\n" + jd({"name": name, "arguments": args})
126
+
127
+
128
+ def renderer_weather(r):
129
+ city = r.choice(CITIES)
130
+ cond = r.choice(CONDITIONS)
131
+ temp = r.randint(-5, 39)
132
+ wind = r.randint(0, 80)
133
+ payload = {"tool":"weather.forecast","result":{"city":city,"condition":cond,"temperature_c":temp,"wind_kmh":wind,"uuid":"ignore"}}
134
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nRispondi in italiano usando solo città, meteo, temperatura e vento. Ignora uuid. Non aggiungere email o calendario."
135
+ assistant = f"A {city} il meteo è {cond}, con {temp} °C e vento a {wind} km/h."
136
+ return one(sys_renderer(), user, assistant, "renderer_weather")
137
+
138
+
139
+ def renderer_finance(r):
140
+ sym = r.choice(STOCKS)
141
+ price = f"{r.randint(10,999)}.{r.randint(0,99):02d}"
142
+ cur = CURRENCIES.get(sym, "USD")
143
+ change = f"{r.uniform(-8,8):.2f}"
144
+ payload = {"tool":"finance.quote","result":{"symbol":sym,"price":price,"currency":cur,"change_percent":change}}
145
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nRispondi in italiano. Non chiamare tool. Copia ESATTAMENTE prezzo, valuta e percentuale."
146
+ assistant = f"{sym} quota {price} {cur}, con variazione del {change}%."
147
+ return one(sys_renderer(), user, assistant, "renderer_finance")
148
+
149
+
150
+ def renderer_spotify(r):
151
+ i = r.randrange(len(ARTISTS))
152
+ payload = {"tool":"spotify.current_song","result":{"artist":ARTISTS[i],"title":SONGS[i],"album":ALBUMS[i]}}
153
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nTrasforma il risultato in italiano usando solo i valori presenti."
154
+ assistant = f"Stai ascoltando {SONGS[i]} di {ARTISTS[i]}, dall'album {ALBUMS[i]}."
155
+ return one(sys_renderer(), user, assistant, "renderer_spotify")
156
+
157
+
158
+ def renderer_mail(r):
159
+ unread = r.randint(0, 99)
160
+ important = r.randint(0, min(15, unread))
161
+ sender = r.choice(SENDERS)
162
+ payload = {"tool":"mail.summary","result":{"unread":unread,"important":important,"latest_sender":sender}}
163
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nUsa solo questi dati. Non inventare altro."
164
+ assistant = f"Hai {unread} email non lette, di cui {important} importanti. L'ultima è di {sender}."
165
+ return one(sys_renderer(), user, assistant, "renderer_mail")
166
+
167
+
168
+ def renderer_calendar(r):
169
+ title = r.choice(EVENTS); date = r.choice(DATES); time = r.choice(TIMES)
170
+ payload = {"tool":"calendar.next_event","result":{"title":title,"date":date,"time":time}}
171
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nRispondi in italiano usando solo questi dati."
172
+ assistant = f"Il prossimo evento è {title}, {date} alle {time}."
173
+ return one(sys_renderer(), user, assistant, "renderer_calendar")
174
+
175
+
176
+ def renderer_dns(r):
177
+ domain = r.choice(DOMAINS)
178
+ ipv4 = r.choice(IPV4S)
179
+ mx = "mail." + domain
180
+ payload = {"tool":"dns.lookup","result":{"domain":domain,"ipv4":ipv4,"mx":mx}}
181
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nRispondi usando solo dominio, IPv4 e MX. Copia i valori esattamente."
182
+ assistant = f"Il dominio {domain} ha IPv4 {ipv4} e record MX {mx}."
183
+ return one(sys_renderer(), user, assistant, "renderer_dns")
184
+
185
+
186
+ def renderer_whois(r):
187
+ domain = r.choice(DOMAINS)
188
+ registrar = r.choice(["ICANN","Namecheap","IONOS","Gandi","Cloudflare"])
189
+ created = f"{r.randint(2000,2026)}-{r.randint(1,12):02d}-{r.randint(1,28):02d}"
190
+ payload = {"tool":"whois.lookup","result":{"domain":domain,"registrar":registrar,"created":created}}
191
+ user = f"TOOL_RESULT:\n{jd(payload)}\n\nRispondi usando solo questi dati. Non aggiungere next steps."
192
+ assistant = f"Il dominio {domain} risulta registrato presso {registrar}; data di creazione: {created}."
193
+ return one(sys_renderer(), user, assistant, "renderer_whois")
194
+
195
+
196
+ def renderer_json_complex(r):
197
+ city = r.choice(CITIES); temp = r.randint(-3, 38)
198
+ unread = r.randint(0, 25)
199
+ title = r.choice(EVENTS); time = r.choice(TIMES)
200
+ payload = {"weather":{"city":city,"temperature_c":temp},"mail":{"unread":unread},"calendar":{"title":title,"time":time}}
201
+ user = f"Trasforma questo JSON in italiano. Usa solo i valori presenti.\n\n{jd(payload)}"
202
+ assistant = f"A {city} ci sono {temp} °C. Hai {unread} email non lette. Il prossimo evento è {title} alle {time}."
203
+ return one(sys_renderer(), user, assistant, "renderer_json_complex")
204
+
205
+
206
+ def exact_hash(r):
207
+ sha = r.choice(HASHES_SHA256)
208
+ md5 = r.choice(HASHES_MD5)
209
+ size = r.choice([2048,1048576,512000,123456789])
210
+ payload = {"file":{"sha256":sha,"md5":md5,"size_bytes":size}}
211
+ user = f"PAYLOAD:\n{jd(payload)}\n\nRispondi in italiano copiando ESATTAMENTE sha256, md5 e size_bytes."
212
+ assistant = f"SHA256: {sha}. MD5: {md5}. Dimensione: {size} byte."
213
+ return one(sys_renderer(), user, assistant, "exact_hash")
214
+
215
+
216
+ def exact_network(r):
217
+ ipv4 = r.choice(IPV4S); ipv6 = r.choice(IPV6S); port = r.choice([80,443,8080,8443,53]); proto = r.choice(["TCP","UDP"])
218
+ payload = {"network":{"ipv4":ipv4,"ipv6":ipv6,"port":port,"protocol":proto},"debug":"ignore"}
219
+ user = f"PAYLOAD:\n{jd(payload)}\n\nVerbalizza i dati. Non cambiare nessun numero, zero iniziale o separatore. Ignora debug."
220
+ assistant = f"Endpoint {proto} {ipv4}:{port}. IPv6 {ipv6}."
221
+ return one(sys_renderer(), user, assistant, "exact_network")
222
+
223
+
224
+ def exact_url_domain(r):
225
+ url = r.choice(URLS); domain = r.choice(DOMAINS)
226
+ payload = {"url":url,"domain":domain,"risk_score":r.randint(0,100)}
227
+ user = f"PAYLOAD:\n{jd(payload)}\n\nCopia esattamente URL, dominio e risk_score."
228
+ assistant = f"URL: {url}. Dominio: {domain}. Risk score: {payload['risk_score']}."
229
+ return one(sys_renderer(), user, assistant, "exact_url_domain")
230
+
231
+
232
+ def tool_weather(r):
233
+ city = r.choice(CITIES)
234
+ sys = system_with_tools([tool_line("weather.forecast", ["city"])])
235
+ user = r.choice([f"Che tempo fa a {city}?", f"Mi dici il meteo di {city}?", f"Weather for {city}."])
236
+ return one(sys, user, call("weather.forecast", {"city":city}), "tool_weather")
237
+
238
+
239
+ def tool_finance(r):
240
+ sym = r.choice(STOCKS)
241
+ sys = system_with_tools([tool_line("finance.quote", ["symbol"])])
242
+ user = r.choice([f"Quanto quota {sym}?", f"Prezzo attuale di {sym}.", f"Quote for {sym}."])
243
+ return one(sys, user, call("finance.quote", {"symbol":sym}), "tool_finance")
244
+
245
+
246
+ def tool_dns(r):
247
+ domain = r.choice(DOMAINS)
248
+ sys = system_with_tools([tool_line("dns.lookup", ["domain","record_type"])])
249
+ rt = r.choice(["A","AAAA","MX","NS","TXT"])
250
+ user = r.choice([f"Qual è il record {rt} di {domain}?", f"Risolvi {domain} record {rt}."])
251
+ return one(sys, user, call("dns.lookup", {"domain":domain,"record_type":rt}), "tool_dns")
252
+
253
+
254
+ def tool_spotify(r):
255
+ sys = system_with_tools([tool_line("spotify.current_song", [])])
256
+ user = r.choice(["Che brano sto ascoltando?", "Che canzone sta suonando?", "Current song?"])
257
+ return one(sys, user, call("spotify.current_song", {}), "tool_spotify")
258
+
259
+
260
+ def tool_mail(r):
261
+ sys = system_with_tools([tool_line("unread_mail_count", [])])
262
+ user = r.choice(["Quante email non lette ho?", "Unread emails?", "Controlla le mail non lette."])
263
+ return one(sys, user, call("unread_mail_count", {}), "tool_mail")
264
+
265
+
266
+ def tool_calendar(r):
267
+ sys = system_with_tools([tool_line("calendar.next_event", [])])
268
+ user = r.choice(["Qual è il mio prossimo appuntamento?", "Cosa ho in agenda?", "Next calendar event?"])
269
+ return one(sys, user, call("calendar.next_event", {}), "tool_calendar")
270
+
271
+
272
+ RENDERERS = [renderer_weather, renderer_finance, renderer_spotify, renderer_mail, renderer_calendar, renderer_dns, renderer_whois, renderer_json_complex]
273
+ EXACTS = [exact_hash, exact_network, exact_url_domain]
274
+ TOOLS = [tool_weather, tool_finance, tool_dns, tool_spotify, tool_mail, tool_calendar]
275
+
276
+
277
+ def key(obj):
278
+ return hashlib.sha256(jd(obj["messages"]).encode("utf-8")).hexdigest()
279
+
280
+
281
+ def generate(out_dir, n_renderer, n_exact, n_tool, val_size, seed):
282
+ r = random.Random(seed)
283
+ out = Path(out_dir); out.mkdir(parents=True, exist_ok=True)
284
+ rows = []
285
+ seen = set()
286
+ counts = {}
287
+
288
+ def add(gen):
289
+ for _ in range(1000):
290
+ obj = gen(r)
291
+ k = key(obj)
292
+ if k not in seen:
293
+ seen.add(k)
294
+ rows.append(obj)
295
+ counts[obj["category"]] = counts.get(obj["category"],0)+1
296
+ return
297
+ rows.append(gen(r))
298
+
299
+ # Distribuzione forzata: ogni categoria riceve lo stesso numero di esempi
300
+ per_renderer = n_renderer // len(RENDERERS)
301
+ rem_renderer = n_renderer % len(RENDERERS)
302
+ for idx, gen in enumerate(RENDERERS):
303
+ target = per_renderer + (1 if idx < rem_renderer else 0)
304
+ for i in range(target):
305
+ add(gen)
306
+ print(f"{gen.__name__}: {target}", flush=True)
307
+ if (i+1) % 5000 == 0:
308
+ print(f"RENDERER {i+1}/{n_renderer}", flush=True)
309
+
310
+ per_exact = n_exact // len(EXACTS)
311
+ rem_exact = n_exact % len(EXACTS)
312
+ for idx, gen in enumerate(EXACTS):
313
+ target = per_exact + (1 if idx < rem_exact else 0)
314
+ for i in range(target):
315
+ add(gen)
316
+ print(f"{gen.__name__}: {target}", flush=True)
317
+ if (i+1) % 2500 == 0:
318
+ print(f"EXACT {i+1}/{n_exact}", flush=True)
319
+
320
+ per_tool = n_tool // len(TOOLS)
321
+ rem_tool = n_tool % len(TOOLS)
322
+ for idx, gen in enumerate(TOOLS):
323
+ target = per_tool + (1 if idx < rem_tool else 0)
324
+ for i in range(target):
325
+ add(gen)
326
+ print(f"{gen.__name__}: {target}", flush=True)
327
+ if (i+1) % 2500 == 0:
328
+ print(f"TOOL {i+1}/{n_tool}", flush=True)
329
+
330
+ r.shuffle(rows)
331
+ val_n = min(val_size, max(1, len(rows)//100))
332
+ val = rows[:val_n]
333
+ train = rows[val_n:]
334
+
335
+ train_path = out / "train.jsonl"
336
+ val_path = out / "validation.jsonl"
337
+
338
+ with train_path.open("w", encoding="utf-8") as f:
339
+ for obj in train:
340
+ f.write(json.dumps(obj, ensure_ascii=False) + "\n")
341
+
342
+ with val_path.open("w", encoding="utf-8") as f:
343
+ for obj in val:
344
+ f.write(json.dumps(obj, ensure_ascii=False) + "\n")
345
+
346
+ (out / "README.md").write_text(
347
+ "---\nlicense: cc-by-4.0\nlanguage:\n- it\n- en\ntask_categories:\n- text-generation\n---\n\n"
348
+ "# Scugnizz Agentic Repair 50k\n\n"
349
+ "Dataset sintetico bilanciato per correggere renderer, copia esatta e tool calling.\n\n"
350
+ f"Train: {len(train)}\n\nValidation: {len(val)}\n\n"
351
+ "Categorie:\n```json\n" + json.dumps(counts, ensure_ascii=False, indent=2) + "\n```\n",
352
+ encoding="utf-8"
353
+ )
354
+
355
+ print("DONE", out, "TRAIN", len(train), "VAL", len(val), flush=True)
356
+ print(json.dumps(counts, ensure_ascii=False, indent=2), flush=True)
357
+ print("SAMPLE", json.dumps(rows[0], ensure_ascii=False)[:2000], flush=True)
358
+ return out
359
+
360
+
361
+ def upload(folder, repo_id, private=False):
362
+ token = os.environ.get("HF_TOKEN") or os.environ.get("UV_SCRIPT_HF_TOKEN") or os.environ.get("HUGGINGFACE_HUB_TOKEN")
363
+ api = HfApi(token=token)
364
+ api.create_repo(repo_id, repo_type="dataset", private=private, exist_ok=True)
365
+ upload_folder(repo_id=repo_id, repo_type="dataset", folder_path=str(folder), commit_message="Add Scugnizz agentic repair dataset", token=token)
366
+ print("UPLOADED", repo_id, flush=True)
367
+
368
+
369
+ def main():
370
+ p = argparse.ArgumentParser()
371
+ p.add_argument("--out-dir", default="data/scugnizz-agentic-repair-50k")
372
+ p.add_argument("--repo-id", default="ProjectScugnizz/scugnizz-agentic-repair-50k")
373
+ p.add_argument("--n-renderer", type=int, default=30000)
374
+ p.add_argument("--n-exact", type=int, default=10000)
375
+ p.add_argument("--n-tool", type=int, default=10000)
376
+ p.add_argument("--val-size", type=int, default=1000)
377
+ p.add_argument("--seed", type=int, default=20260710)
378
+ p.add_argument("--upload", action="store_true")
379
+ p.add_argument("--private", action="store_true")
380
+ a = p.parse_args()
381
+
382
+ folder = generate(a.out_dir, a.n_renderer, a.n_exact, a.n_tool, a.val_size, a.seed)
383
+ if a.upload:
384
+ upload(folder, a.repo_id, a.private)
385
+
386
+
387
+ if __name__ == "__main__":
388
+ main()