Bug description The same toolcalling function fails when the vLLM server is upgraded from 0.8.5-post1 to 0.9.1.

Environment Spring AI 1.0.0

Steps to reproduce

docker run
      --rm
      -p 3000:3000
      -v /data/models:/models
      --name vllm-qwen3-4b
      --privileged=true
      --shm-size=4g
      -e VLLM_CPU_KVCACHE_SPACE=8
      vllm-server-cpu:0.8.5.post1
      --model /models/Qwen3-4B
      --served-model-name Qwen3/qwen3-4b
      --port 3000
      --enable-auto-tool-choice
      --tool-call-parser hermes
      --enable-reasoning
      --reasoning-parser deepseek_r1
package com.lanyuanxiaoyao.service.ai.chat;

import cn.hutool.core.util.StrUtil;
import java.net.http.HttpClient;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.http.client.reactive.JdkClientHttpConnector;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.Disposable;

/**
 * @author lanyuanxiaoyao
 * @version 20250613
 */
public class TestSpringAiTools {
  public static void main(String[] args) {
    ChatClient client = ChatClient.builder(
            OpenAiChatModel.builder()
                .openAiApi(
                    OpenAiApi.builder()
                        .baseUrl("http://xxxx:xxx")
                        .restClientBuilder(restClientBuilder())
                        .webClientBuilder(webClientBuilder())
                        .build()
                )
                .defaultOptions(
                    OpenAiChatOptions.builder()
                        .model("Qwen3/qwen3-1.7b")
                        .build()
                )
                .build()
        )
        .build();
    Disposable disposable = client.prompt()
        .tools(new TestTool())
        .user("Call the function 'submit' to generate a joke with 'pig'")
        .stream()
        .content()
        .subscribe(System.out::println);
    while (!disposable.isDisposed()) {
    }
  }

  private static HttpClient httpClient() {
    return HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .build();
  }

  private static RestClient.Builder restClientBuilder() {
    return RestClient.builder()
        .requestFactory(new JdkClientHttpRequestFactory(httpClient()));
  }

  private static WebClient.Builder webClientBuilder() {
    return WebClient.builder()
        .clientConnector(new JdkClientHttpConnector(httpClient()));
  }

  public static final class TestTool {
    @Tool(description = "Input a name of animal and return a joke for it")
    public String submit(@ToolParam(description = "Name of Animal") String animalName) {
      return StrUtil.format("{} is falling into a hole", animalName);
    }
  }
}

Exception:

2025-06-13 17:40:22.741 ERROR [b12s9] [HttpClient-2-Worker-3] org.springframework.ai.chat.model.MessageAggregator #@# Aggregation Error

java.util.NoSuchElementException: null
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:970)
    at org.springframework.ai.deepseek.api.DeepSeekStreamFunctionCallingHelper.merge(DeepSeekStreamFunctionCallingHelper.java:97)
    at org.springframework.ai.deepseek.api.DeepSeekStreamFunctionCallingHelper.merge(DeepSeekStreamFunctionCallingHelper.java:71)
    at org.springframework.ai.deepseek.api.DeepSeekStreamFunctionCallingHelper.merge(DeepSeekStreamFunctionCallingHelper.java:57)
    at org.springframework.ai.deepseek.api.DeepSeekApi.lambda$chatCompletionStream$5(DeepSeekApi.java:188)
    at reactor.core.publisher.MonoReduceSeed$ReduceSeedSubscriber.onNext(MonoReduceSeed.java:116)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drainRegular(FluxWindowPredicate.java:670)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.drain(FluxWindowPredicate.java:748)
    at reactor.core.publisher.FluxWindowPredicate$WindowFlux.onNext(FluxWindowPredicate.java:790)
    at reactor.core.publisher.FluxWindowPredicate$WindowPredicateMain.onNext(FluxWindowPredicate.java:241)
    at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
    at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
    at reactor.core.publisher.FluxFilter$FilterSubscriber.onNext(FluxFilter.java:113)
    at reactor.core.publisher.FluxTakeUntil$TakeUntilPredicateSubscriber.onNext(FluxTakeUntil.java:95)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:251)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
    at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:259)
    at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:865)
    at reactor.core.publisher.FluxConcatMap$WeakScalarSubscription.request(FluxConcatMap.java:480)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:202)
    at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNextNewBuffer(FluxBufferPredicate.java:317)
    at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.tryOnNext(FluxBufferPredicate.java:227)
    at reactor.core.publisher.FluxBufferPredicate$BufferPredicateSubscriber.onNext(FluxBufferPredicate.java:200)
    at reactor.core.publisher.FluxPeekFuseable$PeekFuseableConditionalSubscriber.onNext(FluxPeekFuseable.java:503)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
    at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
    at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
    at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onNext(FluxConcatArray.java:180)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:453)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:724)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:256)
    at reactor.core.publisher.FluxPublish$PublishSubscriber.drain(FluxPublish.java:571)
    at reactor.core.publisher.FluxPublish$PublishSubscriber.onNext(FluxPublish.java:310)
    at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
    at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drainAsync(FluxFlattenIterable.java:453)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.drain(FluxFlattenIterable.java:724)
    at reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber.onNext(FluxFlattenIterable.java:256)
    at reactor.adapter.JdkFlowAdapter$SubscriberToRS.onNext(JdkFlowAdapter.java:150)
    at java.net.http/jdk.internal.net.http.ResponseSubscribers$PublishingBodySubscriber.onNext(ResponseSubscribers.java:1006)
    at java.net.http/jdk.internal.net.http.ResponseSubscribers$PublishingBodySubscriber.onNext(ResponseSubscribers.java:846)
    at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.onNext(Http1Response.java:382)
    at java.net.http/jdk.internal.net.http.Http1Response$Http1BodySubscriber.onNext(Http1Response.java:297)
    at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:229)
    at java.net.http/jdk.internal.net.http.ResponseContent$ChunkedBodyParser.accept(ResponseContent.java:129)
    at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:790)
    at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.handle(Http1Response.java:720)
    at java.net.http/jdk.internal.net.http.Http1Response$Receiver.accept(Http1Response.java:612)
    at java.net.http/jdk.internal.net.http.Http1Response$BodyReader.tryAsyncReceive(Http1Response.java:750)
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:233)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:840)

Of cause it is ok on vLLM 0.8.5-post1,maybe something change in 0.9

Comment From: Jung-Yeol-Kim

I’m experiencing the same issue. From what I can see, it looks like #3298 might resolve this problem.

Looking forward to seeing it merged.