autoforge-trainer 1.0.2__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/AGENTS.md +1 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/CHANGELOG.md +29 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/PKG-INFO +24 -6
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/README.md +14 -5
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/__init__.py +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/loop.py +116 -39
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/providers.py +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/agent.py +2 -24
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/context.py +9 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/hpo.py +5 -1
- autoforge_trainer-1.2.0/autoforge/gui/agent_chat.py +184 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/app.py +87 -4
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/app.js +164 -2
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/index.html +1 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/style.css +17 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/hardware/detect.py +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/errors.py +5 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/installer.py +14 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/registry/models.py +2 -2
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/schemas/core.py +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/model_mcp.py +2 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/AGENT.md +3 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/DEPLOY.md +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/GUI.md +11 -2
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/IMPROVE.md +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/PACK_SPEC.md +2 -2
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/ROADMAP.md +2 -2
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/index.md +11 -3
- autoforge_trainer-1.2.0/packs/diffusion-lora/KNOWLEDGE.md +61 -0
- autoforge_trainer-1.2.0/packs/diffusion-lora/pack.yaml +9 -0
- autoforge_trainer-1.2.0/packs/diffusion-lora/recipes/sd_lora.yaml +16 -0
- autoforge_trainer-1.2.0/packs/diffusion-lora/tests/test_smoke.py +66 -0
- autoforge_trainer-1.2.0/packs/diffusion-lora/trainers/sd_lora.py +364 -0
- autoforge_trainer-1.2.0/packs/embedding-finetune/KNOWLEDGE.md +61 -0
- autoforge_trainer-1.2.0/packs/embedding-finetune/pack.yaml +10 -0
- autoforge_trainer-1.2.0/packs/embedding-finetune/recipes/contrastive_pairs.yaml +17 -0
- autoforge_trainer-1.2.0/packs/embedding-finetune/tests/test_smoke.py +71 -0
- autoforge_trainer-1.2.0/packs/embedding-finetune/trainers/contrastive.py +287 -0
- autoforge_trainer-1.2.0/packs/image-segmentation/KNOWLEDGE.md +60 -0
- autoforge_trainer-1.2.0/packs/image-segmentation/pack.yaml +10 -0
- autoforge_trainer-1.2.0/packs/image-segmentation/recipes/torchvision_segmenter.yaml +17 -0
- autoforge_trainer-1.2.0/packs/image-segmentation/tests/test_smoke.py +104 -0
- autoforge_trainer-1.2.0/packs/image-segmentation/trainers/segmenter.py +332 -0
- autoforge_trainer-1.2.0/packs/llm-preference/KNOWLEDGE.md +58 -0
- autoforge_trainer-1.2.0/packs/llm-preference/pack.yaml +10 -0
- autoforge_trainer-1.2.0/packs/llm-preference/recipes/dpo_lora.yaml +17 -0
- autoforge_trainer-1.2.0/packs/llm-preference/tests/test_smoke.py +56 -0
- autoforge_trainer-1.2.0/packs/llm-preference/trainers/dpo.py +308 -0
- autoforge_trainer-1.2.0/packs/object-detection/KNOWLEDGE.md +60 -0
- autoforge_trainer-1.2.0/packs/object-detection/pack.yaml +9 -0
- autoforge_trainer-1.2.0/packs/object-detection/recipes/torchvision_detector.yaml +16 -0
- autoforge_trainer-1.2.0/packs/object-detection/tests/test_smoke.py +113 -0
- autoforge_trainer-1.2.0/packs/object-detection/trainers/detector.py +383 -0
- autoforge_trainer-1.2.0/packs/recommendation/KNOWLEDGE.md +65 -0
- autoforge_trainer-1.2.0/packs/recommendation/pack.yaml +9 -0
- autoforge_trainer-1.2.0/packs/recommendation/recipes/implicit_als.yaml +15 -0
- autoforge_trainer-1.2.0/packs/recommendation/recipes/item_knn.yaml +13 -0
- autoforge_trainer-1.2.0/packs/recommendation/tests/test_smoke.py +79 -0
- autoforge_trainer-1.2.0/packs/recommendation/trainers/recsys.py +402 -0
- autoforge_trainer-1.2.0/packs/reinforcement-learning/KNOWLEDGE.md +79 -0
- autoforge_trainer-1.2.0/packs/reinforcement-learning/pack.yaml +10 -0
- autoforge_trainer-1.2.0/packs/reinforcement-learning/recipes/sb3.yaml +16 -0
- autoforge_trainer-1.2.0/packs/reinforcement-learning/tests/test_smoke.py +102 -0
- autoforge_trainer-1.2.0/packs/reinforcement-learning/trainers/sb3_train.py +292 -0
- autoforge_trainer-1.2.0/packs/speech-recognition/KNOWLEDGE.md +61 -0
- autoforge_trainer-1.2.0/packs/speech-recognition/pack.yaml +9 -0
- autoforge_trainer-1.2.0/packs/speech-recognition/recipes/whisper_finetune.yaml +15 -0
- autoforge_trainer-1.2.0/packs/speech-recognition/tests/test_smoke.py +94 -0
- autoforge_trainer-1.2.0/packs/speech-recognition/trainers/whisper_train.py +362 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/KNOWLEDGE.md +25 -4
- autoforge_trainer-1.2.0/packs/timeseries-forecast/pack.yaml +9 -0
- autoforge_trainer-1.2.0/packs/timeseries-forecast/recipes/global_lag_gbm.yaml +14 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/recipes/lag_gbm.yaml +1 -1
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/tests/test_smoke.py +32 -0
- autoforge_trainer-1.2.0/packs/timeseries-forecast/trainers/forecast.py +208 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/pyproject.toml +6 -4
- autoforge_trainer-1.2.0/tests/test_gui_agent.py +215 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_installer.py +16 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p5.py +8 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p7.py +53 -1
- autoforge_trainer-1.0.2/packs/timeseries-forecast/pack.yaml +0 -9
- autoforge_trainer-1.0.2/packs/timeseries-forecast/trainers/forecast.py +0 -97
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/.gitignore +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/LICENSE +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/api.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/data.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/hpo.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/main.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/models.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/output.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/packs.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/quickstart.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/runs.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/config.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/profile.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/registry.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/diagnose.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/runner.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/store.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/validate.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/worker.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/errors.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/examples/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/examples/churn.csv +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/hardware/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/authoring.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/index.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/manifest.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/policy/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/policy/privacy.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/registry/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/schemas/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/__init__.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/mcp_server.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/rest.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/ARCHITECTURE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/MCP.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/RELEASE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/examples/churn_plan.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/examples/data/churn.csv +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/recipes/isolation_forest.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/tests/test_smoke.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/trainers/iforest.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/recipes/timm_finetune.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/tests/test_smoke.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/trainers/timm_train.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/recipes/sft_lora.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/tests/test_smoke.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/trainers/sft.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/recipes/logistic_regression.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/recipes/random_forest.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/tests/test_smoke.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/trainers/sklearn_trainers.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/recipes/tfidf_linear.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/recipes/transformer_finetune.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/tests/test_smoke.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/common.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/tfidf.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/transformer.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/conftest.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/KNOWLEDGE.md +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/pack.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/adjustable.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/crash.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/needs_missing_pkg.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/quick.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/score.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/slow.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/stubborn.yaml +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/trainers/toy.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_agent.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli_packs.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli_runs.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_data_privacy.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_engine.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_gui.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_hardware.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_index.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_manifest.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_mcp.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p6.py +0 -0
- {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_privacy.py +0 -0
|
@@ -69,6 +69,7 @@ the CLI below offers the same operations with `--json`.
|
|
|
69
69
|
| PACK_EXISTS | already installed | use it, or reinstall with `--force` |
|
|
70
70
|
| PACK_INVALID | pack fails validation; `data.problems` lists why | do not use it |
|
|
71
71
|
| SHA_MISMATCH | source changed after indexing | do not install; tell the user |
|
|
72
|
+
| PACK_NEEDS_NEWER_AUTOFORGE | pack sets `min_autoforge` above this version | tell the user: pip install -U autoforge-trainer |
|
|
72
73
|
| INDEX_UNAVAILABLE | index unreachable and no cache | retry later or set AUTOFORGE_INDEX_URL |
|
|
73
74
|
| PACK_SOURCE_ERROR | bad source string or git failure | check the source |
|
|
74
75
|
| PLAN_INVALID | plan fails checks; `data.problems` lists why | fix the plan, validate again |
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# 變更紀錄
|
|
2
2
|
|
|
3
|
+
## 1.2.0 — 2026-10
|
|
4
|
+
|
|
5
|
+
- **網頁介面的 AI 助手**:在 `autoforge gui` 裡和內建 AI 代理對話。可以選 AI 大腦(Claude、OpenAI、本機 Ollama),用一句話說需求;AI 開始訓練、重新訓練或自動調參前會在頁面上請你同意,不同意時可以寫意見讓它修改;訓練完可以接著問。AI 用自己的身分操作,隱私規則和指令列相同。
|
|
6
|
+
- AI 代理改成可以多輪對話(`AgentSession`),`autoforge train` 的行為不變。
|
|
7
|
+
- 修正幾個還寫著舊套件名稱 `autoforge[...]` 的安裝提示。
|
|
8
|
+
|
|
9
|
+
## 1.1.0 — 2026-10
|
|
10
|
+
|
|
11
|
+
### 新方法包(P7 第二批)
|
|
12
|
+
- `object-detection`:物件偵測,torchvision Faster R-CNN 微調,支援 CSV 或 COCO 標註,評估 mAP@0.5。
|
|
13
|
+
- `image-segmentation`:影像分割,LR-ASPP / DeepLabV3,評估 mIoU,可匯出 ONNX。
|
|
14
|
+
- `embedding-finetune`:文字向量微調(搜尋、RAG),對比學習,報告微調前後的 recall@1,可匯出 ONNX。
|
|
15
|
+
- `speech-recognition`:Whisper 語音辨識微調,報告微調前後的 WER / CER。
|
|
16
|
+
- `llm-preference`:LLM 偏好對齊(DPO + LoRA / QLoRA),可從 `llm-finetune` 的結果接著訓練。
|
|
17
|
+
- `diffusion-lora`:Stable Diffusion 1.x/2.x 的 UNet LoRA,生成的圖片在網頁介面直接顯示。
|
|
18
|
+
- `reinforcement-learning`:stable-baselines3(PPO / A2C / DQN / SAC),環境可用 Gymnasium 名稱或自己寫的 `make_env()`。
|
|
19
|
+
- `recommendation`:推薦系統,隱式回饋 ALS 和 Item-KNN,和熱門排行的基準比較。
|
|
20
|
+
- `timeseries-forecast` 0.2.0:新增 `global_lag_gbm`,多條序列共用一個模型。
|
|
21
|
+
|
|
22
|
+
### 核心
|
|
23
|
+
- 安裝方法包時檢查 `min_autoforge`,版本太舊會回 `PACK_NEEDS_NEWER_AUTOFORGE`。
|
|
24
|
+
- 模型輸入新增 `audio` 類型(REST、MCP、網頁介面)。
|
|
25
|
+
- 自動調參能判斷 WER、CER、MAPE、WAPE 是越低越好。
|
|
26
|
+
- `ctx.check_updates()` 只在控制檔變動時才讀檔,每一步呼叫也沒有負擔。
|
|
27
|
+
- 新的安裝選項 `[rl]`;`[deep]` 加上 diffusers、soundfile。
|
|
28
|
+
|
|
29
|
+
### 已知限制
|
|
30
|
+
- 新的 GPU 方法(偵測、分割、Whisper、DPO、Diffusion LoRA)只用極小模型在 CPU 上驗證過流程,尚未在真的 NVIDIA GPU 上跑過。
|
|
31
|
+
|
|
3
32
|
## 1.0.2 — 2026-10
|
|
4
33
|
|
|
5
34
|
- **PyPI 套件名稱改為 `autoforge-trainer`**:`autoforge-ml` 在我們發佈前被別人註冊了。指令和 `import autoforge` 都不變。1.0.1 沒有上傳到 PyPI。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: autoforge-trainer
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: AI that trains AI: analyse your data and hardware, pick a method, train, tune and deploy models on your own machine — from a CLI, MCP, a web GUI or one sentence.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Honguan/autoforge
|
|
6
6
|
Project-URL: Documentation, https://honguan.github.io/autoforge/
|
|
@@ -42,6 +42,8 @@ Requires-Dist: uvicorn>=0.30
|
|
|
42
42
|
Provides-Extra: all
|
|
43
43
|
Requires-Dist: accelerate>=0.30; extra == 'all'
|
|
44
44
|
Requires-Dist: anthropic<2,>=1.14; extra == 'all'
|
|
45
|
+
Requires-Dist: diffusers>=0.30; extra == 'all'
|
|
46
|
+
Requires-Dist: gymnasium>=0.29; extra == 'all'
|
|
45
47
|
Requires-Dist: nvidia-ml-py>=12.0; extra == 'all'
|
|
46
48
|
Requires-Dist: onnx>=1.16; extra == 'all'
|
|
47
49
|
Requires-Dist: onnxruntime>=1.18; extra == 'all'
|
|
@@ -49,6 +51,8 @@ Requires-Dist: onnxscript>=0.1; extra == 'all'
|
|
|
49
51
|
Requires-Dist: peft>=0.12; extra == 'all'
|
|
50
52
|
Requires-Dist: pillow>=10.0; extra == 'all'
|
|
51
53
|
Requires-Dist: skl2onnx>=1.17; extra == 'all'
|
|
54
|
+
Requires-Dist: soundfile>=0.12; extra == 'all'
|
|
55
|
+
Requires-Dist: stable-baselines3>=2.3; extra == 'all'
|
|
52
56
|
Requires-Dist: timm>=1.0; extra == 'all'
|
|
53
57
|
Requires-Dist: torch>=2.3; extra == 'all'
|
|
54
58
|
Requires-Dist: torchvision>=0.18; extra == 'all'
|
|
@@ -57,9 +61,11 @@ Provides-Extra: anthropic
|
|
|
57
61
|
Requires-Dist: anthropic<2,>=1.14; extra == 'anthropic'
|
|
58
62
|
Provides-Extra: deep
|
|
59
63
|
Requires-Dist: accelerate>=0.30; extra == 'deep'
|
|
64
|
+
Requires-Dist: diffusers>=0.30; extra == 'deep'
|
|
60
65
|
Requires-Dist: nvidia-ml-py>=12.0; extra == 'deep'
|
|
61
66
|
Requires-Dist: peft>=0.12; extra == 'deep'
|
|
62
67
|
Requires-Dist: pillow>=10.0; extra == 'deep'
|
|
68
|
+
Requires-Dist: soundfile>=0.12; extra == 'deep'
|
|
63
69
|
Requires-Dist: timm>=1.0; extra == 'deep'
|
|
64
70
|
Requires-Dist: torch>=2.3; extra == 'deep'
|
|
65
71
|
Requires-Dist: torchvision>=0.18; extra == 'deep'
|
|
@@ -78,6 +84,9 @@ Requires-Dist: onnx>=1.16; extra == 'onnx'
|
|
|
78
84
|
Requires-Dist: onnxruntime>=1.18; extra == 'onnx'
|
|
79
85
|
Requires-Dist: onnxscript>=0.1; extra == 'onnx'
|
|
80
86
|
Requires-Dist: skl2onnx>=1.17; extra == 'onnx'
|
|
87
|
+
Provides-Extra: rl
|
|
88
|
+
Requires-Dist: gymnasium>=0.29; extra == 'rl'
|
|
89
|
+
Requires-Dist: stable-baselines3>=2.3; extra == 'rl'
|
|
81
90
|
Description-Content-Type: text/markdown
|
|
82
91
|
|
|
83
92
|
# AutoForge
|
|
@@ -89,7 +98,7 @@ Description-Content-Type: text/markdown
|
|
|
89
98
|
- **方法包**:從 GitHub 安裝的「教學包」,AI 讀了就知道怎麼訓練
|
|
90
99
|
- **隱私**:敏感資料只允許本地模型指揮
|
|
91
100
|
|
|
92
|
-
> **1.0
|
|
101
|
+
> **1.2.0** · 文件網站:<https://honguan.github.io/autoforge/> · [變更紀錄](CHANGELOG.md)
|
|
93
102
|
|
|
94
103
|
## 安裝與第一次使用
|
|
95
104
|
|
|
@@ -102,8 +111,8 @@ autoforge gui # 網頁介面
|
|
|
102
111
|
| 安裝方式 | 包含 |
|
|
103
112
|
|---|---|
|
|
104
113
|
| `pip install autoforge-trainer` | 表格、文字(TF-IDF)、時間序列、異常偵測、自動調參、網頁介面、MCP、REST |
|
|
105
|
-
| `pip install "autoforge-trainer[deep]"` | 再加上 Transformer、LLM
|
|
106
|
-
| `pip install "autoforge-trainer[all]"` |
|
|
114
|
+
| `pip install "autoforge-trainer[deep]"` | 再加上 Transformer、Embedding、LLM 微調與 DPO、圖片分類、物件偵測、影像分割、語音辨識、圖像生成 LoRA(NVIDIA GPU) |
|
|
115
|
+
| `pip install "autoforge-trainer[all]"` | 再加上強化學習、Claude 代理、ONNX 匯出 |
|
|
107
116
|
| `docker run --rm -p 127.0.0.1:8800:8800 -v autoforge:/data ghcr.io/honguan/autoforge:cpu` | CPU 映像,預設啟動網頁介面 |
|
|
108
117
|
| `docker run --rm --gpus all -p 127.0.0.1:8800:8800 -v autoforge:/data ghcr.io/honguan/autoforge:cuda` | NVIDIA CUDA 映像,所有方法包 |
|
|
109
118
|
|
|
@@ -119,8 +128,16 @@ PyPI 上的 `autoforge` 是另一個不相關的專案(`autoforge-ai`、`autof
|
|
|
119
128
|
| `text-classification` | 文字分類(情緒、意圖、主題、垃圾訊息) | TF-IDF + 線性模型;Transformer 微調 | CPU / GPU |
|
|
120
129
|
| `llm-finetune` | 大型語言模型微調 | LoRA / QLoRA(可匯出合併後的權重) | GPU(8GB+) |
|
|
121
130
|
| `image-classification` | 圖片分類 | timm 預訓練模型微調(可匯出 ONNX) | GPU 建議,CPU 可跑小模型 |
|
|
122
|
-
| `timeseries-forecast` |
|
|
131
|
+
| `timeseries-forecast` | 時間序列預測(銷量、用電、流量),單一或多條序列 | 落後特徵 + 梯度提升;多序列共用一個模型;和「明天=今天」的基準比較 | CPU |
|
|
123
132
|
| `anomaly-detection` | 異常偵測(詐欺、故障、離群值) | Isolation Forest | CPU |
|
|
133
|
+
| `object-detection` | 物件偵測(框出物品、瑕疵、人車) | torchvision Faster R-CNN 微調,mAP@0.5 | GPU 建議,CPU 可跑小模型 |
|
|
134
|
+
| `image-segmentation` | 影像分割(逐像素標類別) | torchvision LR-ASPP / DeepLabV3,mIoU | GPU 建議,CPU 可跑小模型 |
|
|
135
|
+
| `embedding-finetune` | 文字向量微調(搜尋、RAG 檢索、相似度) | 對比學習(同批負樣本),和微調前比較 recall@1 | CPU / GPU |
|
|
136
|
+
| `speech-recognition` | 語音辨識 | Whisper 微調,WER / CER,和微調前比較 | GPU 建議 |
|
|
137
|
+
| `llm-preference` | LLM 偏好對齊(選好的回答、避開壞的) | DPO + LoRA / QLoRA | GPU(8GB+) |
|
|
138
|
+
| `diffusion-lora` | 圖像生成風格 / 主體 LoRA | Stable Diffusion 1.x/2.x UNet LoRA | GPU(8GB+) |
|
|
139
|
+
| `reinforcement-learning` | 強化學習(遊戲、控制、自己的模擬器) | stable-baselines3:PPO / A2C / DQN / SAC | CPU(小網路)/ GPU |
|
|
140
|
+
| `recommendation` | 推薦系統(商品、內容) | 隱式回饋 ALS、Item-KNN,和熱門排行比較 | CPU |
|
|
124
141
|
|
|
125
142
|
每個方法包都附給 AI 讀的 `KNOWLEDGE.md`,以及在 CPU 上 30 秒內跑完的測試。自己做方法包:`autoforge pack new`(見 [docs/PACK_SPEC.md](docs/PACK_SPEC.md))。
|
|
126
143
|
|
|
@@ -128,7 +145,7 @@ PyPI 上的 `autoforge` 是另一個不相關的專案(`autoforge-ai`、`autof
|
|
|
128
145
|
|
|
129
146
|
```bash
|
|
130
147
|
git clone https://github.com/Honguan/autoforge && cd autoforge
|
|
131
|
-
pip install -e ".[dev]" # 加上 ,deep
|
|
148
|
+
pip install -e ".[dev]" # 加上 ,deep 安裝深度學習方法包的依賴,,rl 安裝強化學習的依賴
|
|
132
149
|
pytest -q
|
|
133
150
|
```
|
|
134
151
|
|
|
@@ -136,6 +153,7 @@ pytest -q
|
|
|
136
153
|
|
|
137
154
|
```bash
|
|
138
155
|
autoforge gui # 打開瀏覽器:上傳資料 → 選任務 → 選方法 → 訓練 → 調參 → 註冊 → 預測
|
|
156
|
+
# 或在「AI 助手」頁面用一句話讓 AI 幫你做完(開始訓練前會先問你)
|
|
139
157
|
```
|
|
140
158
|
|
|
141
159
|
## 使用
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- **方法包**:從 GitHub 安裝的「教學包」,AI 讀了就知道怎麼訓練
|
|
8
8
|
- **隱私**:敏感資料只允許本地模型指揮
|
|
9
9
|
|
|
10
|
-
> **1.0
|
|
10
|
+
> **1.2.0** · 文件網站:<https://honguan.github.io/autoforge/> · [變更紀錄](CHANGELOG.md)
|
|
11
11
|
|
|
12
12
|
## 安裝與第一次使用
|
|
13
13
|
|
|
@@ -20,8 +20,8 @@ autoforge gui # 網頁介面
|
|
|
20
20
|
| 安裝方式 | 包含 |
|
|
21
21
|
|---|---|
|
|
22
22
|
| `pip install autoforge-trainer` | 表格、文字(TF-IDF)、時間序列、異常偵測、自動調參、網頁介面、MCP、REST |
|
|
23
|
-
| `pip install "autoforge-trainer[deep]"` | 再加上 Transformer、LLM
|
|
24
|
-
| `pip install "autoforge-trainer[all]"` |
|
|
23
|
+
| `pip install "autoforge-trainer[deep]"` | 再加上 Transformer、Embedding、LLM 微調與 DPO、圖片分類、物件偵測、影像分割、語音辨識、圖像生成 LoRA(NVIDIA GPU) |
|
|
24
|
+
| `pip install "autoforge-trainer[all]"` | 再加上強化學習、Claude 代理、ONNX 匯出 |
|
|
25
25
|
| `docker run --rm -p 127.0.0.1:8800:8800 -v autoforge:/data ghcr.io/honguan/autoforge:cpu` | CPU 映像,預設啟動網頁介面 |
|
|
26
26
|
| `docker run --rm --gpus all -p 127.0.0.1:8800:8800 -v autoforge:/data ghcr.io/honguan/autoforge:cuda` | NVIDIA CUDA 映像,所有方法包 |
|
|
27
27
|
|
|
@@ -37,8 +37,16 @@ PyPI 上的 `autoforge` 是另一個不相關的專案(`autoforge-ai`、`autof
|
|
|
37
37
|
| `text-classification` | 文字分類(情緒、意圖、主題、垃圾訊息) | TF-IDF + 線性模型;Transformer 微調 | CPU / GPU |
|
|
38
38
|
| `llm-finetune` | 大型語言模型微調 | LoRA / QLoRA(可匯出合併後的權重) | GPU(8GB+) |
|
|
39
39
|
| `image-classification` | 圖片分類 | timm 預訓練模型微調(可匯出 ONNX) | GPU 建議,CPU 可跑小模型 |
|
|
40
|
-
| `timeseries-forecast` |
|
|
40
|
+
| `timeseries-forecast` | 時間序列預測(銷量、用電、流量),單一或多條序列 | 落後特徵 + 梯度提升;多序列共用一個模型;和「明天=今天」的基準比較 | CPU |
|
|
41
41
|
| `anomaly-detection` | 異常偵測(詐欺、故障、離群值) | Isolation Forest | CPU |
|
|
42
|
+
| `object-detection` | 物件偵測(框出物品、瑕疵、人車) | torchvision Faster R-CNN 微調,mAP@0.5 | GPU 建議,CPU 可跑小模型 |
|
|
43
|
+
| `image-segmentation` | 影像分割(逐像素標類別) | torchvision LR-ASPP / DeepLabV3,mIoU | GPU 建議,CPU 可跑小模型 |
|
|
44
|
+
| `embedding-finetune` | 文字向量微調(搜尋、RAG 檢索、相似度) | 對比學習(同批負樣本),和微調前比較 recall@1 | CPU / GPU |
|
|
45
|
+
| `speech-recognition` | 語音辨識 | Whisper 微調,WER / CER,和微調前比較 | GPU 建議 |
|
|
46
|
+
| `llm-preference` | LLM 偏好對齊(選好的回答、避開壞的) | DPO + LoRA / QLoRA | GPU(8GB+) |
|
|
47
|
+
| `diffusion-lora` | 圖像生成風格 / 主體 LoRA | Stable Diffusion 1.x/2.x UNet LoRA | GPU(8GB+) |
|
|
48
|
+
| `reinforcement-learning` | 強化學習(遊戲、控制、自己的模擬器) | stable-baselines3:PPO / A2C / DQN / SAC | CPU(小網路)/ GPU |
|
|
49
|
+
| `recommendation` | 推薦系統(商品、內容) | 隱式回饋 ALS、Item-KNN,和熱門排行比較 | CPU |
|
|
42
50
|
|
|
43
51
|
每個方法包都附給 AI 讀的 `KNOWLEDGE.md`,以及在 CPU 上 30 秒內跑完的測試。自己做方法包:`autoforge pack new`(見 [docs/PACK_SPEC.md](docs/PACK_SPEC.md))。
|
|
44
52
|
|
|
@@ -46,7 +54,7 @@ PyPI 上的 `autoforge` 是另一個不相關的專案(`autoforge-ai`、`autof
|
|
|
46
54
|
|
|
47
55
|
```bash
|
|
48
56
|
git clone https://github.com/Honguan/autoforge && cd autoforge
|
|
49
|
-
pip install -e ".[dev]" # 加上 ,deep
|
|
57
|
+
pip install -e ".[dev]" # 加上 ,deep 安裝深度學習方法包的依賴,,rl 安裝強化學習的依賴
|
|
50
58
|
pytest -q
|
|
51
59
|
```
|
|
52
60
|
|
|
@@ -54,6 +62,7 @@ pytest -q
|
|
|
54
62
|
|
|
55
63
|
```bash
|
|
56
64
|
autoforge gui # 打開瀏覽器:上傳資料 → 選任務 → 選方法 → 訓練 → 調參 → 註冊 → 預測
|
|
65
|
+
# 或在「AI 助手」頁面用一句話讓 AI 幫你做完(開始訓練前會先問你)
|
|
57
66
|
```
|
|
58
67
|
|
|
59
68
|
## 使用
|
|
@@ -85,35 +85,84 @@ class Transcript:
|
|
|
85
85
|
f.write(json.dumps({"t": time.time(), "kind": kind, **data}, ensure_ascii=False, default=str) + "\n")
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
def describe_call(call: ToolCall) -> str:
|
|
89
|
+
"""Plain-text summary of a gated call (what training would start), shown before asking the person."""
|
|
90
|
+
if call.name == "run_retry":
|
|
91
|
+
return f"retry run {call.arguments.get('run_id')}"
|
|
92
|
+
if call.name == "hpo_start":
|
|
93
|
+
a = call.arguments
|
|
94
|
+
head = (f" hyperparameter search: {a.get('n_trials', 10)} trials, optimise {a.get('metric')}"
|
|
95
|
+
f" ({a.get('direction') or 'auto direction'})\n")
|
|
96
|
+
return head + describe_call(ToolCall(call.id, "run_start", {"plan": a.get("plan")}))
|
|
97
|
+
plan = call.arguments.get("plan") or {}
|
|
98
|
+
task = plan.get("task") or {}
|
|
99
|
+
method = (f"{plan.get('pack')} / {plan.get('recipe')}" if plan.get("pack")
|
|
100
|
+
else f"custom code {plan.get('custom_code_path')}")
|
|
101
|
+
lines = [
|
|
102
|
+
f" task: {task.get('task_type')} (target: {task.get('target')}, metric: {task.get('metric')})",
|
|
103
|
+
f" data: {plan.get('data_path')}",
|
|
104
|
+
f" method: {method}",
|
|
105
|
+
f" device: {plan.get('device', 'auto')}",
|
|
106
|
+
f" hyperparameters: {json.dumps(plan.get('hyperparameters') or {}, ensure_ascii=False)}",
|
|
107
|
+
]
|
|
108
|
+
return "\n".join(lines)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class AgentSession:
|
|
113
|
+
"""A conversation with the agent: enter once, `send()` each user message, exit at the end.
|
|
114
|
+
|
|
115
|
+
The MCP tools stay connected between messages and the provider keeps the whole history, so a
|
|
116
|
+
follow-up like "now tune it" builds on what the agent already did. `should_stop` is checked
|
|
117
|
+
between model turns (the GUI's stop button); a stopped turn ends with stop="stopped".
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, provider: Provider, actor: str, brain: BrainKind, approve: ApproveFn | None = None,
|
|
121
|
+
on_event: EventFn | None = None, max_steps: int = 40,
|
|
122
|
+
should_stop: Callable[[], bool] | None = None, origin: str | None = None):
|
|
123
|
+
self.provider, self.actor, self.brain = provider, actor, brain
|
|
124
|
+
self.approve, self.max_steps = approve, max_steps
|
|
125
|
+
self.emit = on_event or (lambda e: None)
|
|
126
|
+
self.should_stop = should_stop or (lambda: False)
|
|
127
|
+
self.origin = origin
|
|
128
|
+
self.runs: list[str] = []
|
|
129
|
+
self.transcript: Transcript | None = None
|
|
130
|
+
self._client: Client | None = None
|
|
131
|
+
|
|
132
|
+
async def __aenter__(self) -> "AgentSession":
|
|
133
|
+
self.transcript = Transcript()
|
|
134
|
+
self.transcript.write("start", actor=self.actor, brain=self.brain.value, origin=self.origin)
|
|
135
|
+
server = build_server(self.actor, log_level="WARNING") # the agent reports tool errors itself
|
|
136
|
+
self._client = Client(server)
|
|
137
|
+
await self._client.__aenter__()
|
|
138
|
+
listed = await self._client.list_tools()
|
|
110
139
|
tools = [ToolSpec(t.name, t.description or "", t.input_schema) for t in listed.tools]
|
|
111
|
-
provider.start(SYSTEM_PROMPT.format(instructions=INSTRUCTIONS), tools)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
self.provider.start(SYSTEM_PROMPT.format(instructions=INSTRUCTIONS), tools)
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
async def __aexit__(self, *exc: Any) -> None:
|
|
144
|
+
if self._client is not None:
|
|
145
|
+
await self._client.__aexit__(*exc)
|
|
146
|
+
self._client = None
|
|
147
|
+
if self.transcript:
|
|
148
|
+
self.transcript.write("close", runs=self.runs)
|
|
149
|
+
|
|
150
|
+
async def send(self, text: str, data_path: str | None = None) -> AgentResult:
|
|
151
|
+
"""One user message → model turns and tool calls until the model answers (or a limit is hit)."""
|
|
152
|
+
assert self._client is not None and self.transcript is not None, "use `async with AgentSession(...)`"
|
|
153
|
+
mcp, transcript, emit = self._client, self.transcript, self.emit
|
|
154
|
+
user_msg = text + (f"\n\nData: {Path(data_path).expanduser().resolve()}" if data_path else "")
|
|
155
|
+
transcript.write("user", text=text, data_path=data_path)
|
|
156
|
+
self.provider.add_user(user_msg)
|
|
157
|
+
runs: list[str] = []
|
|
158
|
+
|
|
159
|
+
final_text, stop, step = "", "max_steps", 0
|
|
160
|
+
for step in range(1, self.max_steps + 1):
|
|
161
|
+
if self.should_stop():
|
|
162
|
+
stop = "stopped"
|
|
163
|
+
emit(AgentEvent("info", {"message": "stopped by the user"}))
|
|
164
|
+
break
|
|
165
|
+
turn = await self.provider.complete()
|
|
117
166
|
if turn.text:
|
|
118
167
|
final_text = turn.text
|
|
119
168
|
emit(AgentEvent("text", {"text": turn.text}))
|
|
@@ -136,8 +185,8 @@ async def run_agent(
|
|
|
136
185
|
content = json.dumps({"ok": False, "error": {"code": "BAD_TOOL_CALL", "message": call.parse_error}})
|
|
137
186
|
results.append(ToolResult(call.id, content, is_error=True))
|
|
138
187
|
continue
|
|
139
|
-
if call.name in GATED_TOOLS and approve is not None:
|
|
140
|
-
ok, feedback = await approve(call)
|
|
188
|
+
if call.name in GATED_TOOLS and self.approve is not None:
|
|
189
|
+
ok, feedback = await self.approve(call)
|
|
141
190
|
emit(AgentEvent("approval", {"name": call.name, "approved": ok, "feedback": feedback}))
|
|
142
191
|
transcript.write("approval", name=call.name, approved=ok, feedback=feedback)
|
|
143
192
|
if not ok:
|
|
@@ -148,21 +197,49 @@ async def run_agent(
|
|
|
148
197
|
res = await mcp.call_tool(call.name, call.arguments)
|
|
149
198
|
envelope = res.structured_content
|
|
150
199
|
if envelope is None: # unstructured (e.g. argument validation error from the MCP layer)
|
|
151
|
-
|
|
152
|
-
envelope = {"ok": False, "error": {"code": "BAD_TOOL_CALL", "message":
|
|
200
|
+
text_out = "\n".join(getattr(c, "text", "") for c in res.content)
|
|
201
|
+
envelope = {"ok": False, "error": {"code": "BAD_TOOL_CALL", "message": text_out}}
|
|
153
202
|
if call.name in ("run_start", "run_retry") and envelope.get("ok"):
|
|
154
203
|
runs.append(envelope["data"]["run_id"])
|
|
155
204
|
if call.name == "hpo_start" and envelope.get("ok"):
|
|
156
205
|
runs.append(envelope["data"]["study_id"])
|
|
157
206
|
content = _truncate(json.dumps(envelope, ensure_ascii=False, default=str))
|
|
158
207
|
emit(AgentEvent("tool_result", {"name": call.name, "ok": envelope.get("ok"),
|
|
159
|
-
"error": (envelope.get("error") or {}).get("code")
|
|
208
|
+
"error": (envelope.get("error") or {}).get("code"),
|
|
209
|
+
"data": _brief(call.name, envelope)}))
|
|
160
210
|
transcript.write("tool_result", name=call.name, ok=envelope.get("ok"), content=content[:2000])
|
|
161
211
|
results.append(ToolResult(call.id, content, is_error=not envelope.get("ok", False)))
|
|
162
|
-
provider.add_tool_results(results)
|
|
163
|
-
|
|
164
|
-
|
|
212
|
+
self.provider.add_tool_results(results)
|
|
213
|
+
|
|
214
|
+
self.runs += runs
|
|
215
|
+
transcript.write("done", stop=stop, steps=step, runs=runs)
|
|
216
|
+
emit(AgentEvent("done", {"stop": stop, "steps": step, "runs": runs}))
|
|
217
|
+
return AgentResult(final_text=final_text, steps=step, stop=stop, runs=runs, transcript=str(transcript.path))
|
|
165
218
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
219
|
+
|
|
220
|
+
def _brief(name: str, envelope: dict) -> dict[str, Any] | None:
|
|
221
|
+
"""The few result fields a UI shows next to a tool call (ids and metrics), never whole payloads."""
|
|
222
|
+
data = envelope.get("data")
|
|
223
|
+
if not envelope.get("ok") or not isinstance(data, dict):
|
|
224
|
+
return None
|
|
225
|
+
out = {}
|
|
226
|
+
for k in ("run_id", "study_id", "status", "metrics", "model", "version", "name", "best_value", "improvement"):
|
|
227
|
+
v = data.get(k)
|
|
228
|
+
if isinstance(v, (int, float, bool, dict)) or (isinstance(v, str) and len(v) < 200):
|
|
229
|
+
out[k] = v
|
|
230
|
+
return out or None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def run_agent(
|
|
234
|
+
goal: str,
|
|
235
|
+
provider: Provider,
|
|
236
|
+
actor: str,
|
|
237
|
+
brain: BrainKind,
|
|
238
|
+
approve: ApproveFn | None = None,
|
|
239
|
+
on_event: EventFn | None = None,
|
|
240
|
+
max_steps: int = 40,
|
|
241
|
+
data_path: str | None = None,
|
|
242
|
+
) -> AgentResult:
|
|
243
|
+
"""One-shot agent (CLI `autoforge train`): a single goal, run until the model reports back."""
|
|
244
|
+
async with AgentSession(provider, actor, brain, approve, on_event, max_steps, origin="cli") as session:
|
|
245
|
+
return await session.send(goal, data_path)
|
|
@@ -136,7 +136,7 @@ class AnthropicProvider(Provider):
|
|
|
136
136
|
try:
|
|
137
137
|
import anthropic
|
|
138
138
|
except ImportError:
|
|
139
|
-
raise ProviderError("the anthropic package is not installed", hint='pip install "autoforge[anthropic]"')
|
|
139
|
+
raise ProviderError("the anthropic package is not installed", hint='pip install "autoforge-trainer[anthropic]"')
|
|
140
140
|
client = anthropic.AsyncAnthropic()
|
|
141
141
|
self.client = client
|
|
142
142
|
self.model = model
|
|
@@ -8,38 +8,16 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
-
from autoforge.agents.loop import AgentEvent, run_agent
|
|
11
|
+
from autoforge.agents.loop import AgentEvent, describe_call, run_agent
|
|
12
12
|
from autoforge.agents.providers import ToolCall, make_provider, parse_brain
|
|
13
13
|
from autoforge.cli.output import emit, errors_as_envelope, fail
|
|
14
14
|
|
|
15
15
|
_SHORT = {"hardware_profile", "pack_list", "run_status", "run_wait", "run_metrics", "run_list"}
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def _describe_plan(call: ToolCall) -> str:
|
|
19
|
-
if call.name == "run_retry":
|
|
20
|
-
return f"retry run {call.arguments.get('run_id')}"
|
|
21
|
-
if call.name == "hpo_start":
|
|
22
|
-
a = call.arguments
|
|
23
|
-
head = (f" hyperparameter search: {a.get('n_trials', 10)} trials, optimise {a.get('metric')}"
|
|
24
|
-
f" ({a.get('direction') or 'auto direction'})\n")
|
|
25
|
-
return head + _describe_plan(ToolCall(call.id, "run_start", {"plan": a.get("plan")}))
|
|
26
|
-
plan = call.arguments.get("plan") or {}
|
|
27
|
-
task = plan.get("task") or {}
|
|
28
|
-
method = (f"{plan.get('pack')} / {plan.get('recipe')}" if plan.get("pack")
|
|
29
|
-
else f"custom code {plan.get('custom_code_path')}")
|
|
30
|
-
lines = [
|
|
31
|
-
f" task: {task.get('task_type')} (target: {task.get('target')}, metric: {task.get('metric')})",
|
|
32
|
-
f" data: {plan.get('data_path')}",
|
|
33
|
-
f" method: {method}",
|
|
34
|
-
f" device: {plan.get('device', 'auto')}",
|
|
35
|
-
f" hyperparameters: {json.dumps(plan.get('hyperparameters') or {}, ensure_ascii=False)}",
|
|
36
|
-
]
|
|
37
|
-
return "\n".join(lines)
|
|
38
|
-
|
|
39
|
-
|
|
40
18
|
async def _ask(call: ToolCall) -> tuple[bool, str]:
|
|
41
19
|
typer.echo("\n" + typer.style("The agent wants to start training:", bold=True))
|
|
42
|
-
typer.echo(
|
|
20
|
+
typer.echo(describe_call(call))
|
|
43
21
|
answer = typer.prompt("Start? [y = yes / n = no / or type feedback for the agent]", default="n").strip()
|
|
44
22
|
if answer.lower() in ("y", "yes"):
|
|
45
23
|
return True, ""
|
|
@@ -51,6 +51,7 @@ class TrainContext:
|
|
|
51
51
|
self._last_check = 0.0
|
|
52
52
|
self._stop = False
|
|
53
53
|
self._adjust_seq = 0
|
|
54
|
+
self._control_mtime: tuple | None = None
|
|
54
55
|
|
|
55
56
|
# ---- metrics / logging ----
|
|
56
57
|
|
|
@@ -80,8 +81,15 @@ class TrainContext:
|
|
|
80
81
|
|
|
81
82
|
def check_updates(self) -> dict[str, Any]:
|
|
82
83
|
"""Hyperparameter changes sent with `run adjust` since the last call (already applied to
|
|
83
|
-
self.hyperparameters). Trainers that support live changes call this between steps.
|
|
84
|
+
self.hyperparameters). Trainers that support live changes call this between steps; control.json
|
|
85
|
+
is only re-read when it changed, so calling this every step is cheap."""
|
|
84
86
|
try:
|
|
87
|
+
st = self._control.stat()
|
|
88
|
+
# (mtime, size) plus a re-read every 2 s covers filesystems with coarse timestamps
|
|
89
|
+
key = (st.st_mtime_ns, st.st_size, int(time.monotonic() // 2))
|
|
90
|
+
if key == self._control_mtime:
|
|
91
|
+
return {}
|
|
92
|
+
self._control_mtime = key
|
|
85
93
|
control = json.loads(self._control.read_text())
|
|
86
94
|
except (OSError, ValueError):
|
|
87
95
|
return {}
|
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import json
|
|
21
21
|
import math
|
|
22
|
+
import re
|
|
22
23
|
import secrets
|
|
23
24
|
import sys
|
|
24
25
|
import time
|
|
@@ -38,6 +39,7 @@ from autoforge.policy.privacy import brain_from_actor
|
|
|
38
39
|
from autoforge.schemas.core import BrainKind, RunStatus, TrainingPlan
|
|
39
40
|
|
|
40
41
|
MINIMIZE_HINTS = ("loss", "rmse", "mse", "mae", "error", "perplexity")
|
|
42
|
+
MINIMIZE_WORDS = {"wer", "cer", "mape", "wape", "smape"} # whole words only: "wer" must not match "lower"
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class StudyNotFound(AutoForgeError):
|
|
@@ -92,7 +94,9 @@ def studies_dir() -> Path:
|
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
def infer_direction(metric: str) -> str:
|
|
95
|
-
|
|
97
|
+
name = metric.lower()
|
|
98
|
+
words = set(re.split(r"[^a-z0-9]+", name))
|
|
99
|
+
return "minimize" if any(h in name for h in MINIMIZE_HINTS) or words & MINIMIZE_WORDS else "maximize"
|
|
96
100
|
|
|
97
101
|
|
|
98
102
|
def _validate_space(space: dict[str, Any]) -> list[str]:
|