levi-evolve 0.1.0__py3-none-any.whl

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 (56) hide show
  1. levi/__init__.py +124 -0
  2. levi/artifacts/__init__.py +7 -0
  3. levi/artifacts/base.py +80 -0
  4. levi/artifacts/code.py +231 -0
  5. levi/artifacts/prompt.py +609 -0
  6. levi/behavior/__init__.py +8 -0
  7. levi/behavior/extractor.py +215 -0
  8. levi/behavior/features.py +139 -0
  9. levi/clients/__init__.py +14 -0
  10. levi/clients/_cli_common.py +31 -0
  11. levi/clients/base.py +45 -0
  12. levi/clients/claude_code.py +122 -0
  13. levi/clients/codex.py +135 -0
  14. levi/clients/lm.py +225 -0
  15. levi/config/__init__.py +35 -0
  16. levi/config/models.py +260 -0
  17. levi/core/__init__.py +12 -0
  18. levi/core/evaluation.py +31 -0
  19. levi/core/program.py +25 -0
  20. levi/core/types.py +11 -0
  21. levi/demos/__init__.py +15 -0
  22. levi/demos/aime.py +121 -0
  23. levi/demos/circle_packing.py +119 -0
  24. levi/equilibrium/__init__.py +11 -0
  25. levi/equilibrium/equilibrium.py +511 -0
  26. levi/equilibrium/prompts.py +187 -0
  27. levi/init/__init__.py +6 -0
  28. levi/init/diversifier.py +888 -0
  29. levi/init/proxy_benchmark.py +223 -0
  30. levi/methods/__init__.py +9 -0
  31. levi/methods/levi.py +568 -0
  32. levi/pipeline/__init__.py +16 -0
  33. levi/pipeline/consumer.py +300 -0
  34. levi/pipeline/producer.py +157 -0
  35. levi/pipeline/runner.py +432 -0
  36. levi/pipeline/state.py +553 -0
  37. levi/pool/__init__.py +10 -0
  38. levi/pool/cvt_map_elites.py +772 -0
  39. levi/pool/protocol.py +72 -0
  40. levi/prompt_opt/__init__.py +5 -0
  41. levi/prompt_opt/optimizer.py +597 -0
  42. levi/prompts/__init__.py +19 -0
  43. levi/prompts/builder.py +155 -0
  44. levi/prompts/bundle.py +188 -0
  45. levi/selection/__init__.py +17 -0
  46. levi/selection/component.py +242 -0
  47. levi/utils/__init__.py +19 -0
  48. levi/utils/code_extraction.py +73 -0
  49. levi/utils/evaluation.py +161 -0
  50. levi/utils/ids.py +8 -0
  51. levi/utils/preflight.py +77 -0
  52. levi/utils/resilient_pool.py +165 -0
  53. levi_evolve-0.1.0.dist-info/METADATA +203 -0
  54. levi_evolve-0.1.0.dist-info/RECORD +56 -0
  55. levi_evolve-0.1.0.dist-info/WHEEL +4 -0
  56. levi_evolve-0.1.0.dist-info/licenses/LICENSE +21 -0
levi/__init__.py ADDED
@@ -0,0 +1,124 @@
1
+ """
2
+ Levi: Evolutionary optimization framework for algorithms.
3
+
4
+ Simple usage::
5
+
6
+ import levi
7
+
8
+ result = levi.evolve_code(
9
+ "Optimize bin packing to minimize wasted space",
10
+ function_signature="def pack(items, bin_capacity):",
11
+ seed_program="def pack(items, bin_capacity): ...",
12
+ score_fn=my_scorer,
13
+ model="openai/gpt-4o-mini",
14
+ budget_dollars=5.0,
15
+ )
16
+
17
+ Power users can pass any LeviConfig field as a keyword argument::
18
+
19
+ result = levi.evolve_code(
20
+ ...,
21
+ paradigm_model="openai/gpt-4o",
22
+ mutation_model="openai/gpt-4o-mini",
23
+ budget_dollars=10.0,
24
+ punctuated_equilibrium=levi.PunctuatedEquilibriumConfig(enabled=True),
25
+ pipeline=levi.PipelineConfig(n_llm_workers=8),
26
+ )
27
+ """
28
+
29
+ # Core types
30
+ # Behavior
31
+ from .behavior import BehaviorExtractor, FeatureVector
32
+ from .clients import LM, BaseClient, ClaudeCodeClient, ClientResult, CodexClient
33
+
34
+ # Config types
35
+ from .config import (
36
+ BehaviorConfig,
37
+ BudgetConfig,
38
+ CascadeConfig,
39
+ CVTConfig,
40
+ InitConfig,
41
+ LeviConfig,
42
+ LeviResult,
43
+ MetaAdviceConfig,
44
+ PipelineConfig,
45
+ PromptOptConfig,
46
+ ProxyBenchmarkConfig,
47
+ PunctuatedEquilibriumConfig,
48
+ SamplerModelPair,
49
+ )
50
+ from .core import EvaluationResult, MetricDict, Program
51
+
52
+ # Methods
53
+ from .methods import evolve_code, evolve_prompts
54
+
55
+ # Protocols and pools
56
+ from .pool import CVTMAPElitesPool, ProgramPool, SampleResult
57
+
58
+ # Prompts
59
+ from .prompts import (
60
+ OutputMode,
61
+ ProgramWithScore,
62
+ PromptBuilder,
63
+ PromptBundle,
64
+ )
65
+
66
+ # Selection
67
+ from .selection import (
68
+ ComponentSelector,
69
+ RoundRobinComponentSelector,
70
+ StagnationComponentSelector,
71
+ UCBComponentSelector,
72
+ make_component_selector,
73
+ )
74
+
75
+ __version__ = "0.1.0"
76
+
77
+
78
+ __all__ = [
79
+ # Core
80
+ "Program",
81
+ "EvaluationResult",
82
+ "MetricDict",
83
+ # Pool
84
+ "ProgramPool",
85
+ "SampleResult",
86
+ "CVTMAPElitesPool",
87
+ # Clients
88
+ "BaseClient",
89
+ "LM",
90
+ "ClientResult",
91
+ "CodexClient",
92
+ "ClaudeCodeClient",
93
+ # Prompts
94
+ "PromptBuilder",
95
+ "ProgramWithScore",
96
+ "OutputMode",
97
+ "PromptBundle",
98
+ # Selection
99
+ "ComponentSelector",
100
+ "UCBComponentSelector",
101
+ "RoundRobinComponentSelector",
102
+ "StagnationComponentSelector",
103
+ "make_component_selector",
104
+ # Behavior
105
+ "BehaviorExtractor",
106
+ "FeatureVector",
107
+ # Config types
108
+ "LeviConfig",
109
+ "LeviResult",
110
+ "BudgetConfig",
111
+ "SamplerModelPair",
112
+ "CVTConfig",
113
+ "InitConfig",
114
+ "MetaAdviceConfig",
115
+ "BehaviorConfig",
116
+ "CascadeConfig",
117
+ "PipelineConfig",
118
+ "PunctuatedEquilibriumConfig",
119
+ "PromptOptConfig",
120
+ "ProxyBenchmarkConfig",
121
+ # Methods
122
+ "evolve_code",
123
+ "evolve_prompts",
124
+ ]
@@ -0,0 +1,7 @@
1
+ """Internal artifact adapters."""
2
+
3
+ from .base import ArtifactAdapter
4
+ from .code import CodeAdapter, apply_diff
5
+ from .prompt import PromptAdapter
6
+
7
+ __all__ = ["ArtifactAdapter", "CodeAdapter", "PromptAdapter", "apply_diff"]
levi/artifacts/base.py ADDED
@@ -0,0 +1,80 @@
1
+ """Internal artifact adapter abstractions for Levi."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+ from ..clients.base import ClientSpec
10
+ from ..core import Program
11
+ from ..prompts import ProgramWithScore
12
+ from ..utils import ResilientProcessPool
13
+
14
+
15
+ class ArtifactAdapter(ABC):
16
+ """Internal boundary between the generic engine and an artifact domain."""
17
+
18
+ artifact_type: str = "artifact"
19
+
20
+ @abstractmethod
21
+ def make_program(self, content: str, metadata: dict[str, Any] | None = None) -> Program:
22
+ """Wrap raw artifact content into a Program."""
23
+
24
+ @abstractmethod
25
+ def snapshot_content(self, elite_data: Mapping[str, Any]) -> str:
26
+ """Extract canonical content from a serialized snapshot entry."""
27
+
28
+ @abstractmethod
29
+ async def evaluate(
30
+ self,
31
+ executor: ResilientProcessPool,
32
+ content: str,
33
+ *,
34
+ inputs: list[Any] | None = None,
35
+ timeout: float | None = None,
36
+ ) -> dict[str, Any]:
37
+ """Evaluate artifact content with the domain-specific harness."""
38
+
39
+ @abstractmethod
40
+ def build_mutation_prompt(
41
+ self,
42
+ parents: Sequence[ProgramWithScore],
43
+ *,
44
+ meta_advice: str | None = None,
45
+ model: ClientSpec | None = None,
46
+ use_diff: bool = False,
47
+ ) -> str:
48
+ """Build the main mutation prompt for the producer pipeline."""
49
+
50
+ @abstractmethod
51
+ def extract_candidate(
52
+ self,
53
+ response_text: str,
54
+ *,
55
+ parent_content: str | None = None,
56
+ use_diff: bool = False,
57
+ ) -> str | None:
58
+ """Extract candidate content from a model response."""
59
+
60
+ @abstractmethod
61
+ def build_diversity_prompt(self, existing_candidates: Sequence[tuple[str, float]]) -> str:
62
+ """Build the init-phase diversity prompt."""
63
+
64
+ @abstractmethod
65
+ def build_init_variant_prompt(self, parents: Sequence[ProgramWithScore]) -> str:
66
+ """Build the init-phase local-variation prompt."""
67
+
68
+ @abstractmethod
69
+ def build_paradigm_shift_prompt(
70
+ self,
71
+ representatives: Sequence[tuple[int, Any]],
72
+ *,
73
+ n_evaluations: int,
74
+ budget_progress: float = 0.0,
75
+ ) -> str:
76
+ """Build the punctuated-equilibrium paradigm prompt."""
77
+
78
+ @abstractmethod
79
+ def build_variant_prompt(self, base_content: str, base_score: float) -> str:
80
+ """Build a local-variation prompt around a paradigm-shift result."""
levi/artifacts/code.py ADDED
@@ -0,0 +1,231 @@
1
+ """Code artifact adapter for Levi's existing public API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Mapping, Sequence
7
+ from typing import Any
8
+
9
+ from ..clients.base import ClientSpec, client_name
10
+ from ..config import LeviConfig
11
+ from ..core import Program
12
+ from ..equilibrium.prompts import PARADIGM_SHIFT_PROMPTS, VARIANT_GENERATION_PROMPT, get_budget_stage
13
+ from ..prompts import OutputMode, ProgramWithScore, PromptBuilder
14
+ from ..utils import ResilientProcessPool, evaluate_code, extract_code, extract_fn_name
15
+ from .base import ArtifactAdapter
16
+
17
+ DIVERSITY_SEED_PROMPT = """# {problem_title}
18
+
19
+ ## Problem
20
+ {problem_description}
21
+
22
+ ## Function Signature
23
+ ```python
24
+ {function_signature}
25
+ ```
26
+
27
+ ## Your Task: ALGORITHMIC DIVERSITY
28
+
29
+ You MUST design a solution using a **FUNDAMENTALLY DIFFERENT ALGORITHM** than the existing seeds.
30
+
31
+ **DO NOT:**
32
+ - Make minor variations or parameter tweaks to existing approaches
33
+ - Use the same core algorithm with different constants
34
+ - Reorder or refactor existing logic
35
+
36
+ **DO:**
37
+ - Analyze what algorithmic paradigm each existing seed uses
38
+ - Identify what aspects of the problem they exploit (or ignore)
39
+ - Design from first principles using a completely different strategy
40
+ - Think about what information in the problem they are NOT using
41
+ - Consider entirely different ways to model or decompose the problem
42
+
43
+ The goal is to explore different regions of the algorithm design space. A population of diverse algorithms will outperform a population of similar ones.
44
+
45
+ ## Existing Seeds (analyze their algorithms, then do something DIFFERENT):
46
+ {existing_seeds}
47
+
48
+ ## Output
49
+ Output ONLY the complete Python code in a ```python block.
50
+ """
51
+
52
+
53
+ def apply_diff(original: str, diff_response: str) -> str | None:
54
+ """Apply SEARCH/REPLACE diff blocks to original code."""
55
+ result = original
56
+
57
+ pattern = r"<<<<<<< SEARCH\s*(.*?)\s*=======\s*(.*?)\s*>>>>>>> REPLACE"
58
+ matches = re.findall(pattern, diff_response, re.DOTALL)
59
+
60
+ if not matches:
61
+ return extract_code(diff_response)
62
+
63
+ for search, replace in matches:
64
+ search = search.strip()
65
+ replace = replace.strip()
66
+ if search in result:
67
+ result = result.replace(search, replace, 1)
68
+ else:
69
+ return None
70
+
71
+ return result
72
+
73
+
74
+ class CodeAdapter(ArtifactAdapter):
75
+ """Adapter for Levi's existing code-evolution behavior."""
76
+
77
+ artifact_type = "code"
78
+
79
+ def __init__(self, config: LeviConfig):
80
+ self.config = config
81
+ self.fn_name = extract_fn_name(config.function_signature)
82
+
83
+ def make_program(self, content: str, metadata: dict[str, Any] | None = None) -> Program:
84
+ return Program(content=content, metadata=metadata or {})
85
+
86
+ def snapshot_content(self, elite_data: Mapping[str, Any]) -> str:
87
+ content = elite_data.get("content")
88
+ if isinstance(content, str):
89
+ return content
90
+
91
+ legacy_code = elite_data.get("code")
92
+ if isinstance(legacy_code, str):
93
+ return legacy_code
94
+
95
+ raise KeyError("content")
96
+
97
+ async def evaluate(
98
+ self,
99
+ executor: ResilientProcessPool,
100
+ content: str,
101
+ *,
102
+ inputs: list[Any] | None = None,
103
+ timeout: float | None = None,
104
+ ) -> dict[str, Any]:
105
+ return await executor.run(
106
+ evaluate_code,
107
+ content,
108
+ self.config.score_fn,
109
+ self.config.inputs if inputs is None else inputs,
110
+ self.fn_name,
111
+ timeout=self.config.pipeline.eval_timeout if timeout is None else timeout,
112
+ )
113
+
114
+ def build_mutation_prompt(
115
+ self,
116
+ parents: Sequence[ProgramWithScore],
117
+ *,
118
+ meta_advice: str | None = None,
119
+ model: ClientSpec | None = None,
120
+ use_diff: bool = False,
121
+ ) -> str:
122
+ builder = PromptBuilder()
123
+ builder.add_section("Problem", self.config.problem_description, priority=10)
124
+ builder.add_section("Signature", f"```python\n{self.config.function_signature}\n```", priority=20)
125
+ builder.add_parents(list(parents), priority=30)
126
+
127
+ mutation_overrides = self.config.prompt_overrides.get("mutation", {})
128
+ model_key = client_name(model) if model is not None else None
129
+ if model_key and model_key in mutation_overrides:
130
+ builder.set_custom_output(mutation_overrides[model_key])
131
+ else:
132
+ builder.set_output_mode(OutputMode.DIFF if use_diff else OutputMode.FULL)
133
+
134
+ if meta_advice:
135
+ builder.add_section("Meta-Advice", meta_advice, priority=100)
136
+
137
+ return builder.build()
138
+
139
+ def extract_candidate(
140
+ self,
141
+ response_text: str,
142
+ *,
143
+ parent_content: str | None = None,
144
+ use_diff: bool = False,
145
+ ) -> str | None:
146
+ if use_diff:
147
+ if parent_content is None:
148
+ raise ValueError("parent_content is required when use_diff=True")
149
+ return apply_diff(parent_content, response_text)
150
+ return extract_code(response_text)
151
+
152
+ def build_diversity_prompt(self, existing_candidates: Sequence[tuple[str, float]]) -> str:
153
+ existing_seeds_text = "\n\n---\n\n".join(
154
+ [
155
+ f"### Seed {idx + 1} (Score: {score:.17g}):\n```python\n{content}\n```"
156
+ for idx, (content, score) in enumerate(existing_candidates)
157
+ ]
158
+ )
159
+ prompt_template = self.config.init.diversity_prompt or DIVERSITY_SEED_PROMPT
160
+ return prompt_template.format(
161
+ problem_title="Algorithm Optimization",
162
+ problem_description=self.config.problem_description,
163
+ function_signature=self.config.function_signature,
164
+ existing_seeds=existing_seeds_text,
165
+ )
166
+
167
+ def build_init_variant_prompt(self, parents: Sequence[ProgramWithScore]) -> str:
168
+ builder = PromptBuilder()
169
+ builder.add_section("Problem", self.config.problem_description, priority=10)
170
+ builder.add_section("Signature", f"```python\n{self.config.function_signature}\n```", priority=20)
171
+ builder.add_parents(list(parents), priority=30)
172
+ builder.set_output_mode(OutputMode.FULL)
173
+ return builder.build()
174
+
175
+ def build_paradigm_shift_prompt(
176
+ self,
177
+ representatives: Sequence[tuple[int, Any]],
178
+ *,
179
+ n_evaluations: int,
180
+ budget_progress: float = 0.0,
181
+ ) -> str:
182
+ stage = get_budget_stage(budget_progress)
183
+
184
+ rep_text_parts = []
185
+ for idx, (cluster_id, elite) in enumerate(representatives):
186
+ score = elite.result.primary_score
187
+ content = elite.program.content
188
+ rep_text_parts.append(
189
+ f"### Region {idx + 1} (Cluster {cluster_id}, Score: {score:.17g})\n```python\n{content}\n```"
190
+ )
191
+
192
+ representative_solutions = "\n\n".join(rep_text_parts)
193
+
194
+ override = self.config.prompt_overrides.get("paradigm_shift")
195
+ if override:
196
+ return f"""# Algorithmic Paradigm Shift Challenge
197
+
198
+ ## Problem
199
+ {self.config.problem_description}
200
+
201
+ ## Function Signature
202
+ ```python
203
+ {self.config.function_signature}
204
+ ```
205
+
206
+ ## Current Best Solutions ({len(representatives)} regions, {n_evaluations} evaluations)
207
+
208
+ {representative_solutions}
209
+
210
+ ## Your Task
211
+ {override}
212
+
213
+ Output ONLY complete, runnable Python code in a ```python block.
214
+ """
215
+
216
+ template = PARADIGM_SHIFT_PROMPTS[stage]
217
+ return template.format(
218
+ problem_description=self.config.problem_description,
219
+ function_signature=self.config.function_signature,
220
+ n_evaluations=n_evaluations,
221
+ n_regions=len(representatives),
222
+ representative_solutions=representative_solutions,
223
+ )
224
+
225
+ def build_variant_prompt(self, base_content: str, base_score: float) -> str:
226
+ return VARIANT_GENERATION_PROMPT.format(
227
+ problem_description=self.config.problem_description,
228
+ function_signature=self.config.function_signature,
229
+ base_code=base_content,
230
+ base_score=base_score,
231
+ )