spring-projects / spring-projects/spring-ai

Allow access to raw `ChatCompletionChunk` stream before tool call merge in `OpenAiApi`

Open
#5,950 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

status: waiting-for-triage
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
1d 7h
Merged PRs (30d)
6

Description

Spring AI version: 1.1.5

Background

In OpenAiApi.chatCompletionStream(), the OpenAiStreamFunctionCallingHelper merges consecutive tool call chunks into a complete one using windowUntil + Flux.reduce(merge) before emitting them downstream.

.windowUntil(chunk -> helper.isStreamingToolFunctionCallFinish(chunk))
.concatMapIterable(window -> window.reduce(new ChatCompletionChunk(...), helper::merge))

This means that tool call parameters are always delivered in a single, fully-merged chunk. Downstream consumers (e.g. custom ChatModel wrappers, or direct OpenAiApi users) never see incremental argument updates.

Problem

When the model decides to call a tool (e.g. bash, read_file, etc.), the arguments can be long (hundreds of characters of JSON). With the current design, frontend UIs receive the complete parameters all at once — there is no way to show a "loading" state or incremental argument preview while the model is still streaming the tool call arguments.

This is especially noticeable with reasoning/thinking models where tool call parameters are emitted token by token by the API, but Spring AI buffers everything before forwarding.

Feature Request

Please provide a way to access the raw Flux<ChatCompletionChunk> before tool call merging. Possible approaches:

  1. New method in OpenAiApi: a variant of chatCompletionStream() that returns un-merged chunks (only JSON parsing, without windowUntil/reduce merging), letting callers handle tool call aggregation themselves.

  2. Configuration flag: Add an option on ChatCompletionRequest (e.g. mergeToolCallChunks: true/false) to control whether OpenAiApi merges tool call chunks internally.

  3. Callback/Listener: Provide a hook to intercept each raw chunk before merge, so that incremental arguments can be forwarded to custom handlers (e.g. SSE emitters for real-time frontend updates).

Use Case

Real-time frontend update during tool call streaming:

  • When tool name is received → show "🧰 Calling tool: bash" with loading indicator
  • When each argument fragment arrives → update parameter display progressively
  • When execution completes → show result

This significantly improves UX for AI agent chat interfaces that need to show tool call progress to end users.

Workaround

Currently, the only way to get raw streaming data is to bypass OpenAiApi entirely and directly consume the SSE response from the upstream API using a raw WebClient, which duplicates Spring AI's request construction logic.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in OpenAiApi.chatCompletionStream() and trace the OpenAiStreamFunctionCallingHelper windowUntil and Flux.reduce(merge) flow. Compare the proposed method, request flag, and callback approaches, then verify that JSON parsing remains available while callers can receive incremental ChatCompletionChunk values before tool-call merging.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
ai, api, backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.