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;  
    }  
}
相关推荐
小杨同学492 小时前
C 语言实战:堆内存存储字符串 + 多种递归方案计算字符串长度
数据库·后端·算法
golang学习记2 小时前
Go 中防止敏感数据意外泄露的几种姿势
后端
czlczl200209252 小时前
Spring Boot 构建 SaaS 多租户架构
spring boot·后端·架构
小码编匠2 小时前
完美替代 Navicat,一款开源免费、集成了 AIGC 能力的多数据库客户端工具!
数据库·后端·aigc
顺流2 小时前
从零实现一个数据结构可视化调试器(一)
后端
掘金者阿豪2 小时前
Redis键值对批量删除全攻略:安全高效删除包含特定模式的键
后端
星浩AI2 小时前
深入理解 LlamaIndex:RAG 框架核心概念与实践
人工智能·后端·python
用户2190326527352 小时前
配置中心 - 不用改代码就能改配置
后端·spring cloud·微服务