多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Spring AI 对接 vLLM 400 报错实战排查:换掉默认 HTTP 客户端即可

Spring AI 对接 vLLM 400 报错实战排查:换掉默认 HTTP 客户端即可 Spring AI 对接 vLLM 400 报错实战排查换掉默认 HTTP 客户端即可【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-aiTL;DRSpring AI 的 OpenAiChatModel 调用 vLLM 部署的 deepseek 模型时配置全对却稳定返回 HTTP 400Field required。根因是默认 HTTP 客户端用 chunked 分块传输编码发请求体vLLM 解析不到 body。当前最优解把 RestClient 和 WebClient 的请求工厂换成 Jetty 客户端两步依赖 几行 builder 配置即可绕过。 复现路径在什么条件下会踩到同时满足以下条件时几乎必现 400服务端是 vLLM 起的 OpenAI 兼容接口上面跑的 deepseek 系列模型含 DeepSeek-R1 蒸馏版。客户端使用 Spring AI 的 OpenAI 模块OpenAiChatModel未对底层 HTTP 客户端做任何定制。base-url、api-key、model三项配置全部正确健康检查接口能通。用curl直接 POST/v1/chat/completions返回 200换成 Spring AI 调用立刻 400——同一请求、两个客户端、两种结果。只要 body 是 Spring 侧序列化后整体发出去非手工拼字符串就会触发与请求大小无关。对照一遍如果 curl 通、Spring AI 不通且服务端是 vLLM基本可以按本文继续。 报错现场日志里最关键的一行400 - {object:error,message:[{type: missing, loc: (body,), msg: Field required, input: None}],type:BadRequestError,code:400}定位钥匙是input: None。Pydantic 的校验报错里input字段记录的是服务端实际收到的入参。它是None而不是一个 JSON 对象说明 vLLM 根本没拿到请求体——问题不在 body 内容在传输方式。这直接排除了参数拼错字段缺失这类方向。根因链路从协议层到框架层因果链只有四步Spring 默认客户端发 POST 请求采用 chunked 编码不带 Content-LengthvLLM 解析 chunked body 失败视为空body 校验缺失抛 400 BadRequest也就是说Spring AI 侧序列化的 JSON 本身没问题是它在传输层以 chunked 分块编码发出分块传输编码HTTP 里不预先告知 body 长度、边写边发的一种编码方式vLLM 的接收端吃不下这种编码把请求体读成了空。为什么默认行为会走到这条路Spring 的RestClient/WebClient在没有显式指定请求工厂时使用 JDK 内置的 HTTP 实现。它对长度已知的 JSON body也可能走 chunked 路径而这个分支恰好踩中 vLLM 尚未修复的解析短板。Jetty 客户端在发送前会算好长度、以 Content-Length 方式发 body于是绕开了这个坑。 修复方案改哪一行代码就够核心动作一句话给 OpenAiApi 的 builder 注入 Jetty 请求工厂让请求体带 Content-Length 一次性发出vLLM 就能正常解析。改动只有两处不动任何业务代码。最小依赖改动在模块 pom 中加入 Jetty 客户端及其响应式连接器!-- 同步 RestClient 用的 Jetty 请求工厂 -- dependency groupIdorg.eclipse.jetty/groupId artifactIdjetty-client/artifactId /dependency !-- 流式 WebClient 用的 Jetty 连接器 -- dependency groupIdorg.eclipse.jetty/groupId artifactIdjetty-reactive-httpclient/artifactId /dependency验证方式依赖解析无冲突即可此步不产生行为变化。Jetty 客户端配置构建 OpenAiApi 时显式传入两个 builder同步和流式两条链路都要覆盖OpenAiApi openAiApi OpenAiApi.builder() .baseUrl(chatConfig.getBaseUrl()) // 指向 vLLM 服务 .apiKey(chatConfig.getApiKey()) .restClientBuilder(RestClient.builder() .requestFactory(new JettyClientHttpRequestFactory())) // 同步调用走 Jetty .webClientBuilder(WebClient.builder() .clientConnector(new JettyClientHttpConnector())) // 流式调用走 Jetty .build();验证方式发一条普通 chat 请求HTTP 200 且正常返回 token 即修复生效再发一条stream: true的流式请求确认增量输出同样正常。临时方案与长期方案的边界临时方案可立即上线上面的 Jetty 客户端替换行为完全兼容只换传输层。长期方案依赖上游vLLM 侧补齐对 chunked 编码请求体的解析。补丁合入后Jetty 配置可保留无副作用也可回退默认客户端。举一反三同类集成中还要警惕什么关注 vLLM 的传输层修复进度跟踪其 issue 与 release notes 中关于 chunked /Transfer-Encoding的修复项合入后即可把 HTTP 客户端配置收敛回默认减少一个外部依赖。排查 Ollama、DeepSeek 官方接口等 OpenAI 兼容服务端凡是拿curl 通、Spring AI 不通的 400都先查响应体里input字段是否为None。是则优先怀疑传输编码而非参数Spring AI 文档中关于 vLLM 的 extra-body、reasoning_content 等适配说明可参考 openai-chat.adoc。统一收口 Spring AI 的 API 构建入口如果项目里多处 newOpenAiApi/OpenAiChatModel把 RestClient 与 WebClient 的 Jetty 工厂集中到一个配置类或OpenAiHttpClientBuilderCustomizer风格的定制点里避免某处遗漏后问题复活。vLLM deepseek 的推理类模型在 Spring AI 里走的就是 OpenAI 兼容链路多轮场景的 COT 输出形态可以参考官方示例先换客户端再等补丁。【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表