Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions openevolve/llm/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(
# Set up API client (normal mode)
# OpenAI client requires max_retries to be int, not None
max_retries = self.retries if self.retries is not None else 0
self.client = openai.OpenAI(
self.client = openai.AsyncOpenAI(
api_key=self.api_key,
base_url=self.api_base,
timeout=self.timeout,
Expand Down Expand Up @@ -217,11 +217,10 @@ async def _call_api(self, params: Dict[str, Any]) -> str:
if self.client is None:
raise RuntimeError("OpenAI client is not initialized (manual_mode enabled?)")

# Use asyncio to run the blocking API call in a thread pool
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None, lambda: self.client.chat.completions.create(**params, stream=False)
)
# Await the async client directly instead of running a blocking
# sync client in the default thread-pool executor, which can
# deadlock when the executor is saturated (see issue #278)
response = await self.client.chat.completions.create(**params, stream=False)
if isinstance(response, str):
# Some endpoints stream Server-Sent Events even when asked not to
raise ValueError(
Expand Down
4 changes: 2 additions & 2 deletions tests/test_llm_ensemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_ensemble_initialization(self):

class TestTokenUsageLogging(unittest.IsolatedAsyncioTestCase):
async def test_openai_llm_token_usage_logging_and_propagation(self):
from unittest.mock import MagicMock
from unittest.mock import AsyncMock, MagicMock
from openevolve.llm.openai import OpenAILLM

cfg = LLMModelConfig(
Expand All @@ -84,7 +84,7 @@ async def test_openai_llm_token_usage_logging_and_propagation(self):
mock_response.usage.completion_tokens = 567
mock_response.usage.total_tokens = 1801

llm.client.chat.completions.create = MagicMock(return_value=mock_response)
llm.client.chat.completions.create = AsyncMock(return_value=mock_response)

with self.assertLogs("openevolve.llm.openai", level="INFO") as log_ctx:
result = await llm.generate_with_context(
Expand Down
12 changes: 7 additions & 5 deletions tests/test_openai_non_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import asyncio
import unittest
from unittest.mock import Mock, patch
from unittest.mock import AsyncMock, Mock, patch

from openevolve.llm.openai import OpenAILLM

Expand All @@ -20,10 +20,10 @@ def _make_llm():
model_cfg.retries = 0
model_cfg.retry_delay = 0
model_cfg.api_base = "https://example.com/v1"
model_cfg.api_key = None # openai.OpenAI is patched
model_cfg.api_key = None # openai.AsyncOpenAI is patched
model_cfg.random_seed = None
model_cfg.reasoning_effort = None
with patch("openai.OpenAI"):
with patch("openai.AsyncOpenAI"):
return OpenAILLM(model_cfg)


Expand All @@ -33,7 +33,7 @@ def test_stream_false_is_sent(self):
response = Mock()
response.choices = [Mock()]
response.choices[0].message.content = "ok"
llm.client.chat.completions.create.return_value = response
llm.client.chat.completions.create = AsyncMock(return_value=response)

params = {"model": "test-model", "messages": [{"role": "user", "content": "hi"}]}
self.assertEqual(asyncio.run(llm._call_api(params)), "ok")
Expand All @@ -43,7 +43,9 @@ def test_stream_false_is_sent(self):

def test_raw_sse_string_gives_clear_error(self):
llm = _make_llm()
llm.client.chat.completions.create.return_value = 'data: {"object":"chat.completion.chunk"}'
llm.client.chat.completions.create = AsyncMock(
return_value='data: {"object":"chat.completion.chunk"}'
)

with self.assertRaisesRegex(ValueError, "streaming responses are not supported"):
asyncio.run(llm._call_api({"model": "test-model", "messages": []}))
Expand Down
10 changes: 5 additions & 5 deletions tests/test_reasoning_effort_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ def test_openai_llm_uses_reasoning_effort(self):
model_cfg.retries = 3
model_cfg.retry_delay = 5
model_cfg.api_base = "https://api.openai.com/v1"
model_cfg.api_key = None # openai.OpenAI is patched
model_cfg.api_key = None # openai.AsyncOpenAI is patched
model_cfg.random_seed = None
model_cfg.reasoning_effort = "high"

# Mock OpenAI client to avoid actual API calls
with unittest.mock.patch('openai.OpenAI'):
with unittest.mock.patch('openai.AsyncOpenAI'):
llm = OpenAILLM(model_cfg)

# Verify the reasoning_effort is stored
Expand All @@ -152,18 +152,18 @@ def test_reasoning_effort_passed_to_api_params(self):
model_cfg.retries = 3
model_cfg.retry_delay = 5
model_cfg.api_base = "https://api.openai.com/v1"
model_cfg.api_key = None # openai.OpenAI is patched
model_cfg.api_key = None # openai.AsyncOpenAI is patched
model_cfg.random_seed = None
model_cfg.reasoning_effort = "medium"

with unittest.mock.patch('openai.OpenAI'):
with unittest.mock.patch('openai.AsyncOpenAI'):
llm = OpenAILLM(model_cfg)

# Test the _call_api method directly with mocked client
mock_response = Mock()
mock_response.choices = [Mock()]
mock_response.choices[0].message.content = "Test response"
llm.client.chat.completions.create.return_value = mock_response
llm.client.chat.completions.create = unittest.mock.AsyncMock(return_value=mock_response)

# Test OpenAI reasoning model (gpt-oss-120b at openai.com should use reasoning logic)
test_params = {
Expand Down
Loading