Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ Works on **anything text-based** — prompts, code, configs, copy, schemas — i

### Prerequisites

You need one of these CLI tools installed:
You need one of these LLM backends:

- [**Claude Code**](https://docs.anthropic.com/en/docs/claude-code) — `claude` CLI
- [**Codex**](https://github.com/openai/codex) — `codex` CLI
- [**Ollama**](https://ollama.com/) — run local models (Qwen, Llama, Mistral, etc.)
- [**MiniMax**](https://www.minimax.io/) — cloud API with 204K context (`MINIMAX_API_KEY` env var)

No API keys needed. No pip install. Just Python 3.10+ and an LLM.
No pip install. Just Python 3.10+ and an LLM.

### Run it

Expand Down Expand Up @@ -75,6 +76,24 @@ python3 autoprompt.py seed.txt criteria.md -e ollama -m qwen2.5-coder:14b

Fully offline. No API keys. No tokens. Just your GPU.

### ☁️ Run with MiniMax cloud API

```bash
export MINIMAX_API_KEY=your-api-key

# use MiniMax-M2.5 (default, 204K context)
python3 autoprompt.py examples/prompt-optimizer/seed.txt \
examples/prompt-optimizer/criteria.md \
-e minimax --target 9.0

# use the high-speed variant for faster iteration
python3 autoprompt.py examples/prompt-optimizer/seed.txt \
examples/prompt-optimizer/criteria.md \
-e minimax -m MiniMax-M2.5-highspeed
```

No CLI tool installation required — uses Python stdlib only.

---

## 🎯 How It Works
Expand Down Expand Up @@ -144,8 +163,8 @@ python3 autoprompt.py config.yaml criteria.md -e codex
| `-g, --generations` | Max generations to run | `10` |
| `-n, --population` | Mutations per generation | `3` |
| `-b, --bench` | Benchmark command (`{file}` = candidate path) | None |
| `-e, --engine` | LLM backend: `claude`, `codex`, or `ollama` | `claude` |
| `-m, --model` | Ollama model name (ignored for claude/codex) | `qwen3.5:9b` |
| `-e, --engine` | LLM backend: `claude`, `codex`, `ollama`, or `minimax` | `claude` |
| `-m, --model` | Model name for ollama or minimax | `qwen3.5:9b` / `MiniMax-M2.5` |
| `--target` | Stop when score reaches this value | None |
| `--patience` | Stop after N gens with no improvement | None |
| `--timeout` | Stop after N seconds total | None |
Expand Down
50 changes: 45 additions & 5 deletions autoprompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import tempfile
import time
import argparse
import urllib.request
import urllib.error
from pathlib import Path

def read(path):
Expand All @@ -45,6 +47,33 @@ def llm(prompt, engine="claude", reasoning="medium", model=None):
if result.returncode != 0:
raise RuntimeError(f"codex error: {result.stderr[:200]}")
return result.stdout.strip()
elif engine == "minimax":
api_key = os.environ.get("MINIMAX_API_KEY")
if not api_key:
raise RuntimeError("MINIMAX_API_KEY environment variable is required for minimax engine")
m = model or "MiniMax-M2.5"
body = json.dumps({
"model": m,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
}).encode()
req = urllib.request.Request(
"https://api.minimax.io/v1/chat/completions",
data=body,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
},
)
try:
with urllib.request.urlopen(req, timeout=300) as resp:
data = json.loads(resp.read().decode())
return data["choices"][0]["message"]["content"].strip()
except urllib.error.HTTPError as e:
err_body = e.read().decode()[:200] if e.fp else str(e)
raise RuntimeError(f"minimax API error ({e.code}): {err_body}")
except urllib.error.URLError as e:
raise RuntimeError(f"minimax connection error: {e.reason}")
elif engine == "ollama":
m = model or "qwen3.5:9b"
result = subprocess.run(
Expand All @@ -60,7 +89,7 @@ def llm(prompt, engine="claude", reasoning="medium", model=None):
out = re.sub(r'```(?:json)?\s*', '', out).strip()
return out
else:
raise ValueError(f"Unknown engine: {engine}. Use: claude, codex, ollama")
raise ValueError(f"Unknown engine: {engine}. Use: claude, codex, ollama, minimax")

def extract_json(text):
"""Extract JSON from LLM response (handles markdown fences etc)."""
Expand Down Expand Up @@ -182,7 +211,12 @@ def evolve(seed_path, criteria_path, generations=10, population=3, bench_cmd=Non
print(f"{'='*60}")
print(f" seed: {seed_path}")
print(f" criteria: {criteria_path}")
engine_str = engine if engine != "ollama" else f"ollama ({model or 'qwen3.5:9b'})"
if engine == "ollama":
engine_str = f"ollama ({model or 'qwen3.5:9b'})"
elif engine == "minimax":
engine_str = f"minimax ({model or 'MiniMax-M2.5'})"
else:
engine_str = engine
print(f" engine: {engine_str}")
print(f" generations: {generations} max")
print(f" population: {population}/gen")
Expand Down Expand Up @@ -312,17 +346,23 @@ def evolve(seed_path, criteria_path, generations=10, population=3, bench_cmd=Non

# use a small local model for fast iteration
python3 autoprompt.py prompt.txt criteria.md -e ollama -m qwen3.5:2b -g 5

# use MiniMax cloud API (set MINIMAX_API_KEY env var)
MINIMAX_API_KEY=your-key python3 autoprompt.py prompt.txt criteria.md -e minimax

# use MiniMax high-speed model
python3 autoprompt.py prompt.txt criteria.md -e minimax -m MiniMax-M2.5-highspeed
"""
)
p.add_argument("seed", help="path to the seed file to evolve")
p.add_argument("criteria", help="path to criteria markdown with fitness definition")
p.add_argument("-g", "--generations", type=int, default=10, help="max generations (default: 10)")
p.add_argument("-n", "--population", type=int, default=3, help="mutations per generation (default: 3)")
p.add_argument("-b", "--bench", type=str, default=None, help="benchmark command ({file} = temp file path)")
p.add_argument("-e", "--engine", type=str, default="claude", choices=["claude", "codex", "ollama"],
help="LLM engine: claude, codex, or ollama (default: claude)")
p.add_argument("-e", "--engine", type=str, default="claude", choices=["claude", "codex", "ollama", "minimax"],
help="LLM engine: claude, codex, ollama, or minimax (default: claude)")
p.add_argument("-m", "--model", type=str, default=None,
help="model name for ollama (default: qwen3.5:9b). ignored for claude/codex")
help="model name for ollama or minimax (default: qwen3.5:9b / MiniMax-M2.5)")
# stopping conditions
p.add_argument("--target", type=float, default=None, help="stop when score reaches this value")
p.add_argument("--patience", type=int, default=None, help="stop after N generations with no improvement")
Expand Down