openevolve 0.3.0__tar.gz → 0.3.2__tar.gz

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 (110) hide show
  1. {openevolve-0.3.0/openevolve.egg-info → openevolve-0.3.2}/PKG-INFO +40 -5
  2. {openevolve-0.3.0 → openevolve-0.3.2}/README.md +39 -4
  3. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/_version.py +1 -1
  4. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/api.py +6 -2
  5. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/config.py +15 -0
  6. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/controller.py +0 -29
  7. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/database.py +18 -15
  8. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/embedding.py +14 -2
  9. openevolve-0.3.2/openevolve/llm/__init__.py +16 -0
  10. openevolve-0.3.2/openevolve/llm/claude_code.py +138 -0
  11. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/llm/ensemble.py +20 -4
  12. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/llm/openai.py +3 -1
  13. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/process_parallel.py +59 -3
  14. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/format_utils.py +10 -3
  15. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/metrics_utils.py +6 -2
  16. {openevolve-0.3.0 → openevolve-0.3.2/openevolve.egg-info}/PKG-INFO +40 -5
  17. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve.egg-info/SOURCES.txt +7 -0
  18. openevolve-0.3.2/tests/test_boolean_metrics.py +75 -0
  19. openevolve-0.3.2/tests/test_claude_code_llm.py +160 -0
  20. openevolve-0.3.2/tests/test_embedding.py +75 -0
  21. openevolve-0.3.2/tests/test_format_utils.py +15 -0
  22. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_process_parallel.py +59 -1
  23. openevolve-0.3.2/tests/test_provider_propagation.py +83 -0
  24. openevolve-0.3.2/tests/test_sample_from_island_inspirations.py +135 -0
  25. openevolve-0.3.0/openevolve/llm/__init__.py +0 -9
  26. {openevolve-0.3.0 → openevolve-0.3.2}/LICENSE +0 -0
  27. {openevolve-0.3.0 → openevolve-0.3.2}/MANIFEST.in +0 -0
  28. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/__init__.py +0 -0
  29. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/cli.py +0 -0
  30. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/evaluation_result.py +0 -0
  31. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/evaluator.py +0 -0
  32. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/evolution_trace.py +0 -0
  33. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/llm/base.py +0 -0
  34. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/novelty_judge.py +0 -0
  35. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompt/__init__.py +0 -0
  36. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompt/sampler.py +0 -0
  37. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompt/templates.py +0 -0
  38. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/diff_user.txt +0 -0
  39. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/evaluation.txt +0 -0
  40. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/evaluator_system_message.txt +0 -0
  41. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/evolution_history.txt +0 -0
  42. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/fragments.json +0 -0
  43. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/full_rewrite_user.txt +0 -0
  44. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/inspiration_program.txt +0 -0
  45. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/inspirations_section.txt +0 -0
  46. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/previous_attempt.txt +0 -0
  47. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/system_message.txt +0 -0
  48. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/system_message_changes_description.txt +0 -0
  49. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/system_message_with_changes_description.txt +0 -0
  50. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/top_program.txt +0 -0
  51. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/prompts/defaults/user_message_with_changes_description.txt +0 -0
  52. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/test_regional_endpoint.py +0 -0
  53. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/__init__.py +0 -0
  54. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/async_utils.py +0 -0
  55. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/code_utils.py +0 -0
  56. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve/utils/trace_export_utils.py +0 -0
  57. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve.egg-info/dependency_links.txt +0 -0
  58. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve.egg-info/entry_points.txt +0 -0
  59. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve.egg-info/requires.txt +0 -0
  60. {openevolve-0.3.0 → openevolve-0.3.2}/openevolve.egg-info/top_level.txt +0 -0
  61. {openevolve-0.3.0 → openevolve-0.3.2}/pyproject.toml +0 -0
  62. {openevolve-0.3.0 → openevolve-0.3.2}/setup.cfg +0 -0
  63. {openevolve-0.3.0 → openevolve-0.3.2}/setup.py +0 -0
  64. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_api.py +0 -0
  65. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_api_key_from_env.py +0 -0
  66. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_artifacts.py +0 -0
  67. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_artifacts_integration.py +0 -0
  68. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_cascade_validation.py +0 -0
  69. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_changes_description.py +0 -0
  70. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_checkpoint_resume.py +0 -0
  71. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_cli_model_override.py +0 -0
  72. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_code_utils.py +0 -0
  73. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_concurrent_island_access.py +0 -0
  74. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_database.py +0 -0
  75. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_database_cleanup.py +0 -0
  76. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_early_stopping_config.py +0 -0
  77. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_evaluator_timeout.py +0 -0
  78. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_evolution_trace.py +0 -0
  79. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_feature_stats_persistence.py +0 -0
  80. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_file_extension_preservation.py +0 -0
  81. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_grid_stability.py +0 -0
  82. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_initial_program_artifacts.py +0 -0
  83. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_child_placement.py +0 -0
  84. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_isolation.py +0 -0
  85. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_map_elites.py +0 -0
  86. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_migration.py +0 -0
  87. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_parent_consistency.py +0 -0
  88. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_island_tracking.py +0 -0
  89. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_iteration_counting.py +0 -0
  90. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_llm_config_optional_params.py +0 -0
  91. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_llm_ensemble.py +0 -0
  92. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_map_elites_features.py +0 -0
  93. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_migration_no_duplicates.py +0 -0
  94. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_model_parameter_demo.py +0 -0
  95. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_novelty_asyncio_issue.py +0 -0
  96. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_openai_model_detection.py +0 -0
  97. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_orphan_program_removal.py +0 -0
  98. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_population_elite_protection.py +0 -0
  99. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_process_parallel_fix.py +0 -0
  100. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_prompt_inspiration_dedup.py +0 -0
  101. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_prompt_sampler.py +0 -0
  102. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_prompt_sampler_comprehensive.py +0 -0
  103. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_reasoning_effort_config.py +0 -0
  104. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_regional_endpoint.py +0 -0
  105. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_sample_from_island_ratios.py +0 -0
  106. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_snapshot_artifacts_limit.py +0 -0
  107. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_template_dir_resolution.py +0 -0
  108. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_utils.py +0 -0
  109. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_valid_configs.py +0 -0
  110. {openevolve-0.3.0 → openevolve-0.3.2}/tests/test_visualization_sanitization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openevolve
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Open-source implementation of AlphaEvolve
5
5
  Author: codelion
