ComBEE: Scalable Parallel Prompt Learning¶
ComBEE (arXiv:2604.04247) scales GEPA's
reflection step to large minibatches without quality degradation. It ships as
a ReflectionLM implementation:
pass reflection_strategy=ComBEEReflectionLM(...) to gepa.optimize, or set
ReflectionConfig(reflection_strategy=...) in optimize_anything.
How ComBEE works: Map-Shuffle-Reduce¶
ComBEE replaces the single reflection call with a three-phase pipeline, per component:
n traces → [Augmented Shuffle] → k groups → [Level-1: k LM calls] → k proposals → [Level-2: 1 LM call] → final instruction
1. Augmented Shuffle (§3.2)¶
Each reflection record is duplicated p times (duplication_factor, default
2) and the augmented set is shuffled with a seeded RNG before being
distributed across groups. Every record gets multiple chances to be
incorporated, improving robustness at large batch sizes — and runs stay
reproducible.
2. Level-1 — Map (§3.1)¶
The augmented set (p·n items) is split into k = ⌊√n⌋ groups. One
reflection-LM call per group produces k intermediate instruction proposals.
Each group sees p·n/k ≈ p·√n traces — a manageable context even when n is
large. With n=40: k = 6 groups, each seeing ~13 traces instead of 40.
3. Level-2 — Reduce (§3.1)¶
A final LM call synthesizes the k intermediate proposals into one
instruction (aggregation_prompt_template, customizable). The choice
k = ⌊√n⌋ balances both levels: Level-1 processes √n traces per group,
Level-2 aggregates √n proposals — both at the same scale.
Usage¶
Raise reflection_minibatch_size
GEPA's default reflection_minibatch_size is 3. With n < 4,
k = ⌊√n⌋ = 1 and ComBEE falls back to a standard single reflection call
(it logs when this happens). Set reflection_minibatch_size to 20 or
more for meaningful benefit.
gepa.optimize¶
import gepa
from gepa.proposer.reflective_mutation.combee import ComBEEReflectionLM
result = gepa.optimize(
seed_candidate={"system_prompt": "You are a helpful assistant."},
trainset=trainset,
valset=valset,
task_lm="openai/gpt-4.1-mini",
reflection_strategy=ComBEEReflectionLM("openai/gpt-5.1"),
reflection_minibatch_size=40, # n — ComBEE forms k=6 groups automatically
max_metric_calls=600,
)
optimize_anything¶
Through the engine-pluggable API, engine_config maps onto GEPAConfig
field-for-field:
from gepa.optimize_anything import OptimizeAnythingConfig, ReflectionConfig, optimize_anything
from gepa.proposer.reflective_mutation.combee import ComBEEReflectionLM
result = optimize_anything(
seed_candidate=seed,
evaluator=my_evaluator,
dataset=dataset,
config=OptimizeAnythingConfig(
engine="gepa",
max_evals=600,
engine_config={
"reflection": ReflectionConfig(
reflection_strategy=ComBEEReflectionLM("openai/gpt-5.1"),
reflection_minibatch_size=40,
),
},
),
)
The legacy launcher config is also still accepted directly:
from gepa.optimize_anything import GEPAConfig, ReflectionConfig, optimize_anything
from gepa.proposer.reflective_mutation.combee import ComBEEReflectionLM
result = optimize_anything(
seed_candidate=seed,
evaluator=my_evaluator,
dataset=dataset,
config=GEPAConfig(
reflection=ReflectionConfig(
reflection_strategy=ComBEEReflectionLM("openai/gpt-5.1"),
reflection_minibatch_size=40,
),
),
)
Options¶
ComBEEReflectionLM(
lm, # model name string, or any LanguageModel callable
lm_kwargs=None, # completion options for a model-name lm
reflection_prompt_template=None, # Level-1 template: str, or dict per component
aggregation_prompt_template=None, # Level-2 template (must contain <curr_param> and <side_info>)
duplication_factor=2, # p — augmented-shuffle duplication (§3.2)
rng=None, # None -> engine-bound RNG; pass random.Random(...) for an independent stream
logger=None, # GEPA injects its configured logger when omitted
batch_reflection=True, # batches only when the LM provides batch_complete
)
Seeding the shuffle
By default GEPA binds ComBEE to the engine RNG. This preserves the
single-proposal behavior of #307:
shuffles participate in the same random stream as candidate selection and
minibatch sampling. Same-seed runs therefore reproduce the legacy call and
shuffle sequence. Passing an explicit rng=random.Random(...) opts into an
independent, pinned shuffle stream instead.
Public reflection configuration
When ComBEE is supplied as reflection_strategy=, GEPA forwards the
public reflection_prompt_template and reflection_lm_kwargs settings to
it. Constructor values on ComBEEReflectionLM take precedence. Plain
callable LMs provide token estimates only; use a cost-tracking LM when
setting max_reflection_cost.
Cost and observability¶
ComBEE makes k + 1 reflection-LM calls per component per proposal (vs 1
for the default reflector). The per-call intermediates are recorded in
proposal metadata under combee:-namespaced keys — combee:<comp>:k,
combee:<comp>:level1_prompts / level1_outputs,
combee:<comp>:num_lm_calls, combee:<comp>:mode, and
combee:total_lm_calls — visible to on_proposal_end consumers (in the
event's metadata) and experiment trackers (in the
proposal_reflection_metadata table). total_cost and token totals are
exposed by delegation to the wrapped LM, so max_reflection_cost works as a
stop condition.
When the LM provides batch_complete, ComBEE issues the iteration's Level-1
map calls as one batched wave and the Level-2 reduce calls as a second.
That includes the default single-proposal path: one job's k maps go out
together, and a singleton reduce uses a plain completion (a wave of one is
not batched). Under parallel proposals, maps from
all proposals share the first wave and reduces share the second. This
assumes the LM's calls are exchangeable (each reply depends only on its
own prompt — the standard property of stateless completion APIs). Without
that capability, ComBEE automatically executes complete jobs in strict #307
order, preserving sequential results for ordinary and order-dependent
callables. Set batch_reflection=False to request that strict ordering even
for a batch-capable LM. Failed attempts restore the RNG state and memoize
already-completed logical calls, so both reflect() / reflect_many retries
and the engine's per-job recovery path preserve results without
repurchasing work.
Credits¶
ComBEE support was originally contributed by
@nuglifeleoji in
#307 and re-hosted onto the
ReflectionLM protocol introduced in
#369.