Python: [BREAKING] added SerializationMixin and applied to contents, agents, chat client… (#1012)

* added SerializationMixin and applied to contents, agents, chat clients, removed AFBaseModel

* fix annotations type

* mypy fixes

* fix tests

* fix serializable subvalues and added large docstring

* updated indents in code block

* fixed exported urls
This commit is contained in:
Eduard van Valkenburg
2025-09-30 19:53:46 +00:00
committed by GitHub
parent 3eb26632ce
commit 54ad135914
84 changed files with 2302 additions and 1957 deletions
@@ -46,23 +46,26 @@ skip_if_openai_integration_tests_disabled = pytest.mark.skipif(
def create_test_openai_assistants_client(
mock_async_openai: MagicMock,
ai_model_id: str | None = None,
model_id: str | None = None,
assistant_id: str | None = None,
assistant_name: str | None = None,
thread_id: str | None = None,
should_delete_assistant: bool = False,
) -> OpenAIAssistantsClient:
"""Helper function to create OpenAIAssistantsClient instances for testing, bypassing Pydantic validation."""
return OpenAIAssistantsClient.model_construct(
ai_model_id=ai_model_id or "gpt-4",
"""Helper function to create OpenAIAssistantsClient instances for testing."""
client = OpenAIAssistantsClient(
model_id=model_id or "gpt-4",
assistant_id=assistant_id,
assistant_name=assistant_name,
thread_id=thread_id,
api_key="test-api-key",
org_id="test-org-id",
client=mock_async_openai,
_should_delete_assistant=should_delete_assistant,
async_client=mock_async_openai,
)
# Set the _should_delete_assistant flag directly if needed
if should_delete_assistant:
object.__setattr__(client, "_should_delete_assistant", True)
return client
async def create_vector_store(client: OpenAIAssistantsClient) -> tuple[str, HostedVectorStoreContent]:
@@ -117,11 +120,11 @@ def mock_async_openai() -> MagicMock:
def test_openai_assistants_client_init_with_client(mock_async_openai: MagicMock) -> None:
"""Test OpenAIAssistantsClient initialization with existing client."""
chat_client = create_test_openai_assistants_client(
mock_async_openai, ai_model_id="gpt-4", assistant_id="existing-assistant-id", thread_id="test-thread-id"
mock_async_openai, model_id="gpt-4", assistant_id="existing-assistant-id", thread_id="test-thread-id"
)
assert chat_client.client is mock_async_openai
assert chat_client.ai_model_id == "gpt-4"
assert chat_client.model_id == "gpt-4"
assert chat_client.assistant_id == "existing-assistant-id"
assert chat_client.thread_id == "test-thread-id"
assert not chat_client._should_delete_assistant # type: ignore
@@ -133,19 +136,16 @@ def test_openai_assistants_client_init_auto_create_client(
mock_async_openai: MagicMock,
) -> None:
"""Test OpenAIAssistantsClient initialization with auto-created client."""
chat_client = OpenAIAssistantsClient.model_construct(
ai_model_id=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
assistant_id=None,
chat_client = OpenAIAssistantsClient(
model_id=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
assistant_name="TestAssistant",
thread_id=None,
api_key=openai_unit_test_env["OPENAI_API_KEY"],
org_id=openai_unit_test_env["OPENAI_ORG_ID"],
client=mock_async_openai,
_should_delete_assistant=False,
async_client=mock_async_openai,
)
assert chat_client.client is mock_async_openai
assert chat_client.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert chat_client.model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert chat_client.assistant_id is None
assert chat_client.assistant_name == "TestAssistant"
assert not chat_client._should_delete_assistant # type: ignore
@@ -155,7 +155,7 @@ def test_openai_assistants_client_init_validation_fail() -> None:
"""Test OpenAIAssistantsClient initialization with validation failure."""
with pytest.raises(ServiceInitializationError):
# Force failure by providing invalid model ID type - this should cause validation to fail
OpenAIAssistantsClient(ai_model_id=123, api_key="valid-key") # type: ignore
OpenAIAssistantsClient(model_id=123, api_key="valid-key") # type: ignore
@pytest.mark.parametrize("exclude_list", [["OPENAI_CHAT_MODEL_ID"]], indirect=True)
@@ -171,7 +171,7 @@ def test_openai_assistants_client_init_missing_model_id(openai_unit_test_env: di
def test_openai_assistants_client_init_missing_api_key(openai_unit_test_env: dict[str, str]) -> None:
"""Test OpenAIAssistantsClient initialization with missing API key."""
with pytest.raises(ServiceInitializationError):
OpenAIAssistantsClient(ai_model_id="gpt-4", env_file_path="nonexistent.env")
OpenAIAssistantsClient(model_id="gpt-4", env_file_path="nonexistent.env")
def test_openai_assistants_client_init_with_default_headers(openai_unit_test_env: dict[str, str]) -> None:
@@ -179,12 +179,12 @@ def test_openai_assistants_client_init_with_default_headers(openai_unit_test_env
default_headers = {"X-Unit-Test": "test-guid"}
chat_client = OpenAIAssistantsClient(
ai_model_id="gpt-4",
model_id="gpt-4",
api_key=openai_unit_test_env["OPENAI_API_KEY"],
default_headers=default_headers,
)
assert chat_client.ai_model_id == "gpt-4"
assert chat_client.model_id == "gpt-4"
assert isinstance(chat_client, ChatClientProtocol)
# Assert that the default header we added is present in the client's default headers
@@ -211,7 +211,7 @@ async def test_openai_assistants_client_get_assistant_id_or_create_create_new(
) -> None:
"""Test _get_assistant_id_or_create when creating a new assistant."""
chat_client = create_test_openai_assistants_client(
mock_async_openai, ai_model_id="gpt-4", assistant_name="TestAssistant"
mock_async_openai, model_id="gpt-4", assistant_name="TestAssistant"
)
assistant_id = await chat_client._get_assistant_id_or_create() # type: ignore
@@ -269,7 +269,7 @@ def test_openai_assistants_client_serialize(openai_unit_test_env: dict[str, str]
# Test basic initialization and to_dict
chat_client = OpenAIAssistantsClient(
ai_model_id="gpt-4",
model_id="gpt-4",
assistant_id="test-assistant-id",
assistant_name="TestAssistant",
thread_id="test-thread-id",
@@ -280,11 +280,10 @@ def test_openai_assistants_client_serialize(openai_unit_test_env: dict[str, str]
dumped_settings = chat_client.to_dict()
assert dumped_settings["ai_model_id"] == "gpt-4"
assert dumped_settings["model_id"] == "gpt-4"
assert dumped_settings["assistant_id"] == "test-assistant-id"
assert dumped_settings["assistant_name"] == "TestAssistant"
assert dumped_settings["thread_id"] == "test-thread-id"
assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"]
assert dumped_settings["org_id"] == openai_unit_test_env["OPENAI_ORG_ID"]
# Assert that the default header we added is present in the dumped_settings default headers
@@ -915,6 +914,7 @@ def get_weather(
return f"The weather in {location} is sunny with a high of 25°C."
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_get_response() -> None:
"""Test OpenAI Assistants Client response."""
@@ -939,6 +939,7 @@ async def test_openai_assistants_client_get_response() -> None:
assert any(word in response.text.lower() for word in ["sunny", "25", "weather", "seattle"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_get_response_tools() -> None:
"""Test OpenAI Assistants Client response with tools."""
@@ -960,6 +961,7 @@ async def test_openai_assistants_client_get_response_tools() -> None:
assert any(word in response.text.lower() for word in ["sunny", "25", "weather"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_streaming() -> None:
"""Test OpenAI Assistants Client streaming response."""
@@ -990,6 +992,7 @@ async def test_openai_assistants_client_streaming() -> None:
assert any(word in full_message.lower() for word in ["sunny", "25", "weather", "seattle"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_streaming_tools() -> None:
"""Test OpenAI Assistants Client streaming response with tools."""
@@ -1016,6 +1019,7 @@ async def test_openai_assistants_client_streaming_tools() -> None:
assert any(word in full_message.lower() for word in ["sunny", "25", "weather"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_with_existing_assistant() -> None:
"""Test OpenAI Assistants Client with existing assistant ID."""
@@ -1028,7 +1032,7 @@ async def test_openai_assistants_client_with_existing_assistant() -> None:
# Now test using the existing assistant
async with OpenAIAssistantsClient(
ai_model_id="gpt-4o-mini", assistant_id=assistant_id
model_id="gpt-4o-mini", assistant_id=assistant_id
) as openai_assistants_client:
assert isinstance(openai_assistants_client, ChatClientProtocol)
assert openai_assistants_client.assistant_id == assistant_id
@@ -1043,6 +1047,7 @@ async def test_openai_assistants_client_with_existing_assistant() -> None:
assert len(response.text) > 0
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
@pytest.mark.skip(reason="OpenAI file search functionality is currently broken - tracked in GitHub issue")
async def test_openai_assistants_client_file_search() -> None:
@@ -1066,6 +1071,7 @@ async def test_openai_assistants_client_file_search() -> None:
assert any(word in response.text.lower() for word in ["sunny", "25", "weather"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
@pytest.mark.skip(reason="OpenAI file search functionality is currently broken - tracked in GitHub issue")
async def test_openai_assistants_client_file_search_streaming() -> None:
@@ -1096,6 +1102,7 @@ async def test_openai_assistants_client_file_search_streaming() -> None:
assert any(word in full_message.lower() for word in ["sunny", "25", "weather"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_agent_basic_run():
"""Test ChatAgent basic run functionality with OpenAIAssistantsClient."""
@@ -1112,6 +1119,7 @@ async def test_openai_assistants_agent_basic_run():
assert "Hello World" in response.text
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_agent_basic_run_streaming():
"""Test ChatAgent basic streaming functionality with OpenAIAssistantsClient."""
@@ -1131,6 +1139,7 @@ async def test_openai_assistants_agent_basic_run_streaming():
assert "streaming response test" in full_message.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_agent_thread_persistence():
"""Test ChatAgent thread persistence across runs with OpenAIAssistantsClient."""
@@ -1159,6 +1168,7 @@ async def test_openai_assistants_agent_thread_persistence():
assert thread.service_thread_id is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_agent_existing_thread_id():
"""Test ChatAgent with existing thread ID to continue conversations across agent instances."""
@@ -1203,6 +1213,7 @@ async def test_openai_assistants_agent_existing_thread_id():
assert "paris" in response2.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_agent_code_interpreter():
"""Test ChatAgent with code interpreter through OpenAIAssistantsClient."""
@@ -1222,6 +1233,7 @@ async def test_openai_assistants_agent_code_interpreter():
assert "120" in response.text or "factorial" in response.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_assistants_client_agent_level_tool_persistence():
"""Test that agent-level tools persist across multiple runs with OpenAI Assistants Client."""
@@ -41,22 +41,22 @@ def test_init(openai_unit_test_env: dict[str, str]) -> None:
# Test successful initialization
open_ai_chat_completion = OpenAIChatClient()
assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert open_ai_chat_completion.model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
def test_init_validation_fail() -> None:
# Test successful initialization
with pytest.raises(ServiceInitializationError):
OpenAIChatClient(api_key="34523", ai_model_id={"test": "dict"}) # type: ignore
OpenAIChatClient(api_key="34523", model_id={"test": "dict"}) # type: ignore
def test_init_ai_model_id_constructor(openai_unit_test_env: dict[str, str]) -> None:
def test_init_model_id_constructor(openai_unit_test_env: dict[str, str]) -> None:
# Test successful initialization
ai_model_id = "test_model_id"
open_ai_chat_completion = OpenAIChatClient(ai_model_id=ai_model_id)
model_id = "test_model_id"
open_ai_chat_completion = OpenAIChatClient(model_id=model_id)
assert open_ai_chat_completion.ai_model_id == ai_model_id
assert open_ai_chat_completion.model_id == model_id
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
@@ -68,7 +68,7 @@ def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
default_headers=default_headers,
)
assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert open_ai_chat_completion.model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert isinstance(open_ai_chat_completion, ChatClientProtocol)
# Assert that the default header we added is present in the client's default headers
@@ -95,7 +95,7 @@ def test_init_base_url_from_settings_env() -> None:
},
):
client = OpenAIChatClient()
assert client.ai_model_id == "gpt-5"
assert client.model_id == "gpt-5"
assert str(client.client.base_url) == "https://custom-openai-endpoint.com/v1/"
@@ -109,11 +109,11 @@ def test_init_with_empty_model_id(openai_unit_test_env: dict[str, str]) -> None:
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env: dict[str, str]) -> None:
ai_model_id = "test_model_id"
model_id = "test_model_id"
with pytest.raises(ServiceInitializationError):
OpenAIChatClient(
ai_model_id=ai_model_id,
model_id=model_id,
env_file_path="test.env",
)
@@ -122,15 +122,14 @@ def test_serialize(openai_unit_test_env: dict[str, str]) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
"model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
open_ai_chat_completion = OpenAIChatClient.from_dict(settings)
dumped_settings = open_ai_chat_completion.to_dict()
assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"]
assert dumped_settings["model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
# Assert that the default header we added is present in the dumped_settings default headers
for key, value in default_headers.items():
assert key in dumped_settings["default_headers"]
@@ -141,18 +140,17 @@ def test_serialize(openai_unit_test_env: dict[str, str]) -> None:
def test_serialize_with_org_id(openai_unit_test_env: dict[str, str]) -> None:
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
"model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"org_id": openai_unit_test_env["OPENAI_ORG_ID"],
}
open_ai_chat_completion = OpenAIChatClient.from_dict(settings)
dumped_settings = open_ai_chat_completion.to_dict()
assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"]
assert dumped_settings["model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"]
assert dumped_settings["org_id"] == openai_unit_test_env["OPENAI_ORG_ID"]
# Assert that the 'User-Agent' header is not present in the dumped_settings default headers
assert "User-Agent" not in dumped_settings["default_headers"]
assert "User-Agent" not in dumped_settings.get("default_headers", {})
async def test_content_filter_exception_handling(openai_unit_test_env: dict[str, str]) -> None:
@@ -210,6 +208,7 @@ def get_weather(location: str) -> str:
return f"The weather in {location} is sunny and 72°F."
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_completion_response() -> None:
"""Test OpenAI chat completion responses."""
@@ -237,6 +236,7 @@ async def test_openai_chat_completion_response() -> None:
assert "scientists" in response.text
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_completion_response_tools() -> None:
"""Test OpenAI chat completion responses."""
@@ -259,6 +259,7 @@ async def test_openai_chat_completion_response_tools() -> None:
assert "scientists" in response.text
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_streaming() -> None:
"""Test Azure OpenAI chat completion responses."""
@@ -294,6 +295,7 @@ async def test_openai_chat_client_streaming() -> None:
assert "scientists" in full_message
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_streaming_tools() -> None:
"""Test AzureOpenAI chat completion responses."""
@@ -321,10 +323,11 @@ async def test_openai_chat_client_streaming_tools() -> None:
assert "scientists" in full_message
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_web_search() -> None:
# Currently only a select few models support web search tool calls
openai_chat_client = OpenAIChatClient(ai_model_id="gpt-4o-search-preview")
openai_chat_client = OpenAIChatClient(model_id="gpt-4o-search-preview")
assert isinstance(openai_chat_client, ChatClientProtocol)
@@ -361,9 +364,10 @@ async def test_openai_chat_client_web_search() -> None:
assert response.text is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_web_search_streaming() -> None:
openai_chat_client = OpenAIChatClient(ai_model_id="gpt-4o-search-preview")
openai_chat_client = OpenAIChatClient(model_id="gpt-4o-search-preview")
assert isinstance(openai_chat_client, ChatClientProtocol)
@@ -414,11 +418,12 @@ async def test_openai_chat_client_web_search_streaming() -> None:
assert full_message is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_agent_basic_run():
"""Test OpenAI chat client agent basic run functionality with OpenAIChatClient."""
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
) as agent:
# Test basic run
response = await agent.run("Hello! Please respond with 'Hello World' exactly.")
@@ -429,11 +434,12 @@ async def test_openai_chat_client_agent_basic_run():
assert "hello world" in response.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_agent_basic_run_streaming():
"""Test OpenAI chat client agent basic streaming functionality with OpenAIChatClient."""
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
) as agent:
# Test streaming run
full_text = ""
@@ -446,11 +452,12 @@ async def test_openai_chat_client_agent_basic_run_streaming():
assert "streaming response test" in full_text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_agent_thread_persistence():
"""Test OpenAI chat client agent thread persistence across runs with OpenAIChatClient."""
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
instructions="You are a helpful assistant with good memory.",
) as agent:
# Create a new thread that will be reused
@@ -470,6 +477,7 @@ async def test_openai_chat_client_agent_thread_persistence():
assert "alice" in response2.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_agent_existing_thread():
"""Test OpenAI chat client agent with existing thread to continue conversations across agent instances."""
@@ -477,7 +485,7 @@ async def test_openai_chat_client_agent_existing_thread():
preserved_thread = None
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
instructions="You are a helpful assistant with good memory.",
) as first_agent:
# Start a conversation and capture the thread
@@ -493,7 +501,7 @@ async def test_openai_chat_client_agent_existing_thread():
# Second conversation - reuse the thread in a new agent instance
if preserved_thread:
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4o-search-preview"),
chat_client=OpenAIChatClient(model_id="gpt-4o-search-preview"),
instructions="You are a helpful assistant with good memory.",
) as second_agent:
# Reuse the preserved thread
@@ -504,12 +512,13 @@ async def test_openai_chat_client_agent_existing_thread():
assert "alice" in second_response.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_agent_level_tool_persistence():
"""Test that agent-level tools persist across multiple runs with OpenAI Chat Client."""
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4.1"),
chat_client=OpenAIChatClient(model_id="gpt-4.1"),
instructions="You are a helpful assistant that uses available tools.",
tools=[get_weather], # Agent-level tool
) as agent:
@@ -530,6 +539,7 @@ async def test_openai_chat_client_agent_level_tool_persistence():
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_chat_client_run_level_tool_isolation():
"""Test that run-level tools are isolated to specific runs and don't persist with OpenAI Chat Client."""
@@ -544,7 +554,7 @@ async def test_openai_chat_client_run_level_tool_isolation():
return f"The weather in {location} is sunny and 72°F."
async with ChatAgent(
chat_client=OpenAIChatClient(ai_model_id="gpt-4.1"),
chat_client=OpenAIChatClient(model_id="gpt-4.1"),
instructions="You are a helpful assistant.",
) as agent:
# First run - use run-level tool
@@ -571,7 +581,7 @@ async def test_openai_chat_client_run_level_tool_isolation():
async def test_exception_message_includes_original_error_details() -> None:
"""Test that exception messages include original error details in the new format."""
client = OpenAIChatClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIChatClient(model_id="test-model", api_key="test-key")
messages = [ChatMessage(role="user", text="test message")]
mock_response = MagicMock()
@@ -96,22 +96,22 @@ def test_init(openai_unit_test_env: dict[str, str]) -> None:
# Test successful initialization
openai_responses_client = OpenAIResponsesClient()
assert openai_responses_client.ai_model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert openai_responses_client.model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert isinstance(openai_responses_client, ChatClientProtocol)
def test_init_validation_fail() -> None:
# Test successful initialization
with pytest.raises(ServiceInitializationError):
OpenAIResponsesClient(api_key="34523", ai_model_id={"test": "dict"}) # type: ignore
OpenAIResponsesClient(api_key="34523", model_id={"test": "dict"}) # type: ignore
def test_init_ai_model_id_constructor(openai_unit_test_env: dict[str, str]) -> None:
def test_init_model_id_constructor(openai_unit_test_env: dict[str, str]) -> None:
# Test successful initialization
ai_model_id = "test_model_id"
openai_responses_client = OpenAIResponsesClient(ai_model_id=ai_model_id)
model_id = "test_model_id"
openai_responses_client = OpenAIResponsesClient(model_id=model_id)
assert openai_responses_client.ai_model_id == ai_model_id
assert openai_responses_client.model_id == model_id
assert isinstance(openai_responses_client, ChatClientProtocol)
@@ -123,7 +123,7 @@ def test_init_with_default_header(openai_unit_test_env: dict[str, str]) -> None:
default_headers=default_headers,
)
assert openai_responses_client.ai_model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert openai_responses_client.model_id == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert isinstance(openai_responses_client, ChatClientProtocol)
# Assert that the default header we added is present in the client's default headers
@@ -142,11 +142,11 @@ def test_init_with_empty_model_id(openai_unit_test_env: dict[str, str]) -> None:
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env: dict[str, str]) -> None:
ai_model_id = "test_model_id"
model_id = "test_model_id"
with pytest.raises(ServiceInitializationError):
OpenAIResponsesClient(
ai_model_id=ai_model_id,
model_id=model_id,
env_file_path="test.env",
)
@@ -155,15 +155,14 @@ def test_serialize(openai_unit_test_env: dict[str, str]) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"],
"model_id": openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
openai_responses_client = OpenAIResponsesClient.from_dict(settings)
dumped_settings = openai_responses_client.to_dict()
assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"]
assert dumped_settings["model_id"] == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
# Assert that the default header we added is present in the dumped_settings default headers
for key, value in default_headers.items():
assert key in dumped_settings["default_headers"]
@@ -174,24 +173,23 @@ def test_serialize(openai_unit_test_env: dict[str, str]) -> None:
def test_serialize_with_org_id(openai_unit_test_env: dict[str, str]) -> None:
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"],
"model_id": openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"org_id": openai_unit_test_env["OPENAI_ORG_ID"],
}
openai_responses_client = OpenAIResponsesClient.from_dict(settings)
dumped_settings = openai_responses_client.to_dict()
assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"]
assert dumped_settings["model_id"] == openai_unit_test_env["OPENAI_RESPONSES_MODEL_ID"]
assert dumped_settings["org_id"] == openai_unit_test_env["OPENAI_ORG_ID"]
# Assert that the 'User-Agent' header is not present in the dumped_settings default headers
assert "User-Agent" not in dumped_settings["default_headers"]
assert "User-Agent" not in dumped_settings.get("default_headers", {})
def test_get_response_with_invalid_input() -> None:
"""Test get_response with invalid inputs to trigger exception handling."""
client = OpenAIResponsesClient(ai_model_id="invalid-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="invalid-model", api_key="test-key")
# Test with empty messages which should trigger ServiceInvalidRequestError
with pytest.raises(ServiceInvalidRequestError, match="Messages are required"):
@@ -200,7 +198,7 @@ def test_get_response_with_invalid_input() -> None:
def test_get_response_with_all_parameters() -> None:
"""Test get_response with all possible parameters to cover parameter handling logic."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with comprehensive parameter set - should fail due to invalid API key
with pytest.raises(ServiceResponseException):
@@ -232,7 +230,7 @@ def test_get_response_with_all_parameters() -> None:
def test_web_search_tool_with_location() -> None:
"""Test HostedWebSearchTool with location parameters."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test web search tool with location
web_search_tool = HostedWebSearchTool(
@@ -254,7 +252,7 @@ def test_web_search_tool_with_location() -> None:
def test_file_search_tool_with_invalid_inputs() -> None:
"""Test HostedFileSearchTool with invalid vector store inputs."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with invalid inputs type (should trigger ValueError)
file_search_tool = HostedFileSearchTool(inputs=[HostedFileContent(file_id="invalid")])
@@ -268,7 +266,7 @@ def test_file_search_tool_with_invalid_inputs() -> None:
def test_code_interpreter_tool_variations() -> None:
"""Test HostedCodeInterpreterTool with and without file inputs."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test code interpreter without files
code_tool_empty = HostedCodeInterpreterTool()
@@ -293,7 +291,7 @@ def test_code_interpreter_tool_variations() -> None:
def test_content_filter_exception() -> None:
"""Test that content filter errors in get_response are properly handled."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Mock a BadRequestError with content_filter code
mock_error = BadRequestError(
@@ -313,7 +311,7 @@ def test_content_filter_exception() -> None:
def test_hosted_file_search_tool_validation() -> None:
"""Test get_response HostedFileSearchTool validation."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test HostedFileSearchTool without inputs (should raise ValueError)
empty_file_search_tool = HostedFileSearchTool()
@@ -326,7 +324,7 @@ def test_hosted_file_search_tool_validation() -> None:
def test_chat_message_parsing_with_function_calls() -> None:
"""Test get_response message preparation with function call and result content types in conversation flow."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create messages with function call and result content
function_call = FunctionCallContent(
@@ -351,7 +349,7 @@ def test_chat_message_parsing_with_function_calls() -> None:
async def test_response_format_parse_path() -> None:
"""Test get_response response_format parsing path."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Mock successful parse response
mock_parsed_response = MagicMock()
@@ -375,7 +373,7 @@ async def test_response_format_parse_path() -> None:
async def test_bad_request_error_non_content_filter() -> None:
"""Test get_response BadRequestError without content_filter."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Mock a BadRequestError without content_filter code
mock_error = BadRequestError(
@@ -396,7 +394,7 @@ async def test_bad_request_error_non_content_filter() -> None:
async def test_streaming_content_filter_exception_handling() -> None:
"""Test that content filter errors in get_streaming_response are properly handled."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Mock the OpenAI client to raise a BadRequestError with content_filter code
with patch.object(client.client.responses, "create") as mock_create:
@@ -413,10 +411,11 @@ async def test_streaming_content_filter_exception_handling() -> None:
break
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_get_streaming_response_with_all_parameters() -> None:
"""Test get_streaming_response with all possible parameters."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Should fail due to invalid API key
with pytest.raises(ServiceResponseException):
@@ -449,7 +448,7 @@ async def test_get_streaming_response_with_all_parameters() -> None:
def test_response_content_creation_with_annotations() -> None:
"""Test _create_response_content with different annotation types."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with annotated text content
mock_response = MagicMock()
@@ -489,7 +488,7 @@ def test_response_content_creation_with_annotations() -> None:
def test_response_content_creation_with_refusal() -> None:
"""Test _create_response_content with refusal content."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with refusal content
mock_response = MagicMock()
@@ -519,7 +518,7 @@ def test_response_content_creation_with_refusal() -> None:
def test_response_content_creation_with_reasoning() -> None:
"""Test _create_response_content with reasoning content."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with reasoning content
mock_response = MagicMock()
@@ -550,7 +549,7 @@ def test_response_content_creation_with_reasoning() -> None:
def test_response_content_creation_with_code_interpreter() -> None:
"""Test _create_response_content with code interpreter outputs."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with code interpreter outputs
mock_response = MagicMock()
@@ -588,7 +587,7 @@ def test_response_content_creation_with_code_interpreter() -> None:
def test_response_content_creation_with_function_call() -> None:
"""Test _create_response_content with function call content."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with function call
mock_response = MagicMock()
@@ -620,7 +619,7 @@ def test_response_content_creation_with_function_call() -> None:
def test_tools_to_response_tools_with_hosted_mcp() -> None:
"""Test that HostedMCPTool is converted to the correct response tool dict."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
tool = HostedMCPTool(
name="My MCP",
@@ -650,7 +649,7 @@ def test_tools_to_response_tools_with_hosted_mcp() -> None:
def test_create_response_content_with_mcp_approval_request() -> None:
"""Test that a non-streaming mcp_approval_request is parsed into FunctionApprovalRequestContent."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_response = MagicMock()
mock_response.output_parsed = None
@@ -681,7 +680,7 @@ def test_create_response_content_with_mcp_approval_request() -> None:
def test_tools_to_response_tools_with_raw_image_generation() -> None:
"""Test that raw image_generation tool dict is handled correctly with parameter mapping."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with raw tool dict using user-friendly parameter names
tool = {
@@ -710,7 +709,7 @@ def test_tools_to_response_tools_with_raw_image_generation() -> None:
def test_tools_to_response_tools_with_raw_image_generation_openai_responses_params() -> None:
"""Test raw image_generation tool with OpenAI-specific parameters."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with OpenAI-specific parameters
tool = {
@@ -742,7 +741,7 @@ def test_tools_to_response_tools_with_raw_image_generation_openai_responses_para
def test_tools_to_response_tools_with_raw_image_generation_minimal() -> None:
"""Test raw image_generation tool with minimal configuration."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with minimal parameters (just type)
tool = {"type": "image_generation"}
@@ -760,7 +759,7 @@ def test_tools_to_response_tools_with_raw_image_generation_minimal() -> None:
def test_create_streaming_response_content_with_mcp_approval_request() -> None:
"""Test that a streaming mcp_approval_request event is parsed into FunctionApprovalRequestContent."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -787,7 +786,7 @@ def test_end_to_end_mcp_approval_flow(span_exporter) -> None:
"""End-to-end mocked test:
model issues an mcp_approval_request, user approves, client sends mcp_approval_response.
"""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# First mocked response: model issues an mcp_approval_request
mock_response1 = MagicMock()
@@ -851,7 +850,7 @@ def test_end_to_end_mcp_approval_flow(span_exporter) -> None:
def test_usage_details_basic() -> None:
"""Test _usage_details_from_openai without cached or reasoning tokens."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
mock_usage.input_tokens = 100
@@ -869,7 +868,7 @@ def test_usage_details_basic() -> None:
def test_usage_details_with_cached_tokens() -> None:
"""Test _usage_details_from_openai with cached input tokens."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
mock_usage.input_tokens = 200
@@ -887,7 +886,7 @@ def test_usage_details_with_cached_tokens() -> None:
def test_usage_details_with_reasoning_tokens() -> None:
"""Test _usage_details_from_openai with reasoning tokens."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
mock_usage.input_tokens = 150
@@ -905,7 +904,7 @@ def test_usage_details_with_reasoning_tokens() -> None:
def test_get_metadata_from_response() -> None:
"""Test the _get_metadata_from_response method."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with logprobs
mock_output_with_logprobs = MagicMock()
@@ -925,7 +924,7 @@ def test_get_metadata_from_response() -> None:
def test_streaming_response_basic_structure() -> None:
"""Test that _create_streaming_response_content returns proper structure."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions(store=True)
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -942,6 +941,7 @@ def test_streaming_response_basic_structure() -> None:
assert response.raw_representation is mock_event
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_response() -> None:
"""Test OpenAI chat completion responses."""
@@ -986,6 +986,7 @@ async def test_openai_responses_client_response() -> None:
assert output.weather is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_response_tools() -> None:
"""Test OpenAI chat completion responses."""
@@ -1025,6 +1026,7 @@ async def test_openai_responses_client_response_tools() -> None:
assert "sunny" in output.weather.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_streaming() -> None:
"""Test OpenAI chat completion responses."""
@@ -1071,6 +1073,7 @@ async def test_openai_responses_client_streaming() -> None:
assert output.weather is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_streaming_tools() -> None:
"""Test OpenAI chat completion responses."""
@@ -1118,6 +1121,7 @@ async def test_openai_responses_client_streaming_tools() -> None:
assert "sunny" in output.weather.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_web_search() -> None:
openai_responses_client = OpenAIResponsesClient()
@@ -1157,6 +1161,7 @@ async def test_openai_responses_client_web_search() -> None:
assert response.text is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_web_search_streaming() -> None:
openai_responses_client = OpenAIResponsesClient()
@@ -1210,6 +1215,7 @@ async def test_openai_responses_client_web_search_streaming() -> None:
assert full_message is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_file_search() -> None:
openai_responses_client = OpenAIResponsesClient()
@@ -1234,6 +1240,7 @@ async def test_openai_responses_client_file_search() -> None:
assert "75" in response.text
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_streaming_file_search() -> None:
openai_responses_client = OpenAIResponsesClient()
@@ -1268,6 +1275,7 @@ async def test_openai_responses_client_streaming_file_search() -> None:
assert "75" in full_message
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_basic_run():
"""Test OpenAI Responses Client agent basic run functionality with OpenAIResponsesClient."""
@@ -1284,6 +1292,7 @@ async def test_openai_responses_client_agent_basic_run():
assert "hello world" in response.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_basic_run_streaming():
"""Test OpenAI Responses Client agent basic streaming functionality with OpenAIResponsesClient."""
@@ -1301,6 +1310,7 @@ async def test_openai_responses_client_agent_basic_run_streaming():
assert "streaming response test" in full_text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_thread_persistence():
"""Test OpenAI Responses Client agent thread persistence across runs with OpenAIResponsesClient."""
@@ -1324,6 +1334,7 @@ async def test_openai_responses_client_agent_thread_persistence():
assert second_response.text is not None
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_thread_storage_with_store_true():
"""Test OpenAI Responses Client agent with store=True to verify service_thread_id is returned."""
@@ -1355,6 +1366,7 @@ async def test_openai_responses_client_agent_thread_storage_with_store_true():
assert len(thread.service_thread_id) > 0
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_existing_thread():
"""Test OpenAI Responses Client agent with existing thread to continue conversations across agent instances."""
@@ -1389,6 +1401,7 @@ async def test_openai_responses_client_agent_existing_thread():
assert "photography" in second_response.text.lower()
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_hosted_code_interpreter_tool():
"""Test OpenAI Responses Client agent with HostedCodeInterpreterTool through OpenAIResponsesClient."""
@@ -1410,6 +1423,7 @@ async def test_openai_responses_client_agent_hosted_code_interpreter_tool():
assert contains_relevant_content or len(response.text.strip()) > 10
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_raw_image_generation_tool():
"""Test OpenAI Responses Client agent with raw image_generation tool through OpenAIResponsesClient."""
@@ -1446,6 +1460,7 @@ async def test_openai_responses_client_agent_raw_image_generation_tool():
assert image_content_found, "Expected to find image content in response"
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_level_tool_persistence():
"""Test that agent-level tools persist across multiple runs with OpenAI Responses Client."""
@@ -1472,6 +1487,7 @@ async def test_openai_responses_client_agent_level_tool_persistence():
assert any(term in second_response.text.lower() for term in ["miami", "sunny", "72"])
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_run_level_tool_isolation():
"""Test that run-level tools are isolated to specific runs and don't persist with OpenAI Responses Client."""
@@ -1511,6 +1527,7 @@ async def test_openai_responses_client_run_level_tool_isolation():
assert call_count == 1
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_chat_options_run_level() -> None:
"""Integration test for comprehensive ChatOptions parameter coverage with OpenAI Response Agent."""
@@ -1534,6 +1551,7 @@ async def test_openai_responses_client_agent_chat_options_run_level() -> None:
assert len(response.text) > 0
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_chat_options_agent_level() -> None:
"""Integration test for comprehensive ChatOptions parameter coverage with OpenAI Response Agent."""
@@ -1557,6 +1575,7 @@ async def test_openai_responses_client_agent_chat_options_agent_level() -> None:
assert len(response.text) > 0
@pytest.mark.flaky
@skip_if_openai_integration_tests_disabled
async def test_openai_responses_client_agent_hosted_mcp_tool() -> None:
"""Integration test for HostedMCPTool with OpenAI Response Agent using Microsoft Learn MCP."""
@@ -1587,7 +1606,7 @@ async def test_openai_responses_client_agent_hosted_mcp_tool() -> None:
def test_service_response_exception_includes_original_error_details() -> None:
"""Test that ServiceResponseException messages include original error details in the new format."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
messages = [ChatMessage(role="user", text="test message")]
mock_response = MagicMock()
@@ -1612,7 +1631,7 @@ def test_service_response_exception_includes_original_error_details() -> None:
def test_get_streaming_response_with_response_format() -> None:
"""Test get_streaming_response with response_format."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
messages = [ChatMessage(role="user", text="Test streaming with format")]
# It will fail due to invalid API key, but exercises the code path
@@ -1627,7 +1646,7 @@ def test_get_streaming_response_with_response_format() -> None:
def test_openai_content_parser_image_content() -> None:
"""Test _openai_content_parser with image content variations."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test image content with detail parameter and file_id
image_content_with_detail = UriContent(
@@ -1651,7 +1670,7 @@ def test_openai_content_parser_image_content() -> None:
def test_openai_content_parser_audio_content() -> None:
"""Test _openai_content_parser with audio content variations."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test WAV audio content
wav_content = UriContent(uri="data:audio/wav;base64,abc123", media_type="audio/wav")
@@ -1669,7 +1688,7 @@ def test_openai_content_parser_audio_content() -> None:
def test_openai_content_parser_unsupported_content() -> None:
"""Test _openai_content_parser with unsupported content types."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test unsupported audio format
unsupported_audio = UriContent(uri="data:audio/ogg;base64,ghi789", media_type="audio/ogg")
@@ -1684,7 +1703,7 @@ def test_openai_content_parser_unsupported_content() -> None:
def test_create_streaming_response_content_code_interpreter() -> None:
"""Test _create_streaming_response_content with code_interpreter_call."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1708,7 +1727,7 @@ def test_create_streaming_response_content_code_interpreter() -> None:
def test_create_streaming_response_content_reasoning() -> None:
"""Test _create_streaming_response_content with reasoning content."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1732,7 +1751,7 @@ def test_create_streaming_response_content_reasoning() -> None:
def test_openai_content_parser_text_reasoning_comprehensive() -> None:
"""Test _openai_content_parser with TextReasoningContent all additional properties."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test TextReasoningContent with all additional properties
comprehensive_reasoning = TextReasoningContent(
@@ -1754,7 +1773,7 @@ def test_openai_content_parser_text_reasoning_comprehensive() -> None:
def test_streaming_reasoning_text_delta_event() -> None:
"""Test reasoning text delta event creates TextReasoningContent."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1779,7 +1798,7 @@ def test_streaming_reasoning_text_delta_event() -> None:
def test_streaming_reasoning_text_done_event() -> None:
"""Test reasoning text done event creates TextReasoningContent with complete text."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1805,7 +1824,7 @@ def test_streaming_reasoning_text_done_event() -> None:
def test_streaming_reasoning_summary_text_delta_event() -> None:
"""Test reasoning summary text delta event creates TextReasoningContent."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1830,7 +1849,7 @@ def test_streaming_reasoning_summary_text_delta_event() -> None:
def test_streaming_reasoning_summary_text_done_event() -> None:
"""Test reasoning summary text done event creates TextReasoningContent with complete text."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1856,7 +1875,7 @@ def test_streaming_reasoning_summary_text_done_event() -> None:
def test_streaming_reasoning_events_preserve_metadata() -> None:
"""Test that reasoning events preserve metadata like regular text events."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1894,7 +1913,7 @@ def test_streaming_reasoning_events_preserve_metadata() -> None:
def test_create_response_content_image_generation_raw_base64():
"""Test image generation response parsing with raw base64 string."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with raw base64 image data (PNG signature)
mock_response = MagicMock()
@@ -1928,7 +1947,7 @@ def test_create_response_content_image_generation_raw_base64():
def test_create_response_content_image_generation_existing_data_uri():
"""Test image generation response parsing with existing data URI."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with existing data URI
mock_response = MagicMock()
@@ -1961,7 +1980,7 @@ def test_create_response_content_image_generation_existing_data_uri():
def test_create_response_content_image_generation_format_detection():
"""Test different image format detection from base64 data."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test JPEG detection
jpeg_signature = b"\xff\xd8\xff"
@@ -2014,7 +2033,7 @@ def test_create_response_content_image_generation_format_detection():
def test_create_response_content_image_generation_fallback():
"""Test image generation with invalid base64 falls back to PNG."""
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with invalid base64
mock_response = MagicMock()
@@ -2046,7 +2065,7 @@ def test_create_response_content_image_generation_fallback():
def test_prepare_options_store_parameter_handling() -> None:
client = OpenAIResponsesClient(ai_model_id="test-model", api_key="test-key")
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
messages = [ChatMessage(role="user", text="Test message")]
test_conversation_id = "test-conversation-123"