| # Agent OS - Fine-Tuning Guide |
|
|
| Guia completo de como treinar adaptadores LoRA para o Agent OS, incluindo erros encontrados, soluções e boas praticas. |
|
|
| ## O que e o Agent OS Adapter? |
|
|
| Um modelo fine-tunado que converte linguagem natural em portugues para comandos JSON (SQL queries, shell commands, GitHub actions) para o sistema Agent OS da Hub Formaturas. |
|
|
| **Exemplo:** |
| - Input: `"quais contratos ativos"` |
| - Output: `{"action": "sql", "sql": "SELECT * FROM contratos WHERE status='ativo'"}` |
|
|
| ## Modelos Treinados |
|
|
| ### Adapters LoRA (precisam do modelo base pra rodar) |
| | Modelo | Base | Repo HuggingFace | |
| |--------|------|------------------| |
| | 1.5B (rapido) | Qwen/Qwen2.5-1.5B-Instruct | [devsomosahub/agent-os-adapter-1.5b](https://huggingface.co/devsomosahub/agent-os-adapter-1.5b) | |
| | 7B (preciso) | Qwen/Qwen2.5-7B-Instruct | [devsomosahub/agent-os-adapter-7b](https://huggingface.co/devsomosahub/agent-os-adapter-7b) | |
|
|
| ### Modelos Merged (prontos pra usar, incluem base + adapter) |
| | Modelo | Repo HuggingFace | Inference Endpoint | |
| |--------|------------------|--------------------| |
| | 1.5B merged | [devsomosahub/agent-os-1b5-merged](https://huggingface.co/devsomosahub/agent-os-1b5-merged) | Sim (T4) | |
| | 7B merged | [devsomosahub/agent-os-7b-merged](https://huggingface.co/devsomosahub/agent-os-7b-merged) | Sim (A10G) | |
|
|
| **IMPORTANTE:** Use os modelos merged pra Inference Endpoints. Os adapters LoRA nao funcionam direto na Inference API. |
|
|
| ## Dataset |
|
|
| - **Repo:** [devsomosahub/agent-os-dataset](https://huggingface.co/datasets/devsomosahub/agent-os-dataset) |
| - **Formato:** JSONL com campos `input` e `output` |
| - **Tamanho:** 415 exemplos (duplicados 4x para 1660 amostras no treino) |
| - **Dominio:** Queries SQL para banco Supabase do sistema Hub Formaturas (contratos, parcelas, turmas, user_profiles) |
| |
| ## Configuracao do Treino |
| |
| ### LoRA Config |
| ```python |
| LoraConfig( |
| r=32, |
| lora_alpha=64, |
| target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], |
| lora_dropout=0.05, |
| bias="none", |
| task_type="CAUSAL_LM", |
| ) |
| ``` |
| |
| ### Training Args (1.5B - FP16) |
| ```python |
| TrainingArguments( |
| num_train_epochs=7, |
| per_device_train_batch_size=8, |
| gradient_accumulation_steps=1, |
| learning_rate=2e-4, |
| fp16=True, |
| warmup_ratio=0.1, |
| lr_scheduler_type="cosine", |
| save_strategy="epoch", |
| push_to_hub=True, |
| hub_model_id="devsomosahub/agent-os-adapter-1.5b", |
| ) |
| ``` |
|
|
| ### Training Args (7B - Q4 quantizado) |
| ```python |
| TrainingArguments( |
| num_train_epochs=7, |
| per_device_train_batch_size=4, |
| gradient_accumulation_steps=2, |
| learning_rate=2e-4, |
| fp16=True, |
| warmup_ratio=0.1, |
| lr_scheduler_type="cosine", |
| save_strategy="steps", |
| save_steps=500, |
| push_to_hub=True, |
| hub_model_id="devsomosahub/agent-os-adapter-7b", |
| ) |
| ``` |
|
|
| ### Quantizacao para 7B |
| ```python |
| BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_quant_type="nf4", |
| bnb_4bit_compute_dtype=torch.float16, |
| ) |
| ``` |
|
|
| ## Como Treinar na Cloud (HuggingFace AutoTrain) |
|
|
| ### Pre-requisitos |
| ```bash |
| pip install autotrain-advanced |
| ``` |
|
|
| ### 1. Upload do dataset para o HuggingFace |
| ```python |
| from huggingface_hub import HfApi |
| api = HfApi(token="SEU_HF_TOKEN") |
| api.create_repo("SEU_USER/agent-os-dataset", repo_type="dataset") |
| api.upload_file( |
| path_or_fileobj="dataset_v3.jsonl", |
| path_in_repo="train.jsonl", |
| repo_id="SEU_USER/agent-os-dataset", |
| repo_type="dataset", |
| ) |
| ``` |
|
|
| ### 2. Criar script de treino (cloud-train/script.py) |
|
|
| **IMPORTANTE:** NAO coloque tokens HF hardcoded no script - o HuggingFace bloqueia o upload. Use `os.environ["HF_TOKEN"]`. |
|
|
| ```python |
| import os, torch |
| from datasets import load_dataset, concatenate_datasets |
| from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments |
| from peft import LoraConfig, get_peft_model |
| from trl import SFTTrainer |
| from huggingface_hub import login |
| |
| HF_TOKEN = os.environ["HF_TOKEN"] # NUNCA hardcode o token! |
| login(token=HF_TOKEN) |
| |
| ds = load_dataset("SEU_USER/agent-os-dataset", data_files="train.jsonl", split="train") |
| |
| def fmt(ex): |
| return {"text": f"<|im_start|>system\nYou are a command adapter. Output ONLY valid JSON.<|im_end|>\n<|im_start|>user\n{ex['input']}<|im_end|>\n<|im_start|>assistant\n{ex['output']}<|im_end|>"} |
| |
| ds = ds.map(fmt) |
| ds = concatenate_datasets([ds, ds, ds, ds]) |
| |
| tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct", trust_remote_code=True) |
| if tokenizer.pad_token is None: |
| tokenizer.pad_token = tokenizer.eos_token |
| |
| model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-1.5B-Instruct", torch_dtype=torch.float16, device_map="auto", trust_remote_code=True) |
| |
| model = get_peft_model(model, LoraConfig( |
| r=32, lora_alpha=64, |
| target_modules=["q_proj","v_proj","k_proj","o_proj","gate_proj","up_proj","down_proj"], |
| lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", |
| )) |
| |
| trainer = SFTTrainer( |
| model=model, |
| train_dataset=ds, |
| args=TrainingArguments( |
| output_dir="./output", |
| num_train_epochs=7, |
| per_device_train_batch_size=8, |
| learning_rate=2e-4, |
| fp16=True, |
| save_strategy="epoch", |
| push_to_hub=True, |
| hub_model_id="SEU_USER/agent-os-adapter-1.5b", |
| hub_token=HF_TOKEN, |
| ), |
| processing_class=tokenizer, |
| ) |
| |
| trainer.train() |
| trainer.push_to_hub() |
| model.push_to_hub("SEU_USER/agent-os-adapter-1.5b", token=HF_TOKEN) |
| tokenizer.push_to_hub("SEU_USER/agent-os-adapter-1.5b", token=HF_TOKEN) |
| ``` |
|
|
| ### 3. Lancar o treino na cloud |
| ```bash |
| autotrain spacerunner \ |
| --project-name "meu-treino" \ |
| --script-path ./cloud-train \ |
| --username SEU_USER \ |
| --token SEU_HF_TOKEN \ |
| --backend spaces-t4-small \ |
| --env "HF_TOKEN=SEU_HF_TOKEN" |
| ``` |
|
|
| **Backends disponiveis:** |
| | Backend | GPU | VRAM | Custo/hr | |
| |---------|-----|------|----------| |
| | spaces-t4-small | T4 | 16GB | ~$0.60 | |
| | spaces-a10g-small | A10G | 24GB | ~$1.05 | |
| | spaces-a100-large | A100 | 80GB | ~$4.13 | |
|
|
| ### 4. Acompanhar o treino |
| Acesse: `https://huggingface.co/spaces/SEU_USER/autotrain-meu-treino` |
|
|
| ## Merge: Adapter LoRA → Modelo Completo |
|
|
| Para usar na Inference API ou Inference Endpoints do HuggingFace, o adapter LoRA precisa ser mergeado com o modelo base. |
|
|
| ### REGRA CRITICA: Merge SEMPRE em FP16, NUNCA em Q4 |
|
|
| ```python |
| # ERRADO - merge a partir de modelo quantizado Q4 (pesos corrompidos!) |
| base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct", quantization_config=bnb_config) |
| model = PeftModel.from_pretrained(base, "adapter") |
| merged = model.merge_and_unload() # SHAPES ERRADAS! Nao funciona. |
| |
| # CERTO - merge a partir de modelo FP16 na CPU |
| base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct", torch_dtype=torch.float16, device_map="cpu") |
| model = PeftModel.from_pretrained(base, "adapter") |
| merged = model.merge_and_unload() # OK! Pesos corretos. |
| ``` |
|
|
| O merge Q4 gera erro `size mismatch for weight: copying a param with shape torch.Size([33947648, 1])` no Inference Endpoint. O modelo precisa estar em FP16 completo pra merge funcionar. |
|
|
| **RAM necessaria:** 7B em FP16 = ~14GB RAM. Use A100 na cloud ou CPU local com RAM suficiente. |
|
|
| ### Script de merge na cloud (HuggingFace AutoTrain) |
|
|
| ```python |
| # cloud-merge/script.py |
| import os, torch |
| from peft import PeftModel |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| from huggingface_hub import login |
| |
| HF_TOKEN = os.environ["HF_TOKEN"] |
| login(token=HF_TOKEN) |
| |
| base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct", torch_dtype=torch.float16, device_map="cpu", trust_remote_code=True) |
| model = PeftModel.from_pretrained(base, "devsomosahub/agent-os-adapter-7b") |
| merged = model.merge_and_unload() |
| tok = AutoTokenizer.from_pretrained("devsomosahub/agent-os-adapter-7b", trust_remote_code=True) |
| |
| merged.push_to_hub("devsomosahub/agent-os-7b-merged", token=HF_TOKEN, max_shard_size="2GB") |
| tok.push_to_hub("devsomosahub/agent-os-7b-merged", token=HF_TOKEN) |
| ``` |
|
|
| Lancar: `autotrain spacerunner --project-name "merge-7b" --script-path ./cloud-merge --username SEU_USER --token TOKEN --backend spaces-a100-large --env "HF_TOKEN=TOKEN"` |
|
|
| ### Limpar quantization_config do config.json |
| |
| Se o modelo merged ficou com `quantization_config` no `config.json` (heranca do treino Q4), o Inference Endpoint falha com erro de `bitsandbytes not found`. Remova manualmente: |
|
|
| ```python |
| from huggingface_hub import HfApi, hf_hub_download |
| import json |
| api = HfApi(token="TOKEN") |
| path = hf_hub_download("SEU_USER/modelo-merged", "config.json") |
| config = json.load(open(path)) |
| if "quantization_config" in config: |
| del config["quantization_config"] |
| api.upload_file(path_or_fileobj=json.dumps(config, indent=2).encode(), path_in_repo="config.json", repo_id="SEU_USER/modelo-merged") |
| ``` |
|
|
| ## Inference Endpoints |
|
|
| ### Criar Endpoint via SDK |
| ```python |
| from huggingface_hub import HfApi |
| api = HfApi(token="TOKEN") |
| endpoint = api.create_inference_endpoint( |
| name="agent-os-1b5", |
| repository="devsomosahub/agent-os-1b5-merged", |
| framework="pytorch", |
| task="text-generation", |
| accelerator="gpu", |
| vendor="aws", |
| region="us-east-1", |
| type="protected", |
| instance_size="x1", |
| instance_type="nvidia-t4", # T4 pra 1.5B, A10G pra 7B |
| namespace="devsomosahub", |
| ) |
| ``` |
|
|
| ### GPUs recomendadas por modelo |
| | Modelo | GPU minima | instance_type | instance_size | |
| |--------|-----------|---------------|---------------| |
| | 1.5B merged | T4 (16GB) | nvidia-t4 | x1 | |
| | 7B merged | A10G (24GB) | nvidia-a10g | x1 | |
|
|
| ### IMPORTANTE: Pausar endpoints quando nao usar! |
| Endpoints cobram por hora enquanto rodando (~$0.60/hr T4, ~$1.05/hr A10G). |
| ```python |
| api.pause_inference_endpoint("agent-os-1b5", namespace="devsomosahub") |
| # Para religar: |
| api.resume_inference_endpoint("agent-os-1b5", namespace="devsomosahub") |
| ``` |
|
|
| ### O modelo merged precisa de pipeline_tag no README |
| Sem `pipeline_tag: text-generation` no README.md, a Inference API nao reconhece o modelo: |
| ```markdown |
| --- |
| pipeline_tag: text-generation |
| library_name: transformers |
| --- |
| ``` |
| |
| ### Chamar o Endpoint |
| ```python |
| import requests |
| URL = "https://SEU-ENDPOINT.aws.endpoints.huggingface.cloud" |
| headers = {"Authorization": "Bearer HF_TOKEN"} |
| prompt = '<|im_start|>system\nYou are a command adapter. Output ONLY valid JSON.<|im_end|>\n<|im_start|>user\nquais tabelas existem<|im_end|>\n<|im_start|>assistant\n' |
| r = requests.post(URL, headers=headers, json={"inputs": prompt, "parameters": {"max_new_tokens": 200, "return_full_text": False}}) |
| print(r.json()[0]["generated_text"]) |
| ``` |
| |
| ## Teste Real: Modelo vs Banco Supabase (Cloud-Hub) |
| |
| Testamos o modelo 1.5B com tabelas que NUNCA viu no treino (Cloud-Hub: users, boards, vms, activity_log, board_memberships). |
| |
| ### Resultados |
| |
| | Query | SQL gerado | Executou no banco? | Observacao | |
| |-------|-----------|-------------------|------------| |
| | quais colunas tem a tabela vms | `information_schema.columns WHERE table_name='vms'` | OK - 25 colunas | Perfeito | |
| | lista os boards com seus donos | `JOIN boards + users` | OK - 11 resultados | Acertou o JOIN | |
| | quais usuarios tem role admin | `WHERE role='admin'` | OK - 0 resultados | Query valida | |
| | qual o ip das vms rodando | `WHERE power_status='running'` | OK - 11 VMs | Acertou a logica | |
| |
| ### Limitacao: modelo inventa colunas |
| |
| O modelo **generaliza a estrutura** (JSON, action, sql) mas **chuta nomes de colunas** baseado no dataset de treino quando a query e direta. Exemplo: |
| - Gerou `nome_completo` em vez de `display_name` (coluna real) |
| - Gerou `user_profiles` em vez de `users` (tabela real) |
| |
| ### Solucao: fluxo de 2 passos |
| |
| ``` |
| 1. User pergunta: "quais admins tem?" |
| 2. Agent OS pede ao modelo: "quais colunas tem a tabela users" |
| → Modelo: information_schema query (SEMPRE acerta) |
| → Executa no banco → descobre colunas reais |
| 3. Agent OS pede ao modelo com contexto: "a tabela users tem (id, email, display_name, role). liste os admins" |
| → Modelo gera SQL com colunas corretas |
| ``` |
| |
| O modelo acerta 100% das queries de `information_schema`. O problema so aparece quando ele tenta gerar SQL direto sem conhecer o schema. |
| |
| ## Erros Comuns e Solucoes |
| |
| ### 1. "Script must be base64 encoded" |
| O `--script-path` na API precisa de base64. Use o CLI `autotrain spacerunner` que faz isso automaticamente. |
| |
| ### 2. "project_name must be alphanumeric but can contain hyphens" |
| NAO use pontos no nome. `agent-os-1.5b` da erro, use `agent-os-small`. |
| |
| ### 3. "You already created this dataset repo" |
| O autotrain cria um dataset auxiliar `autotrain-NOME`. Se rodar de novo, mude o `--project-name` ou delete o repo antigo: |
| ```python |
| api.delete_repo("SEU_USER/autotrain-NOME-ANTIGO", repo_type="dataset") |
| ``` |
| |
| ### 4. "Offending files contain valid HuggingFace secrets" |
| NUNCA coloque tokens HF hardcoded no script. Use `os.environ["HF_TOKEN"]` e passe via `--env`. |
|
|
| ### 5. Treino termina mas modelo nao aparece no Hub |
| **Causa:** O script antigo so salvava local com `model.save_pretrained()`, sem push. |
| **Solucao:** Use `push_to_hub=True` no TrainingArguments + push explicito no final: |
| ```python |
| TrainingArguments( |
| push_to_hub=True, |
| hub_model_id="SEU_USER/MEU_MODELO", |
| hub_token=HF_TOKEN, |
| save_strategy="epoch", # salva checkpoints intermediarios |
| ) |
| # No final do treino: |
| trainer.push_to_hub() |
| model.push_to_hub("SEU_USER/MEU_MODELO", token=HF_TOKEN) |
| tokenizer.push_to_hub("SEU_USER/MEU_MODELO", token=HF_TOKEN) |
| ``` |
|
|
| ### 6. OOM (Out of Memory) na RTX 5060 (8.5GB VRAM) com 7B |
| Use quantizacao Q4 + batch_size=1: |
| ```python |
| BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4") |
| TrainingArguments(per_device_train_batch_size=1, gradient_accumulation_steps=8) |
| ``` |
| |
| ### 7. Merge Q4 gera pesos corrompidos no Inference Endpoint |
| **Erro:** `RuntimeError: size mismatch for weight: copying a param with shape torch.Size([33947648, 1])` |
| **Causa:** Merge foi feito com modelo base carregado em Q4 (BitsAndBytes 4-bit). Os pesos quantizados tem shapes diferentes. |
| **Solucao:** SEMPRE mergear com modelo base em FP16. Use `device_map="cpu"` se nao tiver VRAM suficiente. Precisa de ~14GB RAM pro 7B. |
| |
| ### 8. Inference Endpoint falha com "bitsandbytes not found" |
| **Causa:** O `config.json` do modelo merged herdou `quantization_config` do treino Q4. |
| **Solucao:** Remova `quantization_config` do config.json (ver secao Merge acima). |
| |
| ### 9. Memory limit exceeded (14Gi) no AutoTrain Space |
| **Causa:** Carregar modelo 7B FP16 na RAM do Space excede o limite. |
| **Solucao:** Use `spaces-a100-large` (80GB) em vez de `spaces-a10g-small` (14GB). |
| |
| ### 10. Inference API retorna "410 Gone" ou "model doesn't support task" |
| **Causa:** A API antiga `api-inference.huggingface.co` foi desativada. Modelos custom nao rodam na Inference API gratuita. |
| **Solucao:** Crie um Inference Endpoint pago. Modelos merged com `pipeline_tag: text-generation` no README funcionam. |
| |
| ### 11. "openai/arcee-ai/trinity-large-preview:free is not a valid model ID" (PentAGI) |
| O PentAGI prefixa o `LLM_SERVER_PROVIDER` ao modelo. Se o provider for `openai` e o modelo `arcee-ai/trinity:free`, vira `openai/arcee-ai/trinity:free`. |
| **Solucao:** Coloque `LLM_SERVER_PROVIDER=arcee-ai` e `LLM_SERVER_MODEL=trinity-large-preview:free`. |
| |
| ## Entendendo os Numeros do Treino |
| |
| ### loss (erro) |
| ``` |
| loss: 9.21 → modelo perdido, respondendo lixo |
| loss: 2.31 → comecou a entender o padrao |
| loss: 0.37 → quase perfeito |
| loss: 0.01 → memorizou o dataset |
| ``` |
| |
| ### grad_norm (forca do ajuste) |
| ``` |
| grad_norm: 14.0 → ajustes grandes (inicio) |
| grad_norm: 1.0 → ajustes finos (modelo convergindo) |
| ``` |
| |
| ### learning_rate (velocidade) |
| - Comeca baixo (warmup) → sobe ate o maximo → desce devagar (cosine) |
| - Padrao: 2e-4 = 0.0002 |
| |
| ### epoch (passadas pelo dataset) |
| - epoch 1.0 = viu todo o dataset 1 vez |
| - Treinamos ate epoch 7 = 7 passadas completas |
| |
| ## Exportar para GGUF (uso local com llama.cpp) |
| |
| ```bash |
| pip install llama-cpp-python |
| python -c " |
| from peft import PeftModel |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| import torch |
|
|
| base = AutoModelForCausalLM.from_pretrained('Qwen/Qwen2.5-1.5B-Instruct', torch_dtype=torch.float16, device_map='cpu') |
| model = PeftModel.from_pretrained(base, 'devsomosahub/agent-os-adapter-1.5b') |
| merged = model.merge_and_unload() |
| merged.save_pretrained('./merged-model') |
| AutoTokenizer.from_pretrained('devsomosahub/agent-os-adapter-1.5b').save_pretrained('./merged-model') |
| " |
| |
| # Converter para GGUF |
| python llama.cpp/convert_hf_to_gguf.py ./merged-model --outfile adapter-q8.gguf --outtype q8_0 |
| ``` |
| |
| ## Infraestrutura |
| |
| ### PentAGI (Pentest AI Autonomo) |
| - **Servidor:** Vultr Sao Paulo, 4CPU 8GB RAM, Ubuntu 24.04 |
| - **URL:** https://216.238.107.254:8443 |
| - **Login:** admin@pentagi.com / admin |
| - **LLM:** OpenRouter + arcee-ai/trinity-large-preview:free (gratis) |
| - **Ferramentas:** nmap, nikto, sqlmap, metasploit em containers Docker isolados |
| |
| ### Agent OS |
| - **Frontend:** React + TypeScript |
| - **Backend:** Python (smol-daemon.py) com smolagents |
| - **Modelo local:** llama.cpp servindo o adapter GGUF |
| - **Banco:** Supabase (PostgreSQL) |
| |