Python: cleanup and refactoring of chat clients (#2937)

* refactoring and unifying naming schemes of internal methods of chat clients

* set tool_choice to auto

* fix for mypy

* added note on naming and fix #2951

* fix responses

* fixes in azure ai agents client
This commit is contained in:
Eduard van Valkenburg
2025-12-18 13:02:23 +01:00
committed by GitHub
Unverified
parent a71f768331
commit e5c11d38d6
26 changed files with 1128 additions and 1068 deletions
@@ -193,7 +193,7 @@ async def test_cmc(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=False,
messages=azure_chat_client._prepare_chat_history_for_request(chat_history), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(chat_history), # type: ignore
)
@@ -216,7 +216,7 @@ async def test_cmc_with_logit_bias(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_client._prepare_chat_history_for_request(chat_history), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(chat_history), # type: ignore
stream=False,
logit_bias=token_bias,
)
@@ -241,7 +241,7 @@ async def test_cmc_with_stop(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_client._prepare_chat_history_for_request(chat_history), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(chat_history), # type: ignore
stream=False,
stop=stop,
)
@@ -311,7 +311,7 @@ async def test_azure_on_your_data(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_client._prepare_chat_history_for_request(messages_out), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(messages_out), # type: ignore
stream=False,
extra_body=expected_data_settings,
)
@@ -381,7 +381,7 @@ async def test_azure_on_your_data_string(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_client._prepare_chat_history_for_request(messages_out), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(messages_out), # type: ignore
stream=False,
extra_body=expected_data_settings,
)
@@ -438,7 +438,7 @@ async def test_azure_on_your_data_fail(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_client._prepare_chat_history_for_request(messages_out), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(messages_out), # type: ignore
stream=False,
extra_body=expected_data_settings,
)
@@ -584,7 +584,7 @@ async def test_get_streaming(
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=True,
messages=azure_chat_client._prepare_chat_history_for_request(chat_history), # type: ignore
messages=azure_chat_client._prepare_messages_for_openai(chat_history), # type: ignore
# NOTE: The `stream_options={"include_usage": True}` is explicitly enforced in
# `OpenAIChatCompletionBase._inner_get_streaming_response`.
# To ensure consistency, we align the arguments here accordingly.
+26 -26
View File
@@ -24,14 +24,14 @@ from agent_framework import (
)
from agent_framework._mcp import (
MCPTool,
_ai_content_to_mcp_types,
_chat_message_to_mcp_types,
_get_input_model_from_mcp_prompt,
_get_input_model_from_mcp_tool,
_mcp_call_tool_result_to_ai_contents,
_mcp_prompt_message_to_chat_message,
_mcp_type_to_ai_content,
_normalize_mcp_name,
_parse_content_from_mcp,
_parse_contents_from_mcp_tool_result,
_parse_message_from_mcp,
_prepare_content_for_mcp,
_prepare_message_for_mcp,
)
from agent_framework.exceptions import ToolException, ToolExecutionException
@@ -60,7 +60,7 @@ def test_normalize_mcp_name():
def test_mcp_prompt_message_to_ai_content():
"""Test conversion from MCP prompt message to AI content."""
mcp_message = types.PromptMessage(role="user", content=types.TextContent(type="text", text="Hello, world!"))
ai_content = _mcp_prompt_message_to_chat_message(mcp_message)
ai_content = _parse_message_from_mcp(mcp_message)
assert isinstance(ai_content, ChatMessage)
assert ai_content.role.value == "user"
@@ -70,7 +70,7 @@ def test_mcp_prompt_message_to_ai_content():
assert ai_content.raw_representation == mcp_message
def test_mcp_call_tool_result_to_ai_contents():
def test_parse_contents_from_mcp_tool_result():
"""Test conversion from MCP tool result to AI contents."""
mcp_result = types.CallToolResult(
content=[
@@ -79,7 +79,7 @@ def test_mcp_call_tool_result_to_ai_contents():
types.ImageContent(type="image", data=b"abc", mimeType="image/webp"),
]
)
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
assert len(ai_contents) == 3
assert isinstance(ai_contents[0], TextContent)
@@ -100,7 +100,7 @@ def test_mcp_call_tool_result_with_meta_error():
_meta={"isError": True, "errorCode": "TOOL_ERROR", "errorMessage": "Tool execution failed"},
)
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
assert len(ai_contents) == 1
assert isinstance(ai_contents[0], TextContent)
@@ -131,7 +131,7 @@ def test_mcp_call_tool_result_with_meta_arbitrary_data():
},
)
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
assert len(ai_contents) == 1
assert isinstance(ai_contents[0], TextContent)
@@ -153,7 +153,7 @@ def test_mcp_call_tool_result_with_meta_merging_existing_properties():
text_content = types.TextContent(type="text", text="Test content")
mcp_result = types.CallToolResult(content=[text_content], _meta={"newField": "newValue", "isError": False})
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
assert len(ai_contents) == 1
content = ai_contents[0]
@@ -169,7 +169,7 @@ def test_mcp_call_tool_result_with_meta_none():
mcp_result = types.CallToolResult(content=[types.TextContent(type="text", text="No meta test")])
# No _meta field set
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
assert len(ai_contents) == 1
assert isinstance(ai_contents[0], TextContent)
@@ -191,7 +191,7 @@ def test_mcp_call_tool_result_regression_successful_workflow():
]
)
ai_contents = _mcp_call_tool_result_to_ai_contents(mcp_result)
ai_contents = _parse_contents_from_mcp_tool_result(mcp_result)
# Verify basic conversion still works correctly
assert len(ai_contents) == 2
@@ -213,7 +213,7 @@ def test_mcp_call_tool_result_regression_successful_workflow():
def test_mcp_content_types_to_ai_content_text():
"""Test conversion of MCP text content to AI content."""
mcp_content = types.TextContent(type="text", text="Sample text")
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, TextContent)
assert ai_content.text == "Sample text"
@@ -224,7 +224,7 @@ def test_mcp_content_types_to_ai_content_image():
"""Test conversion of MCP image content to AI content."""
mcp_content = types.ImageContent(type="image", data="abc", mimeType="image/jpeg")
mcp_content = types.ImageContent(type="image", data=b"abc", mimeType="image/jpeg")
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, DataContent)
assert ai_content.uri == "data:image/jpeg;base64,abc"
@@ -235,7 +235,7 @@ def test_mcp_content_types_to_ai_content_image():
def test_mcp_content_types_to_ai_content_audio():
"""Test conversion of MCP audio content to AI content."""
mcp_content = types.AudioContent(type="audio", data="def", mimeType="audio/wav")
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, DataContent)
assert ai_content.uri == "data:audio/wav;base64,def"
@@ -251,7 +251,7 @@ def test_mcp_content_types_to_ai_content_resource_link():
name="test_resource",
mimeType="application/json",
)
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, UriContent)
assert ai_content.uri == "https://example.com/resource"
@@ -267,7 +267,7 @@ def test_mcp_content_types_to_ai_content_embedded_resource_text():
text="Embedded text content",
)
mcp_content = types.EmbeddedResource(type="resource", resource=text_resource)
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, TextContent)
assert ai_content.text == "Embedded text content"
@@ -283,7 +283,7 @@ def test_mcp_content_types_to_ai_content_embedded_resource_blob():
blob="data:application/octet-stream;base64,dGVzdCBkYXRh",
)
mcp_content = types.EmbeddedResource(type="resource", resource=blob_resource)
ai_content = _mcp_type_to_ai_content(mcp_content)[0]
ai_content = _parse_content_from_mcp(mcp_content)[0]
assert isinstance(ai_content, DataContent)
assert ai_content.uri == "data:application/octet-stream;base64,dGVzdCBkYXRh"
@@ -294,7 +294,7 @@ def test_mcp_content_types_to_ai_content_embedded_resource_blob():
def test_ai_content_to_mcp_content_types_text():
"""Test conversion of AI text content to MCP content."""
ai_content = TextContent(text="Sample text")
mcp_content = _ai_content_to_mcp_types(ai_content)
mcp_content = _prepare_content_for_mcp(ai_content)
assert isinstance(mcp_content, types.TextContent)
assert mcp_content.type == "text"
@@ -304,7 +304,7 @@ def test_ai_content_to_mcp_content_types_text():
def test_ai_content_to_mcp_content_types_data_image():
"""Test conversion of AI data content to MCP content."""
ai_content = DataContent(uri="data:image/png;base64,xyz", media_type="image/png")
mcp_content = _ai_content_to_mcp_types(ai_content)
mcp_content = _prepare_content_for_mcp(ai_content)
assert isinstance(mcp_content, types.ImageContent)
assert mcp_content.type == "image"
@@ -315,7 +315,7 @@ def test_ai_content_to_mcp_content_types_data_image():
def test_ai_content_to_mcp_content_types_data_audio():
"""Test conversion of AI data content to MCP content."""
ai_content = DataContent(uri="data:audio/mpeg;base64,xyz", media_type="audio/mpeg")
mcp_content = _ai_content_to_mcp_types(ai_content)
mcp_content = _prepare_content_for_mcp(ai_content)
assert isinstance(mcp_content, types.AudioContent)
assert mcp_content.type == "audio"
@@ -329,7 +329,7 @@ def test_ai_content_to_mcp_content_types_data_binary():
uri="data:application/octet-stream;base64,xyz",
media_type="application/octet-stream",
)
mcp_content = _ai_content_to_mcp_types(ai_content)
mcp_content = _prepare_content_for_mcp(ai_content)
assert isinstance(mcp_content, types.EmbeddedResource)
assert mcp_content.type == "resource"
@@ -340,7 +340,7 @@ def test_ai_content_to_mcp_content_types_data_binary():
def test_ai_content_to_mcp_content_types_uri():
"""Test conversion of AI URI content to MCP content."""
ai_content = UriContent(uri="https://example.com/resource", media_type="application/json")
mcp_content = _ai_content_to_mcp_types(ai_content)
mcp_content = _prepare_content_for_mcp(ai_content)
assert isinstance(mcp_content, types.ResourceLink)
assert mcp_content.type == "resource_link"
@@ -348,7 +348,7 @@ def test_ai_content_to_mcp_content_types_uri():
assert mcp_content.mimeType == "application/json"
def test_chat_message_to_mcp_types():
def test_prepare_message_for_mcp():
message = ChatMessage(
role="user",
contents=[
@@ -356,7 +356,7 @@ def test_chat_message_to_mcp_types():
DataContent(uri="data:image/png;base64,xyz", media_type="image/png"),
],
)
mcp_contents = _chat_message_to_mcp_types(message)
mcp_contents = _prepare_message_for_mcp(message)
assert len(mcp_contents) == 2
assert isinstance(mcp_contents[0], types.TextContent)
assert isinstance(mcp_contents[1], types.ImageContent)
@@ -463,9 +463,9 @@ async def test_openai_assistants_client_process_stream_events_requires_action(mo
"""Test _process_stream_events with thread.run.requires_action event."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
# Mock the _create_function_call_contents method to return test content
# Mock the _parse_function_calls_from_assistants method to return test content
test_function_content = FunctionCallContent(call_id="call-123", name="test_func", arguments={"arg": "value"})
chat_client._create_function_call_contents = MagicMock(return_value=[test_function_content]) # type: ignore
chat_client._parse_function_calls_from_assistants = MagicMock(return_value=[test_function_content]) # type: ignore
# Create a mock Run object
mock_run = MagicMock(spec=Run)
@@ -498,8 +498,8 @@ async def test_openai_assistants_client_process_stream_events_requires_action(mo
assert update.contents[0] == test_function_content
assert update.raw_representation == mock_run
# Verify _create_function_call_contents was called correctly
chat_client._create_function_call_contents.assert_called_once_with(mock_run, None) # type: ignore
# Verify _parse_function_calls_from_assistants was called correctly
chat_client._parse_function_calls_from_assistants.assert_called_once_with(mock_run, None) # type: ignore
async def test_openai_assistants_client_process_stream_events_run_step_created(mock_async_openai: MagicMock) -> None:
@@ -585,8 +585,8 @@ async def test_openai_assistants_client_process_stream_events_run_completed_with
assert update.raw_representation == mock_run
def test_openai_assistants_client_create_function_call_contents_basic(mock_async_openai: MagicMock) -> None:
"""Test _create_function_call_contents with a simple function call."""
def test_openai_assistants_client_parse_function_calls_from_assistants_basic(mock_async_openai: MagicMock) -> None:
"""Test _parse_function_calls_from_assistants with a simple function call."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
@@ -605,7 +605,7 @@ def test_openai_assistants_client_create_function_call_contents_basic(mock_async
# Call the method
response_id = "response_456"
contents = chat_client._create_function_call_contents(mock_run, response_id) # type: ignore
contents = chat_client._parse_function_calls_from_assistants(mock_run, response_id) # type: ignore
# Test that one function call content was created
assert len(contents) == 1
@@ -825,24 +825,24 @@ def test_openai_assistants_client_prepare_options_with_image_content(mock_async_
assert message["content"][0]["image_url"]["url"] == "https://example.com/image.jpg"
def test_openai_assistants_client_convert_function_results_to_tool_output_empty(mock_async_openai: MagicMock) -> None:
"""Test _convert_function_results_to_tool_output with empty list."""
def test_openai_assistants_client_prepare_tool_outputs_for_assistants_empty(mock_async_openai: MagicMock) -> None:
"""Test _prepare_tool_outputs_for_assistants with empty list."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
run_id, tool_outputs = chat_client._convert_function_results_to_tool_output([]) # type: ignore
run_id, tool_outputs = chat_client._prepare_tool_outputs_for_assistants([]) # type: ignore
assert run_id is None
assert tool_outputs is None
def test_openai_assistants_client_convert_function_results_to_tool_output_valid(mock_async_openai: MagicMock) -> None:
"""Test _convert_function_results_to_tool_output with valid function results."""
def test_openai_assistants_client_prepare_tool_outputs_for_assistants_valid(mock_async_openai: MagicMock) -> None:
"""Test _prepare_tool_outputs_for_assistants with valid function results."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
call_id = json.dumps(["run-123", "call-456"])
function_result = FunctionResultContent(call_id=call_id, result="Function executed successfully")
run_id, tool_outputs = chat_client._convert_function_results_to_tool_output([function_result]) # type: ignore
run_id, tool_outputs = chat_client._prepare_tool_outputs_for_assistants([function_result]) # type: ignore
assert run_id == "run-123"
assert tool_outputs is not None
@@ -851,10 +851,10 @@ def test_openai_assistants_client_convert_function_results_to_tool_output_valid(
assert tool_outputs[0].get("output") == "Function executed successfully"
def test_openai_assistants_client_convert_function_results_to_tool_output_mismatched_run_ids(
def test_openai_assistants_client_prepare_tool_outputs_for_assistants_mismatched_run_ids(
mock_async_openai: MagicMock,
) -> None:
"""Test _convert_function_results_to_tool_output with mismatched run IDs."""
"""Test _prepare_tool_outputs_for_assistants with mismatched run IDs."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
# Create function results with different run IDs
@@ -863,7 +863,7 @@ def test_openai_assistants_client_convert_function_results_to_tool_output_mismat
function_result1 = FunctionResultContent(call_id=call_id1, result="Result 1")
function_result2 = FunctionResultContent(call_id=call_id2, result="Result 2")
run_id, tool_outputs = chat_client._convert_function_results_to_tool_output([function_result1, function_result2]) # type: ignore
run_id, tool_outputs = chat_client._prepare_tool_outputs_for_assistants([function_result1, function_result2]) # type: ignore
# Should only process the first one since run IDs don't match
assert run_id == "run-123"
@@ -182,12 +182,12 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
unsupported_tool.__class__.__name__ = "UnsupportedAITool"
# This should ignore the unsupported ToolProtocol and return empty list
result = client._chat_to_tool_spec([unsupported_tool]) # type: ignore
result = client._prepare_tools_for_openai([unsupported_tool]) # type: ignore
assert result == []
# Also test with a non-ToolProtocol that should be converted to dict
dict_tool = {"type": "function", "name": "test"}
result = client._chat_to_tool_spec([dict_tool]) # type: ignore
result = client._prepare_tools_for_openai([dict_tool]) # type: ignore
assert result == [dict_tool]
@@ -637,7 +637,7 @@ def test_chat_response_content_order_text_before_tool_calls(openai_unit_test_env
)
client = OpenAIChatClient()
response = client._create_chat_response(mock_response, ChatOptions())
response = client._parse_response_from_openai(mock_response, ChatOptions())
# Verify we have both text and tool call content
assert len(response.messages) == 1
@@ -658,7 +658,7 @@ def test_function_result_falsy_values_handling(openai_unit_test_env: dict[str, s
# Test with empty list (falsy but not None)
message_with_empty_list = ChatMessage(role="tool", contents=[FunctionResultContent(call_id="call-123", result=[])])
openai_messages = client._openai_chat_message_parser(message_with_empty_list)
openai_messages = client._prepare_message_for_openai(message_with_empty_list)
assert len(openai_messages) == 1
assert openai_messages[0]["content"] == "[]" # Empty list should be JSON serialized
@@ -667,14 +667,14 @@ def test_function_result_falsy_values_handling(openai_unit_test_env: dict[str, s
role="tool", contents=[FunctionResultContent(call_id="call-456", result="")]
)
openai_messages = client._openai_chat_message_parser(message_with_empty_string)
openai_messages = client._prepare_message_for_openai(message_with_empty_string)
assert len(openai_messages) == 1
assert openai_messages[0]["content"] == "" # Empty string should be preserved
# Test with False (falsy but not None)
message_with_false = ChatMessage(role="tool", contents=[FunctionResultContent(call_id="call-789", result=False)])
openai_messages = client._openai_chat_message_parser(message_with_false)
openai_messages = client._prepare_message_for_openai(message_with_false)
assert len(openai_messages) == 1
assert openai_messages[0]["content"] == "false" # False should be JSON serialized
@@ -695,7 +695,7 @@ def test_function_result_exception_handling(openai_unit_test_env: dict[str, str]
],
)
openai_messages = client._openai_chat_message_parser(message_with_exception)
openai_messages = client._prepare_message_for_openai(message_with_exception)
assert len(openai_messages) == 1
assert openai_messages[0]["content"] == "Error: Function failed."
assert openai_messages[0]["tool_call_id"] == "call-123"
@@ -708,8 +708,8 @@ def test_prepare_function_call_results_string_passthrough():
assert isinstance(result, str)
def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str, str]) -> None:
"""Test _openai_content_parser converts DataContent with image media type to OpenAI format."""
def test_prepare_content_for_openai_data_content_image(openai_unit_test_env: dict[str, str]) -> None:
"""Test _prepare_content_for_openai converts DataContent with image media type to OpenAI format."""
client = OpenAIChatClient()
# Test DataContent with image media type
@@ -718,7 +718,7 @@ def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str
media_type="image/png",
)
result = client._openai_content_parser(image_data_content) # type: ignore
result = client._prepare_content_for_openai(image_data_content) # type: ignore
# Should convert to OpenAI image_url format
assert result["type"] == "image_url"
@@ -727,7 +727,7 @@ def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str
# Test DataContent with non-image media type should use default model_dump
text_data_content = DataContent(uri="data:text/plain;base64,SGVsbG8gV29ybGQ=", media_type="text/plain")
result = client._openai_content_parser(text_data_content) # type: ignore
result = client._prepare_content_for_openai(text_data_content) # type: ignore
# Should use default model_dump format
assert result["type"] == "data"
@@ -740,7 +740,7 @@ def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str
media_type="audio/wav",
)
result = client._openai_content_parser(audio_data_content) # type: ignore
result = client._prepare_content_for_openai(audio_data_content) # type: ignore
# Should convert to OpenAI input_audio format
assert result["type"] == "input_audio"
@@ -751,7 +751,7 @@ def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str
# Test DataContent with MP3 audio
mp3_data_content = DataContent(uri="data:audio/mp3;base64,//uQAAAAWGluZwAAAA8AAAACAAACcQ==", media_type="audio/mp3")
result = client._openai_content_parser(mp3_data_content) # type: ignore
result = client._prepare_content_for_openai(mp3_data_content) # type: ignore
# Should convert to OpenAI input_audio format with mp3
assert result["type"] == "input_audio"
@@ -760,8 +760,8 @@ def test_openai_content_parser_data_content_image(openai_unit_test_env: dict[str
assert result["input_audio"]["format"] == "mp3"
def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[str, str]) -> None:
"""Test _openai_content_parser converts document files (PDF, DOCX, etc.) to OpenAI file format."""
def test_prepare_content_for_openai_document_file_mapping(openai_unit_test_env: dict[str, str]) -> None:
"""Test _prepare_content_for_openai converts document files (PDF, DOCX, etc.) to OpenAI file format."""
client = OpenAIChatClient()
# Test PDF without filename - should omit filename in OpenAI payload
@@ -770,7 +770,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
media_type="application/pdf",
)
result = client._openai_content_parser(pdf_data_content) # type: ignore
result = client._prepare_content_for_openai(pdf_data_content) # type: ignore
# Should convert to OpenAI file format without filename
assert result["type"] == "file"
@@ -787,7 +787,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
additional_properties={"filename": "report.pdf"},
)
result = client._openai_content_parser(pdf_with_filename) # type: ignore
result = client._prepare_content_for_openai(pdf_with_filename) # type: ignore
# Should use custom filename
assert result["type"] == "file"
@@ -820,7 +820,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
media_type=case["media_type"],
)
result = client._openai_content_parser(doc_content) # type: ignore
result = client._prepare_content_for_openai(doc_content) # type: ignore
# All application/* types should now be mapped to file format
assert result["type"] == "file"
@@ -834,7 +834,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
additional_properties={"filename": case["filename"]},
)
result = client._openai_content_parser(doc_with_filename) # type: ignore
result = client._prepare_content_for_openai(doc_with_filename) # type: ignore
# Should now use file format with filename
assert result["type"] == "file"
@@ -848,7 +848,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
additional_properties={},
)
result = client._openai_content_parser(pdf_empty_props) # type: ignore
result = client._prepare_content_for_openai(pdf_empty_props) # type: ignore
assert result["type"] == "file"
assert "filename" not in result["file"]
@@ -860,7 +860,7 @@ def test_openai_content_parser_document_file_mapping(openai_unit_test_env: dict[
additional_properties={"filename": None},
)
result = client._openai_content_parser(pdf_none_filename) # type: ignore
result = client._prepare_content_for_openai(pdf_none_filename) # type: ignore
assert result["type"] == "file"
assert "filename" not in result["file"] # None filename should be omitted
@@ -76,7 +76,7 @@ async def test_cmc(
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=False,
messages=openai_chat_completion._prepare_chat_history_for_request(chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(chat_history), # type: ignore
)
@@ -97,7 +97,7 @@ async def test_cmc_chat_options(
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=False,
messages=openai_chat_completion._prepare_chat_history_for_request(chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(chat_history), # type: ignore
)
@@ -120,7 +120,7 @@ async def test_cmc_no_fcc_in_response(
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=False,
messages=openai_chat_completion._prepare_chat_history_for_request(orig_chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(orig_chat_history), # type: ignore
)
@@ -167,7 +167,7 @@ async def test_scmc_chat_options(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=True,
stream_options={"include_usage": True},
messages=openai_chat_completion._prepare_chat_history_for_request(chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(chat_history), # type: ignore
)
@@ -203,7 +203,7 @@ async def test_cmc_additional_properties(
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=False,
messages=openai_chat_completion._prepare_chat_history_for_request(chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(chat_history), # type: ignore
reasoning_effort="low",
)
@@ -246,7 +246,7 @@ async def test_get_streaming(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=True,
stream_options={"include_usage": True},
messages=openai_chat_completion._prepare_chat_history_for_request(orig_chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(orig_chat_history), # type: ignore
)
@@ -285,7 +285,7 @@ async def test_get_streaming_singular(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=True,
stream_options={"include_usage": True},
messages=openai_chat_completion._prepare_chat_history_for_request(orig_chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(orig_chat_history), # type: ignore
)
@@ -349,7 +349,7 @@ async def test_get_streaming_no_fcc_in_response(
model=openai_unit_test_env["OPENAI_CHAT_MODEL_ID"],
stream=True,
stream_options={"include_usage": True},
messages=openai_chat_completion._prepare_chat_history_for_request(orig_chat_history), # type: ignore
messages=openai_chat_completion._prepare_messages_for_openai(orig_chat_history), # type: ignore
)
@@ -399,7 +399,7 @@ def test_chat_response_created_at_uses_utc(openai_unit_test_env: dict[str, str])
)
client = OpenAIChatClient()
response = client._create_chat_response(mock_response, ChatOptions())
response = client._parse_response_from_openai(mock_response, ChatOptions())
# Verify that created_at is correctly formatted as UTC
assert response.created_at is not None
@@ -431,7 +431,7 @@ def test_chat_response_update_created_at_uses_utc(openai_unit_test_env: dict[str
)
client = OpenAIChatClient()
response_update = client._create_chat_response_update(mock_chunk)
response_update = client._parse_response_update_from_openai(mock_chunk)
# Verify that created_at is correctly formatted as UTC
assert response_update.created_at is not None
@@ -368,6 +368,7 @@ async def test_response_format_parse_path() -> None:
mock_parsed_response.output_parsed = None
mock_parsed_response.usage = None
mock_parsed_response.finish_reason = None
mock_parsed_response.conversation = None # No conversation object
with patch.object(client.client.responses, "parse", return_value=mock_parsed_response):
response = await client.get_response(
@@ -454,7 +455,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."""
"""Test _parse_response_from_openai with different annotation types."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with annotated text content
@@ -485,7 +486,7 @@ def test_response_content_creation_with_annotations() -> None:
mock_response.output = [mock_message_item]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert len(response.messages[0].contents) >= 1
assert isinstance(response.messages[0].contents[0], TextContent)
@@ -494,7 +495,7 @@ def test_response_content_creation_with_annotations() -> None:
def test_response_content_creation_with_refusal() -> None:
"""Test _create_response_content with refusal content."""
"""Test _parse_response_from_openai with refusal content."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with refusal content
@@ -516,7 +517,7 @@ def test_response_content_creation_with_refusal() -> None:
mock_response.output = [mock_message_item]
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert len(response.messages[0].contents) == 1
assert isinstance(response.messages[0].contents[0], TextContent)
@@ -524,7 +525,7 @@ def test_response_content_creation_with_refusal() -> None:
def test_response_content_creation_with_reasoning() -> None:
"""Test _create_response_content with reasoning content."""
"""Test _parse_response_from_openai with reasoning content."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with reasoning content
@@ -546,7 +547,7 @@ def test_response_content_creation_with_reasoning() -> None:
mock_response.output = [mock_reasoning_item]
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert len(response.messages[0].contents) == 2
assert isinstance(response.messages[0].contents[0], TextReasoningContent)
@@ -554,7 +555,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."""
"""Test _parse_response_from_openai with code interpreter outputs."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -582,7 +583,7 @@ def test_response_content_creation_with_code_interpreter() -> None:
mock_response.output = [mock_code_interpreter_item]
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert len(response.messages[0].contents) == 2
assert isinstance(response.messages[0].contents[0], TextContent)
@@ -593,7 +594,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."""
"""Test _parse_response_from_openai with function call content."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Create a mock response with function call
@@ -614,7 +615,7 @@ def test_response_content_creation_with_function_call() -> None:
mock_response.output = [mock_function_call_item]
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert len(response.messages[0].contents) == 1
assert isinstance(response.messages[0].contents[0], FunctionCallContent)
@@ -624,7 +625,7 @@ def test_response_content_creation_with_function_call() -> None:
assert function_call.arguments == '{"location": "Seattle"}'
def test_tools_to_response_tools_with_hosted_mcp() -> None:
def test_prepare_tools_for_openai_with_hosted_mcp() -> None:
"""Test that HostedMCPTool is converted to the correct response tool dict."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -638,7 +639,7 @@ def test_tools_to_response_tools_with_hosted_mcp() -> None:
additional_properties={"custom": "value"},
)
resp_tools = client._tools_to_response_tools([tool])
resp_tools = client._prepare_tools_for_openai([tool])
assert isinstance(resp_tools, list)
assert len(resp_tools) == 1
mcp = resp_tools[0]
@@ -654,7 +655,7 @@ def test_tools_to_response_tools_with_hosted_mcp() -> None:
assert "require_approval" in mcp
def test_create_response_content_with_mcp_approval_request() -> None:
def test_parse_response_from_openai_with_mcp_approval_request() -> None:
"""Test that a non-streaming mcp_approval_request is parsed into FunctionApprovalRequestContent."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -675,7 +676,7 @@ def test_create_response_content_with_mcp_approval_request() -> None:
mock_response.output = [mock_item]
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
assert isinstance(response.messages[0].contents[0], FunctionApprovalRequestContent)
req = response.messages[0].contents[0]
@@ -716,7 +717,7 @@ def test_responses_client_created_at_uses_utc(openai_unit_test_env: dict[str, st
mock_response.output = [mock_message_item]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
# Verify that created_at is correctly formatted as UTC
assert response.created_at is not None
@@ -730,7 +731,7 @@ def test_responses_client_created_at_uses_utc(openai_unit_test_env: dict[str, st
)
def test_tools_to_response_tools_with_raw_image_generation() -> None:
def test_prepare_tools_for_openai_with_raw_image_generation() -> None:
"""Test that raw image_generation tool dict is handled correctly with parameter mapping."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -744,7 +745,7 @@ def test_tools_to_response_tools_with_raw_image_generation() -> None:
"background": "transparent",
}
resp_tools = client._tools_to_response_tools([tool])
resp_tools = client._prepare_tools_for_openai([tool])
assert isinstance(resp_tools, list)
assert len(resp_tools) == 1
@@ -759,7 +760,7 @@ def test_tools_to_response_tools_with_raw_image_generation() -> None:
assert image_tool["output_compression"] == 75
def test_tools_to_response_tools_with_raw_image_generation_openai_responses_params() -> None:
def test_prepare_tools_for_openai_with_raw_image_generation_openai_responses_params() -> None:
"""Test raw image_generation tool with OpenAI-specific parameters."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -773,7 +774,7 @@ def test_tools_to_response_tools_with_raw_image_generation_openai_responses_para
"partial_images": 2, # Should be integer 0-3
}
resp_tools = client._tools_to_response_tools([tool])
resp_tools = client._prepare_tools_for_openai([tool])
assert isinstance(resp_tools, list)
assert len(resp_tools) == 1
@@ -791,14 +792,14 @@ def test_tools_to_response_tools_with_raw_image_generation_openai_responses_para
assert tool_dict["partial_images"] == 2
def test_tools_to_response_tools_with_raw_image_generation_minimal() -> None:
def test_prepare_tools_for_openai_with_raw_image_generation_minimal() -> None:
"""Test raw image_generation tool with minimal configuration."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test with minimal parameters (just type)
tool = {"type": "image_generation"}
resp_tools = client._tools_to_response_tools([tool])
resp_tools = client._prepare_tools_for_openai([tool])
assert isinstance(resp_tools, list)
assert len(resp_tools) == 1
@@ -809,7 +810,7 @@ def test_tools_to_response_tools_with_raw_image_generation_minimal() -> None:
assert len(image_tool) == 1
def test_create_streaming_response_content_with_mcp_approval_request() -> None:
def test_parse_chunk_from_openai_with_mcp_approval_request() -> None:
"""Test that a streaming mcp_approval_request event is parsed into FunctionApprovalRequestContent."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
@@ -825,7 +826,7 @@ def test_create_streaming_response_content_with_mcp_approval_request() -> None:
mock_item.server_label = "My_MCP"
mock_event.item = mock_item
update = client._create_streaming_response_content(mock_event, chat_options, function_call_ids)
update = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)
assert any(isinstance(c, FunctionApprovalRequestContent) for c in update.contents)
fa = next(c for c in update.contents if isinstance(c, FunctionApprovalRequestContent))
assert fa.id == "approval-stream-1"
@@ -901,7 +902,7 @@ async 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."""
"""Test _parse_usage_from_openai without cached or reasoning tokens."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
@@ -911,7 +912,7 @@ def test_usage_details_basic() -> None:
mock_usage.input_tokens_details = None
mock_usage.output_tokens_details = None
details = client._usage_details_from_openai(mock_usage) # type: ignore
details = client._parse_usage_from_openai(mock_usage) # type: ignore
assert details is not None
assert details.input_token_count == 100
assert details.output_token_count == 50
@@ -919,7 +920,7 @@ def test_usage_details_basic() -> None:
def test_usage_details_with_cached_tokens() -> None:
"""Test _usage_details_from_openai with cached input tokens."""
"""Test _parse_usage_from_openai with cached input tokens."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
@@ -930,14 +931,14 @@ def test_usage_details_with_cached_tokens() -> None:
mock_usage.input_tokens_details.cached_tokens = 25
mock_usage.output_tokens_details = None
details = client._usage_details_from_openai(mock_usage) # type: ignore
details = client._parse_usage_from_openai(mock_usage) # type: ignore
assert details is not None
assert details.input_token_count == 200
assert details.additional_counts["openai.cached_input_tokens"] == 25
def test_usage_details_with_reasoning_tokens() -> None:
"""Test _usage_details_from_openai with reasoning tokens."""
"""Test _parse_usage_from_openai with reasoning tokens."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
mock_usage = MagicMock()
@@ -948,7 +949,7 @@ def test_usage_details_with_reasoning_tokens() -> None:
mock_usage.output_tokens_details = MagicMock()
mock_usage.output_tokens_details.reasoning_tokens = 30
details = client._usage_details_from_openai(mock_usage) # type: ignore
details = client._parse_usage_from_openai(mock_usage) # type: ignore
assert details is not None
assert details.output_token_count == 80
assert details.additional_counts["openai.reasoning_tokens"] == 30
@@ -975,7 +976,7 @@ def test_get_metadata_from_response() -> None:
def test_streaming_response_basic_structure() -> None:
"""Test that _create_streaming_response_content returns proper structure."""
"""Test that _parse_chunk_from_openai returns proper structure."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions(store=True)
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -983,7 +984,7 @@ def test_streaming_response_basic_structure() -> None:
# Test with a basic mock event to ensure the method returns proper structure
mock_event = MagicMock()
response = client._create_streaming_response_content(mock_event, chat_options, function_call_ids) # type: ignore
response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids) # type: ignore
# Should get a valid ChatResponseUpdate structure
assert isinstance(response, ChatResponseUpdate)
@@ -1008,7 +1009,7 @@ def test_streaming_annotation_added_with_file_path() -> None:
"index": 42,
}
response = client._create_streaming_response_content(mock_event, chat_options, function_call_ids)
response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)
assert len(response.contents) == 1
content = response.contents[0]
@@ -1035,7 +1036,7 @@ def test_streaming_annotation_added_with_file_citation() -> None:
"index": 15,
}
response = client._create_streaming_response_content(mock_event, chat_options, function_call_ids)
response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)
assert len(response.contents) == 1
content = response.contents[0]
@@ -1064,7 +1065,7 @@ def test_streaming_annotation_added_with_container_file_citation() -> None:
"end_index": 50,
}
response = client._create_streaming_response_content(mock_event, chat_options, function_call_ids)
response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)
assert len(response.contents) == 1
content = response.contents[0]
@@ -1091,7 +1092,7 @@ def test_streaming_annotation_added_with_unknown_type() -> None:
"url": "https://example.com",
}
response = client._create_streaming_response_content(mock_event, chat_options, function_call_ids)
response = client._parse_chunk_from_openai(mock_event, chat_options, function_call_ids)
# url_citation should not produce HostedFileContent
assert len(response.contents) == 0
@@ -1137,8 +1138,8 @@ def test_get_streaming_response_with_response_format() -> None:
asyncio.run(run_streaming())
def test_openai_content_parser_image_content() -> None:
"""Test _openai_content_parser with image content variations."""
def test_prepare_content_for_openai_image_content() -> None:
"""Test _prepare_content_for_openai with image content variations."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test image content with detail parameter and file_id
@@ -1147,7 +1148,7 @@ def test_openai_content_parser_image_content() -> None:
media_type="image/jpeg",
additional_properties={"detail": "high", "file_id": "file_123"},
)
result = client._openai_content_parser(Role.USER, image_content_with_detail, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, image_content_with_detail, {}) # type: ignore
assert result["type"] == "input_image"
assert result["image_url"] == "https://example.com/image.jpg"
assert result["detail"] == "high"
@@ -1155,47 +1156,47 @@ def test_openai_content_parser_image_content() -> None:
# Test image content without additional properties (defaults)
image_content_basic = UriContent(uri="https://example.com/basic.png", media_type="image/png")
result = client._openai_content_parser(Role.USER, image_content_basic, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, image_content_basic, {}) # type: ignore
assert result["type"] == "input_image"
assert result["detail"] == "auto"
assert result["file_id"] is None
def test_openai_content_parser_audio_content() -> None:
"""Test _openai_content_parser with audio content variations."""
def test_prepare_content_for_openai_audio_content() -> None:
"""Test _prepare_content_for_openai with audio content variations."""
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")
result = client._openai_content_parser(Role.USER, wav_content, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, wav_content, {}) # type: ignore
assert result["type"] == "input_audio"
assert result["input_audio"]["data"] == "data:audio/wav;base64,abc123"
assert result["input_audio"]["format"] == "wav"
# Test MP3 audio content
mp3_content = UriContent(uri="data:audio/mp3;base64,def456", media_type="audio/mp3")
result = client._openai_content_parser(Role.USER, mp3_content, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, mp3_content, {}) # type: ignore
assert result["type"] == "input_audio"
assert result["input_audio"]["format"] == "mp3"
def test_openai_content_parser_unsupported_content() -> None:
"""Test _openai_content_parser with unsupported content types."""
def test_prepare_content_for_openai_unsupported_content() -> None:
"""Test _prepare_content_for_openai with unsupported content types."""
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")
result = client._openai_content_parser(Role.USER, unsupported_audio, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, unsupported_audio, {}) # type: ignore
assert result == {}
# Test non-media content
text_uri_content = UriContent(uri="https://example.com/document.txt", media_type="text/plain")
result = client._openai_content_parser(Role.USER, text_uri_content, {}) # type: ignore
result = client._prepare_content_for_openai(Role.USER, text_uri_content, {}) # type: ignore
assert result == {}
def test_create_streaming_response_content_code_interpreter() -> None:
"""Test _create_streaming_response_content with code_interpreter_call."""
def test_parse_chunk_from_openai_code_interpreter() -> None:
"""Test _parse_chunk_from_openai with code_interpreter_call."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1211,15 +1212,15 @@ def test_create_streaming_response_content_code_interpreter() -> None:
mock_item_image.code = None
mock_event_image.item = mock_item_image
result = client._create_streaming_response_content(mock_event_image, chat_options, function_call_ids) # type: ignore
result = client._parse_chunk_from_openai(mock_event_image, chat_options, function_call_ids) # type: ignore
assert len(result.contents) == 1
assert isinstance(result.contents[0], UriContent)
assert result.contents[0].uri == "https://example.com/plot.png"
assert result.contents[0].media_type == "image"
def test_create_streaming_response_content_reasoning() -> None:
"""Test _create_streaming_response_content with reasoning content."""
def test_parse_chunk_from_openai_reasoning() -> None:
"""Test _parse_chunk_from_openai with reasoning content."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
chat_options = ChatOptions()
function_call_ids: dict[int, tuple[str, str]] = {}
@@ -1234,7 +1235,7 @@ def test_create_streaming_response_content_reasoning() -> None:
mock_item_reasoning.summary = ["Problem analysis summary"]
mock_event_reasoning.item = mock_item_reasoning
result = client._create_streaming_response_content(mock_event_reasoning, chat_options, function_call_ids) # type: ignore
result = client._parse_chunk_from_openai(mock_event_reasoning, chat_options, function_call_ids) # type: ignore
assert len(result.contents) == 1
assert isinstance(result.contents[0], TextReasoningContent)
assert result.contents[0].text == "Analyzing the problem step by step..."
@@ -1242,8 +1243,8 @@ def test_create_streaming_response_content_reasoning() -> None:
assert result.contents[0].additional_properties["summary"] == "Problem analysis summary"
def test_openai_content_parser_text_reasoning_comprehensive() -> None:
"""Test _openai_content_parser with TextReasoningContent all additional properties."""
def test_prepare_content_for_openai_text_reasoning_comprehensive() -> None:
"""Test _prepare_content_for_openai with TextReasoningContent all additional properties."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
# Test TextReasoningContent with all additional properties
@@ -1255,7 +1256,7 @@ def test_openai_content_parser_text_reasoning_comprehensive() -> None:
"encrypted_content": "secure_data_456",
},
)
result = client._openai_content_parser(Role.ASSISTANT, comprehensive_reasoning, {}) # type: ignore
result = client._prepare_content_for_openai(Role.ASSISTANT, comprehensive_reasoning, {}) # type: ignore
assert result["type"] == "reasoning"
assert result["summary"]["text"] == "Comprehensive reasoning summary"
assert result["status"] == "in_progress"
@@ -1280,7 +1281,7 @@ def test_streaming_reasoning_text_delta_event() -> None:
)
with patch.object(client, "_get_metadata_from_response", return_value={}) as mock_metadata:
response = client._create_streaming_response_content(event, chat_options, function_call_ids) # type: ignore
response = client._parse_chunk_from_openai(event, chat_options, function_call_ids) # type: ignore
assert len(response.contents) == 1
assert isinstance(response.contents[0], TextReasoningContent)
@@ -1305,7 +1306,7 @@ def test_streaming_reasoning_text_done_event() -> None:
)
with patch.object(client, "_get_metadata_from_response", return_value={"test": "data"}) as mock_metadata:
response = client._create_streaming_response_content(event, chat_options, function_call_ids) # type: ignore
response = client._parse_chunk_from_openai(event, chat_options, function_call_ids) # type: ignore
assert len(response.contents) == 1
assert isinstance(response.contents[0], TextReasoningContent)
@@ -1331,7 +1332,7 @@ def test_streaming_reasoning_summary_text_delta_event() -> None:
)
with patch.object(client, "_get_metadata_from_response", return_value={}) as mock_metadata:
response = client._create_streaming_response_content(event, chat_options, function_call_ids) # type: ignore
response = client._parse_chunk_from_openai(event, chat_options, function_call_ids) # type: ignore
assert len(response.contents) == 1
assert isinstance(response.contents[0], TextReasoningContent)
@@ -1356,7 +1357,7 @@ def test_streaming_reasoning_summary_text_done_event() -> None:
)
with patch.object(client, "_get_metadata_from_response", return_value={"custom": "meta"}) as mock_metadata:
response = client._create_streaming_response_content(event, chat_options, function_call_ids) # type: ignore
response = client._parse_chunk_from_openai(event, chat_options, function_call_ids) # type: ignore
assert len(response.contents) == 1
assert isinstance(response.contents[0], TextReasoningContent)
@@ -1392,8 +1393,8 @@ def test_streaming_reasoning_events_preserve_metadata() -> None:
)
with patch.object(client, "_get_metadata_from_response", return_value={"test": "metadata"}):
text_response = client._create_streaming_response_content(text_event, chat_options, function_call_ids) # type: ignore
reasoning_response = client._create_streaming_response_content(reasoning_event, chat_options, function_call_ids) # type: ignore
text_response = client._parse_chunk_from_openai(text_event, chat_options, function_call_ids) # type: ignore
reasoning_response = client._parse_chunk_from_openai(reasoning_event, chat_options, function_call_ids) # type: ignore
# Both should preserve metadata
assert text_response.additional_properties == {"test": "metadata"}
@@ -1404,7 +1405,7 @@ def test_streaming_reasoning_events_preserve_metadata() -> None:
assert isinstance(reasoning_response.contents[0], TextReasoningContent)
def test_create_response_content_image_generation_raw_base64():
def test_parse_response_from_openai_image_generation_raw_base64():
"""Test image generation response parsing with raw base64 string."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -1428,7 +1429,7 @@ def test_create_response_content_image_generation_raw_base64():
mock_response.output = [mock_item]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
# Verify the response contains DataContent with proper URI and media_type
assert len(response.messages[0].contents) == 1
@@ -1438,7 +1439,7 @@ def test_create_response_content_image_generation_raw_base64():
assert content.media_type == "image/png"
def test_create_response_content_image_generation_existing_data_uri():
def test_parse_response_from_openai_image_generation_existing_data_uri():
"""Test image generation response parsing with existing data URI."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -1461,7 +1462,7 @@ def test_create_response_content_image_generation_existing_data_uri():
mock_response.output = [mock_item]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
# Verify the response contains DataContent with proper media_type parsed from URI
assert len(response.messages[0].contents) == 1
@@ -1471,7 +1472,7 @@ def test_create_response_content_image_generation_existing_data_uri():
assert content.media_type == "image/webp"
def test_create_response_content_image_generation_format_detection():
def test_parse_response_from_openai_image_generation_format_detection():
"""Test different image format detection from base64 data."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -1493,7 +1494,7 @@ def test_create_response_content_image_generation_format_detection():
mock_response_jpeg.output = [mock_item_jpeg]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response_jpeg = client._create_response_content(mock_response_jpeg, chat_options=ChatOptions()) # type: ignore
response_jpeg = client._parse_response_from_openai(mock_response_jpeg, chat_options=ChatOptions()) # type: ignore
content_jpeg = response_jpeg.messages[0].contents[0]
assert isinstance(content_jpeg, DataContent)
assert content_jpeg.media_type == "image/jpeg"
@@ -1517,14 +1518,14 @@ def test_create_response_content_image_generation_format_detection():
mock_response_webp.output = [mock_item_webp]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response_webp = client._create_response_content(mock_response_webp, chat_options=ChatOptions()) # type: ignore
response_webp = client._parse_response_from_openai(mock_response_webp, chat_options=ChatOptions()) # type: ignore
content_webp = response_webp.messages[0].contents[0]
assert isinstance(content_webp, DataContent)
assert content_webp.media_type == "image/webp"
assert "data:image/webp;base64," in content_webp.uri
def test_create_response_content_image_generation_fallback():
def test_parse_response_from_openai_image_generation_fallback():
"""Test image generation with invalid base64 falls back to PNG."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")
@@ -1547,7 +1548,7 @@ def test_create_response_content_image_generation_fallback():
mock_response.output = [mock_item]
with patch.object(client, "_get_metadata_from_response", return_value={}):
response = client._create_response_content(mock_response, chat_options=ChatOptions()) # type: ignore
response = client._parse_response_from_openai(mock_response, chat_options=ChatOptions()) # type: ignore
# Verify it falls back to PNG format for unrecognized binary data
assert len(response.messages[0].contents) == 1
@@ -1563,21 +1564,21 @@ async def test_prepare_options_store_parameter_handling() -> None:
test_conversation_id = "test-conversation-123"
chat_options = ChatOptions(store=True, conversation_id=test_conversation_id)
options = await client.prepare_options(messages, chat_options)
options = await client._prepare_options(messages, chat_options) # type: ignore
assert options["store"] is True
assert options["previous_response_id"] == test_conversation_id
chat_options = ChatOptions(store=False, conversation_id="")
options = await client.prepare_options(messages, chat_options)
options = await client._prepare_options(messages, chat_options) # type: ignore
assert options["store"] is False
chat_options = ChatOptions(store=None, conversation_id=None)
options = await client.prepare_options(messages, chat_options)
options = await client._prepare_options(messages, chat_options) # type: ignore
assert "store" not in options
assert "previous_response_id" not in options
chat_options = ChatOptions()
options = await client.prepare_options(messages, chat_options)
options = await client._prepare_options(messages, chat_options) # type: ignore
assert "store" not in options
assert "previous_response_id" not in options