6
6
  License: Apache-2.0
@@ -34,9 +34,8 @@ Dynamic: license-file
34
34
  *Turn your LLMs into autonomous code optimizers that discover breakthrough algorithms*
35
35
 
36
36
  <p align="center">
37
- <a href="https://github.com/algorithmicsuperintelligence/openevolve/stargazers"><img src="https://img.shields.io/github/stars/algorithmicsuperintelligence/openevolve?style=social" alt="GitHub stars"></a>
38
37
  <a href="https://pypi.org/project/openevolve/"><img src="https://img.shields.io/pypi/v/openevolve" alt="PyPI version"></a>
39
- <a href="https://pypi.org/project/openevolve/"><img src="https://img.shields.io/pypi/dm/openevolve" alt="PyPI downloads"></a>
38
+ <a href="https://pepy.tech/projects/openevolve"><img src="https://static.pepy.tech/personalized-badge/openevolve?period=monthly&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=GREEN&left_text=downloads%2Fmonth" alt="PyPI Downloads"></a>
40
39
  <a href="https://github.com/algorithmicsuperintelligence/openevolve/blob/main/LICENSE"><img src="https://img.shields.io/github/license/algorithmicsuperintelligence/openevolve" alt="License"></a>
41
40
  </p>
42
41
 
@@ -227,7 +226,7 @@ OpenEvolve implements a sophisticated **evolutionary coding pipeline** that goes
227
226
  <details>
228
227
  <summary><b>Advanced LLM Integration</b></summary>
229
228
 
230
- - **Universal API**: Works with OpenAI, Google, local models, and proxies
229
+ - **Universal API**: Works with OpenAI, Google, Claude Code CLI, local models, and proxies
231
230
  - **Intelligent Ensembles**: Weighted combinations with sophisticated fallback
