Text Generation
PEFT
Safetensors
Hindi
English
agent
hinglish
indian-ai
system-agent
qlora
build-in-public
conversational
Instructions to use jhalakdev67dev/Aura-v1-lora-beta with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use jhalakdev67dev/Aura-v1-lora-beta with PEFT:
from peft import PeftModel from transformers import AutoModelForCausalLM base_model = AutoModelForCausalLM.from_pretrained("sarvamai/sarvam-2b") model = PeftModel.from_pretrained(base_model, "jhalakdev67dev/Aura-v1-lora-beta") - Notebooks
- Google Colab
- Kaggle
Delete AURA_onefile (3).py
Browse files- 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|