From a9334ea8255a9b2f5130061fd0643fcd7f5c79dc Mon Sep 17 00:00:00 2001 From: Yushu Li Date: Sat, 31 Oct 2026 13:09:56 -0700 Subject: [PATCH] fix(embedding): preserve vector shapes after provider failures --- openevolve/embedding.py | 8 +++--- tests/test_embedding.py | 64 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/openevolve/embedding.py b/openevolve/embedding.py index 4d81a4cf75..497d19fe0b 100644 --- a/openevolve/embedding.py +++ b/openevolve/embedding.py @@ -90,8 +90,8 @@ def get_embedding(self, code: Union[str, List[str]]) -> Union[List[float], List[ of strings. Returns: - list: Embedding vector for the code or None if an error - occurs. + list: Embedding vector for a string, or one vector per batch input. + On failure, return an empty vector for each input. """ if isinstance(code, str): code = [code] @@ -110,6 +110,6 @@ def get_embedding(self, code: Union[str, List[str]]) -> Union[List[float], List[ except Exception as e: logger.info(f"Error getting embedding: {e}") if single_code: - return [], 0.0 + return [] else: - return [[]], 0.0 + return [[] for _ in code] diff --git a/tests/test_embedding.py b/tests/test_embedding.py index 857ed20ff1..952553be17 100644 --- a/tests/test_embedding.py +++ b/tests/test_embedding.py @@ -70,6 +70,70 @@ def test_get_embedding_returns_batch_embeddings(self): encoding_format="float", ) + def test_single_request_failure_returns_empty_vector_and_can_recover(self): + client = EmbeddingClient.__new__(EmbeddingClient) + client.model = "text-embedding-3-small" + client.client = MagicMock() + client.client.embeddings.create.side_effect = [ + RuntimeError("embedding unavailable"), + SimpleNamespace(data=[SimpleNamespace(embedding=[0.1, 0.2])]), + ] + + with self.assertLogs("openevolve.embedding", level="INFO") as logs: + result = client.get_embedding("first") + self.assertEqual(result, []) + self.assertFalse(result) + self.assertIn("embedding unavailable", logs.output[0]) + self.assertEqual(client.get_embedding("second"), [0.1, 0.2]) + + def test_batch_request_failure_preserves_input_cardinality(self): + for batch in ([], ["first"], ["first", "second", "third"]): + with self.subTest(batch=batch): + client = EmbeddingClient.__new__(EmbeddingClient) + client.model = "text-embedding-3-small" + client.client = MagicMock() + client.client.embeddings.create.side_effect = RuntimeError("embedding unavailable") + result = client.get_embedding(batch) + self.assertEqual(result, [[] for _ in batch]) + client.client.embeddings.create.assert_called_once_with( + model=client.model, input=batch, encoding_format="float" + ) + + def test_failed_embedding_does_not_break_database_novelty_checks(self): + from openevolve.config import DatabaseConfig + from openevolve.database import Program, ProgramDatabase + + for dimensions, failures in ((2, 1), (3, 2)): + with self.subTest(dimensions=dimensions, failures=failures): + seed = [1.0] + [0.0] * (dimensions - 1) + recovered = [0.0, 1.0] + [0.0] * (dimensions - 2) + client = EmbeddingClient.__new__(EmbeddingClient) + client.model = "text-embedding-3-small" + client.client = MagicMock() + client.client.embeddings.create.side_effect = [ + SimpleNamespace(data=[SimpleNamespace(embedding=seed)]), + *[RuntimeError("embedding unavailable") for _ in range(failures)], + SimpleNamespace(data=[SimpleNamespace(embedding=recovered)]), + ] + config = DatabaseConfig( + in_memory=True, + num_islands=1, + embedding_model=client.model, + similarity_threshold=0.9, + ) + with patch("openevolve.embedding.EmbeddingClient", return_value=client): + database = ProgramDatabase(config) + programs = [ + Program(id=str(i), code=f"x = {i}", metrics={"combined_score": i}) + for i in range(failures + 2) + ] + for program in programs: + self.assertEqual(database.add(program), program.id) + for program in programs[1:-1]: + self.assertEqual(program.embedding, []) + self.assertEqual(programs[-1].embedding, recovered) + self.assertIn(programs[-1].id, database.islands[0]) + if __name__ == "__main__": unittest.main()