SpringAI 多轮对话报错 400 Bad Request

版本依赖

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;  
    }  
}
相关推荐
2603_969734504 小时前
2026最新7款企业AI编程工具深度对比实测
ai编程
一tiao咸鱼4 小时前
还在手动海投?我做了个 AI 求职助手,自动筛选 BOSS 职位再精准投递
前端·后端·面试
程序员大狐狸4 小时前
Ultra X7 358H 笔记本电脑,寻找本地用于编码的最强大模型,实测 21.0 tok/s,上下文 256K
llm·ai编程
小虎AI生活4 小时前
三天11万星的逆天开源REA,逆向分析一切,软件开发的门槛被打到了零,最该慌的为什么不是大厂?
aigc·ai编程
曹小强wxx4 小时前
让 AI 写代码不“翻车”:用 Claude Code + Codex 给 Flutter 项目搭一套工程规范
flutter·ai编程·claude
AI大模型-小华4 小时前
AI 编程进入深度开发阶段:什么时候普通方案不够,需要更高算力支持?
人工智能·chatgpt·ai编程·vs code·codex·codex cli
码哥字节4 小时前
Spring Boot 4.1 默认开虚拟线程,连接池为什么反而打满了?
spring boot·后端
fellow994 小时前
DSH Desktop:把 DeepSeek Harness 变成真正的桌面应用
electron·agent·ai编程·deepseek
大白804 小时前
CSS 盒模型精讲:标准盒模型与 IE 怪异盒模型区别
后端
深蓝AI4 小时前
Python 写 JSON 写到一半崩了?用 os.replace 保住上一版配置(附故障注入)
后端