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.
Files changed (179) hide show
  1. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/AGENTS.md +1 -0
  2. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/CHANGELOG.md +29 -0
  3. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/PKG-INFO +24 -6
  4. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/README.md +14 -5
  5. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/__init__.py +1 -1
  6. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/loop.py +116 -39
  7. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/providers.py +1 -1
  8. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/agent.py +2 -24
  9. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/context.py +9 -1
  10. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/hpo.py +5 -1
  11. autoforge_trainer-1.2.0/autoforge/gui/agent_chat.py +184 -0
  12. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/app.py +87 -4
  13. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/app.js +164 -2
  14. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/index.html +1 -0
  15. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/static/style.css +17 -0
  16. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/hardware/detect.py +1 -1
  17. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/errors.py +5 -0
  18. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/installer.py +14 -1
  19. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/registry/models.py +2 -2
  20. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/schemas/core.py +1 -1
  21. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/model_mcp.py +2 -1
  22. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/AGENT.md +3 -1
  23. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/DEPLOY.md +1 -1
  24. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/GUI.md +11 -2
  25. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/IMPROVE.md +1 -1
  26. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/PACK_SPEC.md +2 -2
  27. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/ROADMAP.md +2 -2
  28. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/index.md +11 -3
  29. autoforge_trainer-1.2.0/packs/diffusion-lora/KNOWLEDGE.md +61 -0
  30. autoforge_trainer-1.2.0/packs/diffusion-lora/pack.yaml +9 -0
  31. autoforge_trainer-1.2.0/packs/diffusion-lora/recipes/sd_lora.yaml +16 -0
  32. autoforge_trainer-1.2.0/packs/diffusion-lora/tests/test_smoke.py +66 -0
  33. autoforge_trainer-1.2.0/packs/diffusion-lora/trainers/sd_lora.py +364 -0
  34. autoforge_trainer-1.2.0/packs/embedding-finetune/KNOWLEDGE.md +61 -0
  35. autoforge_trainer-1.2.0/packs/embedding-finetune/pack.yaml +10 -0
  36. autoforge_trainer-1.2.0/packs/embedding-finetune/recipes/contrastive_pairs.yaml +17 -0
  37. autoforge_trainer-1.2.0/packs/embedding-finetune/tests/test_smoke.py +71 -0
  38. autoforge_trainer-1.2.0/packs/embedding-finetune/trainers/contrastive.py +287 -0
  39. autoforge_trainer-1.2.0/packs/image-segmentation/KNOWLEDGE.md +60 -0
  40. autoforge_trainer-1.2.0/packs/image-segmentation/pack.yaml +10 -0
  41. autoforge_trainer-1.2.0/packs/image-segmentation/recipes/torchvision_segmenter.yaml +17 -0
  42. autoforge_trainer-1.2.0/packs/image-segmentation/tests/test_smoke.py +104 -0
  43. autoforge_trainer-1.2.0/packs/image-segmentation/trainers/segmenter.py +332 -0
  44. autoforge_trainer-1.2.0/packs/llm-preference/KNOWLEDGE.md +58 -0
  45. autoforge_trainer-1.2.0/packs/llm-preference/pack.yaml +10 -0
  46. autoforge_trainer-1.2.0/packs/llm-preference/recipes/dpo_lora.yaml +17 -0
  47. autoforge_trainer-1.2.0/packs/llm-preference/tests/test_smoke.py +56 -0
  48. autoforge_trainer-1.2.0/packs/llm-preference/trainers/dpo.py +308 -0
  49. autoforge_trainer-1.2.0/packs/object-detection/KNOWLEDGE.md +60 -0
  50. autoforge_trainer-1.2.0/packs/object-detection/pack.yaml +9 -0
  51. autoforge_trainer-1.2.0/packs/object-detection/recipes/torchvision_detector.yaml +16 -0
  52. autoforge_trainer-1.2.0/packs/object-detection/tests/test_smoke.py +113 -0
  53. autoforge_trainer-1.2.0/packs/object-detection/trainers/detector.py +383 -0
  54. autoforge_trainer-1.2.0/packs/recommendation/KNOWLEDGE.md +65 -0
  55. autoforge_trainer-1.2.0/packs/recommendation/pack.yaml +9 -0
  56. autoforge_trainer-1.2.0/packs/recommendation/recipes/implicit_als.yaml +15 -0
  57. autoforge_trainer-1.2.0/packs/recommendation/recipes/item_knn.yaml +13 -0
  58. autoforge_trainer-1.2.0/packs/recommendation/tests/test_smoke.py +79 -0
  59. autoforge_trainer-1.2.0/packs/recommendation/trainers/recsys.py +402 -0
  60. autoforge_trainer-1.2.0/packs/reinforcement-learning/KNOWLEDGE.md +79 -0
  61. autoforge_trainer-1.2.0/packs/reinforcement-learning/pack.yaml +10 -0
  62. autoforge_trainer-1.2.0/packs/reinforcement-learning/recipes/sb3.yaml +16 -0
  63. autoforge_trainer-1.2.0/packs/reinforcement-learning/tests/test_smoke.py +102 -0
  64. autoforge_trainer-1.2.0/packs/reinforcement-learning/trainers/sb3_train.py +292 -0
  65. autoforge_trainer-1.2.0/packs/speech-recognition/KNOWLEDGE.md +61 -0
  66. autoforge_trainer-1.2.0/packs/speech-recognition/pack.yaml +9 -0
  67. autoforge_trainer-1.2.0/packs/speech-recognition/recipes/whisper_finetune.yaml +15 -0
  68. autoforge_trainer-1.2.0/packs/speech-recognition/tests/test_smoke.py +94 -0
  69. autoforge_trainer-1.2.0/packs/speech-recognition/trainers/whisper_train.py +362 -0
  70. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/KNOWLEDGE.md +25 -4
  71. autoforge_trainer-1.2.0/packs/timeseries-forecast/pack.yaml +9 -0
  72. autoforge_trainer-1.2.0/packs/timeseries-forecast/recipes/global_lag_gbm.yaml +14 -0
  73. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/recipes/lag_gbm.yaml +1 -1
  74. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/timeseries-forecast/tests/test_smoke.py +32 -0
  75. autoforge_trainer-1.2.0/packs/timeseries-forecast/trainers/forecast.py +208 -0
  76. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/pyproject.toml +6 -4
  77. autoforge_trainer-1.2.0/tests/test_gui_agent.py +215 -0
  78. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_installer.py +16 -0
  79. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p5.py +8 -0
  80. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p7.py +53 -1
  81. autoforge_trainer-1.0.2/packs/timeseries-forecast/pack.yaml +0 -9
  82. autoforge_trainer-1.0.2/packs/timeseries-forecast/trainers/forecast.py +0 -97
  83. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/.gitignore +0 -0
  84. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/LICENSE +0 -0
  85. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/agents/__init__.py +0 -0
  86. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/api.py +0 -0
  87. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/__init__.py +0 -0
  88. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/data.py +0 -0
  89. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/hpo.py +0 -0
  90. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/main.py +0 -0
  91. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/models.py +0 -0
  92. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/output.py +0 -0
  93. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/packs.py +0 -0
  94. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/quickstart.py +0 -0
  95. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/cli/runs.py +0 -0
  96. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/config.py +0 -0
  97. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/__init__.py +0 -0
  98. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/profile.py +0 -0
  99. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/data/registry.py +0 -0
  100. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/__init__.py +0 -0
  101. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/diagnose.py +0 -0
  102. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/runner.py +0 -0
  103. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/store.py +0 -0
  104. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/validate.py +0 -0
  105. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/engine/worker.py +0 -0
  106. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/errors.py +0 -0
  107. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/examples/__init__.py +0 -0
  108. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/examples/churn.csv +0 -0
  109. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/gui/__init__.py +0 -0
  110. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/hardware/__init__.py +0 -0
  111. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/__init__.py +0 -0
  112. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/authoring.py +0 -0
  113. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/index.py +0 -0
  114. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/packs/manifest.py +0 -0
  115. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/policy/__init__.py +0 -0
  116. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/policy/privacy.py +0 -0
  117. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/registry/__init__.py +0 -0
  118. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/schemas/__init__.py +0 -0
  119. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/__init__.py +0 -0
  120. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/mcp_server.py +0 -0
  121. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/autoforge/serve/rest.py +0 -0
  122. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/ARCHITECTURE.md +0 -0
  123. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/MCP.md +0 -0
  124. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/docs/RELEASE.md +0 -0
  125. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/examples/churn_plan.yaml +0 -0
  126. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/examples/data/churn.csv +0 -0
  127. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/KNOWLEDGE.md +0 -0
  128. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/pack.yaml +0 -0
  129. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/recipes/isolation_forest.yaml +0 -0
  130. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/tests/test_smoke.py +0 -0
  131. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/anomaly-detection/trainers/iforest.py +0 -0
  132. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/KNOWLEDGE.md +0 -0
  133. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/pack.yaml +0 -0
  134. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/recipes/timm_finetune.yaml +0 -0
  135. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/tests/test_smoke.py +0 -0
  136. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/image-classification/trainers/timm_train.py +0 -0
  137. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/KNOWLEDGE.md +0 -0
  138. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/pack.yaml +0 -0
  139. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/recipes/sft_lora.yaml +0 -0
  140. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/tests/test_smoke.py +0 -0
  141. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/llm-finetune/trainers/sft.py +0 -0
  142. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/KNOWLEDGE.md +0 -0
  143. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/pack.yaml +0 -0
  144. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/recipes/logistic_regression.yaml +0 -0
  145. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/recipes/random_forest.yaml +0 -0
  146. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/tests/test_smoke.py +0 -0
  147. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/tabular-classic/trainers/sklearn_trainers.py +0 -0
  148. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/KNOWLEDGE.md +0 -0
  149. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/pack.yaml +0 -0
  150. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/recipes/tfidf_linear.yaml +0 -0
  151. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/recipes/transformer_finetune.yaml +0 -0
  152. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/tests/test_smoke.py +0 -0
  153. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/common.py +0 -0
  154. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/tfidf.py +0 -0
  155. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/packs/text-classification/trainers/transformer.py +0 -0
  156. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/conftest.py +0 -0
  157. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/KNOWLEDGE.md +0 -0
  158. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/pack.yaml +0 -0
  159. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/adjustable.yaml +0 -0
  160. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/crash.yaml +0 -0
  161. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/needs_missing_pkg.yaml +0 -0
  162. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/quick.yaml +0 -0
  163. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/score.yaml +0 -0
  164. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/slow.yaml +0 -0
  165. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/recipes/stubborn.yaml +0 -0
  166. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/fixtures/engine-pack/trainers/toy.py +0 -0
  167. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_agent.py +0 -0
  168. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli.py +0 -0
  169. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli_packs.py +0 -0
  170. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_cli_runs.py +0 -0
  171. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_data_privacy.py +0 -0
  172. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_engine.py +0 -0
  173. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_gui.py +0 -0
  174. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_hardware.py +0 -0
  175. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_index.py +0 -0
  176. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_manifest.py +0 -0
  177. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_mcp.py +0 -0
  178. {autoforge_trainer-1.0.2 → autoforge_trainer-1.2.0}/tests/test_p6.py +0 -0
  179. {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.2
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.2** · 文件網站:<https://honguan.github.io/autoforge/> · [變更紀錄](CHANGELOG.md)
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 微調(LoRA/QLoRA)、圖片分類(NVIDIA GPU) |
106
- | `pip install "autoforge-trainer[all]"` | 再加上 Claude 代理、ONNX 匯出 |
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` | 時間序列預測(銷量、用電、流量) | 落後特徵 + 梯度提升,和「明天=今天」的基準比較 | CPU |
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.2** · 文件網站:<https://honguan.github.io/autoforge/> · [變更紀錄](CHANGELOG.md)
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 微調(LoRA/QLoRA)、圖片分類(NVIDIA GPU) |
24
- | `pip install "autoforge-trainer[all]"` | 再加上 Claude 代理、ONNX 匯出 |
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` | 時間序列預測(銷量、用電、流量) | 落後特徵 + 梯度提升,和「明天=今天」的基準比較 | CPU |
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
  ## 使用
@@ -1,3 +1,3 @@
1
1
  """AutoForge: AI that trains AI."""
2
2
 
3
- __version__ = "1.0.2"
3
+ __version__ = "1.2.0"
@@ -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
- async def run_agent(
89
- goal: str,
90
- provider: Provider,
91
- actor: str,
92
- brain: BrainKind,
93
- approve: ApproveFn | None = None,
94
- on_event: EventFn | None = None,
95
- max_steps: int = 40,
96
- data_path: str | None = None,
97
- ) -> AgentResult:
98
- emit = on_event or (lambda e: None)
99
- transcript = Transcript()
100
- runs: list[str] = []
101
- server = build_server(actor, log_level="WARNING") # the agent reports tool errors itself
102
-
103
- user_msg = goal
104
- if data_path:
105
- user_msg += f"\n\nData: {Path(data_path).expanduser().resolve()}"
106
- transcript.write("start", goal=goal, actor=actor, brain=brain.value, data_path=data_path)
107
-
108
- async with Client(server) as mcp:
109
- listed = await mcp.list_tools()
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
- provider.add_user(user_msg)
113
-
114
- final_text, stop = "", "max_steps"
115
- for step in range(1, max_steps + 1):
116
- turn = await provider.complete()
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
- text = "\n".join(getattr(c, "text", "") for c in res.content)
152
- envelope = {"ok": False, "error": {"code": "BAD_TOOL_CALL", "message": text}}
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
- else:
164
- step = max_steps
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
- transcript.write("done", stop=stop, steps=step, runs=runs)
167
- emit(AgentEvent("done", {"stop": stop, "steps": step, "runs": runs}))
168
- return AgentResult(final_text=final_text, steps=step, stop=stop, runs=runs, transcript=str(transcript.path))
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(_describe_plan(call))
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
- return "minimize" if any(h in metric.lower() for h in MINIMIZE_HINTS) else "maximize"
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]: