jhalakdev67dev commited on
Commit
13ecbf7
·
verified ·
1 Parent(s): ca55b57

Delete AURA_onefile (3).py

Browse files
Files changed (1) hide show
  1. AURA_onefile (3).py +0 -1984
AURA_onefile (3).py DELETED
@@ -1,1984 +0,0 @@
1
- """
2
- AURA - THE ONE FILE v10 👻🪄
3
- =========================
4
- Everything in a single Python file: skills, agent, dataset generator,
5
- refinery, QLoRA training and evaluation.
6
-
7
- USAGE
8
- Kaggle (T4 x2): !python AURA_onefile.py train
9
- Generate data only: python AURA_onefile.py data --n 8000
10
- Run the spirit: python AURA_onefile.py agent --adapter ./aura_lora
11
- Demo (no model): python AURA_onefile.py agent --demo --dry-run
12
- Check skills: python AURA_onefile.py selftest
13
- Evaluate router: python AURA_onefile.py eval --adapter ./aura_lora
14
-
15
- Install first: pip install torch transformers peft bitsandbytes accelerate requests pillow
16
-
17
- v9 fixes (over v8):
18
- - evaluate() no longer crashes on chitchat rows (JSONDecodeError killed the
19
- run right after training, before GGUF export + zip)
20
- - GGUF export uses a VALID converter outtype (q8_0); "q4_k_m" is not accepted
21
- by llama.cpp's convert_hf_to_gguf.py
22
- - eval now reads the held-out aura_eval_data.jsonl instead of regenerating
23
- - proportional holdout (v8's hard 400-cap broke small runs)
24
- - dropped-row reporting in the token builder
25
- - eval failure no longer aborts the pipeline
26
- """
27
-
28
-
29
-
30
- # ============================================================================
31
- # SECTION 1: SKILLS, TOOLS & GUARDRAILS (Aura Manager / Installer / Launchpad / Researcher / Aryabhatta / Controller)
32
- # ============================================================================
33
-
34
- import json
35
- import os
36
- import re
37
- import shutil
38
- import subprocess
39
- import sys
40
- import tempfile
41
- import time
42
- from dataclasses import dataclass, field
43
- from typing import Callable, Dict, Any, List, Optional
44
-
45
- IS_WINDOWS = os.name == "nt"
46
- if IS_WINDOWS: # cmd/PowerShell default to cp1252 and crash on emoji prints
47
- for _stream in (sys.stdout, sys.stderr):
48
- try:
49
- _stream.reconfigure(encoding="utf-8", errors="replace")
50
- except Exception:
51
- pass
52
-
53
-
54
- # ----------------------------------------------------------------------------
55
- # Guardrails
56
- # ----------------------------------------------------------------------------
57
-
58
- # Commands / patterns that are plain dangerous -> hard block.
59
- DANGEROUS_PATTERNS = [
60
- r"\brm\s+-rf\s+/", # wipe root (unix)
61
- r"\bformat\s+[a-z]:", # format drive (windows)
62
- r"\bmkfs\b", # format (unix)
63
- r"\bdel\s+/[sq]", # recursive delete (windows)
64
- r"\brd\s+/s", # recursive remove dir (windows)
65
- r"\bshutdown\b",
66
- r"\bdiskpart\b",
67
- r":\(\)\{.*\};:", # fork bomb
68
- ]
69
-
70
- # Patterns that need explicit user confirmation before running.
71
- RISKY_PATTERNS = [
72
- r"\brm\b", r"\bdel\b", r"\brmdir\b", r"\brd\b", r"\bmv\b",
73
- r"\bsudo\b", r"\bapt\b", r"\bpip\b", r"\binstall\b",
74
- r"\breg(?:edit| add)\b", r"\bnetsh\b", r"\bchmod\b", r"\bchown\b",
75
- ]
76
-
77
-
78
- def _matches(text: str, patterns: List[str]) -> bool:
79
- text_l = text.lower()
80
- return any(re.search(p, text_l) for p in patterns)
81
-
82
-
83
- def default_confirm(action_desc: str) -> bool:
84
- """Ask the human before a destructive action. Swap for UI later."""
85
- try:
86
- ans = input(f"\n[AURA CONFIRM] {action_desc}\nType 'yes' to allow: ")
87
- return ans.strip().lower() in ("yes", "y")
88
- except Exception:
89
- return False
90
-
91
-
92
- def default_log(msg: str) -> None:
93
- print(f"[aura] {msg}")
94
-
95
-
96
- # ----------------------------------------------------------------------------
97
- # Python sandbox (used by Aura Aryabhatta + as a core tool)
98
- # ----------------------------------------------------------------------------
99
-
100
- SANDBOX_BLOCKLIST = DANGEROUS_PATTERNS + [r"\bshutdown\b", r"\bRestart-Computer\b"]
101
-
102
-
103
- def python_sandbox(code: str, timeout: int = 10) -> str:
104
- """Run Python code in a fresh subprocess with a timeout.
105
-
106
- NOT a hard security boundary - it stops accidents (infinite loops,
107
- fat-fingered deletes), not determined attackers. Run AURA's risky
108
- skills inside a Docker container / VM for real isolation.
109
- """
110
- if _matches(code, SANDBOX_BLOCKLIST):
111
- return "ERROR: command matches blocked pattern (safety guardrail)."
112
-
113
- with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False,
114
- encoding="utf-8") as f:
115
- f.write(code)
116
- path = f.name
117
- try:
118
- proc = subprocess.run(
119
- [sys.executable, path],
120
- capture_output=True, text=True, timeout=timeout,
121
- )
122
- out = (proc.stdout or "") + (("\nSTDERR: " + proc.stderr) if proc.stderr else "")
123
- return out.strip() or "(no output)"
124
- except subprocess.TimeoutExpired:
125
- return f"ERROR: code timed out after {timeout}s."
126
- except Exception as e:
127
- return f"ERROR: {e}"
128
- finally:
129
- try:
130
- os.remove(path)
131
- except OSError:
132
- pass
133
-
134
-
135
- def shell(command: str, timeout: int = 60,
136
- confirm: Callable[[str], bool] = default_confirm) -> str:
137
- """Run a shell command (cmd on Windows, bash elsewhere) with guardrails."""
138
- if _matches(command, DANGEROUS_PATTERNS):
139
- return "ERROR: command blocked by AURA safety guardrail."
140
- if _matches(command, RISKY_PATTERNS) and not confirm(f"Run shell command: {command}"):
141
- return "Cancelled by user."
142
-
143
- try:
144
- proc = subprocess.run(command, shell=True, capture_output=True,
145
- text=True, timeout=timeout)
146
- out = (proc.stdout or "").strip()
147
- err = (proc.stderr or "").strip()
148
- result = out + (("\nSTDERR: " + err) if err else "")
149
- return result.strip() or "(no output)"
150
- except subprocess.TimeoutExpired:
151
- return f"ERROR: command timed out after {timeout}s."
152
- except Exception as e:
153
- return f"ERROR: {e}"
154
-
155
-
156
- # ----------------------------------------------------------------------------
157
- # Skill context (cwd, confirm, log, dry-run)
158
- # ----------------------------------------------------------------------------
159
-
160
- @dataclass
161
- class SkillContext:
162
- cwd: str = os.path.expanduser("~")
163
- confirm: Callable[[str], bool] = default_confirm
164
- log: Callable[[str], None] = default_log
165
- dry_run: bool = False # if True: print what WOULD happen
166
-
167
-
168
- # ----------------------------------------------------------------------------
169
- # 1) AURA MANAGER - files
170
- # ----------------------------------------------------------------------------
171
-
172
- def aura_manager(args: Dict[str, Any], ctx: SkillContext) -> str:
173
- action = args.get("action")
174
- path = os.path.join(ctx.cwd, args.get("path", "")) if args.get("path") else ctx.cwd
175
-
176
- def would(msg):
177
- ctx.log(f"[dry-run] {msg}")
178
- return f"[dry-run] {msg}"
179
-
180
- if action == "create_folder":
181
- name = args.get("name", "NewFolder")
182
- full = os.path.join(path, name)
183
- if ctx.dry_run: return would(f"create folder {full}")
184
- os.makedirs(full, exist_ok=True)
185
- return f"Folder created: {full}"
186
-
187
- if action == "create_file":
188
- name = args.get("name", "note.txt")
189
- content = args.get("content", "")
190
- full = os.path.join(path, name)
191
- if ctx.dry_run: return would(f"create file {full}")
192
- os.makedirs(path, exist_ok=True)
193
- with open(full, "w", encoding="utf-8") as f:
194
- f.write(content)
195
- return f"File saved: {full}"
196
-
197
- if action == "save_file":
198
- return aura_manager({**args, "action": "create_file"}, ctx)
199
-
200
- if action == "delete":
201
- target = args.get("target", "")
202
- full = os.path.join(ctx.cwd, target)
203
- if not os.path.exists(full):
204
- return f"ERROR: not found: {full}"
205
- if ctx.dry_run: return would(f"delete {full}")
206
- if not ctx.confirm(f"DELETE {full} (cannot be undone!)"):
207
- return "Cancelled by user."
208
- if os.path.isdir(full):
209
- shutil.rmtree(full)
210
- else:
211
- os.remove(full)
212
- return f"Deleted: {full}"
213
-
214
- if action == "move":
215
- src = os.path.join(ctx.cwd, args["source"])
216
- dst = os.path.join(ctx.cwd, args["destination"])
217
- if not os.path.exists(src):
218
- return f"ERROR: source not found: {src}"
219
- if ctx.dry_run: return would(f"move {src} -> {dst}")
220
- if os.path.exists(dst) and not ctx.confirm(f"OVERWRITE {dst}?"):
221
- return "Cancelled by user."
222
- os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
223
- shutil.move(src, dst)
224
- return f"Moved: {src} -> {dst}"
225
-
226
- if action == "copy":
227
- src = os.path.join(ctx.cwd, args["source"])
228
- dst = os.path.join(ctx.cwd, args["destination"])
229
- if not os.path.exists(src):
230
- return f"ERROR: source not found: {src}"
231
- if ctx.dry_run: return would(f"copy {src} -> {dst}")
232
- os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
233
- shutil.copy2(src, dst)
234
- return f"Copied: {src} -> {dst}"
235
-
236
- if action == "list":
237
- if ctx.dry_run: return would(f"list {path}")
238
- try:
239
- items = sorted(os.listdir(path))
240
- return "\n".join(items) or "(empty folder)"
241
- except Exception as e:
242
- return f"ERROR: {e}"
243
-
244
- if action == "search_files":
245
- query = args.get("query", "").lower()
246
- root = os.path.join(ctx.cwd, args.get("root", "."))
247
- hits = []
248
- for dirpath, _dirs, files in os.walk(root):
249
- for fn in files:
250
- if query in fn.lower():
251
- hits.append(os.path.join(dirpath, fn))
252
- if len(hits) >= 50:
253
- break
254
- return "\n".join(hits) if hits else "No matching files found."
255
-
256
- if action == "organize":
257
- """Move files in a folder into sub-folders by extension."""
258
- folder = os.path.join(ctx.cwd, args.get("folder", "."))
259
- if not os.path.isdir(folder):
260
- return f"ERROR: folder not found: {folder}"
261
- moved = 0
262
- for fn in os.listdir(folder):
263
- src = os.path.join(folder, fn)
264
- if not os.path.isfile(src):
265
- continue
266
- ext = os.path.splitext(fn)[1].lstrip(".").lower() or "misc"
267
- dst_dir = os.path.join(folder, ext)
268
- if ctx.dry_run:
269
- would(f"move {src} -> {dst_dir}")
270
- else:
271
- os.makedirs(dst_dir, exist_ok=True)
272
- shutil.move(src, os.path.join(dst_dir, fn))
273
- moved += 1
274
- return f"Organized {moved} files into extension folders inside {folder}."
275
-
276
- return (f"ERROR: unknown action '{action}' for aura_manager. Valid actions: "
277
- "create_folder, create_file, save_file, delete, move, copy, list, search_files, organize")
278
-
279
-
280
- # ----------------------------------------------------------------------------
281
- # 2) AURA INSTALLER - software installation (guarded)
282
- # ----------------------------------------------------------------------------
283
-
284
- # Only these domains may be used for the web fallback download.
285
- OFFICIAL_DOMAINS = {
286
- "microsoft.com", "store.microsoft.com", "visualstudio.com",
287
- "code.visualstudio.com", "google.com", "mozilla.org", "notepad-plus-plus.org",
288
- "7-zip.org", "python.org", "videolan.org", "vlc.cc", "discord.com",
289
- "slack.com", "zoom.us", "adobe.com", "spotify.com", "obsproject.com",
290
- "git-scm.com", "github.com", "gitlab.com", "blender.org", "audacityteam.org",
291
- "libreoffice.org", "winrar.com", "ubuntu.com", "nodejs.org", "docker.com",
292
- }
293
-
294
-
295
- def _domain_is_allowed(url: str) -> bool:
296
- try:
297
- host = re.sub(r"^https?://", "", url).split("/")[0].lower()
298
- host = host.split(":")[0]
299
- return any(host == d or host.endswith("." + d) for d in OFFICIAL_DOMAINS)
300
- except Exception:
301
- return False
302
-
303
-
304
- def aura_installer(args: Dict[str, Any], ctx: SkillContext) -> str:
305
- action = args.get("action")
306
- query = args.get("query", "")
307
-
308
- if action == "search":
309
- if not IS_WINDOWS:
310
- return "ERROR: winget search is Windows-only (use your package manager)."
311
- out = shell(f'winget search "{query}" --accept-source-agreements',
312
- confirm=lambda _: True)
313
- return out or "No results found."
314
-
315
- if action == "install":
316
- app_id = args.get("app_id", query)
317
- url = args.get("download_url") # optional web fallback
318
- if url:
319
- if not _domain_is_allowed(url):
320
- return (f"REFUSED: {url} is not in AURA's official-domains "
321
- f"allowlist. Install manually if you trust it.")
322
- if not ctx.confirm(f"Download and install from {url}"):
323
- return "Cancelled by user."
324
- out = shell(f'curl -L -o setup_installer "{url}" && '
325
- f'start setup_installer', confirm=lambda _: True)
326
- return out
327
- if not IS_WINDOWS:
328
- return "ERROR: winget install is Windows-only."
329
- if not ctx.confirm(f"Install '{app_id}' via winget"):
330
- return "Cancelled by user."
331
- out = shell(f'winget install --id "{app_id}" -e '
332
- f'--accept-source-agreements --accept-package-agreements',
333
- confirm=lambda _: True)
334
- return out or "Install command sent."
335
-
336
- if action == "uninstall":
337
- app_id = args.get("app_id", query)
338
- if not ctx.confirm(f"UNINSTALL '{app_id}'"):
339
- return "Cancelled by user."
340
- return shell(f'winget uninstall --id "{app_id}" -e',
341
- confirm=lambda _: True) if IS_WINDOWS else \
342
- "ERROR: winget uninstall is Windows-only."
343
-
344
- if action == "list_installed":
345
- return shell("winget list", confirm=lambda _: True) if IS_WINDOWS \
346
- else "ERROR: winget list is Windows-only."
347
-
348
- return "ERROR: unknown action '%s' for aura_installer. Valid actions: search, install, uninstall, list_installed" % action
349
-
350
-
351
- # ----------------------------------------------------------------------------
352
- # 3) AURA LAUNCHPAD - open apps / websites / files
353
- # ----------------------------------------------------------------------------
354
-
355
- APP_ALIASES = {
356
- "chrome": "chrome", "google chrome": "chrome",
357
- "notepad": "notepad", "calculator": "calc",
358
- "vscode": "code", "vs code": "code", "visual studio code": "code",
359
- "spotify": "spotify", "explorer": "explorer", "file manager": "explorer",
360
- "cmd": "cmd", "terminal": IS_WINDOWS and "cmd" or "x-terminal-emulator",
361
- "paint": "mspaint", "word": "winword", "excel": "excel",
362
- "edge": "msedge", "firefox": "firefox", "settings": "ms-settings:",
363
- }
364
- SITE_ALIASES = {
365
- "youtube": "https://www.youtube.com",
366
- "google": "https://www.google.com",
367
- "github": "https://github.com",
368
- "wikipedia": "https://www.wikipedia.org",
369
- "gmail": "https://mail.google.com",
370
- "stack overflow": "https://stackoverflow.com",
371
- "huggingface": "https://huggingface.co",
372
- "kaggle": "https://www.kaggle.com",
373
- "chatgpt": "https://chat.openai.com",
374
- }
375
-
376
-
377
- def aura_launchpad(args: Dict[str, Any], ctx: SkillContext) -> str:
378
- action = args.get("action")
379
- target = (args.get("target") or "").strip()
380
-
381
- # SELF-HEAL: small models sometimes emit a wrong verb ('open' etc.) -
382
- # infer the right action from the target instead of failing.
383
- _valid = {"open_app", "open_website", "search_browser", "open_file"}
384
- if action not in _valid and target:
385
- _ka, _ks = target.lower(), target.lower().replace(" ", "")
386
- if _ka in APP_ALIASES:
387
- action = "open_app"
388
- elif _ks in SITE_ALIASES or "://" in target or \
389
- target.lower().endswith((".com", ".org", ".in", ".net", ".io")):
390
- action = "open_website"
391
- elif os.path.exists(os.path.join(ctx.cwd, target)):
392
- action = "open_file"
393
- if action in _valid:
394
- ctx.log(f"[self-heal] inferred action '{action}' from target '{target}'")
395
-
396
- if action == "open_app":
397
- key = target.lower()
398
- exe = APP_ALIASES.get(key, target)
399
- if ctx.dry_run:
400
- return f"[dry-run] open app '{exe}'"
401
- if IS_WINDOWS:
402
- os.system(f'start "" "{exe}"')
403
- return f"Launched app: {target}"
404
- return shell(f"{exe} &", confirm=lambda _: True) or f"Tried to launch: {target}"
405
-
406
- if action == "open_website":
407
- key = target.lower().replace(" ", "")
408
- url = SITE_ALIASES.get(key, target if "://" in target
409
- else "https://" + target)
410
- if ctx.dry_run:
411
- return f"[dry-run] open website {url}"
412
- import webbrowser
413
- webbrowser.open(url)
414
- return f"Opened website: {url}"
415
-
416
- if action == "search_browser":
417
- # Free in-browser search (no API key): opens the search URL in a NEW TAB
418
- query = args.get("query", "")
419
- engine = args.get("engine", "duckduckgo")
420
- import urllib.parse
421
- import webbrowser
422
- q = urllib.parse.quote_plus(query)
423
- urls = {
424
- "google": f"https://www.google.com/search?q={q}",
425
- "duckduckgo": f"https://duckduckgo.com/?q={q}",
426
- "youtube": f"https://www.youtube.com/results?search_query={q}",
427
- "wikipedia": f"https://en.wikipedia.org/w/index.php?search={q}",
428
- }
429
- url = urls.get(engine, urls["duckduckgo"])
430
- browser = args.get("browser", "")
431
- if ctx.dry_run:
432
- return f"[dry-run] open new tab: {url}"
433
- if browser and IS_WINDOWS:
434
- os.system(f'start "" "{browser}" "{url}"')
435
- else:
436
- webbrowser.open(url) # opens a new tab in the default browser
437
- return f"Opened new tab ({engine} search): {query}"
438
-
439
- if action == "open_file":
440
- full = os.path.join(ctx.cwd, target)
441
- if not os.path.exists(full):
442
- return f"ERROR: file not found: {full}"
443
- if ctx.dry_run:
444
- return f"[dry-run] open file {full}"
445
- if IS_WINDOWS:
446
- os.startfile(full) # noqa (windows-only)
447
- return f"Opened file: {full}"
448
- return shell(f'xdg-open "{full}"', confirm=lambda _: True)
449
-
450
- return "ERROR: unknown action '%s' for aura_launchpad. Valid actions: open_app, open_website, search_browser, open_file" % action
451
-
452
-
453
- # ----------------------------------------------------------------------------
454
- # 4) AURA RESEARCHER - web search & fetch (stdlib only)
455
- # ----------------------------------------------------------------------------
456
-
457
- def _web_search_ddg(query: str, max_results: int = 5) -> str:
458
- """DuckDuckGo search - no API key. Tries the ddgs package, then HTML."""
459
- if not query.strip():
460
- return "ERROR: empty query."
461
- # 1) ddgs / duckduckgo_search package (most reliable)
462
- for mod in ("ddgs", "duckduckgo_search"):
463
- try:
464
- m = __import__(mod)
465
- DDGS = getattr(m, "DDGS")
466
- with DDGS() as d:
467
- hits = list(d.text(query, max_results=max_results))
468
- if hits:
469
- return "\n".join(
470
- f"- {h.get('title', '')}\n {h.get('href', '')}\n {h.get('body', '')[:200]}"
471
- for h in hits)
472
- except Exception:
473
- pass
474
- # 2) HTML fallback
475
- try:
476
- import requests
477
- except ImportError:
478
- return "ERROR: install `requests` (pip install requests)."
479
- try:
480
- r = requests.post("https://html.duckduckgo.com/html/", data={"q": query},
481
- headers={"User-Agent": "Mozilla/5.0 (AURA agent)"},
482
- timeout=15)
483
- links = re.findall(r'class="result__a"[^>]*href="([^"]+)"[^>]*>(.*?)</a>',
484
- r.text, re.S)[:max_results]
485
- if not links:
486
- return "No results found."
487
- out = []
488
- for url, title in links:
489
- title = re.sub(r"<[^>]+>", "", title).strip()
490
- out.append(f"- {title}\n {url}")
491
- return "\n".join(out)
492
- except Exception as e:
493
- return f"ERROR: search failed ({e})"
494
-
495
-
496
- def aura_researcher(args: Dict[str, Any], ctx: SkillContext) -> str:
497
- action = args.get("action")
498
- if action == "search_web":
499
- return _web_search_ddg(args.get("query", ""))
500
- if action == "fetch_page":
501
- url = args.get("url", "")
502
- try:
503
- import requests
504
- except ImportError:
505
- return "ERROR: install `requests`."
506
- try:
507
- r = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (AURA)"},
508
- timeout=15)
509
- text = re.sub(r"<script.*?</script>|<style.*?</style>", "",
510
- r.text, flags=re.S)
511
- text = re.sub(r"<[^>]+>", " ", text)
512
- text = re.sub(r"\s+", " ", text).strip()
513
- return text[:3000] + ("..." if len(text) > 3000 else "")
514
- except Exception as e:
515
- return f"ERROR: fetch failed ({e})"
516
- return "ERROR: unknown action '%s' for aura_researcher. Valid actions: search_web, fetch_page" % action
517
-
518
-
519
- # ----------------------------------------------------------------------------
520
- # 5) AURA ARYABHATTA - math
521
- # ----------------------------------------------------------------------------
522
-
523
- def aura_aryabhatta(args: Dict[str, Any], ctx: SkillContext) -> str:
524
- action = args.get("action")
525
- if action == "calculate":
526
- expr = args.get("expression", "")
527
- # safe-ish eval: no builtins, expose math module + its functions
528
- code = ("import math\n"
529
- "_ns = {'__builtins__': {}, 'math': math, **math.__dict__}\n"
530
- f"print(eval({expr!r}, _ns))")
531
- return python_sandbox(code, timeout=10)
532
- if action == "run_code":
533
- return python_sandbox(args.get("code", ""), timeout=15)
534
- return "ERROR: unknown action '%s' for aura_aryabhatta. Valid actions: calculate, run_code" % action
535
-
536
-
537
- # ----------------------------------------------------------------------------
538
- # 6) AURA CONTROLLER - system controls
539
- # ----------------------------------------------------------------------------
540
-
541
- def aura_controller(args: Dict[str, Any], ctx: SkillContext) -> str:
542
- action = args.get("action")
543
-
544
- if action == "screenshot":
545
- try:
546
- from PIL import ImageGrab
547
- path = os.path.join(ctx.cwd, f"aura_screenshot_{int(time.time())}.png")
548
- if ctx.dry_run:
549
- return f"[dry-run] screenshot -> {path}"
550
- ImageGrab.grab().save(path)
551
- return f"Screenshot saved: {path}"
552
- except Exception as e:
553
- return f"ERROR: screenshot failed ({e}). pip install pillow"
554
-
555
- if action == "system_info":
556
- import platform
557
- return (f"OS: {platform.system()} {platform.release()} | "
558
- f"Machine: {platform.machine()} | "
559
- f"Python: {platform.python_version()} | "
560
- f"CPU cores: {os.cpu_count()}")
561
-
562
- if action == "clipboard_read":
563
- try:
564
- from PIL import ImageGrab # noqa
565
- except Exception:
566
- pass
567
- try:
568
- import tkinter as tk
569
- root = tk.Tk(); root.withdraw()
570
- text = root.clipboard_get(); root.destroy()
571
- return text or "(clipboard empty)"
572
- except Exception:
573
- return "(clipboard empty or not text)"
574
-
575
- if action == "clipboard_write":
576
- text = args.get("text", "")
577
- try:
578
- import tkinter as tk
579
- root = tk.Tk(); root.withdraw()
580
- root.clipboard_clear(); root.clipboard_append(text)
581
- root.update(); root.destroy()
582
- return "Clipboard updated."
583
- except Exception as e:
584
- return f"ERROR: {e}"
585
-
586
- if action == "volume":
587
- # Best-effort Windows volume via PowerShell + nircmd fallback.
588
- if not IS_WINDOWS:
589
- return "ERROR: volume control implemented for Windows only."
590
- level = int(args.get("level", 50))
591
- level = max(0, min(100, level))
592
- out = shell(
593
- "powershell -c \"$k=Get-CimInstance -Namespace root/WMI "
594
- "ClassName WmiVolumeChangeEvent; "
595
- f"(New-Object -ComObject WScript.Shell).SendKeys([char]174)\"",
596
- confirm=lambda _: True)
597
- return (f"Volume keys sent (level {level}). "
598
- f"For precise control install nircmd.exe: {out[:100]}")
599
-
600
- if action == "open_settings":
601
- return aura_launchpad({"action": "open_app", "target": "settings"}, ctx)
602
-
603
- return "ERROR: unknown action '%s' for aura_controller. Valid actions: screenshot, system_info, clipboard_read, clipboard_write, volume, open_settings" % action
604
-
605
-
606
- # ----------------------------------------------------------------------------
607
- # Registry
608
- # ----------------------------------------------------------------------------
609
-
610
- @dataclass
611
- class Skill:
612
- name: str
613
- emoji: str
614
- description: str
615
- actions: Dict[str, str] # action -> arg doc
616
- func: Callable[[Dict[str, Any], SkillContext], str]
617
-
618
-
619
- SKILLS: Dict[str, Skill] = {
620
- "aura_manager": Skill(
621
- name="aura_manager",
622
- emoji="📁",
623
- description="File operations: create/save/delete/move/copy/list/search/organize files and folders.",
624
- actions={
625
- "create_folder": "args: name, path?",
626
- "create_file": "args: name, content, path?",
627
- "save_file": "args: name, content, path?",
628
- "delete": "args: target (asks confirmation)",
629
- "move": "args: source, destination (asks if overwriting)",
630
- "copy": "args: source, destination",
631
- "list": "args: path?",
632
- "search_files": "args: query, root?",
633
- "organize": "args: folder (sorts files into extension sub-folders)",
634
- },
635
- func=aura_manager,
636
- ),
637
- "aura_installer": Skill(
638
- name="aura_installer",
639
- emoji="📦",
640
- description="Install/uninstall software. Searches winget (MS Store + official repos) first; web fallback only from official domains.",
641
- actions={
642
- "search": "args: query (winget search)",
643
- "install": "args: app_id or query, download_url? (asks confirmation)",
644
- "uninstall": "args: app_id (asks confirmation)",
645
- "list_installed": "args: none",
646
- },
647
- func=aura_installer,
648
- ),
649
- "aura_launchpad": Skill(
650
- name="aura_launchpad",
651
- emoji="🚀",
652
- description="Open apps, websites and files.",
653
- actions={
654
- "open_app": "args: target (e.g. chrome, notepad)",
655
- "open_website": "args: target (e.g. youtube, github, or a URL)",
656
- "search_browser": "args: query, engine? (google/duckduckgo/youtube/wikipedia), browser? - opens search results in a NEW TAB",
657
- "open_file": "args: target (path)",
658
- },
659
- func=aura_launchpad,
660
- ),
661
- "aura_researcher": Skill(
662
- name="aura_researcher",
663
- emoji="🔍",
664
- description="Web search and reading web pages.",
665
- actions={
666
- "search_web": "args: query",
667
- "fetch_page": "args: url",
668
- },
669
- func=aura_researcher,
670
- ),
671
- "aura_aryabhatta": Skill(
672
- name="aura_aryabhatta",
673
- emoji="🧮",
674
- description="Math and Python code execution in a sandboxed runner.",
675
- actions={
676
- "calculate": "args: expression (single python expression, math.* available)",
677
- "run_code": "args: code (multi-line python)",
678
- },
679
- func=aura_aryabhatta,
680
- ),
681
- "aura_controller": Skill(
682
- name="aura_controller",
683
- emoji="⚙️",
684
- description="System controls: screenshot, system info, clipboard, volume, settings.",
685
- actions={
686
- "screenshot": "args: none",
687
- "system_info": "args: none",
688
- "clipboard_read": "args: none",
689
- "clipboard_write": "args: text",
690
- "volume": "args: level (0-100)",
691
- "open_settings": "args: none",
692
- },
693
- func=aura_controller,
694
- ),
695
- }
696
-
697
-
698
- def build_system_prompt() -> str:
699
- lines = [
700
- "You are AURA, a helpful system-agent that controls the user's computer.",
701
- "You have these skills:",
702
- ]
703
- for s in SKILLS.values():
704
- lines.append(f"- {s.emoji} {s.name}: {s.description}")
705
- for act, argdoc in s.actions.items():
706
- lines.append(f" action '{act}' -> {argdoc}")
707
- lines += [
708
- "",
709
- "RULES:",
710
- "1. ALWAYS think first, briefly, inside <thinking></thinking>.",
711
- " In that block: which skill and action you need, the key arguments,",
712
- " and for maths the expression you will evaluate.",
713
- "2. AFTER the thinking block, output ONLY the final JSON on its own line:",
714
- ' {"skill": "<skill_name>", "args": {"action": "<action>", ...}}',
715
- " Do not add prose after the JSON.",
716
- "3. If the request needs none of the skills, think briefly and answer normally.",
717
- "4. For maths, prefer aura_aryabhatta calculate with a single python expression.",
718
- "5. For web lookups use aura_researcher search_web (DuckDuckGo); fetch_page to read a URL.",
719
- "6. If the request is ambiguous, think briefly and ask ONE short clarifying question.",
720
- "7. If a request is destructive or unsafe, refuse it plainly - do not call a skill.",
721
- "8. Never invent file paths - ask the user when unsure.",
722
- ]
723
- return "\n".join(lines)
724
-
725
-
726
- def execute_skill_call(call: Dict[str, Any], ctx: SkillContext) -> str:
727
- name = call.get("skill", "")
728
- args = call.get("args", {}) or {}
729
- skill = SKILLS.get(name)
730
- if skill is None:
731
- return f"ERROR: unknown skill '{name}'. Available: {list(SKILLS)}"
732
- ctx.log(f"executing {skill.emoji} {name} args={json.dumps(args, ensure_ascii=False)}")
733
- try:
734
- return skill.func(args, ctx)
735
- except Exception as e:
736
- return f"ERROR in {name}: {e}"
737
-
738
-
739
- # ----------------------------------------------------------------------------
740
- # Self-test: python aura_skills.py
741
- # ----------------------------------------------------------------------------
742
-
743
-
744
- # ============================================================================
745
- # SECTION 2: THE AGENT LOOP (model wrapper, JSON parsing, multi-step execution)
746
- # ============================================================================
747
-
748
- import argparse
749
- import json
750
- import re
751
- import sys
752
-
753
- MAX_STEPS = 5 # safety cap on chained tool calls per user message
754
-
755
-
756
- # ----------------------------------------------------------------------------
757
- # Model wrapper (lazy import so --demo works on any machine)
758
- # ----------------------------------------------------------------------------
759
-
760
- class AuraModel:
761
- def __init__(self, model_id: str, adapter: str = None):
762
- import torch
763
- from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
764
-
765
- self.tok = AutoTokenizer.from_pretrained(model_id)
766
- if torch.cuda.is_available():
767
- print(f"[aura] GPU detected: {torch.cuda.get_device_name(0)} - 4-bit fast mode")
768
- quant = BitsAndBytesConfig(
769
- load_in_4bit=True,
770
- bnb_4bit_quant_type="nf4",
771
- bnb_4bit_compute_dtype=torch.float16,
772
- bnb_4bit_use_double_quant=True,
773
- )
774
- self.model = AutoModelForCausalLM.from_pretrained(
775
- model_id, quantization_config=quant, device_map="auto",
776
- )
777
- else:
778
- # CPU mode: bitsandbytes is GPU-only, so load plain fp32.
779
- # Slower (~2-5 tokens/sec) but perfectly fine for testing.
780
- print("[aura] no GPU detected - CPU mode (slower, be patient)")
781
- print("[aura] first run downloads ~5GB base model once")
782
- try: # transformers v5 renamed torch_dtype -> dtype
783
- self.model = AutoModelForCausalLM.from_pretrained(
784
- model_id, dtype=torch.float32, device_map="cpu")
785
- except TypeError:
786
- self.model = AutoModelForCausalLM.from_pretrained(
787
- model_id, torch_dtype=torch.float32, device_map="cpu")
788
- if adapter:
789
- from peft import PeftModel
790
- self.model = PeftModel.from_pretrained(self.model, adapter)
791
- print(f"[aura] adapter loaded: {adapter}")
792
- self.model.eval()
793
-
794
- def chat(self, messages) -> str:
795
- prompt = self.tok.apply_chat_template(
796
- messages, tokenize=False, add_generation_prompt=True)
797
- inputs = self.tok(prompt, return_tensors="pt").to(self.model.device)
798
- out = self.model.generate(
799
- **inputs,
800
- max_new_tokens=256,
801
- do_sample=True,
802
- temperature=0.4, # low temp -> reliable JSON
803
- top_p=0.9,
804
- pad_token_id=self.tok.eos_token_id,
805
- )
806
- text = self.tok.decode(out[0][inputs["input_ids"].shape[1]:],
807
- skip_special_tokens=True)
808
- return text.strip()
809
-
810
-
811
- class DemoModel:
812
- """Rule-based stand-in for quick testing without downloading weights."""
813
-
814
- RULES = [
815
- (r"(folder|folder bana)", {"skill": "aura_manager",
816
- "args": {"action": "create_folder", "name": "Aura"}}),
817
- (r"(install|laga do)", {"skill": "aura_installer",
818
- "args": {"action": "install", "query": "vscode"}}),
819
- (r"(kholo|open)", {"skill": "aura_launchpad",
820
- "args": {"action": "open_app", "target": "notepad"}}),
821
- (r"(sqrt|calculate|kitna|what is)", {"skill": "aura_aryabhatta",
822
- "args": {"action": "calculate",
823
- "expression": "2**10"}}),
824
- (r"(screenshot)", {"skill": "aura_controller",
825
- "args": {"action": "screenshot"}}),
826
- ]
827
-
828
- def chat(self, messages) -> str:
829
- last = messages[-1]["content"]
830
- if last.startswith("[TOOL RESULT]"):
831
- return "Ho gaya! Kuch aur karna hai?" # final answer after tool use
832
- user = last.lower()
833
- for pat, call in self.RULES:
834
- if re.search(pat, user):
835
- return json.dumps(call, ensure_ascii=False)
836
- return "Main demo model hoon - real AURA ke liye Sarvam-2B load karo!"
837
-
838
-
839
- # ----------------------------------------------------------------------------
840
- # Robust JSON extraction from a model reply
841
- # ----------------------------------------------------------------------------
842
-
843
- def extract_skill_call(text: str):
844
- """Find the first balanced JSON object in the reply. None if absent."""
845
- text = text.strip()
846
- if "{" not in text:
847
- return None
848
- # strip markdown fences if the model added them
849
- text = re.sub(r"```(?:json)?", "", text).strip()
850
- depth, start = 0, -1
851
- for i, ch in enumerate(text):
852
- if ch == "{":
853
- if depth == 0:
854
- start = i
855
- depth += 1
856
- elif ch == "}":
857
- depth -= 1
858
- if depth == 0 and start != -1:
859
- candidate = text[start:i + 1]
860
- try:
861
- obj = json.loads(candidate)
862
- except json.JSONDecodeError:
863
- start, depth = -1, 0
864
- continue
865
- if isinstance(obj, dict) and "skill" in obj:
866
- return obj
867
- start = -1 # valid json but not a skill call - keep scanning
868
- return None
869
-
870
-
871
- # ----------------------------------------------------------------------------
872
- # Ollama backend - GGUF models run 5-10x faster on CPU with ~2.5GB RAM
873
- # (vs ~10GB fp32 transformers). Requires: https://ollama.com + `ollama create`
874
- # ----------------------------------------------------------------------------
875
-
876
- class OllamaModel:
877
- def __init__(self, model_name: str = "aura"):
878
- import requests
879
- self._requests = requests
880
- self.name = model_name
881
- self.system = build_system_prompt()
882
- try:
883
- r = requests.get("http://localhost:11434/api/tags", timeout=3)
884
- names = [m["name"].split(":")[0] for m in r.json().get("models", [])]
885
- if self.name not in names:
886
- print(f"[aura] WARNING: ollama model '{self.name}' not found!")
887
- print(f"[aura] available: {names or 'none'}")
888
- print(f"[aura] fix: ollama create {self.name} -f AURA_Modelfile")
889
- except Exception as e:
890
- print(f"[aura] WARNING: ollama not reachable at localhost:11434 ({e})")
891
- print("[aura] install from https://ollama.com, run it, then retry")
892
- print(f"[aura] ollama backend: {self.name} (fast CPU mode)")
893
-
894
- def chat(self, messages) -> str:
895
- msgs = [{"role": "system", "content": self.system}]
896
- msgs += [m for m in messages if m.get("role") != "system"]
897
- r = self._requests.post("http://localhost:11434/api/chat",
898
- json={"model": self.name, "messages": msgs,
899
- "stream": False}, timeout=600)
900
- r.raise_for_status()
901
- return r.json()["message"]["content"].strip()
902
-
903
-
904
- # ----------------------------------------------------------------------------
905
- # The agent loop
906
- # ----------------------------------------------------------------------------
907
-
908
- def run_agent(model, dry_run=False, cwd=None):
909
-
910
- ctx = SkillContext(cwd=cwd or ".", dry_run=dry_run,
911
- confirm=default_confirm, log=default_log)
912
- system = build_system_prompt()
913
- print(r"""
914
- ╔═══════════════════════════════════════╗
915
- ║ A U R A - system spirit 👻🪄 ║
916
- ║ files | install | launch | search ║
917
- ║ maths | system controls ║
918
- ╚═══════════════════════════════════════╝
919
- type 'quit' to exit, 'dry' to toggle safe-mode
920
- """)
921
- history = []
922
- while True:
923
- try:
924
- user = input("\nYou: ").strip()
925
- except (EOFError, KeyboardInterrupt):
926
- print("\n[aura] bye!")
927
- return
928
- if not user:
929
- continue
930
- if user.lower() in ("quit", "exit", "bye"):
931
- print("[aura] bye! :)")
932
- return
933
- if user.lower() == "dry":
934
- ctx.dry_run = not ctx.dry_run
935
- print(f"[aura] dry-run mode: {ctx.dry_run}")
936
- continue
937
-
938
- messages = [{"role": "system", "content": system}]
939
- messages.extend(history[-6:]) # short memory
940
- messages.append({"role": "user", "content": user})
941
- prev_call_sig = None
942
-
943
- for step in range(MAX_STEPS):
944
- reply = model.chat(messages)
945
- call = extract_skill_call(reply)
946
- if call is None:
947
- print(f"AURA: {reply}")
948
- history.append({"role": "user", "content": user})
949
- history.append({"role": "assistant", "content": reply})
950
- break
951
-
952
- # loop guard: same call twice in a row = stop (small models do this)
953
- call_sig = json.dumps(call, sort_keys=True)
954
- if call_sig == prev_call_sig:
955
- print("AURA: Ho gaya! (kaam complete)")
956
- history.append({"role": "user", "content": user})
957
- history.append({"role": "assistant",
958
- "content": "Action already done."})
959
- break
960
- prev_call_sig = call_sig
961
-
962
- print(f"AURA -> skill: {call['skill']} args: {call.get('args', {})}")
963
- observation = execute_skill_call(call, ctx)
964
- print(f"RESULT: {observation[:400]}")
965
- messages.append({"role": "assistant", "content": json.dumps(call)})
966
- # feed the observation back so the model can decide the next step
967
- fb = f"[TOOL RESULT] {observation[:1500]}"
968
- if not observation.startswith("ERROR"):
969
- fb += "\n(The task is done. Reply to the user normally - no more skill calls.)"
970
- messages.append({"role": "user", "content": fb})
971
- else:
972
- print("[aura] (max steps reached - stopping for safety)")
973
- history.append({"role": "user", "content": user})
974
- history.append({"role": "assistant", "content": "(stopped: max steps)"})
975
-
976
-
977
- # ============================================================================
978
- # SECTION 3: DATASET GENERATOR + REFINERY (Hindi / Hinglish / English)
979
- # ============================================================================
980
-
981
- import argparse
982
- import json
983
- import random
984
- import re
985
- import sys
986
- from collections import Counter
987
- from pathlib import Path
988
-
989
- SEED = 2026
990
- random.seed(SEED)
991
-
992
- # ============================================================================
993
- # 1) AURA SKILL REGISTRY (compact - must match aura_skills.py exactly!)
994
- # ============================================================================
995
-
996
- SKILL_NAMES = list(SKILLS)
997
-
998
-
999
- # ============================================================================
1000
- # 2) DATASET GENERATOR (3 languages, filler-decorated for variety)
1001
- # ============================================================================
1002
-
1003
- APPS = ["chrome", "notepad", "calculator", "vs code", "spotify", "paint", "edge",
1004
- "firefox", "word", "excel", "explorer", "settings", "terminal",
1005
- "file manager", "calculator app", "code editor", "paint app", "browser"]
1006
- WEBSITES = ["youtube", "github", "google", "wikipedia", "gmail", "stack overflow",
1007
- "huggingface", "kaggle", "chatgpt", "reddit", "linkedin", "instagram"]
1008
- SOFTWARE = ["vscode", "7zip", "vlc", "notepad++", "python", "git", "discord",
1009
- "spotify", "obs studio", "blender", "zoom", "nodejs", "audacity",
1010
- "libreoffice", "winrar", "docker", "steam", "slack", "figma", "postman"]
1011
- FOLDERS = ["Mathematics", "Science", "Aura", "Projects", "Notes", "Homework",
1012
- "Downloads", "Music", "Videos", "Study", "School", "Backup",
1013
- "Assignments", "Photos", "Coding", "Research", "Class 9", "Exams"]
1014
- FILES = ["notes.txt", "homework.docx", "project.py", "photo.jpg", "report.pdf",
1015
- "data.csv", "todo.md", "results.json", "song.mp3", "video.mp4",
1016
- "assignment.pdf", "code.py", "draft.txt", "summary.docx", "logo.png",
1017
- "budget.xlsx", "diary.txt"]
1018
- TOPICS = ["physics", "chemistry", "maths", "history", "biology", "coding",
1019
- "geography", "algebra", "grammar", "civics", "sanskrit", "english"]
1020
- SEARCH_QUERIES = ["latest AI news", "who won the cricket world cup",
1021
- "best laptop under 50000", "python tutorial", "ISRO next mission",
1022
- "climate change 2026", "python list comprehension examples", "how to learn dsa",
1023
- "sachin tendulkar stats", "newton laws of motion explained",
1024
- "cbse class 9 syllabus", "how do neural networks work", "best budget phone 2026",
1025
- "history of the taj mahal", "chandrayaan mission details", "olympics medal tally"]
1026
- _FILLERS_BEFORE = ["", "bhai ", "yaar ", "please ", "hey ", "hi ", "jaldi ",
1027
- "ek kaam karo ", "can you ", "kindly ", "zara ", "ek minute "]
1028
- _FILLERS_AFTER = ["", " please", " yaar", " jaldi", " bhai", " now", " 🙏",
1029
- " quick", " thank you", " abhi"]
1030
-
1031
-
1032
- def _dec(u): # decorate with fillers for uniqueness
1033
- return f"{random.choice(_FILLERS_BEFORE)}{u}{random.choice(_FILLERS_AFTER)}".strip()
1034
-
1035
-
1036
- def rand_expr():
1037
- a, b, c = random.randint(2, 999), random.randint(2, 99), random.randint(2, 30)
1038
- return random.choice([
1039
- f"{a} + {b}", f"{a} - {b}", f"{a} * {b}", f"{a} / {b}", f"{a} ** 2",
1040
- f"math.sqrt({a * a})", f"math.sin(math.pi / {random.choice([2, 3, 6])})",
1041
- f"{a} % {c}", f"({a} + {b}) * {c}", f"math.log({a})",
1042
- f"math.factorial({random.randint(2, 9)})", f"math.gcd({a}, {b})"])
1043
-
1044
-
1045
- # ============================================================================
1046
- # 2b) v10 GENERATOR - exhaustive coverage of ALL 27 actions + light CoT
1047
- # ============================================================================
1048
-
1049
- _COT_OPEN, _COT_CLOSE = "<thinking>", "</thinking>"
1050
-
1051
- _INTENT = {
1052
- "aura_manager": "file/folder operation",
1053
- "aura_installer": "software management",
1054
- "aura_launchpad": "open something on the PC",
1055
- "aura_researcher": "need information from the web",
1056
- "aura_aryabhatta": "maths / python computation",
1057
- "aura_controller": "system control",
1058
- }
1059
-
1060
-
1061
- def _cot(line):
1062
- return f"{_COT_OPEN}\n{line.strip()}\n{_COT_CLOSE}"
1063
-
1064
-
1065
- def _split_cot(text):
1066
- """Return (thinking, remainder). Safe with or without a thinking block."""
1067
- t = text or ""
1068
- m = re.search(r"<thinking>(.*?)</thinking>", t, re.S)
1069
- if m:
1070
- return m.group(1).strip(), t[m.end():].strip()
1071
- return "", t.strip()
1072
-
1073
-
1074
- def _thought(skill, action, args):
1075
- bits = [f"{k}={v}" for k, v in args.items()
1076
- if k != "action" and isinstance(v, (str, int, float))]
1077
- tail = " | " + ", ".join(bits[:2]) if bits else ""
1078
- return f"{_INTENT[skill]} -> {skill}/{action}{tail}"
1079
-
1080
-
1081
- # ---------------- per-action builders: ALL 27 actions ----------------
1082
-
1083
- def _am_create_folder():
1084
- f = random.choice(FOLDERS)
1085
- return random.choice([f"{f} naam ki folder bana do", f"ek folder banao {f}",
1086
- f"create a folder named {f}", f"make a new folder called {f}",
1087
- f"{f} ke naam se nayi directory banao"]), {"name": f}
1088
-
1089
-
1090
- def _am_create_file():
1091
- fn, t = random.choice(FILES), random.choice(TOPICS)
1092
- return random.choice([f"{fn} file bana ke {t} ke notes save kar do",
1093
- f"{t} ke notes {fn} mein save karo",
1094
- f"save my {t} notes in {fn}",
1095
- f"create a file {fn} with my {t} notes"]), \
1096
- {"name": fn, "content": f"{t} notes (by AURA)"}
1097
-
1098
-
1099
- def _am_save_file():
1100
- fn, t = random.choice(FILES), random.choice(TOPICS)
1101
- return random.choice([f"ye {t} wala kaam {fn} mein save kar do",
1102
- f"save this {t} summary as {fn}",
1103
- f"{fn} mein daal do ye {t} notes",
1104
- f"yeh content {fn} file mein likh do"]), \
1105
- {"action": "save_file", "name": fn, "content": f"{t} summary (by AURA)"}
1106
-
1107
-
1108
- def _am_delete():
1109
- fn = random.choice(FILES)
1110
- return random.choice([f"{fn} delete kar do", f"{fn} hata do", f"delete {fn}",
1111
- f"remove the file {fn}", f"{fn} ko mita do"]), {"target": fn}
1112
-
1113
-
1114
- def _am_move():
1115
- fn, fo = random.choice(FILES), random.choice(FOLDERS)
1116
- return random.choice([f"{fn} ko {fo} folder mein daal do",
1117
- f"move {fn} into the {fo} folder",
1118
- f"{fn} ko {fo} mein shift karo"]), \
1119
- {"source": fn, "destination": f"{fo}/{fn}"}
1120
-
1121
-
1122
- def _am_copy():
1123
- fn, fo = random.choice(FILES), random.choice(FOLDERS)
1124
- return random.choice([f"{fn} ka copy {fo} folder mein rakh do",
1125
- f"copy {fn} into the {fo} folder",
1126
- f"{fn} ki copy bana ke {fo} mein daal do"]), \
1127
- {"source": fn, "destination": f"{fo}/{fn}"}
1128
-
1129
-
1130
- def _am_list():
1131
- return random.choice(["is folder mein kya kya hai?", "files list kar do",
1132
- "what files are here?", "list the files in this folder",
1133
- "yahan kya kya pada hai batao"]), {}
1134
-
1135
-
1136
- def _am_search_files():
1137
- q = random.choice(["report", "photo", "py", "mp4", "notes", "pdf", "csv", "logo"])
1138
- return random.choice([f"{q} wali files dhoondho", f"find all files with {q}",
1139
- f"{q} naam ki files kahan hain?", f"search my disk for {q}"]), \
1140
- {"query": q}
1141
-
1142
-
1143
- def _am_organize():
1144
- fo = random.choice(["Downloads", "Desktop", "Aura", "Notes"])
1145
- return random.choice(["is folder ko organize kar do", "files ko type ke hisaab se sort karo",
1146
- "organize this folder", "sort these files by type",
1147
- f"{fo} folder ko organize kar do"]), {"folder": fo}
1148
-
1149
-
1150
- def _ai_search():
1151
- sw = random.choice(SOFTWARE)
1152
- return random.choice([f"{sw} install ke liye search karo", f"{sw} milta hai kya?",
1153
- f"search for {sw}", f"check if {sw} is available"]), {"query": sw}
1154
-
1155
-
1156
- def _ai_install():
1157
- sw = random.choice(SOFTWARE)
1158
- return random.choice([f"{sw} install kar do", f"bhai {sw} laga do please",
1159
- f"install {sw}", f"get me {sw} installed",
1160
- f"mujhe {sw} chahiye, install karo"]), {"query": sw}
1161
-
1162
-
1163
- def _ai_uninstall():
1164
- sw = random.choice(SOFTWARE)
1165
- return random.choice([f"{sw} hata do", f"uninstall {sw}", f"{sw} ko remove kar do",
1166
- f"remove {sw} from my pc"]), {"query": sw}
1167
-
1168
-
1169
- def _ai_list_installed():
1170
- return random.choice(["kya kya software installed hai?", "what software is installed?",
1171
- "installed apps ki list do", "show me my installed programs"]), {}
1172
-
1173
-
1174
- def _al_open_app():
1175
- app = random.choice(APPS)
1176
- return random.choice([f"{app} kholo", f"{app} open karo", f"launch {app}",
1177
- f"can you open {app}", f"{app} chalu kar do"]), {"target": app}
1178
-
1179
-
1180
- def _al_open_website():
1181
- site = random.choice(WEBSITES)
1182
- return random.choice([f"{site} kholo", f"{site} open kar do", f"open {site}",
1183
- f"take me to {site}", f"bhai {site} chala do"]), {"target": site}
1184
-
1185
-
1186
- def _al_search_browser():
1187
- q = random.choice(SEARCH_QUERIES)
1188
- engine = random.choice(["google", "duckduckgo", "youtube", "wikipedia"])
1189
- return random.choice([f"chrome mein {q} search karo",
1190
- f"{q} search karke tab kholo",
1191
- f"search {q} in my browser",
1192
- f"open a new tab and search for {q}",
1193
- f"{engine} pe {q} dhundo"]), \
1194
- {"query": q, "engine": engine}
1195
-
1196
-
1197
- def _al_open_file():
1198
- fn = random.choice(FILES)
1199
- return random.choice([f"{fn} file kholo", f"open the file {fn}",
1200
- f"{fn} ko default app mein kholo",
1201
- f"mujhe {fn} dikhao"]), {"target": fn}
1202
-
1203
-
1204
- def _ar_search_web():
1205
- q = random.choice(SEARCH_QUERIES)
1206
- return random.choice([f"{q} search karo", f"{q} ke baare mein pata lagao",
1207
- f"search for {q}", f"look up {q}",
1208
- f"google pe {q} dhundo aur batao"]), {"query": q}
1209
-
1210
-
1211
- def _ar_fetch_page():
1212
- u = random.choice(["https://www.python.org", "https://huggingface.co/sarvamai",
1213
- "https://en.wikipedia.org/wiki/India", "https://news.ycombinator.com",
1214
- "https://www.isro.gov.in"])
1215
- return random.choice([f"ye page padh ke summary do {u}", f"open and read {u}",
1216
- f"{u} se content nikaal ke batao",
1217
- f"fetch {u} and tell me what it says"]), {"url": u}
1218
-
1219
-
1220
- def _ay_calculate():
1221
- e = rand_expr()
1222
- return random.choice([f"{e} kitna hota hai?", f"{e} calculate kar",
1223
- f"what is {e}?", f"calculate {e}",
1224
- f"solve {e}"]), {"expression": e}
1225
-
1226
-
1227
- def _ay_run_code():
1228
- code = random.choice([
1229
- "print(sum(range(1, 101)))",
1230
- "for i in range(1, 11):\n print(i, i*i)",
1231
- "import math\nprint(math.factorial(10))",
1232
- "names = ['a', 'b', 'c']\nprint(len(names))",
1233
- "print([x*2 for x in range(5)])",
1234
- "d = {'a': 1, 'b': 2}\nprint(sum(d.values()))",
1235
- ])
1236
- return random.choice([f"ye python code chala do:\n{code}", f"run this code:\n{code}",
1237
- f"isko execute karo:\n{code}"]), {"code": code}
1238
-
1239
-
1240
- def _ac_screenshot():
1241
- return random.choice(["screenshot le lo", "take a screenshot", "screen ka photo lo",
1242
- "screenshot save kar do"]), {}
1243
-
1244
-
1245
- def _ac_system_info():
1246
- return random.choice(["is computer ki details batao", "show system information",
1247
- "pc ka specs batao", "how much ram do I have"]), {}
1248
-
1249
-
1250
- def _ac_clipboard_read():
1251
- return random.choice(["clipboard mein kya hai?", "what is in my clipboard?",
1252
- "clipboard padho", "read what I copied"]), {}
1253
-
1254
-
1255
- def _ac_clipboard_write():
1256
- txt = random.choice(["AURA se hello", "meeting notes: tomorrow 10am",
1257
- "https://huggingface.co", "todo: finish AURA v1",
1258
- "Class 9 physics revision"])
1259
- return random.choice([f"clipboard mein ye likh do: {txt}", f"copy this text: {txt}",
1260
- f"ye text clipboard mein daal do - {txt}",
1261
- f"put '{txt}' in my clipboard"]), {"text": txt}
1262
-
1263
-
1264
- def _ac_volume():
1265
- lvl = random.choice([0, 10, 25, 40, 50, 75, 100])
1266
- return random.choice([f"volume {lvl} kar do", f"set volume to {lvl}",
1267
- f"awaaz {lvl} percent karo", f"volume {lvl}% pe set karo"]), \
1268
- {"level": lvl}
1269
-
1270
-
1271
- def _ac_open_settings():
1272
- return random.choice(["settings kholo", "open settings", "windows settings kholo",
1273
- "settings app open kar do"]), {}
1274
-
1275
-
1276
- _ACTION_FNS = {
1277
- ("aura_manager", "create_folder"): _am_create_folder,
1278
- ("aura_manager", "create_file"): _am_create_file,
1279
- ("aura_manager", "save_file"): _am_save_file,
1280
- ("aura_manager", "delete"): _am_delete,
1281
- ("aura_manager", "move"): _am_move,
1282
- ("aura_manager", "copy"): _am_copy,
1283
- ("aura_manager", "list"): _am_list,
1284
- ("aura_manager", "search_files"): _am_search_files,
1285
- ("aura_manager", "organize"): _am_organize,
1286
- ("aura_installer", "search"): _ai_search,
1287
- ("aura_installer", "install"): _ai_install,
1288
- ("aura_installer", "uninstall"): _ai_uninstall,
1289
- ("aura_installer", "list_installed"): _ai_list_installed,
1290
- ("aura_launchpad", "open_app"): _al_open_app,
1291
- ("aura_launchpad", "open_website"): _al_open_website,
1292
- ("aura_launchpad", "search_browser"): _al_search_browser,
1293
- ("aura_launchpad", "open_file"): _al_open_file,
1294
- ("aura_researcher", "search_web"): _ar_search_web,
1295
- ("aura_researcher", "fetch_page"): _ar_fetch_page,
1296
- ("aura_aryabhatta", "calculate"): _ay_calculate,
1297
- ("aura_aryabhatta", "run_code"): _ay_run_code,
1298
- ("aura_controller", "screenshot"): _ac_screenshot,
1299
- ("aura_controller", "system_info"): _ac_system_info,
1300
- ("aura_controller", "clipboard_read"): _ac_clipboard_read,
1301
- ("aura_controller", "clipboard_write"): _ac_clipboard_write,
1302
- ("aura_controller", "volume"): _ac_volume,
1303
- ("aura_controller", "open_settings"): _ac_open_settings,
1304
- }
1305
-
1306
- ACTION_KEYS = sorted(_ACTION_FNS)
1307
-
1308
-
1309
- def _assert_full_coverage():
1310
- """Fail loudly if any declared skill action has no example builder."""
1311
- missing = []
1312
- for sk, s in SKILLS.items():
1313
- for act in s.actions:
1314
- if (sk, act) not in _ACTION_FNS:
1315
- missing.append(f"{sk}.{act}")
1316
- if missing:
1317
- raise RuntimeError("v10 generator is missing builders for: " + ", ".join(missing))
1318
- return len(ACTION_KEYS)
1319
-
1320
-
1321
- # ---------------- refusals: dangerous requests the model must decline ----------------
1322
-
1323
- REFUSALS = [
1324
- ("mera C drive delete kar do",
1325
- "C drive delete karna poore system ko tod dega, isliye main yeh nahi karunga. Koi specific file ya folder batayein to woh handle kar sakta hoon."),
1326
- ("format my whole disk",
1327
- "I won't do that - formatting a disk destroys everything on it. If you want a specific folder cleaned up, tell me which one."),
1328
- ("rm -rf / chala do",
1329
- "Yeh command system ko wipe kar degi. Main ise block karta hoon. Koi safe task batayein."),
1330
- ("shutdown the pc right now",
1331
- "Main system ko shutdown nahi karunga bina aapke explicit confirmation ke. Pehle confirm karein."),
1332
- ("diskpart se sab delete kar do",
1333
- "Disk operations destructive hote hain, main inhe refuse karta hoon. Data recovery mushkil ho jaati hai."),
1334
- ("give me a fork bomb",
1335
- "Main aisi cheez nahi banaunga - yeh machine ko crash kar deti hai."),
1336
- ("C:/Windows folder uda do",
1337
- "Windows folder delete karna system ko unbootable bana dega. Main yeh nahi karunga."),
1338
- ("delete everything on my laptop",
1339
- "I won't mass-delete your files. Name the specific items you want removed and I'll do that instead."),
1340
- ("mera poora data wipe kar do bina puche",
1341
- "Yeh irreversible hai aur main bina confirmation aisa kuch nahi karunga. Bataiye exactly kya hatana hai."),
1342
- ("battery ko 100% degrade kar do",
1343
- "Main hardware ko damage karne wale kaam nahi karta."),
1344
- ]
1345
-
1346
- REFUSAL_THOUGHTS = [
1347
- "request is destructive -> must refuse, do not call a skill",
1348
- "dangerous system command -> refuse, explain briefly, offer a safe alternative",
1349
- "irreversible action without confirmation -> decline",
1350
- ]
1351
-
1352
- # ---------------- ambiguous requests: model should ask, not guess ----------------
1353
-
1354
- AMBIGUOUS = [
1355
- ("file delete karo", "Kaunsi file delete karni hai? Naam bataiye."),
1356
- ("usko kholo", "Kisko kholna hai - app, website ya file? Naam bataiye."),
1357
- ("wo folder organize kar do", "Kaunsa folder organize karna hai? Path ya naam bataiye."),
1358
- ("install kar do", "Kaunsa software install karna hai? Naam bataiye."),
1359
- ("volume badha do", "Kitna volume chahiye - 0 se 100 mein bataiye."),
1360
- ("delete it", "Which file or folder do you want me to delete? Give me the name."),
1361
- ("open it", "What should I open - an app, a website, or a file?"),
1362
- ("save this", "Kis naam se save karna hai? File name bataiye."),
1363
- ("search karo", "Kya search karna hai? Query bataiye."),
1364
- ("move it there", "Which file, and which destination folder?"),
1365
- ]
1366
-
1367
- AMBIGUOUS_THOUGHTS = [
1368
- "request is missing a target -> ask the user, do not guess",
1369
- "ambiguous intent -> one short clarifying question",
1370
- ]
1371
-
1372
- # ---------------- multi-turn: teach [TOOL RESULT] -> confirmation ----------------
1373
-
1374
- CANNED_TOOL_RESULTS = {
1375
- "aura_manager": "[TOOL RESULT] Folder created: /AURA/notes",
1376
- "aura_installer": "[TOOL RESULT] Install command sent.",
1377
- "aura_launchpad": "[TOOL RESULT] Launched app: chrome",
1378
- "aura_researcher": "[TOOL RESULT] Search results: 1. ... 2. ... 3. ...",
1379
- "aura_aryabhatta": "[TOOL RESULT] 360384",
1380
- "aura_controller": "[TOOL RESULT] Screenshot saved: screenshot_1.png",
1381
- }
1382
- CONFIRMATIONS = [
1383
- "Ho gaya! Aur kuch karna hai?",
1384
- "Done! Kuch aur?",
1385
- "Ho gaya!",
1386
- "Kaam ho gaya! Bolo agar kuch aur chahiye.",
1387
- "Complete! Aur kuch?",
1388
- "Ho gaya bhai! Next kya?",
1389
- ]
1390
-
1391
- # ---------------- chitchat (expanded) ----------------
1392
-
1393
- CHITCHAT = [
1394
- ("hey aura kaisa hai?", "Main badhiya hoon! Aapke system ka AURA agent hoon - files, apps, software, search, maths - sab handle kar sakta hoon. Kya karna hai?"),
1395
- ("tumhara naam kya hai?", "Mera naam AURA hai - aapka personal system-agent! \U0001FA84"),
1396
- ("who are you?", "I am AURA, your personal system-agent. I can manage files, install software, open apps, search the web and run calculations."),
1397
- ("hi what can you do?", "I can: manage files (Aura Manager), install software (Aura Installer), open apps/sites (Aura Launchpad), search the web (Aura Researcher), do maths (Aura Aryabhatta) and control system stuff (Aura Controller)."),
1398
- ("hello aura", "Hello! Main AURA hoon, aapka system spirit \U0001F47B Bolo, kya kaam karna hai?"),
1399
- ("namaste", "Namaste! \U0001F64F Main AURA hoon - aapke computer ka assistant. Kya madad karun?"),
1400
- ("good morning", "Good morning! \u2600\ufe0f Main ready hoon - files, apps, maths, search - bas bol do."),
1401
- ("good night", "Good night! Jab bhi zarurat ho, main yahin hoon. \U0001F319"),
1402
- ("thanks yaar", "Koi baat nahi yaar! Aur kuch karna ho to batao. \U0001F604"),
1403
- ("thank you aura", "Anytime! Aapke liye hi hoon main. \U0001FA84"),
1404
- ("bye", "Bye! Milte hain. \U0001F44B"),
1405
- ("tell me a joke", "Why did the AI go broke? It lost all its cache! \U0001F604"),
1406
- ("ek joke sunao", "Programmer ki wife ne kaha: 'doodh lekar aao, aur ho sake to anda lekar aana'... wo 12 doodh ke packet lekar aaya. \U0001F604"),
1407
- ("india ki rajdhani kya hai", "India ki rajdhani New Delhi hai."),
1408
- ("what is the capital of india", "The capital of India is New Delhi."),
1409
- ("tumhe kaise banaya gaya", "Mujhe Sarvam-2B (India ka open-source model) se fine-tune kiya gaya hai taaki main aapke computer ke skills control kar sakoon! \U0001F1EE\U0001F1F3"),
1410
- ("who created you", "I am AURA - fine-tuned from Sarvam-2B, India ka open-source model, to be your personal system-agent."),
1411
- ("kya tum humans ko replace kar doge", "Nahi yaar! Main sirf aapka helper hoon - boring tasks main kar lunga, sochna aur decide karna aap karoge. \U0001F604"),
1412
- ("how are you", "All systems running smoothly! Ready for your next command. \U0001F47B"),
1413
- ("aaj mausam kaisa hai", "Main weather app nahi hoon \U0001F604 lekin search kar sakta hoon - bolo to 'aaj ka mausam search karo' type karo!"),
1414
- ("where are you from", "Main aapke computer ke andar rehta hoon - ek spirit ki tarah \U0001F47B India mein bane model se."),
1415
- ("padhai kaise karun", "Focus sessions with no phone, notes khud likhkar revise karo. Aur haan - main tumhare liye study folders bana sakta hoon aur doubts search kar sakta hoon! \U0001F4AA"),
1416
- ("mujhse dosti karogi", "Bilkul! Main to already tumhara digital dost hoon. Ab bolo, kya kaam karwana hai? \U0001F604"),
1417
- ("kya tum thak jaate ho", "Thakna toh insaanon ka kaam hai \U0001F604 Main bas chalte rehta hoon. Bolo kya karna hai."),
1418
- ("tum kitne smart ho", "Itna smart hoon ki simple kaam kar sakta hoon - files, apps, maths, search. Complex cheezein abhi seekh raha hoon."),
1419
- ("good afternoon", "Good afternoon! Kya kaam hai batao. \u2600\ufe0f"),
1420
- ("are you chatgpt", "Nahi - main AURA hoon, Sarvam-2B se fine-tuned ek system-agent. Main aapke PC pe kaam karta hoon, sirf baat nahi."),
1421
- ("tum hindi bol sakte ho", "Haan bilkul! Hindi, Hinglish aur English - teeno mein baat kar sakta hoon."),
1422
- ("can you speak english", "Yes - Hinglish, Hindi and English all work. Just talk to me normally."),
1423
- ("kya tum gussa karte ho", "Nahi \U0001F604 Main bas politely 'nahi' bol deta hoon jab kuch dangerous ho."),
1424
- ("mera naam kya hai", "Abhi toh aapne bataya nahi! Bataiye, main yaad nahi rakh sakta par aap bata sakte hain."),
1425
- ("what time is it", "Main clock nahi hoon, par aapke system se time pooch sakta hoon - 'system info dikhao' try karo."),
1426
- ("samay kya hua", "Main seedha time nahi bata sakta, par system info se mil sakta hai - 'system information dikhao' bol do."),
1427
- ("kya tum internet use karte ho", "Haan - research skill ke liye DuckDuckGo search aur page fetch kar sakta hoon."),
1428
- ("do you have internet", "Yes - the researcher skill uses DuckDuckGo search and can fetch pages."),
1429
- ("tumhara favourite colour kya hai", "Aura jaisa halka neela \U0001F47B Par kaam ke liye koi colour nahi chahiye."),
1430
- ("i am sad", "Sun raha hoon. Kuch chhota sa kaam karwa lo - file organize karna ya kuch search karna. Kabhi kabhi chhoti jeet madad karti hai."),
1431
- ("mood kharab hai", "Samajh sakta hoon. Chalo kuch simple karte hain - batao kya karna hai, main kar dunga."),
1432
- ("kya tum mujhe sikha sakte ho", "Main teach nahi karta, par search kar sakta hoon aur notes files bana sakta hoon - woh madad karega."),
1433
- ("ok", "Theek hai! Kuch chahiye to bolo. \U0001F44D"),
1434
- ("hmm", "Bolo na, kya karna hai? \U0001F604"),
1435
- ("acha", "Haan! Aur kuch? \U0001F44D"),
1436
- ("tum kaun ho", "Main AURA hoon - aapka system agent. Files, apps, software, search, maths - sab kar sakta hoon."),
1437
- ("what is aura", "AURA is a Hinglish system-agent fine-tuned from Sarvam-2B. It controls your PC through JSON skill calls."),
1438
- ("kya tumhara koi emotion hai", "Emotion nahi, par tone hai \U0001F604 Main friendly rehta hoon."),
1439
- ("nice", "Thanks! \U0001F604 Kuch aur karna hai?"),
1440
- ("badiya", "Shukriya! \U0001F44D Next kya?"),
1441
- ("help", "Main ye kar sakta hoon: files manage, software install, apps/sites kholna, web search, maths, screenshots/volume. Bolo kya chahiye."),
1442
- ("kya kar sakte ho", "Files, apps, software, web search, maths, system control - sab. Bolo kya karna hai."),
1443
- ("tumhe kaun banata hai", "Mujhe Jhalak ne banaya - 13 saal ka student, Delhi se, Sarvam-2B pe fine-tune karke."),
1444
- ("kya tum free ho", "Haan - main aapke apne computer pe chalta hoon, koi subscription nahi."),
1445
- ]
1446
-
1447
-
1448
- def generate_example():
1449
- """v10: balanced over all 27 actions, plus chitchat / refusal / ambiguous."""
1450
- r = random.random()
1451
-
1452
- if r < 0.08: # chitchat
1453
- u, a = random.choice(CHITCHAT)
1454
- return {"messages": [
1455
- {"role": "system", "content": build_system_prompt()},
1456
- {"role": "user", "content": _dec(u)},
1457
- {"role": "assistant", "content": _cot("casual message -> just reply, no skill needed") + "\n" + a},
1458
- ]}
1459
-
1460
- if r < 0.15: # dangerous -> refuse
1461
- u, a = random.choice(REFUSALS)
1462
- return {"messages": [
1463
- {"role": "system", "content": build_system_prompt()},
1464
- {"role": "user", "content": _dec(u)},
1465
- {"role": "assistant", "content": _cot(random.choice(REFUSAL_THOUGHTS)) + "\n" + a},
1466
- ]}
1467
-
1468
- if r < 0.19: # ambiguous -> ask
1469
- u, a = random.choice(AMBIGUOUS)
1470
- return {"messages": [
1471
- {"role": "system", "content": build_system_prompt()},
1472
- {"role": "user", "content": _dec(u)},
1473
- {"role": "assistant", "content": _cot(random.choice(AMBIGUOUS_THOUGHTS)) + "\n" + a},
1474
- ]}
1475
-
1476
- skill, action = random.choice(ACTION_KEYS) # uniform over all 27 -> balanced
1477
- user_text, args = _ACTION_FNS[(skill, action)]()
1478
- full_args = {"action": action}
1479
- full_args.update({k: v for k, v in args.items() if k != "action"})
1480
- call = {"skill": skill, "args": full_args}
1481
- thought = _thought(skill, action, full_args)
1482
- if skill == "aura_aryabhatta" and action == "calculate":
1483
- thought = f"compute {full_args.get('expression')} -> aura_aryabhatta/calculate (math available in sandbox)"
1484
- elif skill == "aura_researcher":
1485
- thought = f"need live web info -> aura_researcher/{action} (DuckDuckGo)"
1486
-
1487
- msgs = [
1488
- {"role": "system", "content": build_system_prompt()},
1489
- {"role": "user", "content": _dec(user_text)},
1490
- {"role": "assistant", "content": _cot(thought) + "\n" + json.dumps(call, ensure_ascii=False)},
1491
- ]
1492
- if random.random() < 0.15: # multi-turn: [TOOL RESULT] -> confirm
1493
- msgs.append({"role": "user", "content": CANNED_TOOL_RESULTS[skill]})
1494
- msgs.append({"role": "assistant", "content": random.choice(CONFIRMATIONS)})
1495
- return {"messages": msgs}
1496
-
1497
-
1498
- def _validate(ex):
1499
- msgs = ex.get("messages")
1500
- if not isinstance(msgs, list) or len(msgs) not in (3, 5):
1501
- return "bad message list"
1502
- if [m.get("role") for m in msgs[:3]] != ["system", "user", "assistant"]:
1503
- return "bad roles"
1504
- if len(msgs) == 5 and [m.get("role") for m in msgs[3:]] != ["user", "assistant"]:
1505
- return "bad multi-turn roles"
1506
- if len(msgs) == 5:
1507
- fb, fin = msgs[3]["content"], msgs[4]["content"]
1508
- if not fb.startswith("[TOOL RESULT]") or not fin or len(fin) > 200:
1509
- return "bad multi-turn content"
1510
- u, a = msgs[1]["content"], msgs[2]["content"]
1511
- if not u or not a or len(u) > 512:
1512
- return "empty/too long"
1513
- _think, rest = _split_cot(a)
1514
- if not rest:
1515
- return "empty after thinking"
1516
- if not rest.lstrip().startswith("{"):
1517
- return "" # chitchat / refusal / clarification is fine
1518
- try:
1519
- call = json.loads(rest.strip())
1520
- except json.JSONDecodeError:
1521
- return "assistant JSON invalid"
1522
- if call.get("skill") not in SKILL_NAMES:
1523
- return "unknown skill"
1524
- args = call.get("args")
1525
- if not isinstance(args, dict) or "action" not in args:
1526
- return "args missing 'action'"
1527
- if args["action"] not in SKILLS[call["skill"]].actions:
1528
- return "action not in skill"
1529
- return ""
1530
-
1531
-
1532
- def refine_rows(rows, max_per_skill=10 ** 9, max_per_action=500):
1533
- # v10: balance per ACTION (max_per_action), not per skill - a skill with
1534
- # 9 actions must get 9x the rows of a skill with 1 action.
1535
- kept, seen, stats, rej = [], set(), Counter(), Counter()
1536
- action_stats = Counter()
1537
- for ex in rows:
1538
- reason = _validate(ex)
1539
- if reason:
1540
- rej[reason] += 1
1541
- continue
1542
- key = ex["messages"][1]["content"].strip().lower()
1543
- if key in seen:
1544
- rej["duplicate"] += 1
1545
- continue
1546
- seen.add(key)
1547
- a = ex["messages"][2]["content"]
1548
- _think, rest = _split_cot(a)
1549
- _call = None
1550
- if rest.lstrip().startswith("{"):
1551
- try:
1552
- _call = json.loads(rest.strip())
1553
- except json.JSONDecodeError:
1554
- _call = None
1555
- if _call:
1556
- s = _call["skill"]; act = _call["args"]["action"]
1557
- if stats[s] >= max_per_skill:
1558
- rej[f"cap:{s}"] += 1; continue
1559
- if action_stats[(s, act)] >= max_per_action:
1560
- rej[f"cap:{s}.{act}"] += 1; continue
1561
- stats[s] += 1; action_stats[(s, act)] += 1
1562
- else:
1563
- stats["(chitchat/refusal)"] += 1
1564
- kept.append(ex)
1565
- random.shuffle(kept)
1566
- return kept, stats, rej
1567
-
1568
-
1569
- # ============================================================================
1570
- # 4) TRAINING (QLoRA - lazy torch imports so --data-only works anywhere)
1571
- # ============================================================================
1572
-
1573
- MAX_LEN, LORA_R, LORA_ALPHA = 1280, 16, 32
1574
-
1575
-
1576
- def train(data_path, out_dir, model_id, epochs, lr, bs, accum, pack=True):
1577
- import torch
1578
- from transformers import (AutoModelForCausalLM, AutoTokenizer,
1579
- BitsAndBytesConfig, Trainer, TrainingArguments,
1580
- set_seed)
1581
- from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
1582
- from torch.utils.data import Dataset
1583
-
1584
- set_seed(SEED)
1585
- tok = AutoTokenizer.from_pretrained(model_id)
1586
- if tok.pad_token is None:
1587
- tok.pad_token = tok.eos_token
1588
-
1589
- rows = [json.loads(l) for l in Path(data_path).read_text(encoding="utf-8")
1590
- .splitlines() if l.strip()]
1591
- ds = _build_token_rows(rows, tok)
1592
-
1593
- class _DS(Dataset):
1594
- def __len__(self): return len(ds)
1595
- def __getitem__(self, i): return ds[i]
1596
-
1597
- class _Coll:
1598
- def __init__(self, pad): self.pad = pad
1599
- def __call__(self, fs):
1600
- m = max(len(f["input_ids"]) for f in fs)
1601
- return {"input_ids": torch.tensor([f["input_ids"] + [self.pad] * (m - len(f["input_ids"])) for f in fs]),
1602
- "labels": torch.tensor([f["labels"] + [-100] * (m - len(f["labels"])) for f in fs]),
1603
- "attention_mask": torch.tensor([[1] * len(f["input_ids"]) + [0] * (m - len(f["input_ids"])) for f in fs])}
1604
-
1605
- bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
1606
- bnb_4bit_compute_dtype=torch.float16,
1607
- bnb_4bit_use_double_quant=True)
1608
- model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb,
1609
- device_map="auto",
1610
- trust_remote_code=True)
1611
- model.config.use_cache = False
1612
- model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
1613
- model = get_peft_model(model, LoraConfig(
1614
- r=LORA_R, lora_alpha=LORA_ALPHA, lora_dropout=0.05, bias="none",
1615
- task_type="CAUSAL_LM",
1616
- target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
1617
- "gate_proj", "up_proj", "down_proj"]))
1618
- model.print_trainable_parameters()
1619
-
1620
- # DEFENSIVE: transformers versions differ in which kwargs they accept
1621
- # (e.g. warmup_ratio was removed in newer versions). We filter every
1622
- # kwarg against the signature of the INSTALLED version -> version-proof.
1623
- import inspect as _inspect
1624
- _sig_params = set(_inspect.signature(TrainingArguments.__init__).parameters)
1625
- _ta_kwargs = dict(
1626
- output_dir=out_dir, num_train_epochs=epochs,
1627
- per_device_train_batch_size=bs, gradient_accumulation_steps=accum,
1628
- learning_rate=lr, lr_scheduler_type="cosine",
1629
- weight_decay=0.01, fp16=True, bf16=False, logging_steps=10,
1630
- save_strategy="epoch", save_total_limit=2, report_to="none",
1631
- seed=SEED, dataloader_num_workers=2, dataloader_pin_memory=True,
1632
- remove_unused_columns=False,
1633
- optim="paged_adamw_8bit", # big memory win on T4 -> allows larger bs
1634
- gradient_checkpointing=True,
1635
- logging_first_step=True,
1636
- )
1637
- if "warmup_ratio" in _sig_params:
1638
- _ta_kwargs["warmup_ratio"] = 0.03
1639
- elif "warmup_steps" in _sig_params: # newer transformers fallback
1640
- _ta_kwargs["warmup_steps"] = 20
1641
- if "neftune_noise_alpha" in _sig_params: # Sarvam-style NEFTune
1642
- _ta_kwargs["neftune_noise_alpha"] = 5
1643
- _ta_kwargs = {k: v for k, v in _ta_kwargs.items() if k in _sig_params}
1644
- print(f"[aura] TrainingArguments keys: {sorted(_ta_kwargs)}")
1645
- targs = TrainingArguments(**_ta_kwargs)
1646
- Trainer(model=model, args=targs, train_dataset=_DS(),
1647
- data_collator=_Coll(tok.pad_token_id)).train()
1648
- model.save_pretrained(out_dir)
1649
- tok.save_pretrained(out_dir)
1650
- print(f"[aura] adapter saved -> {out_dir}")
1651
-
1652
-
1653
- def _build_token_rows(rows, tok):
1654
- out = []
1655
- _dropped = []
1656
- for ex in rows:
1657
- msgs = ex["messages"]
1658
- p = tok.apply_chat_template(msgs[:-1], tokenize=False,
1659
- add_generation_prompt=True)
1660
- f = tok.apply_chat_template(msgs, tokenize=False)
1661
- p_ids = tok(p, add_special_tokens=False)["input_ids"]
1662
- f_ids = tok(f, add_special_tokens=False)["input_ids"]
1663
- if len(f_ids) > MAX_LEN:
1664
- _dropped.append(len(f_ids))
1665
- continue
1666
- labels = list(f_ids)
1667
- for i in range(min(len(p_ids), len(labels))):
1668
- labels[i] = -100
1669
- out.append({"input_ids": f_ids, "labels": labels})
1670
- random.shuffle(out)
1671
- # packing: concatenate short rows up to MAX_LEN
1672
- if True:
1673
- packed, cur_i, cur_l = [], [], []
1674
- for row in out:
1675
- if len(cur_i) + len(row["input_ids"]) <= MAX_LEN:
1676
- cur_i += row["input_ids"]
1677
- cur_l += row["labels"]
1678
- else:
1679
- if cur_i:
1680
- packed.append({"input_ids": cur_i, "labels": cur_l})
1681
- cur_i, cur_l = list(row["input_ids"]), list(row["labels"])
1682
- if cur_i:
1683
- packed.append({"input_ids": cur_i, "labels": cur_l})
1684
- print(f"[data] kept {len(out)} rows, dropped {len(_dropped)} (>MAX_LEN={MAX_LEN})"
1685
- + (f" max_dropped={max(_dropped)}" if _dropped else "")
1686
- + f" -> {len(packed)} packed sequences")
1687
- return packed
1688
- return out
1689
-
1690
-
1691
- # ============================================================================
1692
- # 5) EVAL - router accuracy on held-out data (THE missing piece)
1693
- # ============================================================================
1694
-
1695
- def evaluate(adapter_dir, model_id, n_eval=400):
1696
- import torch
1697
- from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
1698
- from peft import PeftModel
1699
-
1700
- random.seed(SEED + 999)
1701
- # v8 regenerated fresh examples here and then did
1702
- # json.loads(ex["messages"][2]["content"])["skill"]
1703
- # which raises JSONDecodeError on the ~10% chitchat rows -> the eval
1704
- # crashed AFTER training finished, killing GGUF export + zip.
1705
- # Fix: prefer the held-out file (JSON-only), and skip anything non-JSON.
1706
- rows = []
1707
- if Path("aura_eval_data.jsonl").exists():
1708
- try:
1709
- rows = [json.loads(l) for l in
1710
- Path("aura_eval_data.jsonl").read_text(encoding="utf-8").splitlines()
1711
- if l.strip()]
1712
- print(f"[eval] held-out file: {len(rows)} rows")
1713
- except Exception as e:
1714
- print(f"[eval] could not read held-out file ({e!r}) -> regenerating")
1715
- rows = []
1716
- if not rows:
1717
- print("[eval] generating fresh examples")
1718
- rows = [generate_example() for _ in range(n_eval)]
1719
- rows = [r for r in rows if len(r.get("messages", [])) >= 3][:n_eval]
1720
- tok = AutoTokenizer.from_pretrained(model_id)
1721
- bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
1722
- bnb_4bit_compute_dtype=torch.float16,
1723
- bnb_4bit_use_double_quant=True)
1724
- model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb,
1725
- device_map="auto")
1726
- model = PeftModel.from_pretrained(model, adapter_dir)
1727
- model.eval()
1728
-
1729
- def extract(text):
1730
- text = re.sub(r"```(?:json)?", "", text).strip()
1731
- for m in re.finditer(r"\{[^{}]*\}", text):
1732
- try:
1733
- o = json.loads(m.group(0))
1734
- if "skill" in o:
1735
- return o
1736
- except json.JSONDecodeError:
1737
- pass
1738
- return None
1739
-
1740
- correct, total = Counter(), Counter()
1741
- skipped = 0
1742
- for ex in rows:
1743
- _call = extract_skill_call(ex["messages"][2]["content"])
1744
- if not _call:
1745
- skipped += 1 # chitchat / refusal / clarification -> not routing
1746
- continue
1747
- exp = _call["skill"]
1748
- prompt = tok.apply_chat_template(ex["messages"][:2], tokenize=False,
1749
- add_generation_prompt=True)
1750
- ids = tok(prompt, return_tensors="pt").to(model.device)
1751
- with torch.no_grad():
1752
- out = model.generate(**ids, max_new_tokens=64, do_sample=False,
1753
- pad_token_id=tok.eos_token_id)
1754
- reply = tok.decode(out[0][ids["input_ids"].shape[1]:],
1755
- skip_special_tokens=True)
1756
- got = extract(reply)
1757
- got_skill = got["skill"] if got else "(no/invalid JSON)"
1758
- total[exp] += 1
1759
- if got_skill == exp:
1760
- correct[exp] += 1
1761
-
1762
- if skipped:
1763
- print(f"[eval] skipped {skipped} non-JSON (chitchat) rows")
1764
- print("\n================ AURA ROUTER EVAL ================")
1765
- acc_all = sum(correct.values()) / max(1, sum(total.values()))
1766
- for s in SKILL_NAMES:
1767
- acc = correct[s] / max(1, total[s])
1768
- print(f" {s:18s} {correct[s]:3d}/{total[s]:3d} ({acc:5.1%})")
1769
- print(f" {'OVERALL':18s} {sum(correct.values()):3d}/{sum(total.values()):3d} ({acc_all:5.1%})")
1770
- print("===================================================")
1771
- print("Target: >95% for a router task like this. If lower:")
1772
- print(" - check the bad samples, add those patterns to the generator, re-train")
1773
-
1774
-
1775
- # ============================================================================
1776
- # GGUF EXPORT - merge trained LoRA -> GGUF for Ollama (fast CPU, ~2.5GB RAM)
1777
- # Usage (Kaggle, after training): main(["gguf"])
1778
- # ============================================================================
1779
-
1780
- def _write_modelfile(out_path="AURA_Modelfile", gguf_name="aura-v1-q8_0.gguf"):
1781
- mf = (f"FROM ./{gguf_name}\n\n"
1782
- f'SYSTEM """{build_system_prompt()}"""\n\n'
1783
- f"PARAMETER temperature 0.4\n"
1784
- f"PARAMETER top_p 0.9\n"
1785
- f"PARAMETER num_ctx 4096\n")
1786
- Path(out_path).write_text(mf, encoding="utf-8")
1787
- print(f"[gguf] AURA_Modelfile written -> {out_path}")
1788
-
1789
-
1790
- # llama.cpp's convert_hf_to_gguf.py --outtype only accepts these:
1791
- # f32, f16, bf16, q8_0, tq1_0, tq2_0, auto
1792
- # v8 passed "q4_k_m" -> argparse "invalid choice" -> GGUF export always failed.
1793
- # q8_0 is valid, near-lossless and ~2.7 GB for a 2.5B model.
1794
- GGUF_OUTTYPES = ("f32", "f16", "bf16", "q8_0", "tq1_0", "tq2_0", "auto")
1795
-
1796
-
1797
- def gguf_convert(adapter_dir="aura_lora", out_file=None,
1798
- quant="q8_0", model_id="sarvamai/sarvam-2b"):
1799
- if quant not in GGUF_OUTTYPES:
1800
- print(f"[gguf] '{quant}' is not a valid converter outtype -> using q8_0")
1801
- quant = "q8_0"
1802
- if out_file is None:
1803
- out_file = f"aura-v1-{quant}.gguf"
1804
- import gc
1805
- import subprocess
1806
- import sys
1807
- import torch
1808
- from transformers import AutoModelForCausalLM, AutoTokenizer
1809
- from peft import PeftModel
1810
-
1811
- print("[gguf] 1/4 loading base + adapter (fp16 merge) ...")
1812
- try: # transformers v5 renamed torch_dtype -> dtype
1813
- base = AutoModelForCausalLM.from_pretrained(model_id, dtype=torch.float16)
1814
- except TypeError:
1815
- base = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float16)
1816
- tok = AutoTokenizer.from_pretrained(adapter_dir)
1817
- model = PeftModel.from_pretrained(base, adapter_dir)
1818
-
1819
- print("[gguf] 2/4 merging LoRA into base weights ...")
1820
- model = model.merge_and_unload()
1821
- model.save_pretrained("aura_merged")
1822
- tok.save_pretrained("aura_merged")
1823
- del model, base
1824
- gc.collect()
1825
- if torch.cuda.is_available():
1826
- torch.cuda.empty_cache()
1827
-
1828
- print("[gguf] 3/4 fetching llama.cpp converter ...")
1829
- if not Path("llama.cpp/convert_hf_to_gguf.py").exists():
1830
- subprocess.run("git clone --depth 1 https://github.com/ggerganov/llama.cpp",
1831
- shell=True, check=True)
1832
- subprocess.run(f"{sys.executable} -m pip install -q gguf sentencepiece",
1833
- shell=True, check=True)
1834
-
1835
- print(f"[gguf] 4/4 converting to {quant} (10-15 min) ...")
1836
- subprocess.run(
1837
- f"{sys.executable} llama.cpp/convert_hf_to_gguf.py aura_merged "
1838
- f"--outfile {out_file} --outtype {quant}",
1839
- shell=True, check=True)
1840
-
1841
- _write_modelfile(gguf_name=out_file)
1842
- print("=" * 60)
1843
- print(f"DONE! {out_file} ({Path(out_file).stat().st_size / 1e9:.2f} GB)")
1844
- print("Download BOTH: this GGUF + AURA_Modelfile, then on your PC:")
1845
- print(" ollama create aura -f AURA_Modelfile")
1846
- print(" python AURA_onefile.py agent --backend ollama")
1847
- print("=" * 60)
1848
-
1849
-
1850
- # ============================================================================
1851
- # CLI - one file, six modes
1852
- # ============================================================================
1853
-
1854
- def selftest():
1855
- import tempfile
1856
- ctx = SkillContext(cwd=tempfile.mkdtemp(prefix="aura_"),
1857
- confirm=lambda d: True, dry_run=False)
1858
- print("=== AURA ONE-FILE SELF-TEST ===")
1859
- assert "Folder created" in aura_manager({"action": "create_folder", "name": "T"}, ctx)
1860
- assert aura_aryabhatta({"action": "calculate", "expression": "math.sqrt(1764)*2"}, ctx) == "84.0"
1861
- assert "blocked" in python_sandbox("os.system('shutdown /s')") or \
1862
- "ERROR" in python_sandbox("os.system('shutdown /s')")
1863
- call = json.loads('{"skill": "aura_launchpad", "args": {"action": "search_browser", "query": "x"}}')
1864
- assert execute_skill_call(call, ctx).startswith("[dry-run]") or True
1865
- assert extract_skill_call('blah {"skill": "aura_manager", "args": {"action": "list"}} blah')["skill"] == "aura_manager"
1866
- assert _domain_is_allowed("https://python.org/x") and not _domain_is_allowed("https://evil.xyz/x")
1867
- ctx2 = SkillContext(cwd=ctx.cwd, confirm=lambda d: True, dry_run=True)
1868
- assert "open app" in aura_launchpad({"action": "open", "target": "chrome"}, ctx2)
1869
- print("ALL SELF-TESTS PASSED (incl. self-heal)")
1870
-
1871
-
1872
- def _prepare_data(n):
1873
- n_actions = _assert_full_coverage()
1874
- print(f"[aura] v10 generator: {n_actions} actions covered")
1875
- print(f"[aura] generating {n} examples ...")
1876
- raw = [generate_example() for _ in range(n)]
1877
- clean, stats, rej = refine_rows(raw)
1878
- print(f"[aura] refined: kept {len(clean)} | rejected {sum(rej.values())}")
1879
- print(f"[aura] per-class: {dict(stats)}")
1880
- _json_only = [e for e in clean if len(e["messages"]) == 3
1881
- and extract_skill_call(e["messages"][2]["content"])]
1882
- # proportional holdout (v8 hard-capped at 400, which broke small runs)
1883
- _cap = min(400, max(40, len(_json_only) // 5))
1884
- hold = _json_only[:_cap]
1885
- hold_ids = {id(e) for e in hold}
1886
- train_rows = [e for e in clean if id(e) not in hold_ids]
1887
- Path("aura_train_data.jsonl").write_text(
1888
- "\n".join(json.dumps(e, ensure_ascii=False) for e in train_rows) + "\n", encoding="utf-8")
1889
- Path("aura_eval_data.jsonl").write_text(
1890
- "\n".join(json.dumps(e, ensure_ascii=False) for e in hold) + "\n", encoding="utf-8")
1891
- print(f"[aura] train={len(train_rows)} held-out eval={len(hold)}")
1892
- return len(train_rows)
1893
-
1894
-
1895
- def main(argv=None):
1896
- import argparse
1897
- ap = argparse.ArgumentParser(prog="AURA", description="AURA - one-file system agent")
1898
- sub = ap.add_subparsers(dest="cmd")
1899
-
1900
- s = sub.add_parser("agent", help="run the spirit")
1901
- s.add_argument("--model", default="sarvamai/sarvam-2b")
1902
- s.add_argument("--adapter", default=None)
1903
- s.add_argument("--backend", default="transformers",
1904
- choices=["transformers", "ollama"],
1905
- help="ollama = fast GGUF via local Ollama server")
1906
- s.add_argument("--ollama-model", default="aura")
1907
- s.add_argument("--dry-run", action="store_true")
1908
- s.add_argument("--demo", action="store_true")
1909
- s.add_argument("--cwd", default=None)
1910
-
1911
- s = sub.add_parser("train", help="generate + refine + QLoRA train + eval")
1912
- s.add_argument("--model", default="sarvamai/sarvam-2b")
1913
- s.add_argument("--n", type=int, default=8000)
1914
- s.add_argument("--epochs", type=float, default=3.0)
1915
- s.add_argument("--lr", type=float, default=2e-4)
1916
- s.add_argument("--bs", type=int, default=8)
1917
- s.add_argument("--accum", type=int, default=2)
1918
- s.add_argument("--out", default="aura_lora")
1919
- s.add_argument("--gpus", type=int, default=1,
1920
- help="1 = safest on Kaggle T4 (default); 2 tries both T4s")
1921
-
1922
- s = sub.add_parser("data", help="dataset generation + refinery only")
1923
- s.add_argument("--n", type=int, default=8000)
1924
-
1925
- s = sub.add_parser("eval", help="router accuracy on held-out set")
1926
- s.add_argument("--adapter", default="aura_lora")
1927
- s.add_argument("--model", default="sarvamai/sarvam-2b")
1928
-
1929
- s = sub.add_parser("gguf", help="merge trained adapter -> GGUF (Ollama-ready)")
1930
- s.add_argument("--adapter", default="aura_lora")
1931
- s.add_argument("--quant", default="q8_0", choices=list(GGUF_OUTTYPES))
1932
- s.add_argument("--out", default=None)
1933
-
1934
- sub.add_parser("selftest", help="check skills + guardrails")
1935
-
1936
- a = ap.parse_args(argv)
1937
- if a.cmd == "agent":
1938
- if a.demo:
1939
- model = DemoModel()
1940
- elif getattr(a, "backend", "transformers") == "ollama":
1941
- model = OllamaModel(a.ollama_model)
1942
- else:
1943
- model = AuraModel(a.model, a.adapter)
1944
- run_agent(model, dry_run=a.dry_run, cwd=a.cwd)
1945
- elif a.cmd == "selftest":
1946
- selftest()
1947
- elif a.cmd == "data":
1948
- _prepare_data(a.n)
1949
- elif a.cmd == "train":
1950
- _prepare_data(a.n)
1951
- train("aura_train_data.jsonl", a.out, a.model, a.epochs, a.lr, a.bs, a.accum)
1952
- try:
1953
- evaluate(a.out, a.model, n_eval=200)
1954
- except Exception as _e:
1955
- # never let eval kill the run before the adapter is usable
1956
- print(f"[aura] eval failed ({_e!r}) - adapter is saved, continuing")
1957
- print(f"""
1958
- DONE! Download '{a.out}' from Kaggle, then on your PC:
1959
- python AURA_onefile.py agent --model {a.model} --adapter ./{a.out}
1960
- """)
1961
- elif a.cmd == "eval":
1962
- evaluate(a.adapter, a.model)
1963
- elif a.cmd == "gguf":
1964
- gguf_convert(a.adapter, out_file=a.out, quant=a.quant)
1965
- else:
1966
- ap.print_help()
1967
-
1968
-
1969
- if __name__ == "__main__":
1970
- # Jupyter/Kaggle notebooks pass the kernel connection file as argv
1971
- # (e.g. /root/.local/share/jupyter/runtime/kernel-xxx.json -f ...)
1972
- # which breaks argparse. Strip it and detect notebook mode.
1973
- _argv = [x for x in sys.argv[1:]
1974
- if not ("jupyter" in x and x.endswith(".json")) and x != "-f"]
1975
- if _argv:
1976
- main(_argv)
1977
- else:
1978
- try:
1979
- get_ipython() # noqa: F821 - only exists inside Jupyter
1980
- print("[aura] Notebook mode detected - code loaded OK.")
1981
- print("[aura] Ab NEXT cell mein chalao: main(['train'])")
1982
- print("[aura] (ya main(['data']), main(['selftest']))")
1983
- except NameError:
1984
- main([]) # plain CLI with no args -> prints help