From 169653cb1d6017565e2e4a924f0fa6832983f6b5 Mon Sep 17 00:00:00 2001 From: Wangzy Date: Wed, 14 Jan 2026 22:56:23 +0800 Subject: [PATCH 1/6] fix: fix a typo in a comment --- cookbooks/data_refinement/refinement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/data_refinement/refinement.py b/cookbooks/data_refinement/refinement.py index 4f46f750a..c773a3874 100644 --- a/cookbooks/data_refinement/refinement.py +++ b/cookbooks/data_refinement/refinement.py @@ -43,7 +43,7 @@ def format_messages(messages: List[ChatMessage]) -> str: GENERATE_RESPONSE_PROMPT = """# Task -Please generate a respoonse as the conversation required. +Please generate a response as the conversation required. # Conversation history {history} From 05c01ad1daa360c50e31dd2c34e0de065a47b19a Mon Sep 17 00:00:00 2001 From: Wangzy Date: Sat, 7 Feb 2026 15:19:50 +0800 Subject: [PATCH 2/6] Add max_retries and timeout support to OpenaiChatModel --- openjudge/models/openai_chat_model.py | 7 ++ tests/models/test_openai_chat_model.py | 88 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/openjudge/models/openai_chat_model.py b/openjudge/models/openai_chat_model.py index 4213c5309..59a264dbd 100644 --- a/openjudge/models/openai_chat_model.py +++ b/openjudge/models/openai_chat_model.py @@ -49,6 +49,8 @@ def __init__( reasoning_effort: Literal["low", "medium", "high"] | None = None, organization: str | None = None, client_args: Dict[str, Any] | None = None, + max_retries: int | None = None, + timeout: float | None = None, **kwargs: Any, ) -> None: """Initialize the openai client. @@ -85,6 +87,11 @@ def __init__( if organization: client_args["organization"] = organization + if max_retries is not None: + client_args["max_retries"] = max_retries + + if timeout is not None: + client_args["timeout"] = timeout self.client = AsyncOpenAI(**client_args) diff --git a/tests/models/test_openai_chat_model.py b/tests/models/test_openai_chat_model.py index 4c2122c17..b3501d573 100644 --- a/tests/models/test_openai_chat_model.py +++ b/tests/models/test_openai_chat_model.py @@ -318,6 +318,94 @@ def test_qwen_omni_audio_formatting(self): "data:;base64,", ) + @patch("openjudge.models.openai_chat_model.AsyncOpenAI") + def test_max_retries_and_timeout_parameters(self, mock_async_openai): + """Test that max_retries and timeout parameters are passed to AsyncOpenAI client.""" + # Test with specific max_retries and timeout values + model = OpenAIChatModel( + model="gpt-4", + api_key="test-key", + max_retries=5, + timeout=120.0, + ) + + # Verify AsyncOpenAI was called with correct parameters + mock_async_openai.assert_called_once() + call_kwargs = mock_async_openai.call_args[1] + assert call_kwargs["api_key"] == "test-key" + assert call_kwargs["max_retries"] == 5 + assert call_kwargs["timeout"] == 120.0 + + @patch("openjudge.models.openai_chat_model.AsyncOpenAI") + def test_max_retries_and_timeout_default_values(self, mock_async_openai): + """Test that max_retries and timeout use SDK defaults when not provided.""" + # Test without providing max_retries and timeout + model = OpenAIChatModel( + model="gpt-4", + api_key="test-key", + ) + + # Verify AsyncOpenAI was called + mock_async_openai.assert_called_once() + call_kwargs = mock_async_openai.call_args[1] + assert call_kwargs["api_key"] == "test-key" + # When None, these parameters should not be in the call_kwargs + assert "max_retries" not in call_kwargs + assert "timeout" not in call_kwargs + + @patch("openjudge.models.openai_chat_model.AsyncOpenAI") + def test_max_retries_and_timeout_with_none_values(self, mock_async_openai): + """Test that explicitly passing None for max_retries and timeout doesn't add them to client_args.""" + # Test with explicit None values + model = OpenAIChatModel( + model="gpt-4", + api_key="test-key", + max_retries=None, + timeout=None, + ) + + # Verify AsyncOpenAI was called + mock_async_openai.assert_called_once() + call_kwargs = mock_async_openai.call_args[1] + assert call_kwargs["api_key"] == "test-key" + # When explicitly None, these parameters should not be in the call_kwargs + assert "max_retries" not in call_kwargs + assert "timeout" not in call_kwargs + + @patch("openjudge.models.openai_chat_model.AsyncOpenAI") + def test_max_retries_only(self, mock_async_openai): + """Test that only max_retries is passed when timeout is not provided.""" + # Test with only max_retries + model = OpenAIChatModel( + model="gpt-4", + api_key="test-key", + max_retries=3, + ) + + # Verify AsyncOpenAI was called with correct parameters + mock_async_openai.assert_called_once() + call_kwargs = mock_async_openai.call_args[1] + assert call_kwargs["api_key"] == "test-key" + assert call_kwargs["max_retries"] == 3 + assert "timeout" not in call_kwargs + + @patch("openjudge.models.openai_chat_model.AsyncOpenAI") + def test_timeout_only(self, mock_async_openai): + """Test that only timeout is passed when max_retries is not provided.""" + # Test with only timeout + model = OpenAIChatModel( + model="gpt-4", + api_key="test-key", + timeout=30.0, + ) + + # Verify AsyncOpenAI was called with correct parameters + mock_async_openai.assert_called_once() + call_kwargs = mock_async_openai.call_args[1] + assert call_kwargs["api_key"] == "test-key" + assert call_kwargs["timeout"] == 30.0 + assert "max_retries" not in call_kwargs + if __name__ == "__main__": pytest.main([__file__]) From 4d56629476caa5689a1d5bdb2d18f326dadf4d09 Mon Sep 17 00:00:00 2001 From: Wangzy Date: Sat, 7 Feb 2026 15:34:14 +0800 Subject: [PATCH 3/6] fix --- tests/models/test_openai_chat_model.py | 113 ++++++++----------------- 1 file changed, 36 insertions(+), 77 deletions(-) diff --git a/tests/models/test_openai_chat_model.py b/tests/models/test_openai_chat_model.py index b3501d573..d574d2776 100644 --- a/tests/models/test_openai_chat_model.py +++ b/tests/models/test_openai_chat_model.py @@ -318,94 +318,53 @@ def test_qwen_omni_audio_formatting(self): "data:;base64,", ) + @pytest.mark.parametrize( + "init_kwargs, expected_in_call, not_expected_in_call", + [ + ( + {"max_retries": 5, "timeout": 120.0}, + {"max_retries": 5, "timeout": 120.0}, + [], + ), + ({}, {}, ["max_retries", "timeout"]), + ({"max_retries": None, "timeout": None}, {}, ["max_retries", "timeout"]), + ({"max_retries": 3}, {"max_retries": 3}, ["timeout"]), + ({"timeout": 30.0}, {"timeout": 30.0}, ["max_retries"]), + ], + ids=[ + "with_retries_and_timeout", + "defaults", + "with_none_values", + "with_retries_only", + "with_timeout_only", + ], + ) @patch("openjudge.models.openai_chat_model.AsyncOpenAI") - def test_max_retries_and_timeout_parameters(self, mock_async_openai): - """Test that max_retries and timeout parameters are passed to AsyncOpenAI client.""" - # Test with specific max_retries and timeout values - model = OpenAIChatModel( + def test_client_initialization_with_retries_and_timeout( + self, + mock_async_openai, + init_kwargs, + expected_in_call, + not_expected_in_call, + ): + """Test that max_retries and timeout parameters are passed to AsyncOpenAI client correctly.""" + OpenAIChatModel( model="gpt-4", api_key="test-key", - max_retries=5, - timeout=120.0, + **init_kwargs, ) - # Verify AsyncOpenAI was called with correct parameters mock_async_openai.assert_called_once() call_kwargs = mock_async_openai.call_args[1] - assert call_kwargs["api_key"] == "test-key" - assert call_kwargs["max_retries"] == 5 - assert call_kwargs["timeout"] == 120.0 - - @patch("openjudge.models.openai_chat_model.AsyncOpenAI") - def test_max_retries_and_timeout_default_values(self, mock_async_openai): - """Test that max_retries and timeout use SDK defaults when not provided.""" - # Test without providing max_retries and timeout - model = OpenAIChatModel( - model="gpt-4", - api_key="test-key", - ) - - # Verify AsyncOpenAI was called - mock_async_openai.assert_called_once() - call_kwargs = mock_async_openai.call_args[1] - assert call_kwargs["api_key"] == "test-key" - # When None, these parameters should not be in the call_kwargs - assert "max_retries" not in call_kwargs - assert "timeout" not in call_kwargs - - @patch("openjudge.models.openai_chat_model.AsyncOpenAI") - def test_max_retries_and_timeout_with_none_values(self, mock_async_openai): - """Test that explicitly passing None for max_retries and timeout doesn't add them to client_args.""" - # Test with explicit None values - model = OpenAIChatModel( - model="gpt-4", - api_key="test-key", - max_retries=None, - timeout=None, - ) - - # Verify AsyncOpenAI was called - mock_async_openai.assert_called_once() - call_kwargs = mock_async_openai.call_args[1] - assert call_kwargs["api_key"] == "test-key" - # When explicitly None, these parameters should not be in the call_kwargs - assert "max_retries" not in call_kwargs - assert "timeout" not in call_kwargs - @patch("openjudge.models.openai_chat_model.AsyncOpenAI") - def test_max_retries_only(self, mock_async_openai): - """Test that only max_retries is passed when timeout is not provided.""" - # Test with only max_retries - model = OpenAIChatModel( - model="gpt-4", - api_key="test-key", - max_retries=3, - ) - - # Verify AsyncOpenAI was called with correct parameters - mock_async_openai.assert_called_once() - call_kwargs = mock_async_openai.call_args[1] assert call_kwargs["api_key"] == "test-key" - assert call_kwargs["max_retries"] == 3 - assert "timeout" not in call_kwargs - @patch("openjudge.models.openai_chat_model.AsyncOpenAI") - def test_timeout_only(self, mock_async_openai): - """Test that only timeout is passed when max_retries is not provided.""" - # Test with only timeout - model = OpenAIChatModel( - model="gpt-4", - api_key="test-key", - timeout=30.0, - ) - - # Verify AsyncOpenAI was called with correct parameters - mock_async_openai.assert_called_once() - call_kwargs = mock_async_openai.call_args[1] - assert call_kwargs["api_key"] == "test-key" - assert call_kwargs["timeout"] == 30.0 - assert "max_retries" not in call_kwargs + for key, value in expected_in_call.items(): + assert key in call_kwargs + assert call_kwargs[key] == value + for key in not_expected_in_call: + assert key not in call_kwargs if __name__ == "__main__": pytest.main([__file__]) From 6c2fadee5eac2a2582953d2b4dd562ee343369fb Mon Sep 17 00:00:00 2001 From: Wangzy Date: Sat, 7 Feb 2026 15:34:29 +0800 Subject: [PATCH 4/6] fix --- tests/models/test_openai_chat_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models/test_openai_chat_model.py b/tests/models/test_openai_chat_model.py index d574d2776..d9dac0ec5 100644 --- a/tests/models/test_openai_chat_model.py +++ b/tests/models/test_openai_chat_model.py @@ -367,4 +367,4 @@ def test_client_initialization_with_retries_and_timeout( assert key not in call_kwargs if __name__ == "__main__": - pytest.main([__file__]) + pytest.main([__file__]) \ No newline at end of file From 1cad29592a813cdc7c35ac59fed5172576539ce3 Mon Sep 17 00:00:00 2001 From: Wangzy Date: Sat, 7 Feb 2026 15:59:06 +0800 Subject: [PATCH 5/6] fix format --- openjudge/models/openai_chat_model.py | 37 +++++++++++++++++++------- tests/models/test_openai_chat_model.py | 4 ++- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/openjudge/models/openai_chat_model.py b/openjudge/models/openai_chat_model.py index 59a264dbd..4207ed74b 100644 --- a/openjudge/models/openai_chat_model.py +++ b/openjudge/models/openai_chat_model.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """OpenAI Client.""" + import os from typing import Any, AsyncGenerator, Callable, Dict, Literal, Type @@ -34,7 +35,9 @@ def _format_audio_data_for_qwen_omni(messages: list[dict | ChatMessage]) -> None and isinstance(block["input_audio"].get("data"), str) ): if not block["input_audio"]["data"].startswith("http"): - block["input_audio"]["data"] = "data:;base64," + block["input_audio"]["data"] + block["input_audio"]["data"] = ( + "data:;base64," + block["input_audio"]["data"] + ) class OpenAIChatModel(BaseChatModel): @@ -89,7 +92,7 @@ def __init__( client_args["organization"] = organization if max_retries is not None: client_args["max_retries"] = max_retries - + if timeout is not None: client_args["timeout"] = timeout @@ -147,10 +150,16 @@ async def achat( # checking messages if not isinstance(messages, list): raise ValueError( - "OpenAI `messages` field expected type `list`, " f"got `{type(messages)}` instead.", + "OpenAI `messages` field expected type `list`, " + f"got `{type(messages)}` instead.", ) - messages = [msg.to_dict() if isinstance(msg, ChatMessage) else msg for msg in messages] - if not all(isinstance(msg, dict) and "role" in msg and "content" in msg for msg in messages): + messages = [ + msg.to_dict() if isinstance(msg, ChatMessage) else msg for msg in messages + ] + if not all( + isinstance(msg, dict) and "role" in msg and "content" in msg + for msg in messages + ): raise ValueError( "Each message in the 'messages' list must contain a 'role' and 'content' key for OpenAI API.", ) @@ -255,7 +264,9 @@ def _handle_non_streaming_response( parsed = repair_and_load_json(content) # Ensure parsed is always a dict if not isinstance(parsed, dict): - parsed = {"result": parsed} if parsed is not None else {} + parsed = ( + {"result": parsed} if parsed is not None else {} + ) parsed_response.parsed = parsed else: parsed_response.parsed = {} @@ -281,7 +292,10 @@ def _handle_non_streaming_response( parsed_response.parsed.update(callback_result) except Exception as e: # Log the exception but don't fail the entire operation - logger.warning(f"Callback function raised an exception: {type(e).__name__}: {e}", exc_info=True) + logger.warning( + f"Callback function raised an exception: {type(e).__name__}: {e}", + exc_info=True, + ) return parsed_response @@ -367,7 +381,9 @@ async def _handle_streaming_response( parsed = {"result": parsed} if parsed is not None else {} final_response.parsed = parsed except Exception as e: - logger.warning(f"Failed to parse structured output from streamed response: {e}") + logger.warning( + f"Failed to parse structured output from streamed response: {e}" + ) final_response.parsed = {} # Apply callback if provided @@ -378,6 +394,9 @@ async def _handle_streaming_response( final_response.parsed = final_response.parsed or {} final_response.parsed.update(callback_result) except Exception as e: - logger.warning(f"Callback function raised an exception: {type(e).__name__}: {e}", exc_info=True) + logger.warning( + f"Callback function raised an exception: {type(e).__name__}: {e}", + exc_info=True, + ) yield final_response diff --git a/tests/models/test_openai_chat_model.py b/tests/models/test_openai_chat_model.py index d9dac0ec5..19a1303ab 100644 --- a/tests/models/test_openai_chat_model.py +++ b/tests/models/test_openai_chat_model.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Unit tests for OpenAIChatModel.""" + import asyncio from unittest.mock import AsyncMock, patch @@ -366,5 +367,6 @@ def test_client_initialization_with_retries_and_timeout( for key in not_expected_in_call: assert key not in call_kwargs + if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file + pytest.main([__file__]) From 7e72bc625bbc876f8d960e3eeee4a9981de7fe47 Mon Sep 17 00:00:00 2001 From: Wangzy Date: Sat, 7 Feb 2026 16:17:09 +0800 Subject: [PATCH 6/6] fix --- openjudge/models/openai_chat_model.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/openjudge/models/openai_chat_model.py b/openjudge/models/openai_chat_model.py index d6e570d7d..4a9bc97f4 100644 --- a/openjudge/models/openai_chat_model.py +++ b/openjudge/models/openai_chat_model.py @@ -37,9 +37,7 @@ def _format_audio_data_for_qwen_omni(messages: list[dict | ChatMessage]) -> list and isinstance(block["input_audio"].get("data"), str) ): if not block["input_audio"]["data"].startswith("http"): - block["input_audio"]["data"] = ( - "data:;base64," + block["input_audio"]["data"] - ) + block["input_audio"]["data"] = "data:;base64," + block["input_audio"]["data"] try: msg_copy = copy.deepcopy(msg) msg_dict = msg_copy.to_dict() if isinstance(msg_copy, ChatMessage) else msg_copy @@ -169,16 +167,10 @@ async def achat( # checking messages if not isinstance(messages, list): raise ValueError( - "OpenAI `messages` field expected type `list`, " - f"got `{type(messages)}` instead.", + "OpenAI `messages` field expected type `list`, " f"got `{type(messages)}` instead.", ) - messages = [ - msg.to_dict() if isinstance(msg, ChatMessage) else msg for msg in messages - ] - if not all( - isinstance(msg, dict) and "role" in msg and "content" in msg - for msg in messages - ): + messages = [msg.to_dict() if isinstance(msg, ChatMessage) else msg for msg in messages] + if not all(isinstance(msg, dict) and "role" in msg and "content" in msg for msg in messages): raise ValueError( "Each message in the 'messages' list must contain a 'role' and 'content' key for OpenAI API.", ) @@ -289,9 +281,7 @@ def _handle_non_streaming_response( parsed = repair_and_load_json(content) # Ensure parsed is always a dict if not isinstance(parsed, dict): - parsed = ( - {"result": parsed} if parsed is not None else {} - ) + parsed = {"result": parsed} if parsed is not None else {} parsed_response.parsed = parsed else: parsed_response.parsed = {} @@ -406,9 +396,7 @@ async def _handle_streaming_response( parsed = {"result": parsed} if parsed is not None else {} final_response.parsed = parsed except Exception as e: - logger.warning( - f"Failed to parse structured output from streamed response: {e}" - ) + logger.warning(f"Failed to parse structured output from streamed response: {e}") final_response.parsed = {} # Apply callback if provided