Skip to content

Conversation

@PupiBott
Copy link

@PupiBott PupiBott commented Nov 5, 2025

Problem

When using Responses API streaming with multiple function tools, the response.function_call_arguments.done event returns name=None, making it impossible to determine which function to call.

Example from issue #2723:

async for event in stream:
    if event.type == 'response.function_call_arguments.done':
        print(f'Function: {event.name}')  # ❌ Prints: Function: None

This breaks multi-tool scenarios where the application needs to know which function was called to route the execution correctly.

Root Cause

ResponseStreamState.handle_event() only processes delta events for function calls, not done events:

elif event.type == "response.function_call_arguments.delta":
    # ✅ Handled - emits custom event with snapshot
    ...
# ❌ Missing: elif event.type == "response.function_call_arguments.done"
elif event.type == "response.completed":
    ...
else:
    events.append(event)  # ← done event falls through here

The raw event from the API doesn't include the name field - it must be taken from the accumulated snapshot.

Solution

Added elif block to handle response.function_call_arguments.done events, following the proven pattern from Chat Completions API's _add_tool_done_event() method.

Changes:

  1. Added ResponseFunctionCallArgumentsDoneEvent import
  2. Added parse_function_tool_arguments import
  3. Added elif block to emit done event with:
    • name from accumulated snapshot (not raw event)
    • parsed_arguments using input_tools
  4. Exported ResponseFunctionCallArgumentsDoneEvent in __init__.py

After fix:

elif event.type == "response.function_call_arguments.done":
    output = snapshot.output[event.output_index]
    assert output.type == "function_call"
    
    parsed_arguments = parse_function_tool_arguments(
        input_tools=self._input_tools,
        function_call=output
    )
    output.parsed_arguments = parsed_arguments
    
    events.append(
        build(
            ResponseFunctionCallArgumentsDoneEvent,
            arguments=output.arguments,
            item_id=event.item_id,
            name=output.name,  # ← FROM SNAPSHOT
            output_index=event.output_index,
            sequence_number=event.sequence_number,
            type="response.function_call_arguments.done",
        )
    )

Pattern

This follows the same approach as Chat Completions streaming (src/openai/lib/streaming/chat/_completions.py:708-731), which correctly emits done events with name from the snapshot.

Impact

Non-breaking change:

  • Only populates a field that was previously None
  • Existing code continues to work
  • New code can now access the function name

Related Issues

Example Usage

Before:

async for event in stream:
    if event.type == 'response.function_call_arguments.done':
        print(event.name)  # None ❌

After:

async for event in stream:
    if event.type == 'response.function_call_arguments.done':
        print(event.name)  # 'get_weather' ✅
        if event.name == 'get_weather':
            result = get_weather(**json.loads(event.arguments))
        elif event.name == 'get_stock_price':
            result = get_stock_price(**json.loads(event.arguments))

Files Changed

  • src/openai/lib/streaming/responses/_responses.py: Added elif block + imports (24 lines)
  • src/openai/lib/streaming/responses/__init__.py: Exported done event (1 line)

Testing

The fix follows the proven pattern from Chat Completions, which has been working correctly for months. The type ResponseFunctionCallArgumentsDoneEvent already has the name field defined, and the snapshot ParsedResponseFunctionToolCall contains the name - this change simply connects them.

…shot

Fixes openai#2723

Problem:
When streaming Responses API with multiple function tools, the
response.function_call_arguments.done event returns name=None,
making it impossible to determine which function to call.

Root Cause:
ResponseStreamState.handle_event() only handles delta events for
function calls, but not done events. The done event falls through
to 'else: events.append(event)', returning the raw event without
processing. The raw event from the API doesn't include the name
field - it must be taken from the accumulated snapshot.

Solution:
Added elif block to handle response.function_call_arguments.done
events, following the proven pattern from Chat Completions API's
_add_tool_done_event() method.

Changes:
1. Added ResponseFunctionCallArgumentsDoneEvent import
2. Added parse_function_tool_arguments import
3. Added elif block to emit done event with:
   - name from accumulated snapshot (not raw event)
   - parsed_arguments using input_tools
4. Exported ResponseFunctionCallArgumentsDoneEvent in __init__.py

Pattern:
This follows the same approach as Chat Completions streaming,
which correctly emits done events with name from the snapshot.

Impact:
Non-breaking change - only populates a field that was previously
None. Existing code continues to work, new code can now access
the function name to determine which function was called.
@PupiBott PupiBott requested a review from a team as a code owner November 5, 2025 14:57
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 309 to 329
elif event.type == "response.function_call_arguments.done":
output = snapshot.output[event.output_index]
assert output.type == "function_call"

# Parse arguments using input_tools
parsed_arguments = parse_function_tool_arguments(
input_tools=self._input_tools,
function_call=output
)
output.parsed_arguments = parsed_arguments

# Emit event with name from accumulated snapshot
events.append(
build(
ResponseFunctionCallArgumentsDoneEvent,
arguments=output.arguments,
item_id=event.item_id,
name=output.name, # FROM SNAPSHOT, not raw event
output_index=event.output_index,
sequence_number=event.sequence_number,
type="response.function_call_arguments.done",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve arguments when only a done event is emitted

The new handler builds ResponseFunctionCallArgumentsDoneEvent using output.arguments, which is only populated by prior response.function_call_arguments.delta events. If the server delivers the full arguments payload only in the response.function_call_arguments.done event (and never emits deltas), output.arguments remains empty and callers now see blank arguments even though the raw event contained them. Previously the raw event was forwarded untouched so the arguments were available. Consider sourcing the field from event.arguments (or falling back to it) to avoid dropping data in backends that do not emit delta updates.

Useful? React with 👍 / 👎.

Address bot feedback on PR openai#2731.

The bot correctly identified an edge case where the server might
emit a response.function_call_arguments.done event WITHOUT prior
delta events. In this scenario:

- output.arguments would be empty (only populated by deltas)
- event.arguments contains the complete arguments payload

Changed to use event.arguments as the source of truth, which
ensures arguments are preserved in all scenarios:

✅ With deltas: event.arguments contains accumulated result
✅ Without deltas: event.arguments contains full payload

This maintains backward compatibility while fixing the edge case.

Co-authored-by: chatgpt-codex-connector[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Responses API streaming response.function_call_arguments.done event missing function name

1 participant