fix: convert forced tool_choice and non-streaming tool calls for Responses API connections (#30095)

On a connection with API type "responses", a forced tool choice sent in
the Chat Completions shape was forwarded to the provider unchanged, so
providers that validate the Responses API schema rejected the request.
A non-streaming reply that carried a function call was also flattened
to empty text with finish_reason "stop", so API clients never saw the
tool call.

The request converter now flattens a forced function choice to the
Responses API shape, and the result converter turns every function_call
output item into a Chat Completions tool_calls entry, mirroring the
existing Responses output mapping in utils/misc.py.

Fixes #30085
This commit is contained in:
Classic298
2026-09-18 19:18:26 -04:00
committed by GitHub
parent c82634b9d0
commit d5cacb3c0a
+23 -2
View File
@@ -1425,6 +1425,11 @@ def convert_to_responses_payload(payload: dict) -> dict:
converted_tools.append(tool)
responses_payload['tools'] = converted_tools
# Responses API expects a forced function choice as {"type": "function", "name": ...}
tool_choice = responses_payload.get('tool_choice')
if isinstance(tool_choice, dict) and isinstance(tool_choice.get('function'), dict):
responses_payload['tool_choice'] = {'type': 'function', 'name': tool_choice['function'].get('name', '')}
return responses_payload
@@ -1432,17 +1437,32 @@ def convert_responses_result(response: dict) -> dict:
"""
Convert non-streaming Responses API result to Chat Completions format.
Extracts text from message output items so all downstream consumers
Extracts text and function calls from output items so all downstream consumers
(frontend tasks, get_content_from_response) work without modification.
"""
output_items = response.get('output', [])
content = ''
tool_calls = []
for item in output_items:
if item.get('type') == 'message':
for part in item.get('content', []):
if part.get('type') == 'output_text':
content += part.get('text', '')
elif item.get('type') == 'function_call':
arguments = item.get('arguments', '{}')
if not isinstance(arguments, str):
arguments = JSONCodec.dumps(arguments)
tool_calls.append(
{
'id': item.get('call_id', ''),
'type': 'function',
'function': {
'name': item.get('name', ''),
'arguments': arguments,
},
}
)
return {
'id': response.get('id', ''),
@@ -1454,8 +1474,9 @@ def convert_responses_result(response: dict) -> dict:
'message': {
'role': 'assistant',
'content': content,
**({'tool_calls': tool_calls} if tool_calls else {}),
},
'finish_reason': 'stop',
'finish_reason': 'tool_calls' if tool_calls else 'stop',
}
],
'usage': response.get('usage', {}),