Merge pull request #9 from afourniernv/fix/pr85581-completed-sparse-fields

fix(openai): complete sparse response normalization
This commit is contained in:
Bryan Bednarski
2026-08-17 10:34:14 -07:00
committed by GitHub
2 changed files with 70 additions and 10 deletions
+16 -5
View File
@@ -756,9 +756,9 @@ class ChatCompletionsTransport(ProviderTransport):
preserved for downstream replay.
"""
choice = response.choices[0]
msg = choice.message
msg = getattr(choice, "message", None)
# Poolside returns integer finish_reason (e.g. 24) instead of string
_fr = choice.finish_reason
_fr = getattr(choice, "finish_reason", None)
if isinstance(_fr, int):
_fr = str(_fr)
finish_reason = _fr or "stop"
@@ -768,6 +768,13 @@ class ChatCompletionsTransport(ProviderTransport):
if message_tool_calls:
tool_calls = []
for tc in message_tool_calls:
tc_function = getattr(tc, "function", None)
function_name = getattr(tc_function, "name", None)
# Match Relay's codec: skip absent function/name fields, but
# preserve an explicit blank name for Hermes's recovery path.
if tc_function is None or function_name is None:
continue
function_arguments = getattr(tc_function, "arguments", None)
# Preserve provider-specific extras on the tool call.
# Gemini 3 thinking models attach extra_content with
# thought_signature — without replay on the next turn the API
@@ -790,9 +797,13 @@ class ChatCompletionsTransport(ProviderTransport):
tc_provider_data["extra_content"] = extra
tool_calls.append(
ToolCall(
id=tc.id,
name=tc.function.name,
arguments=tc.function.arguments,
id=getattr(tc, "id", None),
name=function_name,
arguments=(
function_arguments
if function_arguments is not None
else "{}"
),
provider_data=tc_provider_data or None,
)
)
@@ -18,12 +18,46 @@ def transport():
class TestChatCompletionsBasic:
def test_normalize_response_allows_missing_optional_message_fields(self, transport):
@pytest.mark.parametrize(
"choice",
[SimpleNamespace(message=SimpleNamespace()), SimpleNamespace()],
)
def test_normalize_response_allows_missing_optional_message_fields(
self, transport, choice
):
response = SimpleNamespace(choices=[choice], usage=None)
normalized = transport.normalize_response(response)
assert normalized.content is None
assert normalized.tool_calls is None
assert normalized.finish_reason == "stop"
def test_normalize_response_allows_sparse_tool_call_fields(self, transport):
response = SimpleNamespace(
choices=[
SimpleNamespace(
message=SimpleNamespace(),
finish_reason="stop",
message=SimpleNamespace(
tool_calls=[
SimpleNamespace(
function=SimpleNamespace(arguments='{"city":"Paris"}')
),
SimpleNamespace(),
SimpleNamespace(
id="call-3",
function=SimpleNamespace(name="lookup"),
),
SimpleNamespace(
id="call-4",
function=SimpleNamespace(name="", arguments="{}"),
),
SimpleNamespace(
function=SimpleNamespace(
name="weather", arguments="{}"
)
),
]
)
)
],
usage=None,
@@ -31,8 +65,23 @@ class TestChatCompletionsBasic:
normalized = transport.normalize_response(response)
assert normalized.content is None
assert normalized.tool_calls is None
assert normalized.finish_reason == "stop"
assert normalized.tool_calls is not None
assert [tool.id for tool in normalized.tool_calls] == [
"call-3",
"call-4",
None,
]
assert [tool.name for tool in normalized.tool_calls] == [
"lookup",
"",
"weather",
]
assert [tool.arguments for tool in normalized.tool_calls] == [
"{}",
"{}",
"{}",
]
@pytest.mark.parametrize("provider", ["nous", "openrouter"])
def test_gpt56_ultra_uses_max_wire_effort(self, transport, provider):