news 2026/9/18 23:23:06

Spring AI 调 vLLM 报 400 错误?分块传输编码是坑,换 Jetty 客户端就通了

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI 调 vLLM 报 400 错误?分块传输编码是坑,换 Jetty 客户端就通了

Spring AI 调 vLLM 报 400 错误?分块传输编码是坑,换 Jetty 客户端就通了

【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai

根因是 vLLM 不认分块传输编码(Chunked Transfer Encoding),而 Spring AI 默认 HTTP 客户端正按分块方式发请求体(Request Body),调用直接吃 400 错误。最简修复:把 RestClient、WebClient 底下的客户端换成 Jetty。

报错现场:vLLM 回 400,说 body 字段缺失

控制台里第一条报错长这样:

400 - {"object":"error","message":"[{'type': 'missing', 'loc': ('body',), 'msg': 'Field required', 'input': None}]","type":"BadRequestError","param":null,"code":400}

人话翻译:vLLM 没收到任何一个字节的请求体(input 是 None),它把这次请求当成“空请求”,按缺字段拒掉了。和模型参数无关——body 根本没送到。

我们当时跑的就是这种 DeepSeek 多轮对话,第一轮请求就 400,第二轮根本没发出去。

排查回放:从 URL 密钥查到分块传输编码

先怀疑的是配置。baseUrl、apiKey、模型名逐项核对,都对得上。顺手用 curl 直连同一个 vLLM 端点,完整响应回来了。配置排除。

再改调用方式:切非流式、调 temperature 这类参数,400 依旧。请求参数也排除。

转折点是 vLLM 那侧的日志:input: None。服务端确实没收到 body。嫌疑挪到传输层,我拉出 Wireshark 看了 Spring AI 发出的原始请求。

绕了一圈发现,请求头里是 Transfer-Encoding: chunked,而不是 Content-Length。body 被拆成块发出去了。坑就在这:vLLM 的解析器只认事先声明长度的请求,chunked 这种它直接当空 body 处理,报错发生在模型被调用之前。

原理拆解:Content-Length 与分块传输编码的传输层差异

说白了,两条路径差一行请求头。

curl 的 body 是内存里现成的字节,长度提前知道,头里写 Content-Length: 312,body 整包送达。Spring AI 的客户端边序列化边发,长度不确定时就切到分块传输,把 body 拆成一块块发,末尾加结束符。

类比一下快递:整包直发,封条上写了总重;分块传输是一车车拉,最后一车写“运完”。vLLM 只认封条,见不到封条就当空包裹拒收。

再看报错格式,是 pydantic 的校验输出:vLLM 的 API 层拿到空 body,校验器按必填字段缺失处理。所以这个 400 不是“参数错”,是“信封格式不同”——东西在,对方没收到。

动手修复:RestClient 和 WebClient 换 Jetty 客户端

第一步,加 Jetty 客户端及其响应式适配器。后者是给 WebClient 复用 Jetty 连接池的:

<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-client</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-reactive-httpclient</artifactId> </dependency>

第二步,把 RestClient 的请求工厂换成 JettyClientHttpRequestFactory。非流式 chat() 调用走的是同步 RestClient,换完它会回到带 Content-Length 的发送方式。

第三步,把 WebClient 的连接器换成 JettyClientHttpConnector。流式调用走这条路,两处都得换,只换一处会在另一条链路再次 400。

完整配置如下,baseUrl 和 apiKey 按你的 vLLM 地址填:

OpenAiApi openAiApi = OpenAiApi.builder() .baseUrl("http://localhost:8000/v1") .apiKey("EMPTY") .restClientBuilder(RestClient.builder() .requestFactory(new JettyClientHttpRequestFactory())) .webClientBuilder(WebClient.builder() .clientConnector(new JettyClientHttpConnector())) .build();

避坑清单:自部署 OpenAI 兼容服务的 400 排查检查项

☐ 报错带 Field required 且 input: None:先认定 body 没送达,别去动模型参数 ☐ 同端点 curl 直连一次:直连通、Spring AI 不通,嫌疑收窄到客户端传输层 ☐ 抓包看请求头有没有 Transfer-Encoding: chunked,这是确认根因的关键证据 ☐ 对接自部署服务(vLLM、LM Studio 之类),先试换 HTTP 客户端,再回头调参数 ☐ 关注 vLLM 上游对分块传输的支持修复,修复落地后 Jetty 这套依赖可以撤掉,回归默认客户端

【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 23:22:58

用 BabelDOC 一条命令保留排版完成 PDF 中英互译

用 BabelDOC 一条命令保留排版完成 PDF 中英互译 【免费下载链接】BabelDOC Yet Another Document Translator 项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC BabelDOC 是一个开源的 PDF 智能翻译工具&#xff0c;能把英文论文、技术文档翻译成中文&#…

作者头像 李华
网站建设 2026/9/18 23:20:01

Chart.js 轴标签技术指南:轴标题配置与自定义刻度格式

Chart.js 轴标签技术指南&#xff1a;轴标题配置与自定义刻度格式 【免费下载链接】Chart.js Simple HTML5 Charts using the tag项目地址: https://gitcode.com/gh_mirrors/ch/Chart.js 当使用 Chart.js 创建图表时&#xff0c;为了让查看者理解正在查看的数据含义&#…

作者头像 李华
网站建设 2026/9/18 23:19:51

Sunshine 零成本串流实测:书房的显卡,客厅电视也能跑

Sunshine 零成本串流实测&#xff1a;书房的显卡&#xff0c;客厅电视也能跑 【免费下载链接】Sunshine Self-hosted game stream host for Moonlight. 项目地址: https://gitcode.com/GitHub_Trending/su/Sunshine 周六下午瘫在沙发上想打两把 3A&#xff0c;主机却锁在…

作者头像 李华
网站建设 2026/9/18 23:17:45

观察 Agent 测试时算力扩展,TaoToken Key 只负责调用凭据吗

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华