vectrify
LLMs still struggle to generate perfect vector images from a reference raster in one shot. vectrify turns raster images into editable vector code by treating vectorization as a search problem: an LLM proposes candidate SVG/Graphviz/Typst code, a vision scorer ranks how close each candidate looks to the source, and an optimization loop iteratively refines the best candidates.
The output is human-readable code you can keep editing by hand.
Features
- Output formats: SVG (default), Graphviz DOT, Typst. HTML and TikZ planned.
- LLM providers: OpenAI, Anthropic, Google Gemini, auto-detected from env vars.
- Search strategies: NSGA-II for diversity-preserving multi-objective optimization, or beam search for a cheaper single-best run.
- Scoring: local vision-model embeddings (perceptual), with pixel-diff and LLM-as-judge as alternatives.
- Resumable runs: pick up where you left off, or fork from the top-N nodes of a previous run.
- Live dashboard: pool stats, scoring, and convergence criteria.
Install
pipx or uv tool keeps vectrify in its own environment and on your PATH:
pipx install vectrify # or: uv tool install vectrify
pipx install "vectrify[vision]" # recommended for best quality
pipx install "vectrify[all]" # everything
Plain pip install works too, into whatever environment is active; when using --user, check that ~/.local/bin is on your PATH.
The base install covers SVG output and the pixel-difference scorer. The extras add the rest:
| Extra | What it adds |
|---|---|
| vision | torch + transformers for the perceptual (CLIP/SigLIP) scorer |
| graphviz | the graphviz Python bindings (system Graphviz still required) |
| typst | the typst Python compiler |
| all | vision + graphviz + typst |
Cairo is required for SVG output (apt install libcairo2 or brew install cairo), and the graphviz format additionally needs the Graphviz binaries (apt install graphviz or brew install graphviz). A GPU is optional; the vision scorer falls back to CPU or MPS.
Finally, set an API key for one provider:
export OPENAI_API_KEY=...
export ANTHROPIC_API_KEY=...
export GEMINI_API_KEY=...
The provider is auto-detected from whichever key is set; override it with --provider {openai,anthropic,gemini} if you have several keys.
Quickstart
vectrify input.png -o output.svg
The defaults run up to 4 NSGA-II epochs and stop early once the search stops finding improvements (see Convergence and cost). Worst case, it runs for an hour and gives up.
A few useful variations:
# Bigger budget, longer runs
vectrify photo.jpg -o sketch.svg --epoch-patience 60 --max-wall-seconds 1800
# Steer the search with a goal
vectrify logo.png --goal "Use thick strokes only and avoid gradients"
# Output Graphviz DOT instead of SVG
vectrify diagram.png -o out.dot --format graphviz
# Resume from a previous run, keeping only the 20 best nodes
vectrify input.png --resume --resume-top 20
Run vectrify --help for the full flag reference, organized into LLM provider, scoring, search strategy, epoch control, resume, output artifacts, and runtime sections.
How it works
vectrify runs an evolutionary loop over a pool of candidate vector representations. The pool is seeded with a few LLM-generated candidates. On each iteration a parent is sampled, and:
- with probability 1 − llm-rate, mutated locally (color tweaks, path nudges, crossover);
- otherwise, sent to the LLM for a refined edit.
The new candidate is scored against the source image (perceptual via vision-transformer embeddings, pixel-space, or LLM-as-judge), then either replaces a worse pool member or is dropped.
Search strategies
NSGA-II, the default, keeps a diverse Pareto front and does better when you can afford several epochs. Beam search converges faster on a single answer. NSGA-only flags are epoch-diversity, epoch-variance and epoch-seeds; beam-only flags are beams and cull-keep. The CLI rejects mixed usage.
NSGA-II objectives
The search minimizes three objectives at once:
| Objective | Measure |
|---|---|
| visual error | scorer distance to the source image |
| visual complexity | JPEG-compressed size of the render |
| structural complexity | code size (whitespace-stripped source length) |
Visual error is the primary objective; the complexity measures only break ties among the best-scoring candidates, biasing toward small, clean output once the image is already close. Raising tournament-size pushes harder toward visual quality at the cost of pool diversity.
Convergence and cost
Each epoch ends when one of these fires; the next re-seeds from the current Pareto front. The run stops at max-epochs, max-wall-seconds, or the max-llm-calls cap.
| Flag | Default | Triggers when… |
|---|---|---|
| max-epochs | 4 | hard cap on epoch count |
| epoch-patience | 20 | this many LLM calls in a row produce no improvement |
| epoch-steps | 50 | this many LLM calls have run in the current epoch |
| epoch-variance | 0 | (NSGA-only) score std-dev in the pool drops below value |
| epoch-diversity | 0 | (NSGA-only) mean pairwise diversity drops below value |
| max-wall-seconds | 3600 | wall-clock budget; ends the run, not just the epoch |
| max-llm-calls | 0 | hard cap on total LLM calls; 0 disables |
Patience and step counters only tick on LLM calls, not on the cheap local mutations that make up most tasks, and a new best resets patience. The two NSGA stop criteria are off by default; good thresholds depend on your scorer and image.
The defaults cap LLM calls near max_epochs × epoch_steps, so around 220, and most runs stop well before that. A full run costs on the order of a dollar on flagship models; set max-llm-calls for a hard ceiling.
Output layout
Given --output sketch.svg, vectrify writes:
sketch.svg # the best final candidate (written at the end)
sketch/
└── runs/
└── 2026-04-26_14-30-21/ # one directory per run, timestamped
├── lineage.csv # accepted node history (all three objectives, parent, ops)
└── nodes/
├── 0.0421_0001.svg # one file per accepted node, prefixed by score
├── 0.0421_0001.png # rendered preview (--save-raster)
└── ...
Disable artifacts you don't need with no-write-lineage or no-save-raster. Enable save-heatmap to also dump perceptual diff maps next to each node.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vectrify-0.1.2.tar.gz.
File metadata
- Download URL: vectrify-0.1.2.tar.gz
- Upload date:
- Size: 309.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49bf6b4a812d8822d48ca5786402b70370d40f725643b8d690d036d97a799d16
|
|
| MD5 |
552f0c55dde7bc2aebe667476fac7b6a
|
|
| BLAKE2b-256 |
3c4c7e51911f0692b48c9e69c4907074ca2a37b90279cd28c9c49fddf930372a
|
File details
Details for the file vectrify-0.1.2-py3-none-any.whl.
File metadata
- Download URL: vectrify-0.1.2-py3-none-any.whl
- Upload date:
- Size: 87.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.12.0 {"installer":{"name":"uv","version":"0.12.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a55a860b9c8f3e6f6be844750d1a3d4a6466bd77a11e2ac6e834a12f9c3265e
|
|
| MD5 |
362f69ca5b4baad3875e6eab4aa2e28d
|
|
| BLAKE2b-256 |
5d33ce0120cc8ebe32c9ddd73c08391ac0907599b46d5179f332b42dc95e9152
|