Merge pull request #9 from afourniernv/fix/pr85581-completed-sparse-fields
fix(openai): complete sparse response normalization
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user