-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
When using Spring AI 1.0.3 with Qwen3 (e.g. qwen3-30b-a3b-instruct-2507) in streaming mode (stream=true),
an exception occurs during streaming tool calls:
java.lang.IllegalArgumentException: toolName cannot be null or empty
at org.springframework.util.Assert.hasText(Assert.java:253)
at org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver.resolve(DelegatingToolCallbackResolver.java:45)
This happens because Qwen streams tool_calls incrementally —
only the first frame contains "function.name", while subsequent frames only contain "function.arguments".
Spring AI currently expects each frame to include the tool name,
so when a partial frame without "name" arrives, it fails with the above error.
This is not a model-side bug. Qwen follows the OpenAI-compatible streaming protocol
where tool call data is split across multiple chunks.
Environment
Spring AI version: 1.0.3
Java version: 17
Model: qwen3-30b-a3b-instruct-2507 (DashScope-compatible API)
Mode: stream=true
Error: toolName cannot be null or empty
Expected behavior
Spring AI should correctly aggregate tool_call fragments across streaming chunks,
retaining the last non-empty function.name and concatenating function.arguments until the stream completes.
No exception should be thrown, and the tool callback should execute once the tool call is fully assembled.
Qwen3 output:
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","created":1762178257774,"object":"chat.completion.chunk","usage":null,"choices":[{"logprobs":null,"index":0,"delta":{"content":"","role":"assistant","tool_calls":[{"index":0,"id":"call_f7e76b4bdf8242b68b7124","type":"function","function":{"name":"init_work_status","arguments":""}}]}}]}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"call_f7e76b4bdf8242b68b7124","type":"function","function":{"name":"","arguments":"{"firstStep"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"": "开始需求"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"收集阶段", ""}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"reason": "用户"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"提出需要撰写2"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"025年上半年"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"区教育局的工作"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"总结,这是一个新的"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"文档编写任务,"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"需启动需求收集"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"content":"","tool_calls":[{"index":0,"id":"","type":"function","function":{"name":"","arguments":"流程。"}"}}],"role":null},"index":0}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{"tool_calls":[{"function":{"arguments":null},"index":0,"id":"","type":"function"}],"content":""}}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: {"model":"qwen3-30b-a3b-instruct-2507","id":"chatcmpl-6a13abdb-a3be-4557-8dc2-8381fcd83696","choices":[{"delta":{},"index":0,"finish_reason":"tool_calls"}],"created":1762178257774,"object":"chat.completion.chunk","usage":null}
data: [DONE]
exception stack:
2025-11-03 22:29:45.777 [boundedElastic-1] ERROR o.s.ai.chat.model.MessageAggregator - Aggregation Error
java.lang.IllegalArgumentException: toolName cannot be null or empty
at org.springframework.util.Assert.hasText(Assert.java:253)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly trace from producer [reactor.core.publisher.FluxDefer] :
reactor.core.publisher.Flux.defer(Flux.java:810)
org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:366)
Error has been observed at the following site(s):
*_________Flux.defer ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:366)
| Flux.subscribeOn ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:381)
*__Flux.flatMap ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:364)
| Flux.doOnError ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:387)
| Flux.doFinally ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:388)
| Flux.contextWrite ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:389)
| Flux.doOnSubscribe ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:79)
| Flux.doOnNext ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:91)
| Flux.doOnComplete ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:142)
Original Stack Trace:
at org.springframework.util.Assert.hasText(Assert.java:253)
at org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver.resolve(DelegatingToolCallbackResolver.java:45)
at org.springframework.ai.model.tool.DefaultToolCallingManager.lambda$executeToolCall$3(DefaultToolCallingManager.java:207)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCall(DefaultToolCallingManager.java:207)
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCalls(DefaultToolCallingManager.java:138)
at com.lx.ai.plugin.service.impl.MyToolCallingManager.executeToolCalls(MyToolCallingManager.java:85)
at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$9(OpenAiChatModel.java:369)
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:46)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:68)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1575)
2025-11-03 22:29:45.790 [boundedElastic-2] ERROR o.s.ai.chat.model.MessageAggregator - Aggregation Error
java.lang.IllegalArgumentException: toolName cannot be null or empty
at org.springframework.util.Assert.hasText(Assert.java:253)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly trace from producer [reactor.core.publisher.FluxDefer] :
reactor.core.publisher.Flux.defer(Flux.java:810)
org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:366)
Error has been observed at the following site(s):
*___________Flux.defer ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:366)
| Flux.subscribeOn ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$10(OpenAiChatModel.java:381)
*Flux.flatMap ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:364)
| Flux.doOnError ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:387)
| Flux.doFinally ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:388)
| Flux.contextWrite ⇢ at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$13(OpenAiChatModel.java:389)
| Flux.doOnSubscribe ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:79)
| Flux.doOnNext ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:91)
| Flux.doOnComplete ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:142)
| Flux.doOnError ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:182)
*Flux.deferContextual ⇢ at org.springframework.ai.openai.OpenAiChatModel.internalStream(OpenAiChatModel.java:271)
| Flux.map ⇢ at org.springframework.ai.chat.client.advisor.ChatModelStreamAdvisor.adviseStream(ChatModelStreamAdvisor.java:53)
| Flux.publishOn ⇢ at org.springframework.ai.chat.client.advisor.ChatModelStreamAdvisor.adviseStream(ChatModelStreamAdvisor.java:57)
| Flux.doOnError ⇢ at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextStream$5(DefaultAroundAdvisorChain.java:137)
| Flux.doFinally ⇢ at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextStream$5(DefaultAroundAdvisorChain.java:138)
| Flux.contextWrite ⇢ at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextStream$5(DefaultAroundAdvisorChain.java:139)
*____________Flux.defer ⇢ at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextStream$6(DefaultAroundAdvisorChain.java:136)
*__Flux.deferContextual ⇢ at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextStream(DefaultAroundAdvisorChain.java:117)
*__Mono.flatMapMany ⇢ at com.lx.ai.plugin.service.impl.MyMessageAdvisor.adviseStream(MyMessageAdvisor.java:258)
| Flux.mapNotNull ⇢ at org.springframework.ai.chat.client.ChatClientMessageAggregator.aggregateChatClientResponse(ChatClientMessageAggregator.java:48)
| Flux.doOnSubscribe ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:79)
| Flux.doOnNext ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:91)
| Flux.doOnComplete ⇢ at org.springframework.ai.chat.model.MessageAggregator.aggregate(MessageAggregator.java:142)
Original Stack Trace:
at org.springframework.util.Assert.hasText(Assert.java:253)
at org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver.resolve(DelegatingToolCallbackResolver.java:45)
at org.springframework.ai.model.tool.DefaultToolCallingManager.lambda$executeToolCall$3(DefaultToolCallingManager.java:207)
at java.base/java.util.Optional.orElseGet(Optional.java:364)
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCall(DefaultToolCallingManager.java:207)
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCalls(DefaultToolCallingManager.java:138)
at com.lx.ai.plugin.service.impl.MyToolCallingManager.executeToolCalls(MyToolCallingManager.java:85)
at org.springframework.ai.openai.OpenAiChatModel.lambda$internalStream$9(OpenAiChatModel.java:369)
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:46)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:68)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1575)