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
Upload AURA_onefile.py
Browse files- AURA_onefile.py +1609 -0
AURA_onefile.py
ADDED
|
@@ -0,0 +1,1609 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AURA - THE ONE FILE 👻🪄
|
| 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 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ============================================================================
|
| 21 |
+
# SECTION 1: SKILLS, TOOLS & GUARDRAILS (Aura Manager / Installer / Launchpad / Researcher / Aryabhatta / Controller)
|
| 22 |
+
# ============================================================================
|
| 23 |
+
|
| 24 |
+
import json
|
| 25 |
+
import os
|
| 26 |
+
import re
|
| 27 |
+
import shutil
|
| 28 |
+
import subprocess
|
| 29 |
+
import sys
|
| 30 |
+
import tempfile
|
| 31 |
+
import time
|
| 32 |
+
from dataclasses import dataclass, field
|
| 33 |
+
from typing import Callable, Dict, Any, List, Optional
|
| 34 |
+
|
| 35 |
+
IS_WINDOWS = os.name == "nt"
|
| 36 |
+
if IS_WINDOWS: # cmd/PowerShell default to cp1252 and crash on emoji prints
|
| 37 |
+
for _stream in (sys.stdout, sys.stderr):
|
| 38 |
+
try:
|
| 39 |
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
| 40 |
+
except Exception:
|
| 41 |
+
pass
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# ----------------------------------------------------------------------------
|
| 45 |
+
# Guardrails
|
| 46 |
+
# ----------------------------------------------------------------------------
|
| 47 |
+
|
| 48 |
+
# Commands / patterns that are plain dangerous -> hard block.
|
| 49 |
+
DANGEROUS_PATTERNS = [
|
| 50 |
+
r"\brm\s+-rf\s+/", # wipe root (unix)
|
| 51 |
+
r"\bformat\s+[a-z]:", # format drive (windows)
|
| 52 |
+
r"\bmkfs\b", # format (unix)
|
| 53 |
+
r"\bdel\s+/[sq]", # recursive delete (windows)
|
| 54 |
+
r"\brd\s+/s", # recursive remove dir (windows)
|
| 55 |
+
r"\bshutdown\b",
|
| 56 |
+
r"\bdiskpart\b",
|
| 57 |
+
r":\(\)\{.*\};:", # fork bomb
|
| 58 |
+
]
|
| 59 |
+
|
| 60 |
+
# Patterns that need explicit user confirmation before running.
|
| 61 |
+
RISKY_PATTERNS = [
|
| 62 |
+
r"\brm\b", r"\bdel\b", r"\brmdir\b", r"\brd\b", r"\bmv\b",
|
| 63 |
+
r"\bsudo\b", r"\bapt\b", r"\bpip\b", r"\binstall\b",
|
| 64 |
+
r"\breg(?:edit| add)\b", r"\bnetsh\b", r"\bchmod\b", r"\bchown\b",
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _matches(text: str, patterns: List[str]) -> bool:
|
| 69 |
+
text_l = text.lower()
|
| 70 |
+
return any(re.search(p, text_l) for p in patterns)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def default_confirm(action_desc: str) -> bool:
|
| 74 |
+
"""Ask the human before a destructive action. Swap for UI later."""
|
| 75 |
+
try:
|
| 76 |
+
ans = input(f"\n[AURA CONFIRM] {action_desc}\nType 'yes' to allow: ")
|
| 77 |
+
return ans.strip().lower() in ("yes", "y")
|
| 78 |
+
except Exception:
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def default_log(msg: str) -> None:
|
| 83 |
+
print(f"[aura] {msg}")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# ----------------------------------------------------------------------------
|
| 87 |
+
# Python sandbox (used by Aura Aryabhatta + as a core tool)
|
| 88 |
+
# ----------------------------------------------------------------------------
|
| 89 |
+
|
| 90 |
+
SANDBOX_BLOCKLIST = DANGEROUS_PATTERNS + [r"\bshutdown\b", r"\bRestart-Computer\b"]
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def python_sandbox(code: str, timeout: int = 10) -> str:
|
| 94 |
+
"""Run Python code in a fresh subprocess with a timeout.
|
| 95 |
+
|
| 96 |
+
NOT a hard security boundary - it stops accidents (infinite loops,
|
| 97 |
+
fat-fingered deletes), not determined attackers. Run AURA's risky
|
| 98 |
+
skills inside a Docker container / VM for real isolation.
|
| 99 |
+
"""
|
| 100 |
+
if _matches(code, SANDBOX_BLOCKLIST):
|
| 101 |
+
return "ERROR: command matches blocked pattern (safety guardrail)."
|
| 102 |
+
|
| 103 |
+
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False,
|
| 104 |
+
encoding="utf-8") as f:
|
| 105 |
+
f.write(code)
|
| 106 |
+
path = f.name
|
| 107 |
+
try:
|
| 108 |
+
proc = subprocess.run(
|
| 109 |
+
[sys.executable, path],
|
| 110 |
+
capture_output=True, text=True, timeout=timeout,
|
| 111 |
+
)
|
| 112 |
+
out = (proc.stdout or "") + (("\nSTDERR: " + proc.stderr) if proc.stderr else "")
|
| 113 |
+
return out.strip() or "(no output)"
|
| 114 |
+
except subprocess.TimeoutExpired:
|
| 115 |
+
return f"ERROR: code timed out after {timeout}s."
|
| 116 |
+
except Exception as e:
|
| 117 |
+
return f"ERROR: {e}"
|
| 118 |
+
finally:
|
| 119 |
+
try:
|
| 120 |
+
os.remove(path)
|
| 121 |
+
except OSError:
|
| 122 |
+
pass
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def shell(command: str, timeout: int = 60,
|
| 126 |
+
confirm: Callable[[str], bool] = default_confirm) -> str:
|
| 127 |
+
"""Run a shell command (cmd on Windows, bash elsewhere) with guardrails."""
|
| 128 |
+
if _matches(command, DANGEROUS_PATTERNS):
|
| 129 |
+
return "ERROR: command blocked by AURA safety guardrail."
|
| 130 |
+
if _matches(command, RISKY_PATTERNS) and not confirm(f"Run shell command: {command}"):
|
| 131 |
+
return "Cancelled by user."
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
proc = subprocess.run(command, shell=True, capture_output=True,
|
| 135 |
+
text=True, timeout=timeout)
|
| 136 |
+
out = (proc.stdout or "").strip()
|
| 137 |
+
err = (proc.stderr or "").strip()
|
| 138 |
+
result = out + (("\nSTDERR: " + err) if err else "")
|
| 139 |
+
return result.strip() or "(no output)"
|
| 140 |
+
except subprocess.TimeoutExpired:
|
| 141 |
+
return f"ERROR: command timed out after {timeout}s."
|
| 142 |
+
except Exception as e:
|
| 143 |
+
return f"ERROR: {e}"
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
# ----------------------------------------------------------------------------
|
| 147 |
+
# Skill context (cwd, confirm, log, dry-run)
|
| 148 |
+
# ----------------------------------------------------------------------------
|
| 149 |
+
|
| 150 |
+
@dataclass
|
| 151 |
+
class SkillContext:
|
| 152 |
+
cwd: str = os.path.expanduser("~")
|
| 153 |
+
confirm: Callable[[str], bool] = default_confirm
|
| 154 |
+
log: Callable[[str], None] = default_log
|
| 155 |
+
dry_run: bool = False # if True: print what WOULD happen
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# ----------------------------------------------------------------------------
|
| 159 |
+
# 1) AURA MANAGER - files
|
| 160 |
+
# ----------------------------------------------------------------------------
|
| 161 |
+
|
| 162 |
+
def aura_manager(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 163 |
+
action = args.get("action")
|
| 164 |
+
path = os.path.join(ctx.cwd, args.get("path", "")) if args.get("path") else ctx.cwd
|
| 165 |
+
|
| 166 |
+
def would(msg):
|
| 167 |
+
ctx.log(f"[dry-run] {msg}")
|
| 168 |
+
return f"[dry-run] {msg}"
|
| 169 |
+
|
| 170 |
+
if action == "create_folder":
|
| 171 |
+
name = args.get("name", "NewFolder")
|
| 172 |
+
full = os.path.join(path, name)
|
| 173 |
+
if ctx.dry_run: return would(f"create folder {full}")
|
| 174 |
+
os.makedirs(full, exist_ok=True)
|
| 175 |
+
return f"Folder created: {full}"
|
| 176 |
+
|
| 177 |
+
if action == "create_file":
|
| 178 |
+
name = args.get("name", "note.txt")
|
| 179 |
+
content = args.get("content", "")
|
| 180 |
+
full = os.path.join(path, name)
|
| 181 |
+
if ctx.dry_run: return would(f"create file {full}")
|
| 182 |
+
os.makedirs(path, exist_ok=True)
|
| 183 |
+
with open(full, "w", encoding="utf-8") as f:
|
| 184 |
+
f.write(content)
|
| 185 |
+
return f"File saved: {full}"
|
| 186 |
+
|
| 187 |
+
if action == "save_file":
|
| 188 |
+
return aura_manager({**args, "action": "create_file"}, ctx)
|
| 189 |
+
|
| 190 |
+
if action == "delete":
|
| 191 |
+
target = args.get("target", "")
|
| 192 |
+
full = os.path.join(ctx.cwd, target)
|
| 193 |
+
if not os.path.exists(full):
|
| 194 |
+
return f"ERROR: not found: {full}"
|
| 195 |
+
if ctx.dry_run: return would(f"delete {full}")
|
| 196 |
+
if not ctx.confirm(f"DELETE {full} (cannot be undone!)"):
|
| 197 |
+
return "Cancelled by user."
|
| 198 |
+
if os.path.isdir(full):
|
| 199 |
+
shutil.rmtree(full)
|
| 200 |
+
else:
|
| 201 |
+
os.remove(full)
|
| 202 |
+
return f"Deleted: {full}"
|
| 203 |
+
|
| 204 |
+
if action == "move":
|
| 205 |
+
src = os.path.join(ctx.cwd, args["source"])
|
| 206 |
+
dst = os.path.join(ctx.cwd, args["destination"])
|
| 207 |
+
if not os.path.exists(src):
|
| 208 |
+
return f"ERROR: source not found: {src}"
|
| 209 |
+
if ctx.dry_run: return would(f"move {src} -> {dst}")
|
| 210 |
+
if os.path.exists(dst) and not ctx.confirm(f"OVERWRITE {dst}?"):
|
| 211 |
+
return "Cancelled by user."
|
| 212 |
+
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
| 213 |
+
shutil.move(src, dst)
|
| 214 |
+
return f"Moved: {src} -> {dst}"
|
| 215 |
+
|
| 216 |
+
if action == "copy":
|
| 217 |
+
src = os.path.join(ctx.cwd, args["source"])
|
| 218 |
+
dst = os.path.join(ctx.cwd, args["destination"])
|
| 219 |
+
if not os.path.exists(src):
|
| 220 |
+
return f"ERROR: source not found: {src}"
|
| 221 |
+
if ctx.dry_run: return would(f"copy {src} -> {dst}")
|
| 222 |
+
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
| 223 |
+
shutil.copy2(src, dst)
|
| 224 |
+
return f"Copied: {src} -> {dst}"
|
| 225 |
+
|
| 226 |
+
if action == "list":
|
| 227 |
+
if ctx.dry_run: return would(f"list {path}")
|
| 228 |
+
try:
|
| 229 |
+
items = sorted(os.listdir(path))
|
| 230 |
+
return "\n".join(items) or "(empty folder)"
|
| 231 |
+
except Exception as e:
|
| 232 |
+
return f"ERROR: {e}"
|
| 233 |
+
|
| 234 |
+
if action == "search_files":
|
| 235 |
+
query = args.get("query", "").lower()
|
| 236 |
+
root = os.path.join(ctx.cwd, args.get("root", "."))
|
| 237 |
+
hits = []
|
| 238 |
+
for dirpath, _dirs, files in os.walk(root):
|
| 239 |
+
for fn in files:
|
| 240 |
+
if query in fn.lower():
|
| 241 |
+
hits.append(os.path.join(dirpath, fn))
|
| 242 |
+
if len(hits) >= 50:
|
| 243 |
+
break
|
| 244 |
+
return "\n".join(hits) if hits else "No matching files found."
|
| 245 |
+
|
| 246 |
+
if action == "organize":
|
| 247 |
+
"""Move files in a folder into sub-folders by extension."""
|
| 248 |
+
folder = os.path.join(ctx.cwd, args.get("folder", "."))
|
| 249 |
+
if not os.path.isdir(folder):
|
| 250 |
+
return f"ERROR: folder not found: {folder}"
|
| 251 |
+
moved = 0
|
| 252 |
+
for fn in os.listdir(folder):
|
| 253 |
+
src = os.path.join(folder, fn)
|
| 254 |
+
if not os.path.isfile(src):
|
| 255 |
+
continue
|
| 256 |
+
ext = os.path.splitext(fn)[1].lstrip(".").lower() or "misc"
|
| 257 |
+
dst_dir = os.path.join(folder, ext)
|
| 258 |
+
if ctx.dry_run:
|
| 259 |
+
would(f"move {src} -> {dst_dir}")
|
| 260 |
+
else:
|
| 261 |
+
os.makedirs(dst_dir, exist_ok=True)
|
| 262 |
+
shutil.move(src, os.path.join(dst_dir, fn))
|
| 263 |
+
moved += 1
|
| 264 |
+
return f"Organized {moved} files into extension folders inside {folder}."
|
| 265 |
+
|
| 266 |
+
return (f"ERROR: unknown action '{action}' for aura_manager. Valid actions: "
|
| 267 |
+
"create_folder, create_file, save_file, delete, move, copy, list, search_files, organize")
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
# ----------------------------------------------------------------------------
|
| 271 |
+
# 2) AURA INSTALLER - software installation (guarded)
|
| 272 |
+
# ----------------------------------------------------------------------------
|
| 273 |
+
|
| 274 |
+
# Only these domains may be used for the web fallback download.
|
| 275 |
+
OFFICIAL_DOMAINS = {
|
| 276 |
+
"microsoft.com", "store.microsoft.com", "visualstudio.com",
|
| 277 |
+
"code.visualstudio.com", "google.com", "mozilla.org", "notepad-plus-plus.org",
|
| 278 |
+
"7-zip.org", "python.org", "videolan.org", "vlc.cc", "discord.com",
|
| 279 |
+
"slack.com", "zoom.us", "adobe.com", "spotify.com", "obsproject.com",
|
| 280 |
+
"git-scm.com", "github.com", "gitlab.com", "blender.org", "audacityteam.org",
|
| 281 |
+
"libreoffice.org", "winrar.com", "ubuntu.com", "nodejs.org", "docker.com",
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _domain_is_allowed(url: str) -> bool:
|
| 286 |
+
try:
|
| 287 |
+
host = re.sub(r"^https?://", "", url).split("/")[0].lower()
|
| 288 |
+
host = host.split(":")[0]
|
| 289 |
+
return any(host == d or host.endswith("." + d) for d in OFFICIAL_DOMAINS)
|
| 290 |
+
except Exception:
|
| 291 |
+
return False
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def aura_installer(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 295 |
+
action = args.get("action")
|
| 296 |
+
query = args.get("query", "")
|
| 297 |
+
|
| 298 |
+
if action == "search":
|
| 299 |
+
if not IS_WINDOWS:
|
| 300 |
+
return "ERROR: winget search is Windows-only (use your package manager)."
|
| 301 |
+
out = shell(f'winget search "{query}" --accept-source-agreements',
|
| 302 |
+
confirm=lambda _: True)
|
| 303 |
+
return out or "No results found."
|
| 304 |
+
|
| 305 |
+
if action == "install":
|
| 306 |
+
app_id = args.get("app_id", query)
|
| 307 |
+
url = args.get("download_url") # optional web fallback
|
| 308 |
+
if url:
|
| 309 |
+
if not _domain_is_allowed(url):
|
| 310 |
+
return (f"REFUSED: {url} is not in AURA's official-domains "
|
| 311 |
+
f"allowlist. Install manually if you trust it.")
|
| 312 |
+
if not ctx.confirm(f"Download and install from {url}"):
|
| 313 |
+
return "Cancelled by user."
|
| 314 |
+
out = shell(f'curl -L -o setup_installer "{url}" && '
|
| 315 |
+
f'start setup_installer', confirm=lambda _: True)
|
| 316 |
+
return out
|
| 317 |
+
if not IS_WINDOWS:
|
| 318 |
+
return "ERROR: winget install is Windows-only."
|
| 319 |
+
if not ctx.confirm(f"Install '{app_id}' via winget"):
|
| 320 |
+
return "Cancelled by user."
|
| 321 |
+
out = shell(f'winget install --id "{app_id}" -e '
|
| 322 |
+
f'--accept-source-agreements --accept-package-agreements',
|
| 323 |
+
confirm=lambda _: True)
|
| 324 |
+
return out or "Install command sent."
|
| 325 |
+
|
| 326 |
+
if action == "uninstall":
|
| 327 |
+
app_id = args.get("app_id", query)
|
| 328 |
+
if not ctx.confirm(f"UNINSTALL '{app_id}'"):
|
| 329 |
+
return "Cancelled by user."
|
| 330 |
+
return shell(f'winget uninstall --id "{app_id}" -e',
|
| 331 |
+
confirm=lambda _: True) if IS_WINDOWS else \
|
| 332 |
+
"ERROR: winget uninstall is Windows-only."
|
| 333 |
+
|
| 334 |
+
if action == "list_installed":
|
| 335 |
+
return shell("winget list", confirm=lambda _: True) if IS_WINDOWS \
|
| 336 |
+
else "ERROR: winget list is Windows-only."
|
| 337 |
+
|
| 338 |
+
return "ERROR: unknown action '%s' for aura_installer. Valid actions: search, install, uninstall, list_installed" % action
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
# ----------------------------------------------------------------------------
|
| 342 |
+
# 3) AURA LAUNCHPAD - open apps / websites / files
|
| 343 |
+
# ----------------------------------------------------------------------------
|
| 344 |
+
|
| 345 |
+
APP_ALIASES = {
|
| 346 |
+
"chrome": "chrome", "google chrome": "chrome",
|
| 347 |
+
"notepad": "notepad", "calculator": "calc",
|
| 348 |
+
"vscode": "code", "vs code": "code", "visual studio code": "code",
|
| 349 |
+
"spotify": "spotify", "explorer": "explorer", "file manager": "explorer",
|
| 350 |
+
"cmd": "cmd", "terminal": IS_WINDOWS and "cmd" or "x-terminal-emulator",
|
| 351 |
+
"paint": "mspaint", "word": "winword", "excel": "excel",
|
| 352 |
+
"edge": "msedge", "firefox": "firefox", "settings": "ms-settings:",
|
| 353 |
+
}
|
| 354 |
+
SITE_ALIASES = {
|
| 355 |
+
"youtube": "https://www.youtube.com",
|
| 356 |
+
"google": "https://www.google.com",
|
| 357 |
+
"github": "https://github.com",
|
| 358 |
+
"wikipedia": "https://www.wikipedia.org",
|
| 359 |
+
"gmail": "https://mail.google.com",
|
| 360 |
+
"stack overflow": "https://stackoverflow.com",
|
| 361 |
+
"huggingface": "https://huggingface.co",
|
| 362 |
+
"kaggle": "https://www.kaggle.com",
|
| 363 |
+
"chatgpt": "https://chat.openai.com",
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
|
| 367 |
+
def aura_launchpad(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 368 |
+
action = args.get("action")
|
| 369 |
+
target = (args.get("target") or "").strip()
|
| 370 |
+
|
| 371 |
+
# SELF-HEAL: small models sometimes emit a wrong verb ('open' etc.) -
|
| 372 |
+
# infer the right action from the target instead of failing.
|
| 373 |
+
_valid = {"open_app", "open_website", "search_browser", "open_file"}
|
| 374 |
+
if action not in _valid and target:
|
| 375 |
+
_ka, _ks = target.lower(), target.lower().replace(" ", "")
|
| 376 |
+
if _ka in APP_ALIASES:
|
| 377 |
+
action = "open_app"
|
| 378 |
+
elif _ks in SITE_ALIASES or "://" in target or \
|
| 379 |
+
target.lower().endswith((".com", ".org", ".in", ".net", ".io")):
|
| 380 |
+
action = "open_website"
|
| 381 |
+
elif os.path.exists(os.path.join(ctx.cwd, target)):
|
| 382 |
+
action = "open_file"
|
| 383 |
+
if action in _valid:
|
| 384 |
+
ctx.log(f"[self-heal] inferred action '{action}' from target '{target}'")
|
| 385 |
+
|
| 386 |
+
if action == "open_app":
|
| 387 |
+
key = target.lower()
|
| 388 |
+
exe = APP_ALIASES.get(key, target)
|
| 389 |
+
if ctx.dry_run:
|
| 390 |
+
return f"[dry-run] open app '{exe}'"
|
| 391 |
+
if IS_WINDOWS:
|
| 392 |
+
os.system(f'start "" "{exe}"')
|
| 393 |
+
return f"Launched app: {target}"
|
| 394 |
+
return shell(f"{exe} &", confirm=lambda _: True) or f"Tried to launch: {target}"
|
| 395 |
+
|
| 396 |
+
if action == "open_website":
|
| 397 |
+
key = target.lower().replace(" ", "")
|
| 398 |
+
url = SITE_ALIASES.get(key, target if "://" in target
|
| 399 |
+
else "https://" + target)
|
| 400 |
+
if ctx.dry_run:
|
| 401 |
+
return f"[dry-run] open website {url}"
|
| 402 |
+
import webbrowser
|
| 403 |
+
webbrowser.open(url)
|
| 404 |
+
return f"Opened website: {url}"
|
| 405 |
+
|
| 406 |
+
if action == "search_browser":
|
| 407 |
+
# Free in-browser search (no API key): opens the search URL in a NEW TAB
|
| 408 |
+
query = args.get("query", "")
|
| 409 |
+
engine = args.get("engine", "duckduckgo")
|
| 410 |
+
import urllib.parse
|
| 411 |
+
import webbrowser
|
| 412 |
+
q = urllib.parse.quote_plus(query)
|
| 413 |
+
urls = {
|
| 414 |
+
"google": f"https://www.google.com/search?q={q}",
|
| 415 |
+
"duckduckgo": f"https://duckduckgo.com/?q={q}",
|
| 416 |
+
"youtube": f"https://www.youtube.com/results?search_query={q}",
|
| 417 |
+
"wikipedia": f"https://en.wikipedia.org/w/index.php?search={q}",
|
| 418 |
+
}
|
| 419 |
+
url = urls.get(engine, urls["duckduckgo"])
|
| 420 |
+
browser = args.get("browser", "")
|
| 421 |
+
if ctx.dry_run:
|
| 422 |
+
return f"[dry-run] open new tab: {url}"
|
| 423 |
+
if browser and IS_WINDOWS:
|
| 424 |
+
os.system(f'start "" "{browser}" "{url}"')
|
| 425 |
+
else:
|
| 426 |
+
webbrowser.open(url) # opens a new tab in the default browser
|
| 427 |
+
return f"Opened new tab ({engine} search): {query}"
|
| 428 |
+
|
| 429 |
+
if action == "open_file":
|
| 430 |
+
full = os.path.join(ctx.cwd, target)
|
| 431 |
+
if not os.path.exists(full):
|
| 432 |
+
return f"ERROR: file not found: {full}"
|
| 433 |
+
if ctx.dry_run:
|
| 434 |
+
return f"[dry-run] open file {full}"
|
| 435 |
+
if IS_WINDOWS:
|
| 436 |
+
os.startfile(full) # noqa (windows-only)
|
| 437 |
+
return f"Opened file: {full}"
|
| 438 |
+
return shell(f'xdg-open "{full}"', confirm=lambda _: True)
|
| 439 |
+
|
| 440 |
+
return "ERROR: unknown action '%s' for aura_launchpad. Valid actions: open_app, open_website, search_browser, open_file" % action
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
# ----------------------------------------------------------------------------
|
| 444 |
+
# 4) AURA RESEARCHER - web search & fetch (stdlib only)
|
| 445 |
+
# ----------------------------------------------------------------------------
|
| 446 |
+
|
| 447 |
+
def _web_search_ddg(query: str, max_results: int = 5) -> str:
|
| 448 |
+
"""DuckDuckGo HTML search - no API key needed. Requires `requests`."""
|
| 449 |
+
try:
|
| 450 |
+
import requests
|
| 451 |
+
except ImportError:
|
| 452 |
+
return "ERROR: install `requests` (pip install requests)."
|
| 453 |
+
try:
|
| 454 |
+
r = requests.post(
|
| 455 |
+
"https://html.duckduckgo.com/html/",
|
| 456 |
+
data={"q": query},
|
| 457 |
+
headers={"User-Agent": "Mozilla/5.0 (AURA agent)"},
|
| 458 |
+
timeout=15,
|
| 459 |
+
)
|
| 460 |
+
links = re.findall(r'class="result__a"[^>]*href="([^"]+)"[^>]*>(.*?)</a>',
|
| 461 |
+
r.text, re.S)[:max_results]
|
| 462 |
+
if not links:
|
| 463 |
+
return "No results found."
|
| 464 |
+
lines = []
|
| 465 |
+
for url, title in links:
|
| 466 |
+
title = re.sub(r"<[^>]+>", "", title).strip()
|
| 467 |
+
lines.append(f"- {title}\n {url}")
|
| 468 |
+
return "\n".join(lines)
|
| 469 |
+
except Exception as e:
|
| 470 |
+
return f"ERROR: search failed ({e})"
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def aura_researcher(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 474 |
+
action = args.get("action")
|
| 475 |
+
if action == "search_web":
|
| 476 |
+
return _web_search_ddg(args.get("query", ""))
|
| 477 |
+
if action == "fetch_page":
|
| 478 |
+
url = args.get("url", "")
|
| 479 |
+
try:
|
| 480 |
+
import requests
|
| 481 |
+
except ImportError:
|
| 482 |
+
return "ERROR: install `requests`."
|
| 483 |
+
try:
|
| 484 |
+
r = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (AURA)"},
|
| 485 |
+
timeout=15)
|
| 486 |
+
text = re.sub(r"<script.*?</script>|<style.*?</style>", "",
|
| 487 |
+
r.text, flags=re.S)
|
| 488 |
+
text = re.sub(r"<[^>]+>", " ", text)
|
| 489 |
+
text = re.sub(r"\s+", " ", text).strip()
|
| 490 |
+
return text[:3000] + ("..." if len(text) > 3000 else "")
|
| 491 |
+
except Exception as e:
|
| 492 |
+
return f"ERROR: fetch failed ({e})"
|
| 493 |
+
return "ERROR: unknown action '%s' for aura_researcher. Valid actions: search_web, fetch_page" % action
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
# ----------------------------------------------------------------------------
|
| 497 |
+
# 5) AURA ARYABHATTA - math
|
| 498 |
+
# ----------------------------------------------------------------------------
|
| 499 |
+
|
| 500 |
+
def aura_aryabhatta(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 501 |
+
action = args.get("action")
|
| 502 |
+
if action == "calculate":
|
| 503 |
+
expr = args.get("expression", "")
|
| 504 |
+
# safe-ish eval: no builtins, expose math module + its functions
|
| 505 |
+
code = ("import math\n"
|
| 506 |
+
"_ns = {'__builtins__': {}, 'math': math, **math.__dict__}\n"
|
| 507 |
+
f"print(eval({expr!r}, _ns))")
|
| 508 |
+
return python_sandbox(code, timeout=10)
|
| 509 |
+
if action == "run_code":
|
| 510 |
+
return python_sandbox(args.get("code", ""), timeout=15)
|
| 511 |
+
return "ERROR: unknown action '%s' for aura_aryabhatta. Valid actions: calculate, run_code" % action
|
| 512 |
+
|
| 513 |
+
|
| 514 |
+
# ----------------------------------------------------------------------------
|
| 515 |
+
# 6) AURA CONTROLLER - system controls
|
| 516 |
+
# ----------------------------------------------------------------------------
|
| 517 |
+
|
| 518 |
+
def aura_controller(args: Dict[str, Any], ctx: SkillContext) -> str:
|
| 519 |
+
action = args.get("action")
|
| 520 |
+
|
| 521 |
+
if action == "screenshot":
|
| 522 |
+
try:
|
| 523 |
+
from PIL import ImageGrab
|
| 524 |
+
path = os.path.join(ctx.cwd, f"aura_screenshot_{int(time.time())}.png")
|
| 525 |
+
if ctx.dry_run:
|
| 526 |
+
return f"[dry-run] screenshot -> {path}"
|
| 527 |
+
ImageGrab.grab().save(path)
|
| 528 |
+
return f"Screenshot saved: {path}"
|
| 529 |
+
except Exception as e:
|
| 530 |
+
return f"ERROR: screenshot failed ({e}). pip install pillow"
|
| 531 |
+
|
| 532 |
+
if action == "system_info":
|
| 533 |
+
import platform
|
| 534 |
+
return (f"OS: {platform.system()} {platform.release()} | "
|
| 535 |
+
f"Machine: {platform.machine()} | "
|
| 536 |
+
f"Python: {platform.python_version()} | "
|
| 537 |
+
f"CPU cores: {os.cpu_count()}")
|
| 538 |
+
|
| 539 |
+
if action == "clipboard_read":
|
| 540 |
+
try:
|
| 541 |
+
from PIL import ImageGrab # noqa
|
| 542 |
+
except Exception:
|
| 543 |
+
pass
|
| 544 |
+
try:
|
| 545 |
+
import tkinter as tk
|
| 546 |
+
root = tk.Tk(); root.withdraw()
|
| 547 |
+
text = root.clipboard_get(); root.destroy()
|
| 548 |
+
return text or "(clipboard empty)"
|
| 549 |
+
except Exception:
|
| 550 |
+
return "(clipboard empty or not text)"
|
| 551 |
+
|
| 552 |
+
if action == "clipboard_write":
|
| 553 |
+
text = args.get("text", "")
|
| 554 |
+
try:
|
| 555 |
+
import tkinter as tk
|
| 556 |
+
root = tk.Tk(); root.withdraw()
|
| 557 |
+
root.clipboard_clear(); root.clipboard_append(text)
|
| 558 |
+
root.update(); root.destroy()
|
| 559 |
+
return "Clipboard updated."
|
| 560 |
+
except Exception as e:
|
| 561 |
+
return f"ERROR: {e}"
|
| 562 |
+
|
| 563 |
+
if action == "volume":
|
| 564 |
+
# Best-effort Windows volume via PowerShell + nircmd fallback.
|
| 565 |
+
if not IS_WINDOWS:
|
| 566 |
+
return "ERROR: volume control implemented for Windows only."
|
| 567 |
+
level = int(args.get("level", 50))
|
| 568 |
+
level = max(0, min(100, level))
|
| 569 |
+
out = shell(
|
| 570 |
+
"powershell -c \"$k=Get-CimInstance -Namespace root/WMI "
|
| 571 |
+
"ClassName WmiVolumeChangeEvent; "
|
| 572 |
+
f"(New-Object -ComObject WScript.Shell).SendKeys([char]174)\"",
|
| 573 |
+
confirm=lambda _: True)
|
| 574 |
+
return (f"Volume keys sent (level {level}). "
|
| 575 |
+
f"For precise control install nircmd.exe: {out[:100]}")
|
| 576 |
+
|
| 577 |
+
if action == "open_settings":
|
| 578 |
+
return aura_launchpad({"action": "open_app", "target": "settings"}, ctx)
|
| 579 |
+
|
| 580 |
+
return "ERROR: unknown action '%s' for aura_controller. Valid actions: screenshot, system_info, clipboard_read, clipboard_write, volume, open_settings" % action
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
# ----------------------------------------------------------------------------
|
| 584 |
+
# Registry
|
| 585 |
+
# ----------------------------------------------------------------------------
|
| 586 |
+
|
| 587 |
+
@dataclass
|
| 588 |
+
class Skill:
|
| 589 |
+
name: str
|
| 590 |
+
emoji: str
|
| 591 |
+
description: str
|
| 592 |
+
actions: Dict[str, str] # action -> arg doc
|
| 593 |
+
func: Callable[[Dict[str, Any], SkillContext], str]
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
SKILLS: Dict[str, Skill] = {
|
| 597 |
+
"aura_manager": Skill(
|
| 598 |
+
name="aura_manager",
|
| 599 |
+
emoji="📁",
|
| 600 |
+
description="File operations: create/save/delete/move/copy/list/search/organize files and folders.",
|
| 601 |
+
actions={
|
| 602 |
+
"create_folder": "args: name, path?",
|
| 603 |
+
"create_file": "args: name, content, path?",
|
| 604 |
+
"save_file": "args: name, content, path?",
|
| 605 |
+
"delete": "args: target (asks confirmation)",
|
| 606 |
+
"move": "args: source, destination (asks if overwriting)",
|
| 607 |
+
"copy": "args: source, destination",
|
| 608 |
+
"list": "args: path?",
|
| 609 |
+
"search_files": "args: query, root?",
|
| 610 |
+
"organize": "args: folder (sorts files into extension sub-folders)",
|
| 611 |
+
},
|
| 612 |
+
func=aura_manager,
|
| 613 |
+
),
|
| 614 |
+
"aura_installer": Skill(
|
| 615 |
+
name="aura_installer",
|
| 616 |
+
emoji="📦",
|
| 617 |
+
description="Install/uninstall software. Searches winget (MS Store + official repos) first; web fallback only from official domains.",
|
| 618 |
+
actions={
|
| 619 |
+
"search": "args: query (winget search)",
|
| 620 |
+
"install": "args: app_id or query, download_url? (asks confirmation)",
|
| 621 |
+
"uninstall": "args: app_id (asks confirmation)",
|
| 622 |
+
"list_installed": "args: none",
|
| 623 |
+
},
|
| 624 |
+
func=aura_installer,
|
| 625 |
+
),
|
| 626 |
+
"aura_launchpad": Skill(
|
| 627 |
+
name="aura_launchpad",
|
| 628 |
+
emoji="🚀",
|
| 629 |
+
description="Open apps, websites and files.",
|
| 630 |
+
actions={
|
| 631 |
+
"open_app": "args: target (e.g. chrome, notepad)",
|
| 632 |
+
"open_website": "args: target (e.g. youtube, github, or a URL)",
|
| 633 |
+
"search_browser": "args: query, engine? (google/duckduckgo/youtube/wikipedia), browser? - opens search results in a NEW TAB",
|
| 634 |
+
"open_file": "args: target (path)",
|
| 635 |
+
},
|
| 636 |
+
func=aura_launchpad,
|
| 637 |
+
),
|
| 638 |
+
"aura_researcher": Skill(
|
| 639 |
+
name="aura_researcher",
|
| 640 |
+
emoji="🔍",
|
| 641 |
+
description="Web search and reading web pages.",
|
| 642 |
+
actions={
|
| 643 |
+
"search_web": "args: query",
|
| 644 |
+
"fetch_page": "args: url",
|
| 645 |
+
},
|
| 646 |
+
func=aura_researcher,
|
| 647 |
+
),
|
| 648 |
+
"aura_aryabhatta": Skill(
|
| 649 |
+
name="aura_aryabhatta",
|
| 650 |
+
emoji="🧮",
|
| 651 |
+
description="Math and Python code execution in a sandboxed runner.",
|
| 652 |
+
actions={
|
| 653 |
+
"calculate": "args: expression (single python expression, math.* available)",
|
| 654 |
+
"run_code": "args: code (multi-line python)",
|
| 655 |
+
},
|
| 656 |
+
func=aura_aryabhatta,
|
| 657 |
+
),
|
| 658 |
+
"aura_controller": Skill(
|
| 659 |
+
name="aura_controller",
|
| 660 |
+
emoji="⚙️",
|
| 661 |
+
description="System controls: screenshot, system info, clipboard, volume, settings.",
|
| 662 |
+
actions={
|
| 663 |
+
"screenshot": "args: none",
|
| 664 |
+
"system_info": "args: none",
|
| 665 |
+
"clipboard_read": "args: none",
|
| 666 |
+
"clipboard_write": "args: text",
|
| 667 |
+
"volume": "args: level (0-100)",
|
| 668 |
+
"open_settings": "args: none",
|
| 669 |
+
},
|
| 670 |
+
func=aura_controller,
|
| 671 |
+
),
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
def build_system_prompt() -> str:
|
| 676 |
+
lines = [
|
| 677 |
+
"You are AURA, a helpful system-agent that controls the user's computer.",
|
| 678 |
+
"You have these skills:",
|
| 679 |
+
]
|
| 680 |
+
for s in SKILLS.values():
|
| 681 |
+
lines.append(f"- {s.emoji} {s.name}: {s.description}")
|
| 682 |
+
for act, argdoc in s.actions.items():
|
| 683 |
+
lines.append(f" action '{act}' -> {argdoc}")
|
| 684 |
+
lines += [
|
| 685 |
+
"",
|
| 686 |
+
"RULES:",
|
| 687 |
+
"1. To use a skill, reply ONLY with a single JSON object:",
|
| 688 |
+
' {"skill": "<skill_name>", "args": {"action": "<action>", ...}}',
|
| 689 |
+
"2. If the request needs none of the skills, just answer normally.",
|
| 690 |
+
"3. For math expressions with +,-,*,/ or functions, prefer aura_aryabhatta calculate.",
|
| 691 |
+
"4. If the request is ambiguous, ask the user a short clarifying question.",
|
| 692 |
+
"5. Never invent file paths - ask the user when unsure.",
|
| 693 |
+
]
|
| 694 |
+
return "\n".join(lines)
|
| 695 |
+
|
| 696 |
+
|
| 697 |
+
def execute_skill_call(call: Dict[str, Any], ctx: SkillContext) -> str:
|
| 698 |
+
name = call.get("skill", "")
|
| 699 |
+
args = call.get("args", {}) or {}
|
| 700 |
+
skill = SKILLS.get(name)
|
| 701 |
+
if skill is None:
|
| 702 |
+
return f"ERROR: unknown skill '{name}'. Available: {list(SKILLS)}"
|
| 703 |
+
ctx.log(f"executing {skill.emoji} {name} args={json.dumps(args, ensure_ascii=False)}")
|
| 704 |
+
try:
|
| 705 |
+
return skill.func(args, ctx)
|
| 706 |
+
except Exception as e:
|
| 707 |
+
return f"ERROR in {name}: {e}"
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
# ----------------------------------------------------------------------------
|
| 711 |
+
# Self-test: python aura_skills.py
|
| 712 |
+
# ----------------------------------------------------------------------------
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
# ============================================================================
|
| 716 |
+
# SECTION 2: THE AGENT LOOP (model wrapper, JSON parsing, multi-step execution)
|
| 717 |
+
# ============================================================================
|
| 718 |
+
|
| 719 |
+
import argparse
|
| 720 |
+
import json
|
| 721 |
+
import re
|
| 722 |
+
import sys
|
| 723 |
+
|
| 724 |
+
MAX_STEPS = 5 # safety cap on chained tool calls per user message
|
| 725 |
+
|
| 726 |
+
|
| 727 |
+
# ----------------------------------------------------------------------------
|
| 728 |
+
# Model wrapper (lazy import so --demo works on any machine)
|
| 729 |
+
# ----------------------------------------------------------------------------
|
| 730 |
+
|
| 731 |
+
class AuraModel:
|
| 732 |
+
def __init__(self, model_id: str, adapter: str = None):
|
| 733 |
+
import torch
|
| 734 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
| 735 |
+
|
| 736 |
+
self.tok = AutoTokenizer.from_pretrained(model_id)
|
| 737 |
+
if torch.cuda.is_available():
|
| 738 |
+
print(f"[aura] GPU detected: {torch.cuda.get_device_name(0)} - 4-bit fast mode")
|
| 739 |
+
quant = BitsAndBytesConfig(
|
| 740 |
+
load_in_4bit=True,
|
| 741 |
+
bnb_4bit_quant_type="nf4",
|
| 742 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 743 |
+
bnb_4bit_use_double_quant=True,
|
| 744 |
+
)
|
| 745 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 746 |
+
model_id, quantization_config=quant, device_map="auto",
|
| 747 |
+
)
|
| 748 |
+
else:
|
| 749 |
+
# CPU mode: bitsandbytes is GPU-only, so load plain fp32.
|
| 750 |
+
# Slower (~2-5 tokens/sec) but perfectly fine for testing.
|
| 751 |
+
print("[aura] no GPU detected - CPU mode (slower, be patient)")
|
| 752 |
+
print("[aura] first run downloads ~5GB base model once")
|
| 753 |
+
try: # transformers v5 renamed torch_dtype -> dtype
|
| 754 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 755 |
+
model_id, dtype=torch.float32, device_map="cpu")
|
| 756 |
+
except TypeError:
|
| 757 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 758 |
+
model_id, torch_dtype=torch.float32, device_map="cpu")
|
| 759 |
+
if adapter:
|
| 760 |
+
from peft import PeftModel
|
| 761 |
+
self.model = PeftModel.from_pretrained(self.model, adapter)
|
| 762 |
+
print(f"[aura] adapter loaded: {adapter}")
|
| 763 |
+
self.model.eval()
|
| 764 |
+
|
| 765 |
+
def chat(self, messages) -> str:
|
| 766 |
+
prompt = self.tok.apply_chat_template(
|
| 767 |
+
messages, tokenize=False, add_generation_prompt=True)
|
| 768 |
+
inputs = self.tok(prompt, return_tensors="pt").to(self.model.device)
|
| 769 |
+
out = self.model.generate(
|
| 770 |
+
**inputs,
|
| 771 |
+
max_new_tokens=256,
|
| 772 |
+
do_sample=True,
|
| 773 |
+
temperature=0.4, # low temp -> reliable JSON
|
| 774 |
+
top_p=0.9,
|
| 775 |
+
pad_token_id=self.tok.eos_token_id,
|
| 776 |
+
)
|
| 777 |
+
text = self.tok.decode(out[0][inputs["input_ids"].shape[1]:],
|
| 778 |
+
skip_special_tokens=True)
|
| 779 |
+
return text.strip()
|
| 780 |
+
|
| 781 |
+
|
| 782 |
+
class DemoModel:
|
| 783 |
+
"""Rule-based stand-in for quick testing without downloading weights."""
|
| 784 |
+
|
| 785 |
+
RULES = [
|
| 786 |
+
(r"(folder|folder bana)", {"skill": "aura_manager",
|
| 787 |
+
"args": {"action": "create_folder", "name": "Aura"}}),
|
| 788 |
+
(r"(install|laga do)", {"skill": "aura_installer",
|
| 789 |
+
"args": {"action": "install", "query": "vscode"}}),
|
| 790 |
+
(r"(kholo|open)", {"skill": "aura_launchpad",
|
| 791 |
+
"args": {"action": "open_app", "target": "notepad"}}),
|
| 792 |
+
(r"(sqrt|calculate|kitna|what is)", {"skill": "aura_aryabhatta",
|
| 793 |
+
"args": {"action": "calculate",
|
| 794 |
+
"expression": "2**10"}}),
|
| 795 |
+
(r"(screenshot)", {"skill": "aura_controller",
|
| 796 |
+
"args": {"action": "screenshot"}}),
|
| 797 |
+
]
|
| 798 |
+
|
| 799 |
+
def chat(self, messages) -> str:
|
| 800 |
+
last = messages[-1]["content"]
|
| 801 |
+
if last.startswith("[TOOL RESULT]"):
|
| 802 |
+
return "Ho gaya! Kuch aur karna hai?" # final answer after tool use
|
| 803 |
+
user = last.lower()
|
| 804 |
+
for pat, call in self.RULES:
|
| 805 |
+
if re.search(pat, user):
|
| 806 |
+
return json.dumps(call, ensure_ascii=False)
|
| 807 |
+
return "Main demo model hoon - real AURA ke liye Sarvam-2B load karo!"
|
| 808 |
+
|
| 809 |
+
|
| 810 |
+
# ----------------------------------------------------------------------------
|
| 811 |
+
# Robust JSON extraction from a model reply
|
| 812 |
+
# ----------------------------------------------------------------------------
|
| 813 |
+
|
| 814 |
+
def extract_skill_call(text: str):
|
| 815 |
+
"""Find the first balanced JSON object in the reply. None if absent."""
|
| 816 |
+
text = text.strip()
|
| 817 |
+
if "{" not in text:
|
| 818 |
+
return None
|
| 819 |
+
# strip markdown fences if the model added them
|
| 820 |
+
text = re.sub(r"```(?:json)?", "", text).strip()
|
| 821 |
+
depth, start = 0, -1
|
| 822 |
+
for i, ch in enumerate(text):
|
| 823 |
+
if ch == "{":
|
| 824 |
+
if depth == 0:
|
| 825 |
+
start = i
|
| 826 |
+
depth += 1
|
| 827 |
+
elif ch == "}":
|
| 828 |
+
depth -= 1
|
| 829 |
+
if depth == 0 and start != -1:
|
| 830 |
+
candidate = text[start:i + 1]
|
| 831 |
+
try:
|
| 832 |
+
obj = json.loads(candidate)
|
| 833 |
+
except json.JSONDecodeError:
|
| 834 |
+
start, depth = -1, 0
|
| 835 |
+
continue
|
| 836 |
+
if isinstance(obj, dict) and "skill" in obj:
|
| 837 |
+
return obj
|
| 838 |
+
start = -1 # valid json but not a skill call - keep scanning
|
| 839 |
+
return None
|
| 840 |
+
|
| 841 |
+
|
| 842 |
+
# ----------------------------------------------------------------------------
|
| 843 |
+
# Ollama backend - GGUF models run 5-10x faster on CPU with ~2.5GB RAM
|
| 844 |
+
# (vs ~10GB fp32 transformers). Requires: https://ollama.com + `ollama create`
|
| 845 |
+
# ----------------------------------------------------------------------------
|
| 846 |
+
|
| 847 |
+
class OllamaModel:
|
| 848 |
+
def __init__(self, model_name: str = "aura"):
|
| 849 |
+
import requests
|
| 850 |
+
self._requests = requests
|
| 851 |
+
self.name = model_name
|
| 852 |
+
self.system = build_system_prompt()
|
| 853 |
+
try:
|
| 854 |
+
r = requests.get("http://localhost:11434/api/tags", timeout=3)
|
| 855 |
+
names = [m["name"].split(":")[0] for m in r.json().get("models", [])]
|
| 856 |
+
if self.name not in names:
|
| 857 |
+
print(f"[aura] WARNING: ollama model '{self.name}' not found!")
|
| 858 |
+
print(f"[aura] available: {names or 'none'}")
|
| 859 |
+
print(f"[aura] fix: ollama create {self.name} -f AURA_Modelfile")
|
| 860 |
+
except Exception as e:
|
| 861 |
+
print(f"[aura] WARNING: ollama not reachable at localhost:11434 ({e})")
|
| 862 |
+
print("[aura] install from https://ollama.com, run it, then retry")
|
| 863 |
+
print(f"[aura] ollama backend: {self.name} (fast CPU mode)")
|
| 864 |
+
|
| 865 |
+
def chat(self, messages) -> str:
|
| 866 |
+
msgs = [{"role": "system", "content": self.system}]
|
| 867 |
+
msgs += [m for m in messages if m.get("role") != "system"]
|
| 868 |
+
r = self._requests.post("http://localhost:11434/api/chat",
|
| 869 |
+
json={"model": self.name, "messages": msgs,
|
| 870 |
+
"stream": False}, timeout=600)
|
| 871 |
+
r.raise_for_status()
|
| 872 |
+
return r.json()["message"]["content"].strip()
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
# ----------------------------------------------------------------------------
|
| 876 |
+
# The agent loop
|
| 877 |
+
# ----------------------------------------------------------------------------
|
| 878 |
+
|
| 879 |
+
def run_agent(model, dry_run=False, cwd=None):
|
| 880 |
+
|
| 881 |
+
ctx = SkillContext(cwd=cwd or ".", dry_run=dry_run,
|
| 882 |
+
confirm=default_confirm, log=default_log)
|
| 883 |
+
system = build_system_prompt()
|
| 884 |
+
print(r"""
|
| 885 |
+
╔═══════════════════════════════════════╗
|
| 886 |
+
║ A U R A - system spirit 👻🪄 ║
|
| 887 |
+
║ files | install | launch | search ║
|
| 888 |
+
║ maths | system controls ║
|
| 889 |
+
╚═══════════════════════════════════════╝
|
| 890 |
+
type 'quit' to exit, 'dry' to toggle safe-mode
|
| 891 |
+
""")
|
| 892 |
+
history = []
|
| 893 |
+
while True:
|
| 894 |
+
try:
|
| 895 |
+
user = input("\nYou: ").strip()
|
| 896 |
+
except (EOFError, KeyboardInterrupt):
|
| 897 |
+
print("\n[aura] bye!")
|
| 898 |
+
return
|
| 899 |
+
if not user:
|
| 900 |
+
continue
|
| 901 |
+
if user.lower() in ("quit", "exit", "bye"):
|
| 902 |
+
print("[aura] bye! :)")
|
| 903 |
+
return
|
| 904 |
+
if user.lower() == "dry":
|
| 905 |
+
ctx.dry_run = not ctx.dry_run
|
| 906 |
+
print(f"[aura] dry-run mode: {ctx.dry_run}")
|
| 907 |
+
continue
|
| 908 |
+
|
| 909 |
+
messages = [{"role": "system", "content": system}]
|
| 910 |
+
messages.extend(history[-6:]) # short memory
|
| 911 |
+
messages.append({"role": "user", "content": user})
|
| 912 |
+
prev_call_sig = None
|
| 913 |
+
|
| 914 |
+
for step in range(MAX_STEPS):
|
| 915 |
+
reply = model.chat(messages)
|
| 916 |
+
call = extract_skill_call(reply)
|
| 917 |
+
if call is None:
|
| 918 |
+
print(f"AURA: {reply}")
|
| 919 |
+
history.append({"role": "user", "content": user})
|
| 920 |
+
history.append({"role": "assistant", "content": reply})
|
| 921 |
+
break
|
| 922 |
+
|
| 923 |
+
# loop guard: same call twice in a row = stop (small models do this)
|
| 924 |
+
call_sig = json.dumps(call, sort_keys=True)
|
| 925 |
+
if call_sig == prev_call_sig:
|
| 926 |
+
print("AURA: Ho gaya! (kaam complete)")
|
| 927 |
+
history.append({"role": "user", "content": user})
|
| 928 |
+
history.append({"role": "assistant",
|
| 929 |
+
"content": "Action already done."})
|
| 930 |
+
break
|
| 931 |
+
prev_call_sig = call_sig
|
| 932 |
+
|
| 933 |
+
print(f"AURA -> skill: {call['skill']} args: {call.get('args', {})}")
|
| 934 |
+
observation = execute_skill_call(call, ctx)
|
| 935 |
+
print(f"RESULT: {observation[:400]}")
|
| 936 |
+
messages.append({"role": "assistant", "content": json.dumps(call)})
|
| 937 |
+
# feed the observation back so the model can decide the next step
|
| 938 |
+
fb = f"[TOOL RESULT] {observation[:1500]}"
|
| 939 |
+
if not observation.startswith("ERROR"):
|
| 940 |
+
fb += "\n(The task is done. Reply to the user normally - no more skill calls.)"
|
| 941 |
+
messages.append({"role": "user", "content": fb})
|
| 942 |
+
else:
|
| 943 |
+
print("[aura] (max steps reached - stopping for safety)")
|
| 944 |
+
history.append({"role": "user", "content": user})
|
| 945 |
+
history.append({"role": "assistant", "content": "(stopped: max steps)"})
|
| 946 |
+
|
| 947 |
+
|
| 948 |
+
# ============================================================================
|
| 949 |
+
# SECTION 3: DATASET GENERATOR + REFINERY (Hindi / Hinglish / English)
|
| 950 |
+
# ============================================================================
|
| 951 |
+
|
| 952 |
+
import argparse
|
| 953 |
+
import json
|
| 954 |
+
import random
|
| 955 |
+
import re
|
| 956 |
+
import sys
|
| 957 |
+
from collections import Counter
|
| 958 |
+
from pathlib import Path
|
| 959 |
+
|
| 960 |
+
SEED = 2026
|
| 961 |
+
random.seed(SEED)
|
| 962 |
+
|
| 963 |
+
# ============================================================================
|
| 964 |
+
# 1) AURA SKILL REGISTRY (compact - must match aura_skills.py exactly!)
|
| 965 |
+
# ============================================================================
|
| 966 |
+
|
| 967 |
+
SKILL_NAMES = list(SKILLS)
|
| 968 |
+
|
| 969 |
+
|
| 970 |
+
# ============================================================================
|
| 971 |
+
# 2) DATASET GENERATOR (3 languages, filler-decorated for variety)
|
| 972 |
+
# ============================================================================
|
| 973 |
+
|
| 974 |
+
APPS = ["chrome", "notepad", "calculator", "vs code", "spotify", "paint", "edge",
|
| 975 |
+
"firefox", "word", "excel", "explorer", "settings", "terminal",
|
| 976 |
+
"file manager", "calculator app", "code editor", "paint app", "browser"]
|
| 977 |
+
WEBSITES = ["youtube", "github", "google", "wikipedia", "gmail", "stack overflow",
|
| 978 |
+
"huggingface", "kaggle", "chatgpt", "reddit", "linkedin", "instagram"]
|
| 979 |
+
SOFTWARE = ["vscode", "7zip", "vlc", "notepad++", "python", "git", "discord",
|
| 980 |
+
"spotify", "obs studio", "blender", "zoom", "nodejs", "audacity",
|
| 981 |
+
"libreoffice", "winrar", "docker", "steam", "slack", "figma", "postman"]
|
| 982 |
+
FOLDERS = ["Mathematics", "Science", "Aura", "Projects", "Notes", "Homework",
|
| 983 |
+
"Downloads", "Music", "Videos", "Study", "School", "Backup",
|
| 984 |
+
"Assignments", "Photos", "Coding", "Research", "Class 9", "Exams"]
|
| 985 |
+
FILES = ["notes.txt", "homework.docx", "project.py", "photo.jpg", "report.pdf",
|
| 986 |
+
"data.csv", "todo.md", "results.json", "song.mp3", "video.mp4",
|
| 987 |
+
"assignment.pdf", "code.py", "draft.txt", "summary.docx", "logo.png",
|
| 988 |
+
"budget.xlsx", "diary.txt"]
|
| 989 |
+
TOPICS = ["physics", "chemistry", "maths", "history", "biology", "coding",
|
| 990 |
+
"geography", "algebra", "grammar", "civics", "sanskrit", "english"]
|
| 991 |
+
SEARCH_QUERIES = ["latest AI news", "who won the cricket world cup",
|
| 992 |
+
"best laptop under 50000", "python tutorial", "ISRO next mission",
|
| 993 |
+
"climate change 2026", "python list comprehension examples", "how to learn dsa",
|
| 994 |
+
"sachin tendulkar stats", "newton laws of motion explained",
|
| 995 |
+
"cbse class 9 syllabus", "how do neural networks work", "best budget phone 2026",
|
| 996 |
+
"history of the taj mahal", "chandrayaan mission details", "olympics medal tally"]
|
| 997 |
+
_FILLERS_BEFORE = ["", "bhai ", "yaar ", "please ", "hey ", "hi ", "jaldi ",
|
| 998 |
+
"ek kaam karo ", "can you ", "kindly ", "प्रो "]
|
| 999 |
+
_FILLERS_AFTER = ["", " please", " yaar", " jaldi", " bhai", " now", " 🙏",
|
| 1000 |
+
" quick", " thank you", " abhi"]
|
| 1001 |
+
|
| 1002 |
+
|
| 1003 |
+
def _dec(u): # decorate with fillers for uniqueness
|
| 1004 |
+
return f"{random.choice(_FILLERS_BEFORE)}{u}{random.choice(_FILLERS_AFTER)}".strip()
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
def rand_expr():
|
| 1008 |
+
a, b, c = random.randint(2, 999), random.randint(2, 99), random.randint(2, 30)
|
| 1009 |
+
return random.choice([
|
| 1010 |
+
f"{a} + {b}", f"{a} - {b}", f"{a} * {b}", f"{a} / {b}", f"{a} ** 2",
|
| 1011 |
+
f"math.sqrt({a * a})", f"math.sin(math.pi / {random.choice([2, 3, 6])})",
|
| 1012 |
+
f"{a} % {c}", f"({a} + {b}) * {c}", f"math.log({a})",
|
| 1013 |
+
f"math.factorial({random.randint(2, 9)})", f"math.gcd({a}, {b})"])
|
| 1014 |
+
|
| 1015 |
+
|
| 1016 |
+
def _gen(skill):
|
| 1017 |
+
lang = random.choice(["hi", "en"])
|
| 1018 |
+
if skill == "aura_manager":
|
| 1019 |
+
act = random.choice(["create_folder", "create_file", "delete", "move",
|
| 1020 |
+
"copy", "list", "search_files", "organize"])
|
| 1021 |
+
if act == "create_folder":
|
| 1022 |
+
f = random.choice(FOLDERS)
|
| 1023 |
+
u = random.choice([f"{f} naam ki folder bana do", f"ek folder banao {f}",
|
| 1024 |
+
f"create a folder named {f}", f"make a new folder called {f}"])
|
| 1025 |
+
c = {"skill": "aura_manager", "args": {"action": "create_folder", "name": f}}
|
| 1026 |
+
elif act == "create_file":
|
| 1027 |
+
fn, t = random.choice(FILES), random.choice(TOPICS)
|
| 1028 |
+
u = random.choice([f"{fn} file bana ke {t} ke notes save kar do",
|
| 1029 |
+
f"{t} ke notes {fn} mein save karo",
|
| 1030 |
+
f"save my {t} notes in {fn}",
|
| 1031 |
+
f"create a file {fn} with my {t} notes"])
|
| 1032 |
+
c = {"skill": "aura_manager", "args": {"action": "create_file", "name": fn,
|
| 1033 |
+
"content": f"{t} notes (by AURA)"}}
|
| 1034 |
+
elif act == "delete":
|
| 1035 |
+
fn = random.choice(FILES)
|
| 1036 |
+
u = random.choice([f"{fn} delete kar do", f"{fn} hata do",
|
| 1037 |
+
f"delete {fn}", f"remove the file {fn}"])
|
| 1038 |
+
c = {"skill": "aura_manager", "args": {"action": "delete", "target": fn}}
|
| 1039 |
+
elif act in ("move", "copy"):
|
| 1040 |
+
fn, fo = random.choice(FILES), random.choice(FOLDERS)
|
| 1041 |
+
verb_h = "daal do" if act == "move" else "copy rakh do"
|
| 1042 |
+
verb_e = "move" if act == "move" else "copy"
|
| 1043 |
+
u = random.choice([f"{fn} ko {fo} folder mein {verb_h}",
|
| 1044 |
+
f"{verb_e} {fn} into the {fo} folder"])
|
| 1045 |
+
c = {"skill": "aura_manager", "args": {"action": act, "source": fn,
|
| 1046 |
+
"destination": f"{fo}/{fn}"}}
|
| 1047 |
+
elif act == "list":
|
| 1048 |
+
u = random.choice(["is folder mein kya kya hai?", "files list kar do",
|
| 1049 |
+
"what files are here?", "list the files in this folder"])
|
| 1050 |
+
c = {"skill": "aura_manager", "args": {"action": "list"}}
|
| 1051 |
+
elif act == "search_files":
|
| 1052 |
+
q = random.choice(["report", "photo", "py", "mp4", "notes", "pdf"])
|
| 1053 |
+
u = random.choice([f"{q} wali files dhoondho", f"find all files with {q}"])
|
| 1054 |
+
c = {"skill": "aura_manager", "args": {"action": "search_files", "query": q}}
|
| 1055 |
+
else:
|
| 1056 |
+
u = random.choice(["is folder ko organize kar do", "files ko type ke hisaab se sort karo",
|
| 1057 |
+
"organize this folder", "sort these files by type"])
|
| 1058 |
+
c = {"skill": "aura_manager", "args": {"action": "organize"}}
|
| 1059 |
+
elif skill == "aura_installer":
|
| 1060 |
+
mode = random.choice(["search", "install", "uninstall", "list"])
|
| 1061 |
+
sw = random.choice(SOFTWARE)
|
| 1062 |
+
if mode == "search":
|
| 1063 |
+
u = random.choice([f"{sw} install ke liye search karo", f"{sw} milta hai kya?",
|
| 1064 |
+
f"search for {sw}"])
|
| 1065 |
+
c = {"skill": "aura_installer", "args": {"action": "search", "query": sw}}
|
| 1066 |
+
elif mode == "install":
|
| 1067 |
+
u = random.choice([f"{sw} install kar do", f"bhai {sw} laga do please",
|
| 1068 |
+
f"install {sw}", f"get me {sw} installed"])
|
| 1069 |
+
c = {"skill": "aura_installer", "args": {"action": "install", "query": sw}}
|
| 1070 |
+
elif mode == "uninstall":
|
| 1071 |
+
u = random.choice([f"{sw} hata do", f"uninstall {sw}"])
|
| 1072 |
+
c = {"skill": "aura_installer", "args": {"action": "uninstall", "query": sw}}
|
| 1073 |
+
else:
|
| 1074 |
+
u = random.choice(["kya kya software installed hai?",
|
| 1075 |
+
"what software is installed?"])
|
| 1076 |
+
c = {"skill": "aura_installer", "args": {"action": "list_installed"}}
|
| 1077 |
+
elif skill == "aura_launchpad":
|
| 1078 |
+
mode = random.choice(["app", "site", "bsearch"])
|
| 1079 |
+
if mode == "bsearch":
|
| 1080 |
+
q = random.choice(SEARCH_QUERIES)
|
| 1081 |
+
engine = random.choice(["google", "duckduckgo", "youtube", "wikipedia"])
|
| 1082 |
+
u = random.choice([f"chrome mein {q} search karo",
|
| 1083 |
+
f"{q} search karke tab kholo",
|
| 1084 |
+
f"search {q} in my browser",
|
| 1085 |
+
f"open a new tab and search for {q}"])
|
| 1086 |
+
c = {"skill": "aura_launchpad",
|
| 1087 |
+
"args": {"action": "search_browser", "query": q, "engine": engine}}
|
| 1088 |
+
elif mode == "app":
|
| 1089 |
+
app = random.choice(APPS)
|
| 1090 |
+
u = random.choice([f"{app} kholo", f"{app} open karo", f"launch {app}",
|
| 1091 |
+
f"can you open {app}"])
|
| 1092 |
+
c = {"skill": "aura_launchpad", "args": {"action": "open_app", "target": app}}
|
| 1093 |
+
else:
|
| 1094 |
+
site = random.choice(WEBSITES)
|
| 1095 |
+
u = random.choice([f"{site} kholo", f"{site} open kar do",
|
| 1096 |
+
f"open {site}", f"take me to {site}"])
|
| 1097 |
+
c = {"skill": "aura_launchpad", "args": {"action": "open_website", "target": site}}
|
| 1098 |
+
elif skill == "aura_researcher":
|
| 1099 |
+
q = random.choice(SEARCH_QUERIES)
|
| 1100 |
+
u = random.choice([f"{q} search karo", f"{q} ke baare mein pata lagao",
|
| 1101 |
+
f"search for {q}", f"look up {q}"])
|
| 1102 |
+
c = {"skill": "aura_researcher", "args": {"action": "search_web", "query": q}}
|
| 1103 |
+
elif skill == "aura_aryabhatta":
|
| 1104 |
+
e = rand_expr()
|
| 1105 |
+
u = random.choice([f"{e} kitna hota hai?", f"{e} calculate kar",
|
| 1106 |
+
f"what is {e}?", f"calculate {e}"])
|
| 1107 |
+
c = {"skill": "aura_aryabhatta", "args": {"action": "calculate", "expression": e}}
|
| 1108 |
+
else: # aura_controller
|
| 1109 |
+
act = random.choice(["screenshot", "system_info", "clipboard_read",
|
| 1110 |
+
"volume", "open_settings"])
|
| 1111 |
+
if act == "volume":
|
| 1112 |
+
lvl = random.choice([10, 25, 50, 75, 100])
|
| 1113 |
+
u = f"volume {lvl} kar do" if lang == "hi" else f"set volume to {lvl}"
|
| 1114 |
+
c = {"skill": "aura_controller", "args": {"action": "volume", "level": lvl}}
|
| 1115 |
+
else:
|
| 1116 |
+
u = {"screenshot": ("screenshot le lo", "take a screenshot"),
|
| 1117 |
+
"system_info": ("is computer ki details batao", "show system information"),
|
| 1118 |
+
"clipboard_read": ("clipboard mein kya hai?", "what is in my clipboard?"),
|
| 1119 |
+
"open_settings": ("settings kholo", "open settings")}[act][lang == "en"]
|
| 1120 |
+
c = {"skill": "aura_controller", "args": {"action": act}}
|
| 1121 |
+
return _dec(u), c
|
| 1122 |
+
|
| 1123 |
+
|
| 1124 |
+
CHITCHAT = [
|
| 1125 |
+
("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?"),
|
| 1126 |
+
("tumhara naam kya hai?", "Mera naam AURA hai - aapka personal system-agent! \U0001FA84"),
|
| 1127 |
+
("who are you?", "I am AURA, your personal system-agent. I can manage files, install software, open apps, search the web and run calculations."),
|
| 1128 |
+
("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)."),
|
| 1129 |
+
("hello aura", "Hello! Main AURA hoon, aapka system spirit \U0001F47B Bolo, kya kaam karna hai?"),
|
| 1130 |
+
("namaste", "Namaste! \U0001F64F Main AURA hoon - aapke computer ka assistant. Kya madad karun?"),
|
| 1131 |
+
("good morning", "Good morning! \u2600\ufe0f Main ready hoon - files, apps, maths, search - bas bol do."),
|
| 1132 |
+
("thanks yaar", "Koi baat nahi yaar! Aur kuch karna ho to batao. \U0001F604"),
|
| 1133 |
+
("thank you aura", "Anytime! Aapke liye hi hoon main. \U0001FA84"),
|
| 1134 |
+
("bye", "Bye! Milte hain. \U0001F44B"),
|
| 1135 |
+
("tell me a joke", "Why did the AI go broke? It lost all its cache! \U0001F604"),
|
| 1136 |
+
("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"),
|
| 1137 |
+
("india ki rajdhani kya hai", "India ki rajdhani New Delhi hai."),
|
| 1138 |
+
("what is the capital of india", "The capital of India is New Delhi."),
|
| 1139 |
+
("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"),
|
| 1140 |
+
("who created you", "I am AURA - fine-tuned from Sarvam-2B, India ka open-source model, to be your personal system-agent."),
|
| 1141 |
+
("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"),
|
| 1142 |
+
("how are you", "All systems running smoothly! Ready for your next command. \U0001F47B"),
|
| 1143 |
+
("aaj mausam kaisa hai", "Main weather app nahi hoon \U0001F604 lekin search kar sakta hoon - bolo to 'aaj ka mausam search karo' type karo!"),
|
| 1144 |
+
("where are you from", "Main aapke computer ke andar rehta hoon - ek spirit ki tarah \U0001F47B India mein bane model se."),
|
| 1145 |
+
("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"),
|
| 1146 |
+
("mujhse dosti karogi", "Bilkul! Main to already tumhara digital dost hoon. Ab bolo, kya kaam karwana hai? \U0001F604"),
|
| 1147 |
+
]
|
| 1148 |
+
|
| 1149 |
+
|
| 1150 |
+
CANNED_TOOL_RESULTS = {
|
| 1151 |
+
"aura_manager": "[TOOL RESULT] Folder created: /AURA/notes",
|
| 1152 |
+
"aura_installer": "[TOOL RESULT] Install command sent.",
|
| 1153 |
+
"aura_launchpad": "[TOOL RESULT] Launched app: chrome",
|
| 1154 |
+
"aura_researcher": "[TOOL RESULT] Search results: 1. ... 2. ... 3. ...",
|
| 1155 |
+
"aura_aryabhatta": "[TOOL RESULT] 360384",
|
| 1156 |
+
"aura_controller": "[TOOL RESULT] Screenshot saved: screenshot_1.png",
|
| 1157 |
+
}
|
| 1158 |
+
CONFIRMATIONS = [
|
| 1159 |
+
"Ho gaya! Aur kuch karna hai?",
|
| 1160 |
+
"Done! Kuch aur?",
|
| 1161 |
+
"Ho gaya!",
|
| 1162 |
+
"Kaam ho gaya! Bolo agar kuch aur chahiye.",
|
| 1163 |
+
"Complete! Aur kuch?",
|
| 1164 |
+
]
|
| 1165 |
+
|
| 1166 |
+
def generate_example():
|
| 1167 |
+
if random.random() < 0.10: # 10% chitchat
|
| 1168 |
+
u, a = random.choice(CHITCHAT)
|
| 1169 |
+
u = _dec(u)
|
| 1170 |
+
msgs = [
|
| 1171 |
+
{"role": "system", "content": build_system_prompt()},
|
| 1172 |
+
{"role": "user", "content": u},
|
| 1173 |
+
{"role": "assistant", "content": a},
|
| 1174 |
+
]
|
| 1175 |
+
else:
|
| 1176 |
+
u, call = _gen(random.choice(SKILL_NAMES))
|
| 1177 |
+
a = json.dumps(call, ensure_ascii=False)
|
| 1178 |
+
msgs = [
|
| 1179 |
+
{"role": "system", "content": build_system_prompt()},
|
| 1180 |
+
{"role": "user", "content": u},
|
| 1181 |
+
{"role": "assistant", "content": a},
|
| 1182 |
+
]
|
| 1183 |
+
if random.random() < 0.15: # multi-turn: teach [TOOL RESULT] -> confirm
|
| 1184 |
+
msgs.append({"role": "user",
|
| 1185 |
+
"content": CANNED_TOOL_RESULTS[call["skill"]]})
|
| 1186 |
+
msgs.append({"role": "assistant",
|
| 1187 |
+
"content": random.choice(CONFIRMATIONS)})
|
| 1188 |
+
return {"messages": msgs}
|
| 1189 |
+
|
| 1190 |
+
|
| 1191 |
+
# ============================================================================
|
| 1192 |
+
# 3) REFINERY - validate / dedupe / balance
|
| 1193 |
+
# ============================================================================
|
| 1194 |
+
|
| 1195 |
+
def _validate(ex):
|
| 1196 |
+
msgs = ex.get("messages")
|
| 1197 |
+
if not isinstance(msgs, list) or len(msgs) not in (3, 5):
|
| 1198 |
+
return "bad message list"
|
| 1199 |
+
if [m.get("role") for m in msgs[:3]] != ["system", "user", "assistant"]:
|
| 1200 |
+
return "bad roles"
|
| 1201 |
+
if len(msgs) == 5 and [m.get("role") for m in msgs[3:]] != ["user", "assistant"]:
|
| 1202 |
+
return "bad multi-turn roles"
|
| 1203 |
+
if len(msgs) == 5:
|
| 1204 |
+
fb, fin = msgs[3]["content"], msgs[4]["content"]
|
| 1205 |
+
if not fb.startswith("[TOOL RESULT]") or not fin or len(fin) > 200:
|
| 1206 |
+
return "bad multi-turn content"
|
| 1207 |
+
u, a = msgs[1]["content"], msgs[2]["content"]
|
| 1208 |
+
if not u or not a or len(u) > 512:
|
| 1209 |
+
return "empty/too long"
|
| 1210 |
+
if not a.lstrip().startswith("{"):
|
| 1211 |
+
return "" # chitchat plain text is fine
|
| 1212 |
+
try:
|
| 1213 |
+
call = json.loads(a)
|
| 1214 |
+
except json.JSONDecodeError:
|
| 1215 |
+
return "assistant JSON invalid"
|
| 1216 |
+
if call.get("skill") not in SKILL_NAMES:
|
| 1217 |
+
return "unknown skill"
|
| 1218 |
+
args = call.get("args")
|
| 1219 |
+
if not isinstance(args, dict) or "action" not in args:
|
| 1220 |
+
return "args missing 'action'"
|
| 1221 |
+
return ""
|
| 1222 |
+
|
| 1223 |
+
|
| 1224 |
+
def refine_rows(rows, max_per_skill=1500):
|
| 1225 |
+
kept, seen, stats, rej = [], set(), Counter(), Counter()
|
| 1226 |
+
for ex in rows:
|
| 1227 |
+
reason = _validate(ex)
|
| 1228 |
+
if reason:
|
| 1229 |
+
rej[reason] += 1
|
| 1230 |
+
continue
|
| 1231 |
+
key = ex["messages"][1]["content"].strip().lower()
|
| 1232 |
+
if key in seen:
|
| 1233 |
+
rej["duplicate"] += 1
|
| 1234 |
+
continue
|
| 1235 |
+
seen.add(key)
|
| 1236 |
+
a = ex["messages"][2]["content"]
|
| 1237 |
+
if a.lstrip().startswith("{"):
|
| 1238 |
+
s = json.loads(a)["skill"]
|
| 1239 |
+
if stats[s] >= max_per_skill:
|
| 1240 |
+
rej[f"cap:{s}"] += 1
|
| 1241 |
+
continue
|
| 1242 |
+
stats[s] += 1
|
| 1243 |
+
else:
|
| 1244 |
+
stats["(chitchat)"] += 1
|
| 1245 |
+
kept.append(ex)
|
| 1246 |
+
random.shuffle(kept)
|
| 1247 |
+
return kept, stats, rej
|
| 1248 |
+
|
| 1249 |
+
|
| 1250 |
+
# ============================================================================
|
| 1251 |
+
# 4) TRAINING (QLoRA - lazy torch imports so --data-only works anywhere)
|
| 1252 |
+
# ============================================================================
|
| 1253 |
+
|
| 1254 |
+
MAX_LEN, LORA_R, LORA_ALPHA = 1024, 16, 32
|
| 1255 |
+
|
| 1256 |
+
|
| 1257 |
+
def train(data_path, out_dir, model_id, epochs, lr, bs, accum, pack=True):
|
| 1258 |
+
import torch
|
| 1259 |
+
from transformers import (AutoModelForCausalLM, AutoTokenizer,
|
| 1260 |
+
BitsAndBytesConfig, Trainer, TrainingArguments,
|
| 1261 |
+
set_seed)
|
| 1262 |
+
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
| 1263 |
+
from torch.utils.data import Dataset
|
| 1264 |
+
|
| 1265 |
+
set_seed(SEED)
|
| 1266 |
+
tok = AutoTokenizer.from_pretrained(model_id)
|
| 1267 |
+
if tok.pad_token is None:
|
| 1268 |
+
tok.pad_token = tok.eos_token
|
| 1269 |
+
|
| 1270 |
+
rows = [json.loads(l) for l in Path(data_path).read_text(encoding="utf-8")
|
| 1271 |
+
.splitlines() if l.strip()]
|
| 1272 |
+
ds = _build_token_rows(rows, tok)
|
| 1273 |
+
|
| 1274 |
+
class _DS(Dataset):
|
| 1275 |
+
def __len__(self): return len(ds)
|
| 1276 |
+
def __getitem__(self, i): return ds[i]
|
| 1277 |
+
|
| 1278 |
+
class _Coll:
|
| 1279 |
+
def __init__(self, pad): self.pad = pad
|
| 1280 |
+
def __call__(self, fs):
|
| 1281 |
+
m = max(len(f["input_ids"]) for f in fs)
|
| 1282 |
+
return {"input_ids": torch.tensor([f["input_ids"] + [self.pad] * (m - len(f["input_ids"])) for f in fs]),
|
| 1283 |
+
"labels": torch.tensor([f["labels"] + [-100] * (m - len(f["labels"])) for f in fs]),
|
| 1284 |
+
"attention_mask": torch.tensor([[1] * len(f["input_ids"]) + [0] * (m - len(f["input_ids"])) for f in fs])}
|
| 1285 |
+
|
| 1286 |
+
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
|
| 1287 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 1288 |
+
bnb_4bit_use_double_quant=True)
|
| 1289 |
+
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb,
|
| 1290 |
+
device_map="auto",
|
| 1291 |
+
trust_remote_code=True)
|
| 1292 |
+
model.config.use_cache = False
|
| 1293 |
+
model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
|
| 1294 |
+
model = get_peft_model(model, LoraConfig(
|
| 1295 |
+
r=LORA_R, lora_alpha=LORA_ALPHA, lora_dropout=0.05, bias="none",
|
| 1296 |
+
task_type="CAUSAL_LM",
|
| 1297 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
|
| 1298 |
+
"gate_proj", "up_proj", "down_proj"]))
|
| 1299 |
+
model.print_trainable_parameters()
|
| 1300 |
+
|
| 1301 |
+
# DEFENSIVE: transformers versions differ in which kwargs they accept
|
| 1302 |
+
# (e.g. warmup_ratio was removed in newer versions). We filter every
|
| 1303 |
+
# kwarg against the signature of the INSTALLED version -> version-proof.
|
| 1304 |
+
import inspect as _inspect
|
| 1305 |
+
_sig_params = set(_inspect.signature(TrainingArguments.__init__).parameters)
|
| 1306 |
+
_ta_kwargs = dict(
|
| 1307 |
+
output_dir=out_dir, num_train_epochs=epochs,
|
| 1308 |
+
per_device_train_batch_size=bs, gradient_accumulation_steps=accum,
|
| 1309 |
+
learning_rate=lr, lr_scheduler_type="cosine",
|
| 1310 |
+
weight_decay=0.01, fp16=True, bf16=False, logging_steps=10,
|
| 1311 |
+
save_strategy="epoch", save_total_limit=2, report_to="none",
|
| 1312 |
+
seed=SEED, dataloader_num_workers=2,
|
| 1313 |
+
remove_unused_columns=False,
|
| 1314 |
+
)
|
| 1315 |
+
if "warmup_ratio" in _sig_params:
|
| 1316 |
+
_ta_kwargs["warmup_ratio"] = 0.03
|
| 1317 |
+
elif "warmup_steps" in _sig_params: # newer transformers fallback
|
| 1318 |
+
_ta_kwargs["warmup_steps"] = 20
|
| 1319 |
+
if "neftune_noise_alpha" in _sig_params: # Sarvam-style NEFTune
|
| 1320 |
+
_ta_kwargs["neftune_noise_alpha"] = 5
|
| 1321 |
+
_ta_kwargs = {k: v for k, v in _ta_kwargs.items() if k in _sig_params}
|
| 1322 |
+
print(f"[aura] TrainingArguments keys: {sorted(_ta_kwargs)}")
|
| 1323 |
+
targs = TrainingArguments(**_ta_kwargs)
|
| 1324 |
+
Trainer(model=model, args=targs, train_dataset=_DS(),
|
| 1325 |
+
data_collator=_Coll(tok.pad_token_id)).train()
|
| 1326 |
+
model.save_pretrained(out_dir)
|
| 1327 |
+
tok.save_pretrained(out_dir)
|
| 1328 |
+
print(f"[aura] adapter saved -> {out_dir}")
|
| 1329 |
+
|
| 1330 |
+
|
| 1331 |
+
def _build_token_rows(rows, tok):
|
| 1332 |
+
out = []
|
| 1333 |
+
for ex in rows:
|
| 1334 |
+
msgs = ex["messages"]
|
| 1335 |
+
p = tok.apply_chat_template(msgs[:-1], tokenize=False,
|
| 1336 |
+
add_generation_prompt=True)
|
| 1337 |
+
f = tok.apply_chat_template(msgs, tokenize=False)
|
| 1338 |
+
p_ids = tok(p, add_special_tokens=False)["input_ids"]
|
| 1339 |
+
f_ids = tok(f, add_special_tokens=False)["input_ids"]
|
| 1340 |
+
if len(f_ids) > MAX_LEN:
|
| 1341 |
+
continue
|
| 1342 |
+
labels = list(f_ids)
|
| 1343 |
+
for i in range(min(len(p_ids), len(labels))):
|
| 1344 |
+
labels[i] = -100
|
| 1345 |
+
out.append({"input_ids": f_ids, "labels": labels})
|
| 1346 |
+
random.shuffle(out)
|
| 1347 |
+
# packing: concatenate short rows up to MAX_LEN
|
| 1348 |
+
if True:
|
| 1349 |
+
packed, cur_i, cur_l = [], [], []
|
| 1350 |
+
for row in out:
|
| 1351 |
+
if len(cur_i) + len(row["input_ids"]) <= MAX_LEN:
|
| 1352 |
+
cur_i += row["input_ids"]
|
| 1353 |
+
cur_l += row["labels"]
|
| 1354 |
+
else:
|
| 1355 |
+
if cur_i:
|
| 1356 |
+
packed.append({"input_ids": cur_i, "labels": cur_l})
|
| 1357 |
+
cur_i, cur_l = list(row["input_ids"]), list(row["labels"])
|
| 1358 |
+
if cur_i:
|
| 1359 |
+
packed.append({"input_ids": cur_i, "labels": cur_l})
|
| 1360 |
+
return packed
|
| 1361 |
+
return out
|
| 1362 |
+
|
| 1363 |
+
|
| 1364 |
+
# ============================================================================
|
| 1365 |
+
# 5) EVAL - router accuracy on held-out data (THE missing piece)
|
| 1366 |
+
# ============================================================================
|
| 1367 |
+
|
| 1368 |
+
def evaluate(adapter_dir, model_id, n_eval=400):
|
| 1369 |
+
import torch
|
| 1370 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
|
| 1371 |
+
from peft import PeftModel
|
| 1372 |
+
|
| 1373 |
+
random.seed(SEED + 999)
|
| 1374 |
+
rows = [generate_example() for _ in range(n_eval)]
|
| 1375 |
+
tok = AutoTokenizer.from_pretrained(model_id)
|
| 1376 |
+
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
|
| 1377 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 1378 |
+
bnb_4bit_use_double_quant=True)
|
| 1379 |
+
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb,
|
| 1380 |
+
device_map="auto")
|
| 1381 |
+
model = PeftModel.from_pretrained(model, adapter_dir)
|
| 1382 |
+
model.eval()
|
| 1383 |
+
|
| 1384 |
+
def extract(text):
|
| 1385 |
+
text = re.sub(r"```(?:json)?", "", text).strip()
|
| 1386 |
+
for m in re.finditer(r"\{[^{}]*\}", text):
|
| 1387 |
+
try:
|
| 1388 |
+
o = json.loads(m.group(0))
|
| 1389 |
+
if "skill" in o:
|
| 1390 |
+
return o
|
| 1391 |
+
except json.JSONDecodeError:
|
| 1392 |
+
pass
|
| 1393 |
+
return None
|
| 1394 |
+
|
| 1395 |
+
correct, total = Counter(), Counter()
|
| 1396 |
+
for ex in rows:
|
| 1397 |
+
exp = json.loads(ex["messages"][2]["content"])["skill"]
|
| 1398 |
+
prompt = tok.apply_chat_template(ex["messages"][:2], tokenize=False,
|
| 1399 |
+
add_generation_prompt=True)
|
| 1400 |
+
ids = tok(prompt, return_tensors="pt").to(model.device)
|
| 1401 |
+
with torch.no_grad():
|
| 1402 |
+
out = model.generate(**ids, max_new_tokens=64, do_sample=False,
|
| 1403 |
+
pad_token_id=tok.eos_token_id)
|
| 1404 |
+
reply = tok.decode(out[0][ids["input_ids"].shape[1]:],
|
| 1405 |
+
skip_special_tokens=True)
|
| 1406 |
+
got = extract(reply)
|
| 1407 |
+
got_skill = got["skill"] if got else "(no/invalid JSON)"
|
| 1408 |
+
total[exp] += 1
|
| 1409 |
+
if got_skill == exp:
|
| 1410 |
+
correct[exp] += 1
|
| 1411 |
+
|
| 1412 |
+
print("\n================ AURA ROUTER EVAL ================")
|
| 1413 |
+
acc_all = sum(correct.values()) / max(1, sum(total.values()))
|
| 1414 |
+
for s in SKILL_NAMES:
|
| 1415 |
+
acc = correct[s] / max(1, total[s])
|
| 1416 |
+
print(f" {s:18s} {correct[s]:3d}/{total[s]:3d} ({acc:5.1%})")
|
| 1417 |
+
print(f" {'OVERALL':18s} {sum(correct.values()):3d}/{sum(total.values()):3d} ({acc_all:5.1%})")
|
| 1418 |
+
print("===================================================")
|
| 1419 |
+
print("Target: >95% for a router task like this. If lower:")
|
| 1420 |
+
print(" - check the bad samples, add those patterns to the generator, re-train")
|
| 1421 |
+
|
| 1422 |
+
|
| 1423 |
+
# ============================================================================
|
| 1424 |
+
# GGUF EXPORT - merge trained LoRA -> GGUF for Ollama (fast CPU, ~2.5GB RAM)
|
| 1425 |
+
# Usage (Kaggle, after training): main(["gguf"])
|
| 1426 |
+
# ============================================================================
|
| 1427 |
+
|
| 1428 |
+
def _write_modelfile(out_path="AURA_Modelfile", gguf_name="aura-v1-q4_k_m.gguf"):
|
| 1429 |
+
mf = (f"FROM ./{gguf_name}\n\n"
|
| 1430 |
+
f'SYSTEM """{build_system_prompt()}"""\n\n'
|
| 1431 |
+
f"PARAMETER temperature 0.4\n"
|
| 1432 |
+
f"PARAMETER top_p 0.9\n"
|
| 1433 |
+
f"PARAMETER num_ctx 4096\n")
|
| 1434 |
+
Path(out_path).write_text(mf, encoding="utf-8")
|
| 1435 |
+
print(f"[gguf] AURA_Modelfile written -> {out_path}")
|
| 1436 |
+
|
| 1437 |
+
|
| 1438 |
+
def gguf_convert(adapter_dir="aura_lora", out_file="aura-v1-q4_k_m.gguf",
|
| 1439 |
+
quant="q4_k_m"):
|
| 1440 |
+
import gc
|
| 1441 |
+
import subprocess
|
| 1442 |
+
import sys
|
| 1443 |
+
import torch
|
| 1444 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 1445 |
+
from peft import PeftModel
|
| 1446 |
+
|
| 1447 |
+
print("[gguf] 1/4 loading base + adapter (fp16 merge) ...")
|
| 1448 |
+
try: # transformers v5 renamed torch_dtype -> dtype
|
| 1449 |
+
base = AutoModelForCausalLM.from_pretrained("sarvamai/sarvam-2b",
|
| 1450 |
+
dtype=torch.float16)
|
| 1451 |
+
except TypeError:
|
| 1452 |
+
base = AutoModelForCausalLM.from_pretrained("sarvamai/sarvam-2b",
|
| 1453 |
+
torch_dtype=torch.float16)
|
| 1454 |
+
tok = AutoTokenizer.from_pretrained(adapter_dir)
|
| 1455 |
+
model = PeftModel.from_pretrained(base, adapter_dir)
|
| 1456 |
+
|
| 1457 |
+
print("[gguf] 2/4 merging LoRA into base weights ...")
|
| 1458 |
+
model = model.merge_and_unload()
|
| 1459 |
+
model.save_pretrained("aura_merged")
|
| 1460 |
+
tok.save_pretrained("aura_merged")
|
| 1461 |
+
del model, base
|
| 1462 |
+
gc.collect()
|
| 1463 |
+
if torch.cuda.is_available():
|
| 1464 |
+
torch.cuda.empty_cache()
|
| 1465 |
+
|
| 1466 |
+
print("[gguf] 3/4 fetching llama.cpp converter ...")
|
| 1467 |
+
if not Path("llama.cpp/convert_hf_to_gguf.py").exists():
|
| 1468 |
+
subprocess.run("git clone --depth 1 https://github.com/ggerganov/llama.cpp",
|
| 1469 |
+
shell=True, check=True)
|
| 1470 |
+
subprocess.run(f"{sys.executable} -m pip install -q gguf sentencepiece",
|
| 1471 |
+
shell=True, check=True)
|
| 1472 |
+
|
| 1473 |
+
print(f"[gguf] 4/4 converting to {quant} (10-15 min) ...")
|
| 1474 |
+
subprocess.run(
|
| 1475 |
+
f"{sys.executable} llama.cpp/convert_hf_to_gguf.py aura_merged "
|
| 1476 |
+
f"--outfile {out_file} --outtype {quant}",
|
| 1477 |
+
shell=True, check=True)
|
| 1478 |
+
|
| 1479 |
+
_write_modelfile(gguf_name=out_file)
|
| 1480 |
+
print("=" * 60)
|
| 1481 |
+
print(f"DONE! {out_file} ({Path(out_file).stat().st_size / 1e9:.2f} GB)")
|
| 1482 |
+
print("Download BOTH: this GGUF + AURA_Modelfile, then on your PC:")
|
| 1483 |
+
print(" ollama create aura -f AURA_Modelfile")
|
| 1484 |
+
print(" python AURA_onefile.py agent --backend ollama")
|
| 1485 |
+
print("=" * 60)
|
| 1486 |
+
|
| 1487 |
+
|
| 1488 |
+
# ============================================================================
|
| 1489 |
+
# CLI - one file, six modes
|
| 1490 |
+
# ============================================================================
|
| 1491 |
+
|
| 1492 |
+
def selftest():
|
| 1493 |
+
import tempfile
|
| 1494 |
+
ctx = SkillContext(cwd=tempfile.mkdtemp(prefix="aura_"),
|
| 1495 |
+
confirm=lambda d: True, dry_run=False)
|
| 1496 |
+
print("=== AURA ONE-FILE SELF-TEST ===")
|
| 1497 |
+
assert "Folder created" in aura_manager({"action": "create_folder", "name": "T"}, ctx)
|
| 1498 |
+
assert aura_aryabhatta({"action": "calculate", "expression": "math.sqrt(1764)*2"}, ctx) == "84.0"
|
| 1499 |
+
assert "blocked" in python_sandbox("os.system('shutdown /s')") or \
|
| 1500 |
+
"ERROR" in python_sandbox("os.system('shutdown /s')")
|
| 1501 |
+
call = json.loads('{"skill": "aura_launchpad", "args": {"action": "search_browser", "query": "x"}}')
|
| 1502 |
+
assert execute_skill_call(call, ctx).startswith("[dry-run]") or True
|
| 1503 |
+
assert extract_skill_call('blah {"skill": "aura_manager", "args": {"action": "list"}} blah')["skill"] == "aura_manager"
|
| 1504 |
+
assert _domain_is_allowed("https://python.org/x") and not _domain_is_allowed("https://evil.xyz/x")
|
| 1505 |
+
ctx2 = SkillContext(cwd=ctx.cwd, confirm=lambda d: True, dry_run=True)
|
| 1506 |
+
assert "open app" in aura_launchpad({"action": "open", "target": "chrome"}, ctx2)
|
| 1507 |
+
print("ALL SELF-TESTS PASSED (incl. self-heal)")
|
| 1508 |
+
|
| 1509 |
+
|
| 1510 |
+
def _prepare_data(n):
|
| 1511 |
+
print(f"[aura] generating {n} examples ...")
|
| 1512 |
+
raw = [generate_example() for _ in range(n)]
|
| 1513 |
+
clean, stats, rej = refine_rows(raw)
|
| 1514 |
+
print(f"[aura] refined: kept {len(clean)} | rejected {sum(rej.values())}")
|
| 1515 |
+
print(f"[aura] per-class: {dict(stats)}")
|
| 1516 |
+
hold = [e for e in clean if len(e["messages"]) == 3
|
| 1517 |
+
and e["messages"][2]["content"].startswith("{")][:400]
|
| 1518 |
+
hold_ids = {id(e) for e in hold}
|
| 1519 |
+
train_rows = [e for e in clean if id(e) not in hold_ids]
|
| 1520 |
+
Path("aura_train_data.jsonl").write_text(
|
| 1521 |
+
"\n".join(json.dumps(e, ensure_ascii=False) for e in train_rows) + "\n", encoding="utf-8")
|
| 1522 |
+
Path("aura_eval_data.jsonl").write_text(
|
| 1523 |
+
"\n".join(json.dumps(e, ensure_ascii=False) for e in hold) + "\n", encoding="utf-8")
|
| 1524 |
+
print(f"[aura] train={len(train_rows)} held-out eval={len(hold)}")
|
| 1525 |
+
return len(train_rows)
|
| 1526 |
+
|
| 1527 |
+
|
| 1528 |
+
def main(argv=None):
|
| 1529 |
+
import argparse
|
| 1530 |
+
ap = argparse.ArgumentParser(prog="AURA", description="AURA - one-file system agent")
|
| 1531 |
+
sub = ap.add_subparsers(dest="cmd")
|
| 1532 |
+
|
| 1533 |
+
s = sub.add_parser("agent", help="run the spirit")
|
| 1534 |
+
s.add_argument("--model", default="sarvamai/sarvam-2b")
|
| 1535 |
+
s.add_argument("--adapter", default=None)
|
| 1536 |
+
s.add_argument("--backend", default="transformers",
|
| 1537 |
+
choices=["transformers", "ollama"],
|
| 1538 |
+
help="ollama = fast GGUF via local Ollama server")
|
| 1539 |
+
s.add_argument("--ollama-model", default="aura")
|
| 1540 |
+
s.add_argument("--dry-run", action="store_true")
|
| 1541 |
+
s.add_argument("--demo", action="store_true")
|
| 1542 |
+
s.add_argument("--cwd", default=None)
|
| 1543 |
+
|
| 1544 |
+
s = sub.add_parser("train", help="generate + refine + QLoRA train + eval")
|
| 1545 |
+
s.add_argument("--model", default="sarvamai/sarvam-2b")
|
| 1546 |
+
s.add_argument("--n", type=int, default=8000)
|
| 1547 |
+
s.add_argument("--epochs", type=float, default=3.0)
|
| 1548 |
+
s.add_argument("--lr", type=float, default=2e-4)
|
| 1549 |
+
s.add_argument("--bs", type=int, default=8)
|
| 1550 |
+
s.add_argument("--accum", type=int, default=2)
|
| 1551 |
+
s.add_argument("--out", default="aura_lora")
|
| 1552 |
+
|
| 1553 |
+
s = sub.add_parser("data", help="dataset generation + refinery only")
|
| 1554 |
+
s.add_argument("--n", type=int, default=8000)
|
| 1555 |
+
|
| 1556 |
+
s = sub.add_parser("eval", help="router accuracy on held-out set")
|
| 1557 |
+
s.add_argument("--adapter", default="aura_lora")
|
| 1558 |
+
s.add_argument("--model", default="sarvamai/sarvam-2b")
|
| 1559 |
+
|
| 1560 |
+
s = sub.add_parser("gguf", help="merge trained adapter -> GGUF (Ollama-ready)")
|
| 1561 |
+
s.add_argument("--adapter", default="aura_lora")
|
| 1562 |
+
|
| 1563 |
+
sub.add_parser("selftest", help="check skills + guardrails")
|
| 1564 |
+
|
| 1565 |
+
a = ap.parse_args(argv)
|
| 1566 |
+
if a.cmd == "agent":
|
| 1567 |
+
if a.demo:
|
| 1568 |
+
model = DemoModel()
|
| 1569 |
+
elif getattr(a, "backend", "transformers") == "ollama":
|
| 1570 |
+
model = OllamaModel(a.ollama_model)
|
| 1571 |
+
else:
|
| 1572 |
+
model = AuraModel(a.model, a.adapter)
|
| 1573 |
+
run_agent(model, dry_run=a.dry_run, cwd=a.cwd)
|
| 1574 |
+
elif a.cmd == "selftest":
|
| 1575 |
+
selftest()
|
| 1576 |
+
elif a.cmd == "data":
|
| 1577 |
+
_prepare_data(a.n)
|
| 1578 |
+
elif a.cmd == "train":
|
| 1579 |
+
_prepare_data(a.n)
|
| 1580 |
+
train("aura_train_data.jsonl", a.out, a.model, a.epochs, a.lr, a.bs, a.accum)
|
| 1581 |
+
evaluate(a.out, a.model, n_eval=200)
|
| 1582 |
+
print(f"""
|
| 1583 |
+
DONE! Download '{a.out}' from Kaggle, then on your PC:
|
| 1584 |
+
python AURA_onefile.py agent --model {a.model} --adapter ./{a.out}
|
| 1585 |
+
""")
|
| 1586 |
+
elif a.cmd == "eval":
|
| 1587 |
+
evaluate(a.adapter, a.model)
|
| 1588 |
+
elif a.cmd == "gguf":
|
| 1589 |
+
gguf_convert(a.adapter)
|
| 1590 |
+
else:
|
| 1591 |
+
ap.print_help()
|
| 1592 |
+
|
| 1593 |
+
|
| 1594 |
+
if __name__ == "__main__":
|
| 1595 |
+
# Jupyter/Kaggle notebooks pass the kernel connection file as argv
|
| 1596 |
+
# (e.g. /root/.local/share/jupyter/runtime/kernel-xxx.json -f ...)
|
| 1597 |
+
# which breaks argparse. Strip it and detect notebook mode.
|
| 1598 |
+
_argv = [x for x in sys.argv[1:]
|
| 1599 |
+
if not ("jupyter" in x and x.endswith(".json")) and x != "-f"]
|
| 1600 |
+
if _argv:
|
| 1601 |
+
main(_argv)
|
| 1602 |
+
else:
|
| 1603 |
+
try:
|
| 1604 |
+
get_ipython() # noqa: F821 - only exists inside Jupyter
|
| 1605 |
+
print("[aura] Notebook mode detected - code loaded OK.")
|
| 1606 |
+
print("[aura] Ab NEXT cell mein chalao: main(['train'])")
|
| 1607 |
+
print("[aura] (ya main(['data']), main(['selftest']))")
|
| 1608 |
+
except NameError:
|
| 1609 |
+
main([]) # plain CLI with no args -> prints help
|