Skip to content

Commit 6a56527

Browse files
feat: support Gemini embedding models (#470)
1 parent 06d4605 commit 6a56527

3 files changed

Lines changed: 96 additions & 2 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,10 @@ database:
493493
migration_interval: 20
494494
feature_dimensions: ["complexity", "diversity", "performance"]
495495

496+
# Optional novelty filtering with Gemini embeddings
497+
embedding_model: "gemini-embedding-001"
498+
similarity_threshold: 0.99
499+
496500
evaluator:
497501
enable_artifacts: true # Error feedback to LLM
498502
cascade_evaluation: true # Multi-stage testing
@@ -509,6 +513,9 @@ prompt:
509513
use_template_stochasticity: true # Randomized prompts
510514
```
511515
516+
For Gemini embeddings, set `GEMINI_API_KEY`. `GOOGLE_API_KEY` is also supported
517+
as a fallback. OpenEvolve uses Google's OpenAI-compatible endpoint automatically.
518+
512519
<details>
513520
<summary><b>🎯 Feature Engineering</b></summary>
514521

openevolve/embedding.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
Original source: https://github.com/SakanaAI/ShinkaEvolve/blob/main/shinka/llm/embedding.py
44
"""
55

6+
import logging
67
import os
8+
from typing import List, Union
9+
710
import openai
8-
from typing import Union, List
9-
import logging
1011

1112
logger = logging.getLogger(__name__)
1213

@@ -22,6 +23,10 @@
2223
"azure-text-embedding-3-large",
2324
]
2425

26+
GEMINI_EMBEDDING_MODELS = [
27+
"gemini-embedding-001",
28+
]
29+
2530
OPENAI_EMBEDDING_COSTS = {
2631
"text-embedding-3-small": 0.02 / M,
2732
"text-embedding-3-large": 0.13 / M,
@@ -53,6 +58,13 @@ def _get_client_model(self, model_name: str) -> tuple[openai.OpenAI, str]:
5358
api_version=os.getenv("AZURE_API_VERSION"),
5459
azure_endpoint=os.getenv("AZURE_API_ENDPOINT"),
5560
)
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
5668
else:
5769
raise ValueError(f"Invalid embedding model: {model_name}")
5870

tests/test_embedding.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import os
2+
import unittest
3+
from types import SimpleNamespace
4+
from unittest.mock import MagicMock, patch
5+
6+
from openevolve.embedding import EmbeddingClient
7+
8+
9+
class TestEmbeddingClient(unittest.TestCase):
10+
@patch("openevolve.embedding.openai.OpenAI")
11+
@patch.dict(
12+
os.environ,
13+
{"GEMINI_API_KEY": "gemini-key", "GOOGLE_API_KEY": "google-key"},
14+
clear=True,
15+
)
16+
def test_uses_gemini_key_and_openai_compatible_endpoint(self, mock_openai):
17+
client = EmbeddingClient("gemini-embedding-001")
18+
19+
mock_openai.assert_called_once_with(
20+
api_key="gemini-key",
21+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
22+
)
23+
self.assertEqual(client.model, "gemini-embedding-001")
24+
25+
@patch("openevolve.embedding.openai.OpenAI")
26+
@patch.dict(os.environ, {"GOOGLE_API_KEY": "google-key"}, clear=True)
27+
def test_falls_back_to_google_api_key(self, mock_openai):
28+
EmbeddingClient("gemini-embedding-001")
29+
30+
self.assertEqual(mock_openai.call_args.kwargs["api_key"], "google-key")
31+
32+
def test_rejects_unknown_embedding_model(self):
33+
with self.assertRaisesRegex(ValueError, "Invalid embedding model: unknown-model"):
34+
EmbeddingClient("unknown-model")
35+
36+
def test_get_embedding_returns_a_single_embedding(self):
37+
client = EmbeddingClient.__new__(EmbeddingClient)
38+
client.model = "gemini-embedding-001"
39+
client.client = MagicMock()
40+
client.client.embeddings.create.return_value = SimpleNamespace(
41+
data=[SimpleNamespace(embedding=[0.1, 0.2])]
42+
)
43+
44+
result = client.get_embedding("def example(): pass")
45+
46+
self.assertEqual(result, [0.1, 0.2])
47+
client.client.embeddings.create.assert_called_once_with(
48+
model="gemini-embedding-001",
49+
input=["def example(): pass"],
50+
encoding_format="float",
51+
)
52+
53+
def test_get_embedding_returns_batch_embeddings(self):
54+
client = EmbeddingClient.__new__(EmbeddingClient)
55+
client.model = "gemini-embedding-001"
56+
client.client = MagicMock()
57+
client.client.embeddings.create.return_value = SimpleNamespace(
58+
data=[
59+
SimpleNamespace(embedding=[0.1, 0.2]),
60+
SimpleNamespace(embedding=[0.3, 0.4]),
61+
]
62+
)
63+
64+
result = client.get_embedding(["first", "second"])
65+
66+
self.assertEqual(result, [[0.1, 0.2], [0.3, 0.4]])
67+
client.client.embeddings.create.assert_called_once_with(
68+
model="gemini-embedding-001",
69+
input=["first", "second"],
70+
encoding_format="float",
71+
)
72+
73+
74+
if __name__ == "__main__":
75+
unittest.main()

0 commit comments

Comments
 (0)