版本依赖
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.5.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
BUG 描述
当进行多轮对话的时候,会出现报错
php
2026-01-12 20:17:02.116 [http-nio-8438-exec-5] ERROR -RuntimeException
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST https://xxxx.com/v1/chat/completions
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:321)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Assembly trace from producer [reactor.core.publisher.MonoIgnoreThen] :
reactor.core.publisher.Mono.then(Mono.java:4879)
刚开始的时候怀疑是因为有 advisor 干扰了 SYSTEM message 的顺序,但是我使用最小 demo 也不行
less
@RestController
@RequestMapping("/chat")
public class ChatController {
private final ChatClient chatClient;
private final JdbcChatMemoryRepository jdbcChatMemoryRepository;
public ChatController(ChatClient.Builder chatClientBuilder, JdbcChatMemoryRepository jdbcChatMemoryRepository) {
this.chatClient = chatClientBuilder.build();
this.jdbcChatMemoryRepository = jdbcChatMemoryRepository;
}
@GetMapping(value = "/stream/{sessionId}")
public Flux<String> stream(@PathVariable(name = "sessionId") String sessionId, @RequestParam(name = "message") String message) {
MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(jdbcChatMemoryRepository)
.maxMessages(100)
.build();
return chatClient.prompt()
.system("You are a helpful assistant.")
.user(message)
.advisors(advisorSpec -> advisorSpec.param(CONVERSATION_ID, sessionId))
.advisors(MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build())
.stream()
.content();
}
}
DEBUG
debug 位置 org.springframework.ai.openai.api.OpenAiApi#chatCompletionStream

第一次对话:
SYSTEM 类型在 0 位置

第二次对话:
SYSTEM 类型位置在 1,不符合 OpenAI 的规范。
OpenAI API 规范要求:
System 消息必须是消息数组中的第一条消息。如果 system 消息不在第一位,API 将返回 400 Bad Request 错误。

解决方案
使用一个 advisor 调整一下最后 message 的顺序
注意: 这里的 order 需要尽量小,也就是尽量最后执行,防止中间被修改
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.prompt.Prompt;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* 确保 SYSTEM 信息始终在 [0] 位置,否者报错 400
* @author leikooo
*/@Slf4j
public class SystemMessageFirstAdvisor implements CallAdvisor, StreamAdvisor {
private static final int DEFAULT_ORDER = Integer.MAX_VALUE - 1000;
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
ChatClientRequest newRequest = reorderSystemMessageFirst(chatClientRequest);
return callAdvisorChain.nextCall(newRequest);
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
ChatClientRequest newRequest = reorderSystemMessageFirst(chatClientRequest);
return streamAdvisorChain.nextStream(newRequest);
}
private ChatClientRequest reorderSystemMessageFirst(ChatClientRequest request) {
Prompt prompt = request.prompt();
List<Message> messages = new ArrayList<>(prompt.getInstructions());
if (messages.size() <= 1) {
return request;
}
messages.sort(Comparator.comparing(m -> !(m instanceof SystemMessage) ? 1 : 0));
Prompt newPrompt = new Prompt(messages, prompt.getOptions());
return new ChatClientRequest(newPrompt, request.context());
}
@Override
public String getName() {
return "SystemMessageFirstAdvisor";
}
@Override
public int getOrder() {
return DEFAULT_ORDER;
}
}
less
@GetMapping(value = "/stream/{sessionId}")
public Flux<String> stream(@PathVariable(name = "sessionId") String sessionId, @RequestParam(name = "message") String message) {
MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder()
.chatMemoryRepository(jdbcChatMemoryRepository)
.maxMessages(100)
.build();
return chatClient.prompt()
.system("You are a helpful assistant.")
.user(message)
.advisors(advisorSpec -> advisorSpec.param(CONVERSATION_ID, sessionId))
.advisors(MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build())
.advisors(new SystemMessageFirstAdvisor())
.stream()
.content();
}
debug 发现 SYSTEM 在 0 位置,可以正常对话

关于顺序问题:
测试的话,创建了一个 LogAdvisor 他的 order 是 0。然后 SystemMessageFirstAdvisor 的 order 是 Integer.MAX_VALUE - 1000
debug 位置 org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain#nextStream


typescript
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.ChatClientResponse;
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisor;
import org.springframework.ai.chat.client.advisor.api.StreamAdvisorChain;
import reactor.core.publisher.Flux;
/**
* @author <a href="https://github.com/lieeew">leikooo</a>
* @date 2026/1/1
* @description 输出日志 log
*/@Slf4j
public class LogAdvisor implements StreamAdvisor, CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("log advisor before request {}", callAdvisorChain);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("log advisor after request {}", chatClientResponse);
return chatClientResponse;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) {
log.info("stream log advisor before request {}", chatClientRequest);
return streamAdvisorChain.nextStream(chatClientRequest);
}
@Override
public String getName() {
return "LogAdvisor";
}
@Override
public int getOrder() {
return 0;
}
}