232
231
  - **Test-Time Compute**: Enhanced reasoning through proxy systems (see [OptiLLM setup](#llm-provider-setup))
233
232
  - **Plugin Ecosystem**: Support for advanced reasoning plugins
@@ -258,7 +257,7 @@ OpenEvolve implements a sophisticated **evolutionary coding pipeline** that goes
258
257
 
259
258
  ### Requirements
260
259
  - **Python**: 3.10+
261
- - **LLM Access**: Any OpenAI-compatible API
260
+ - **LLM Access**: Any OpenAI-compatible API, or [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
262
261
  - **Optional**: Docker for containerized runs
263
262
 
264
263
  ### Installation Options
@@ -381,6 +380,35 @@ llm:
381
380
 
382
381
  </details>
383
382
 
383
+ <details>
384
+ <summary><b>🔮 Claude Code CLI (No API Key)</b></summary>
385
+
386
+ Use the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) as the LLM backend — no API keys needed, authentication uses the CLI's OAuth session.
387
+
388
+ ```bash
389
+ # Install and authenticate
390
+ npm install -g @anthropic-ai/claude-code
391
+ claude login
392
+ ```
393
+
394
+ ```yaml
395
+ # config.yaml
396
+ llm:
397
+ provider: "claude_code"
398
+ models:
399
+ - name: "sonnet"
400
+ weight: 0.8
401
+ max_tokens: 16000
402
+ max_budget_usd: 1.0
403
+ - name: "haiku"
404
+ weight: 0.2
405
+ max_tokens: 8000
406
+ ```
407
+
408
+ See the [Claude Code quickstart example](examples/claude_code_quickstart/) for a complete walkthrough.
409
+
410
+ </details>
411
+
384
412
  ## Examples Gallery
385
413
 
386
414
  <div align="center">
@@ -489,6 +517,10 @@ database:
489
517
  migration_interval: 20
490
518
  feature_dimensions: ["complexity", "diversity", "performance"]
491
519
 
520
+ # Optional novelty filtering with Gemini embeddings
521
+ embedding_model: "gemini-embedding-001"
522
+ similarity_threshold: 0.99
523
+
492
524
  evaluator:
493
525
  enable_artifacts: true # Error feedback to LLM
494
526
  cascade_evaluation: true # Multi-stage testing
@@ -505,6 +537,9 @@ prompt:
505
537
  use_template_stochasticity: true # Randomized prompts
506
538
  ```
507
539
 
540
+ For Gemini embeddings, set `GEMINI_API_KEY`. `GOOGLE_API_KEY` is also supported
541
+ as a fallback. OpenEvolve uses Google's OpenAI-compatible endpoint automatically.
542
+
508
543
  <details>
509
544
  <summary><b>🎯 Feature Engineering</b></summary>
510
545
 
@@ -9,9 +9,8 @@
9
9
  *Turn your LLMs into autonomous code optimizers that discover breakthrough algorithms*
10
10
 
11
11
  <p align="center">
12
- <a href="https://github.com/algorithmicsuperintelligence/openevolve/stargazers"><img src="https://img.shields.io/github/stars/algorithmicsuperintelligence/openevolve?style=social" alt="GitHub stars"></a>
13
12
  <a href="https://pypi.org/project/openevolve/"><img src="https://img.shields.io/pypi/v/openevolve" alt="PyPI version"></a>
14
- <a href="https://pypi.org/project/openevolve/"><img src="https://img.shields.io/pypi/dm/openevolve" alt="PyPI downloads"></a>
13
+ <a href="https://pepy.tech/projects/openevolve"><img src="https://static.pepy.tech/personalized-badge/openevolve?period=monthly&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=GREEN&left_text=downloads%2Fmonth" alt="PyPI Downloads"></a>
15
14
  <a href="https://github.com/algorithmicsuperintelligence/openevolve/blob/main/LICENSE"><img src="https://img.shields.io/github/license/algorithmicsuperintelligence/openevolve" alt="License"></a>
16
15
  </p>
17
16
 
@@ -202,7 +201,7 @@ OpenEvolve implements a sophisticated **evolutionary coding pipeline** that goes
202
201
  <details>
203
202
  <summary><b>Advanced LLM Integration</b></summary>
204
203
 
205
- - **Universal API**: Works with OpenAI, Google, local models, and proxies
204
+ - **Universal API**: Works with OpenAI, Google, Claude Code CLI, local models, and proxies
206
205
  - **Intelligent Ensembles**: Weighted combinations with sophisticated fallback
207
206
  - **Test-Time Compute**: Enhanced reasoning through proxy systems (see [OptiLLM setup](#llm-provider-setup))
208
207
  - **Plugin Ecosystem**: Support for advanced reasoning plugins
@@ -233,7 +232,7 @@ OpenEvolve implements a sophisticated **evolutionary coding pipeline** that goes
233
232
 
234
233
  ### Requirements
235
234
  - **Python**: 3.10+
236
- - **LLM Access**: Any OpenAI-compatible API
235
+ - **LLM Access**: Any OpenAI-compatible API, or [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
237
236
  - **Optional**: Docker for containerized runs
238
237
 
239
238
  ### Installation Options
@@ -356,6 +355,35 @@ llm:
356
355
 
357
356
  </details>
358
357
 
358
+ <details>
359
+ <summary><b>🔮 Claude Code CLI (No API Key)</b></summary>
360
+
361
+ Use the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) as the LLM backend — no API keys needed, authentication uses the CLI's OAuth session.
362
+
363
+ ```bash
364
+ # Install and authenticate
365
+ npm install -g @anthropic-ai/claude-code
366
+ claude login
367
+ ```
368
+
369
+ ```yaml
370
+ # config.yaml
371
+ llm:
372
+ provider: "claude_code"
373
+ models:
374
+ - name: "sonnet"
375
+ weight: 0.8
376
+ max_tokens: 16000
377
+ max_budget_usd: 1.0
378
+ - name: "haiku"
379
+ weight: 0.2
380
+ max_tokens: 8000
381
+ ```
382
+
383
+ See the [Claude Code quickstart example](examples/claude_code_quickstart/) for a complete walkthrough.
384
+
385
+ </details>
386
+
359
387
  ## Examples Gallery
360
388
 
361
389
  <div align="center">
@@ -464,6 +492,10 @@ database:
464
492
  migration_interval: 20
465
493
  feature_dimensions: ["complexity", "diversity", "performance"]
466
494
 
495
+ # Optional novelty filtering with Gemini embeddings
496
+ embedding_model: "gemini-embedding-001"
497
+ similarity_threshold: 0.99
498
+
467
499
  evaluator:
468
500
  enable_artifacts: true # Error feedback to LLM
469
501
  cascade_evaluation: true # Multi-stage testing
@@ -480,6 +512,9 @@ prompt:
480
512
  use_template_stochasticity: true # Randomized prompts
481
513
  ```
482
514
 
515
+ For Gemini embeddings, set `GEMINI_API_KEY`. `GOOGLE_API_KEY` is also supported
516
+ as a fallback. OpenEvolve uses Google's OpenAI-compatible endpoint automatically.
517
+
483
518
  <details>
484
519
  <summary><b>🎯 Feature Engineering</b></summary>
485
520
 
@@ -1,3 +1,3 @@
1
1
  """Version information for openevolve package."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.2"
@@ -37,6 +37,8 @@ def run_evolution(
37
37
  iterations: Optional[int] = None,
38
38
  output_dir: Optional[str] = None,
39
39
  cleanup: bool = True,
40
+ target_score: Optional[float] = None,
41
+ checkpoint_path: Optional[str] = None,
40
42
  ) -> EvolutionResult:
41
43
  """
42
44
  Run evolution with flexible inputs - the main library API
@@ -90,7 +92,7 @@ def run_evolution(
90
92
  )
91
93
  """
92
94
  return asyncio.run(
93
- _run_evolution_async(initial_program, evaluator, config, iterations, output_dir, cleanup)
95
+ _run_evolution_async(initial_program, evaluator, config, iterations, output_dir, cleanup, target_score, checkpoint_path)
94
96
  )
95
97
 
96
98
 
@@ -101,6 +103,8 @@ async def _run_evolution_async(
101
103
  iterations: Optional[int],
102
104
  output_dir: Optional[str],
103
105
  cleanup: bool,
106
+ target_score: Optional[float] = None,
107
+ checkpoint_path: Optional[str] = None,
104
108
  ) -> EvolutionResult:
105
109
  """Async implementation of run_evolution"""
106
110
 
@@ -156,7 +160,7 @@ async def _run_evolution_async(
156
160
  output_dir=actual_output_dir,
157
161
  )
158
162
 
159
- best_program = await controller.run(iterations=iterations)
163
+ best_program = await controller.run(iterations=iterations,target_score=target_score,checkpoint_path=checkpoint_path)
160
164
 
161
165
  # Prepare result
162
166
  best_score = 0.0
@@ -56,6 +56,9 @@ class LLMModelConfig:
56
56
  api_key: Optional[str] = None
57
57
  name: str = None
58
58
 
59
+ # LLM provider: "openai" (default), "claude_code" (Claude Code CLI)
60
+ provider: Optional[str] = None
61
+
59
62
  # Custom LLM client
60
63
  init_client: Optional[Callable] = None
61
64
 
@@ -79,6 +82,9 @@ class LLMModelConfig:
79
82
  # Reasoning parameters
80
83
  reasoning_effort: Optional[str] = None
81
84
 
85
+ # Claude Code CLI budget per call (USD)
86
+ max_budget_usd: Optional[float] = None
87
+
82
88
  # Manual mode (human-in-the-loop)
83
89
  manual_mode: Optional[bool] = None
84
90
  _manual_queue_dir: Optional[str] = None
@@ -168,6 +174,12 @@ class LLMConfig(LLMModelConfig):
168
174
 
169
175
  # Update models with shared configuration values
170
176
  shared_config = {
177
+ # `provider` must be shared: without it a top-level `llm.provider`
178
+ # (e.g. "claude_code") never reaches the per-model configs, every model
179
+ # keeps provider=None, and LLMEnsemble silently routes them all to the
180
+ # OpenAI backend. Propagation uses overwrite=False, so an explicit
181
+ # per-model provider still wins.
182
+ "provider": self.provider,
171
183
  "api_base": self.api_base,
172
184
  "api_key": self.api_key,
173
185
  "temperature": self.temperature,
@@ -222,6 +234,9 @@ class LLMConfig(LLMModelConfig):
222
234
 
223
235
  # Update models with shared configuration values
224
236
  shared_config = {
237
+ # See the note in __post_init__: `provider` has to be propagated here too,
238
+ # or rebuilding the models drops a top-level provider back to None.
239
+ "provider": self.provider,
225
240
  "api_base": self.api_base,
226
241
  "api_key": self.api_key,
227
242
  "temperature": self.temperature,
@@ -25,34 +25,6 @@ from openevolve.utils.format_utils import format_improvement_safe, format_metric
25
25
  logger = logging.getLogger(__name__)
26
26
 
27
27
 
28
- def _format_metrics(metrics: Dict[str, Any]) -> str:
29
- """Safely format metrics, handling both numeric and string values"""
30
- formatted_parts = []
31
- for name, value in metrics.items():
32
- if isinstance(value, (int, float)) and not isinstance(value, bool):
33
- try:
34
- formatted_parts.append(f"{name}={value:.4f}")
35
- except (ValueError, TypeError):
36
- formatted_parts.append(f"{name}={value}")
37
- else:
38
- formatted_parts.append(f"{name}={value}")
39
- return ", ".join(formatted_parts)
40
-
41
-
42
- def _format_improvement(improvement: Dict[str, Any]) -> str:
43
- """Safely format improvement metrics"""
44
- formatted_parts = []
45
- for name, diff in improvement.items():
46
- if isinstance(diff, (int, float)) and not isinstance(diff, bool):
47
- try:
48
- formatted_parts.append(f"{name}={diff:+.4f}")
49
- except (ValueError, TypeError):
50
- formatted_parts.append(f"{name}={diff}")
51
- else:
52
- formatted_parts.append(f"{name}={diff}")
53
- return ", ".join(formatted_parts)
54
-
55
-
56
28
  class OpenEvolve:
57
29
  """
58
30
  Main controller for OpenEvolve
@@ -268,7 +240,6 @@ class OpenEvolve:
268
240
  Best program found
269
241
  """
270
242
  max_iterations = iterations or self.config.max_iterations
271
-
272
243
  # Determine starting iteration
273
244
  start_iteration = 0
274
245
  if checkpoint_path and os.path.exists(checkpoint_path):
@@ -467,21 +467,13 @@ class ProgramDatabase:
467
467
  parent = self._sample_from_island_weighted(island_id)
468
468
  sampling_mode = "weighted"
469
469
 
470
- # Select inspirations from the same island
470
+ # Select inspirations using the same elite/diversity-aware strategy as sample().
471
+ # Pass the requested island explicitly so parallel workers remain isolated even
472
+ # if an archive fallback returns a parent whose metadata points elsewhere.
471
473
  if num_inspirations is None:
472
474
  num_inspirations = 5 # Default for backward compatibility
473
475
 
474
- # Get other programs from the island for inspirations
475
- other_programs = [pid for pid in island_programs if pid != parent.id]
476
-
477
- if len(other_programs) < num_inspirations:
478
- # Not enough programs in island, use what we have
479
- inspiration_ids = other_programs
480
- else:
481
- # Sample inspirations
482
- inspiration_ids = random.sample(other_programs, num_inspirations)
483
-
484
- inspirations = [self.programs[pid] for pid in inspiration_ids if pid in self.programs]
476
+ inspirations = self._sample_inspirations(parent, n=num_inspirations, island_id=island_id)
485
477
 
486
478
  logger.debug(
487
479
  f"Sampled parent {parent.id} and {len(inspirations)} inspirations from island {island_id} "
@@ -1569,7 +1561,9 @@ class ProgramDatabase:
1569
1561
  parent_id = random.choice(valid_archive)
1570
1562
  return self.programs[parent_id]
1571
1563
 
1572
- def _sample_inspirations(self, parent: Program, n: int = 5) -> List[Program]:
1564
+ def _sample_inspirations(
1565
+ self, parent: Program, n: int = 5, island_id: Optional[int] = None
1566
+ ) -> List[Program]:
1573
1567
  """
1574
1568
  Sample inspiration programs for the next evolution step.
1575
1569
 
@@ -1579,14 +1573,23 @@ class ProgramDatabase:
1579
1573
  Args:
1580
1574
  parent: Parent program
1581
1575
  n: Number of inspirations to sample
1576
+ island_id: Explicit island to sample from. If omitted, use the
1577
+ parent program's island metadata.
1582
1578
 
1583
1579
  Returns:
1584
1580
  List of inspiration programs from the current island
1585
1581
  """
1586
1582
  inspirations = []
1587
1583
 
1588
- # Get the parent's island (should be current_island)
1589
- parent_island = parent.metadata.get("island", self.current_island)
1584
+ # Prefer an explicitly requested island. This matters for
1585
+ # sample_from_island(), where archive fallback may return a parent whose
1586
+ # metadata belongs to a different island.
1587
+ if island_id is None:
1588
+ parent_island = parent.metadata.get("island", self.current_island)
1589
+ else:
1590
+ parent_island = island_id
1591
+
1592
+ parent_island %= len(self.islands)
1590
1593
 
1591
1594
  # Get all programs from the current island
1592
1595
  island_program_ids = list(self.islands[parent_island])
@@ -3,10 +3,11 @@ Adapted from SakanaAI/ShinkaEvolve (Apache-2.0 License)
3
3
  Original source: https://github.com/SakanaAI/ShinkaEvolve/blob/main/shinka/llm/embedding.py
4
4
  """
5
5
 
6
+ import logging
6
7
  import os
8
+ from typing import List, Union
9
+
7
10
  import openai
8
- from typing import Union, List
9
- import logging
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
@@ -22,6 +23,10 @@ AZURE_EMBEDDING_MODELS = [
22
23
  "azure-text-embedding-3-large",
23
24
  ]
24
25
 
26
+ GEMINI_EMBEDDING_MODELS = [
27
+ "gemini-embedding-001",
28
+ ]
29
+
25
30
  OPENAI_EMBEDDING_COSTS = {
26
31
  "text-embedding-3-small": 0.02 / M,
27
32
  "text-embedding-3-large": 0.13 / M,
@@ -53,6 +58,13 @@ class EmbeddingClient:
53
58
  api_version=os.getenv("AZURE_API_VERSION"),
54
59
  azure_endpoint=os.getenv("AZURE_API_ENDPOINT"),
55
60
  )
61
+ elif model_name in GEMINI_EMBEDDING_MODELS:
62
+ gemini_api_key = os.getenv("GEMINI_API_KEY") or os.getenv("GOOGLE_API_KEY")
63
+ client = openai.OpenAI(
64
+ api_key=gemini_api_key,
65
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
66
+ )
67
+ model_to_use = model_name
56
68
  else:
57
69
  raise ValueError(f"Invalid embedding model: {model_name}")
58
70
 
@@ -0,0 +1,16 @@
1
+ """
2
+ LLM module initialization
3
+ """
4
+
5
+ from openevolve.llm.base import LLMInterface
6
+ from openevolve.llm.ensemble import LLMEnsemble
7
+ from openevolve.llm.openai import OpenAILLM
8
+ from openevolve.llm.claude_code import ClaudeCodeLLM, init_claude_code_client
9
+
10
+ __all__ = [
11
+ "LLMInterface",
12
+ "OpenAILLM",
13
+ "ClaudeCodeLLM",
14
+ "init_claude_code_client",
15
+ "LLMEnsemble",
16
+ ]
@@ -0,0 +1,138 @@
1
+ """
2
+ Claude Code CLI interface for LLMs.
3
+
4
+ Uses the Claude Code CLI (`claude -p`) as a non-interactive LLM backend,
5
+ enabling OpenEvolve to run with Anthropic's Claude models without requiring
6
+ direct API keys — authentication is handled by the CLI's OAuth session.
7
+
8
+ Usage in config.yaml:
9
+ llm:
10
+ provider: "claude_code"
11
+ models:
12
+ - name: "sonnet"
13
+ weight: 1.0
14
+ max_tokens: 16000
15
+ timeout: 300
16
+
17
+ Or inject programmatically:
18
+ from openevolve.llm.claude_code import init_claude_code_client
19
+ for model_cfg in config.llm.models:
20
+ model_cfg.init_client = init_claude_code_client
21
+ """
22
+
23
+ import asyncio
24
+ import logging
25
+ import subprocess
26
+ from typing import Dict, List, Optional
27
+
28
+ from openevolve.llm.base import LLMInterface
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class ClaudeCodeLLM(LLMInterface):
34
+ """LLM interface that uses the Claude Code CLI for generation.
35
+
36
+ Requires `claude` CLI to be installed and authenticated
37
+ (run `claude login` first).
38
+ """
39
+
40
+ def __init__(self, model_cfg=None):
41
+ self.model = getattr(model_cfg, "name", "sonnet")
42
+ self.system_message = getattr(model_cfg, "system_message", None)
43
+ self.max_tokens = getattr(model_cfg, "max_tokens", 16000)
44
+ self.timeout = getattr(model_cfg, "timeout", 300)
45
+ self.weight = getattr(model_cfg, "weight", 1.0)
46
+ self.retries = getattr(model_cfg, "retries", 3)
47
+ self.retry_delay = getattr(model_cfg, "retry_delay", 5)
48
+ self.max_budget_usd = getattr(model_cfg, "max_budget_usd", 1.0)
49
+ self.cwd = getattr(model_cfg, "cwd", None)
50
+ logger.info(f"Initialized ClaudeCodeLLM with model: {self.model}")
51
+
52
+ async def generate(self, prompt: str, **kwargs) -> str:
53
+ sys_msg = kwargs.pop("system_message", self.system_message) or ""
54
+ return await self.generate_with_context(
55
+ system_message=sys_msg,
56
+ messages=[{"role": "user", "content": prompt}],
57
+ **kwargs,
58
+ )
59
+
60
+ async def generate_with_context(
61
+ self, system_message: str, messages: List[Dict[str, str]], **kwargs
62
+ ) -> str:
63
+ user_content = "\n\n".join(
64
+ m.get("content", "") for m in messages if m.get("role") == "user"
65
+ )
66
+
67
+ cmd = [
68
+ "claude",
69
+ "-p",
70
+ "--model",
71
+ self.model,
72
+ "--no-session-persistence",
73
+ "--output-format",
74
+ "text",
75
+ ]
76
+ if system_message:
77
+ cmd.extend(["--system-prompt", system_message])
78
+
79
+ budget = kwargs.get("max_budget_usd", self.max_budget_usd)
80
+ cmd.extend(["--max-budget-usd", str(budget)])
81
+
82
+ cmd.append(user_content)
83
+
84
+ timeout = kwargs.get("timeout", self.timeout)
85
+ retries = kwargs.get("retries", self.retries)
86
+ retry_delay = kwargs.get("retry_delay", self.retry_delay)
87
+
88
+ loop = asyncio.get_event_loop()
89
+ for attempt in range(retries + 1):
90
+ try:
91
+ result = await asyncio.wait_for(
92
+ loop.run_in_executor(None, lambda: self._run_cli(cmd, timeout)),
93
+ timeout=timeout + 30,
94
+ )
95
+ return result
96
+ except asyncio.TimeoutError:
97
+ if attempt < retries:
98
+ logger.warning(
99
+ f"Claude Code CLI timeout on attempt {attempt + 1}/{retries + 1}. Retrying..."
100
+ )
101
+ await asyncio.sleep(retry_delay)
102
+ else:
103
+ logger.error(f"All {retries + 1} attempts failed with timeout")
104
+ raise
105
+ except Exception as e:
106
+ if attempt < retries:
107
+ logger.warning(
108
+ f"Claude Code CLI error on attempt {attempt + 1}/{retries + 1}: {e}. Retrying..."
109
+ )
110
+ await asyncio.sleep(retry_delay)
111
+ else:
112
+ logger.error(f"All {retries + 1} attempts failed with error: {e}")
113
+ raise
114
+
115
+ def _run_cli(self, cmd: list, timeout: int) -> str:
116
+ try:
117
+ result = subprocess.run(
118
+ cmd,
119
+ capture_output=True,
120
+ text=True,
121
+ timeout=timeout,
122
+ cwd=self.cwd,
123
+ )
124
+ if result.returncode != 0:
125
+ stderr = result.stderr.strip()
126
+ if stderr:
127
+ logger.warning(f"Claude CLI stderr: {stderr[:500]}")
128
+ output = result.stdout.strip()
129
+ if not output:
130
+ raise RuntimeError(f"Empty response from Claude CLI. stderr: {result.stderr[:500]}")
131
+ return output
132
+ except subprocess.TimeoutExpired:
133
+ raise asyncio.TimeoutError("Claude CLI subprocess timed out")
134
+
135
+
136
+ def init_claude_code_client(model_cfg):
137
+ """Factory function compatible with OpenEvolve's init_client config hook."""
138
+ return ClaudeCodeLLM(model_cfg)
@@ -13,6 +13,25 @@ from openevolve.config import LLMModelConfig
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
 
16
+ _PROVIDER_REGISTRY = {
17
+ "openai": lambda cfg: OpenAILLM(cfg),
18
+ }
19
+
20
+ try:
21
+ from openevolve.llm.claude_code import ClaudeCodeLLM
22
+ _PROVIDER_REGISTRY["claude_code"] = lambda cfg: ClaudeCodeLLM(cfg)
23
+ except ImportError:
24
+ pass
25
+
26
+
27
+ def _create_model(model_cfg: LLMModelConfig) -> LLMInterface:
28
+ if model_cfg.init_client:
29
+ return model_cfg.init_client(model_cfg)
30
+ provider = getattr(model_cfg, "provider", None)
31
+ if provider and provider in _PROVIDER_REGISTRY:
32
+ return _PROVIDER_REGISTRY[provider](model_cfg)
33
+ return OpenAILLM(model_cfg)
34
+
16
35
 
17
36
  class LLMEnsemble:
18
37
  """Ensemble of LLMs"""
@@ -21,10 +40,7 @@ class LLMEnsemble:
21
40
  self.models_cfg = models_cfg
22
41
 
23
42
  # Initialize models from the configuration
24
- self.models = [
25
- model_cfg.init_client(model_cfg) if model_cfg.init_client else OpenAILLM(model_cfg)
26
- for model_cfg in models_cfg
27
- ]
43
+ self.models = [_create_model(model_cfg) for model_cfg in models_cfg]
28
44
 
29
45
  # Extract and normalize model weights
30
46
  self.weights = [model.weight for model in models_cfg]
@@ -155,9 +155,11 @@ class OpenAILLM(LLMInterface):
155
155
  "model": self.model,
156
156
  "messages": formatted_messages,
157
157
  "temperature": kwargs.get("temperature", self.temperature),
158
- "top_p": kwargs.get("top_p", self.top_p),
159
158
  "max_tokens": kwargs.get("max_tokens", self.max_tokens),
160
159
  }
160
+ top_p = kwargs.get("top_p", self.top_p)
161
+ if top_p is not None:
162
+ params["top_p"] = top_p
161
163
 
162
164
  # Handle reasoning_effort for open source reasoning models.
163
165
  reasoning_effort = kwargs.get("reasoning_effort", self.reasoning_effort)