Skip to content

AI Assistant: OpenAI streaming tool calls split into two entries (function_call_arguments.delta correlated on the wrong field) #10348

Description

@dpage

Describe the bug

In web/pgadmin/llm/providers/openai.py, chat_stream() accumulates streamed function-call arguments in a tool_calls_data dict keyed by call_id:

elif event_type == 'response.output_item.added':
    item = data.get('item', {})
    if item.get('type') == 'function_call':
        call_id = item.get('call_id', '')
        tool_calls_data[call_id] = {'name': item.get('name', ''), 'arguments': ''}

elif event_type == 'response.function_call_arguments.delta':
    call_id = data.get('call_id', '')
    if call_id not in tool_calls_data:
        tool_calls_data[call_id] = {'name': '', 'arguments': ''}
    tool_calls_data[call_id]['arguments'] += data.get('delta', '')

But per OpenAI's Responses API streaming event reference, response.function_call_arguments.delta events carry item_id, output_index, delta and sequence_number — there is no call_id field on that event type (source). So data.get('call_id', '') always evaluates to ''.

That means every response.function_call_arguments.delta event fails the call_id not in tool_calls_data check (since the real call_id entry was already created by response.output_item.added), creates a second dict entry keyed by the empty string, and accumulates all the argument deltas there instead. The result: a tool call comes back as two separate ToolCall entries — one with the correct name and empty arguments, and another with empty name and the actual (accumulated) arguments — rather than one complete tool call.

To Reproduce

  1. Configure the AI Assistant / SQL Chat feature with an OpenAI provider and a model exposed via the Responses API (/v1/responses).
  2. Ask a question that triggers a streamed tool/function call.
  3. Inspect the resulting tool calls: the name and arguments arrive in two separate entries instead of one.

Expected behavior

The delta events should be correlated using item_id (matching item.id from the response.output_item.added event, not item.call_id) so all argument deltas accumulate against the same entry that was seeded with the function name, and the final ToolCall is emitted with both name and arguments populated correctly.

Found while re-verifying #9795 (which is otherwise correctly fixed for the "unsupported model" fallback to /v1/responses) — this is a separate, follow-up defect in that same streaming path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions