GEPA Elixir Logo

GEPA for Elixir

Hex.pmElixirOTPTestsCoverageLicense

An Elixir implementation of GEPA (Genetic-Pareto), a framework for optimizing text-based system components using LLM-based reflection and Pareto-efficient evolutionary search.

Installation

Add gepa_ex to your list of dependencies in mix.exs:

def deps do
  [
    {:gepa_ex, "~> 0.1.2"}
  ]
end

About GEPA

GEPA optimizes arbitrary systems composed of text componentsβ€”like AI prompts, code snippets, or textual specsβ€”against any evaluation metric. It employs LLMs to reflect on system behavior, using feedback from execution traces to drive targeted improvements.

This is an Elixir port of the Python GEPA library, designed to leverage:

Production Ready

Core Features

Optimization System:

Phase 1 Additions - NEW! πŸŽ‰

Production LLM Integration:

Advanced Batch Sampling:

Working Examples:

Phase 2 Additions - NEW! πŸŽ‰

Merge Proposer:

Incremental Evaluation:

Advanced Stop Conditions:

Test Quality:

What's Next?

βœ… Phase 1: Production Viability - COMPLETE!

βœ… Phase 2: Core Completeness - COMPLETE!

Phase 3: Production Hardening - in progress

Phase 4: Ecosystem Expansion - 12-14 weeks

Quick Start

With Mock LLM (No API Key Required)

# Define training data
trainset = [
  %{input: "What is 2+2?", answer: "4"},
  %{input: "What is 3+3?", answer: "6"}
]

valset = [%{input: "What is 5+5?", answer: "10"}]

# Create adapter with mock LLM (for testing)
adapter = GEPA.Adapters.Basic.new(llm: GEPA.LLM.Mock.new())

# Run optimization
{:ok, result} = GEPA.optimize(
  seed_candidate: %{"instruction" => "You are a helpful assistant."},
  trainset: trainset,
  valset: valset,
  adapter: adapter,
  max_metric_calls: 50
)

# Access results
best_program = GEPA.Result.best_candidate(result)
best_score = GEPA.Result.best_score(result)

IO.puts("Best score: #{best_score}")
IO.puts("Iterations: #{result.i}")

With Production LLMs (NEW!)

# OpenAI (GPT-4o-mini) - Requires OPENAI_API_KEY
llm = GEPA.LLM.ReqLLM.new(provider: :openai)
adapter = GEPA.Adapters.Basic.new(llm: llm)

# Or Gemini (`gemini-flash-lite-latest`) - Requires GEMINI_API_KEY
llm = GEPA.LLM.ReqLLM.new(provider: :gemini)
adapter = GEPA.Adapters.Basic.new(llm: llm)

# Then run optimization as above
{:ok, result} = GEPA.optimize(
  seed_candidate: %{"instruction" => "..."},
  trainset: trainset,
  valset: valset,
  adapter: adapter,
  max_metric_calls: 50
)

See Examples overview for complete working examples!

Candidate Selection Strategies (NEW)

GEPA includes multiple candidate selectors to balance exploration vs. exploitation:

Stateful selectors (like epsilon-greedy) are carried forward automatically so decay persists across iterations.

To enable epsilon-greedy with decay:

selector =
  GEPA.Strategies.CandidateSelector.EpsilonGreedy.new(
    epsilon: 0.3,
    epsilon_decay: 0.95,
    epsilon_min: 0.05
  )

{:ok, result} =
  GEPA.optimize(
    seed_candidate: %{"instruction" => "..."},
    trainset: trainset,
    valset: valset,
    adapter: adapter,
    max_metric_calls: 50,
    candidate_selector: selector
  )

LLM-Based Instruction Proposal (NEW!)

Use an LLM to propose improved component instructions based on reflective feedback. You can also provide a custom proposal template.

reflection_llm = GEPA.LLM.ReqLLM.new(provider: :openai, model: "gpt-4o-mini")

custom_template = """
Improve {component_name}:
Current: {current_instruction}
Feedback: {reflective_dataset}
New instruction:
"""

{:ok, result} = GEPA.optimize(
  seed_candidate: %{"instruction" => "You are a concise math tutor."},
  trainset: trainset,
  valset: valset,
  adapter: adapter,
  max_metric_calls: 50,
  reflection_llm: reflection_llm,
  proposal_template: custom_template
)

When reflection_llm is not provided, GEPA falls back to a simple testing-only improvement marker ("[Optimized]").

Interactive Livebooks (NEW!)

For interactive learning and experimentation:

# Install Livebook
mix escript.install hex livebook

# Open a livebook
livebook server livebooks/01_quick_start.livemd

Available Livebooks:

See livebooks/README.md for details!

With State Persistence

{:ok, result} = GEPA.optimize(
  seed_candidate: seed,
  trainset: trainset,
  valset: valset,
  adapter: GEPA.Adapters.Basic.new(),
  max_metric_calls: 100,
  run_dir: "./my_optimization"  # State saved here, can resume
)

Development

# Get dependencies
mix deps.get

# Run tests
mix test

# Run with coverage
mix test --cover

# Run specific tests
mix test test/gepa/utils/pareto_test.exs

# Format code
mix format

# Type checking
mix dialyzer

Architecture

Based on behavior-driven design with functional core:

GEPA.optimize/1
  ↓
GEPA.Engine ← Behaviors β†’ User Implementations
  β”œβ”€β†’ Adapter (evaluate, reflect, propose)
  β”œβ”€β†’ Proposer (reflective, merge)
  β”œβ”€β†’ Strategies (selection, sampling, evaluation)
  └─→ StopCondition (budget, time, threshold)

Documentation

Technical Documentation

Changelog

v0.1.2 (2025-11-29)

v0.1.1 (2025-11-29)

v0.1.0 (2025-10-29)

Related Projects

License

MIT License