十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring AI 对接 vLLM 部署的 DeepSeek 报 400?一次 HTTP 分块传输的排查实录

Spring AI 对接 vLLM 部署的 DeepSeek 报 400?一次 HTTP 分块传输的排查实录 Spring AI 对接 vLLM 部署的 DeepSeek 报 400一次 HTTP 分块传输的排查实录【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai本文记录在 Spring AI 中使用 OpenAiChatModel 组件对接 vLLM 部署的 DeepSeek 大模型时遇到的 body 字段缺失 400 报错的完整排查过程并给出通过切换 Jetty HTTP 客户端彻底解决该问题的操作方法。一句话结论请求被分段发货服务端拒收先把答案放在前面这个 400 报错和你的 baseUrl、API Key、model 参数统统无关根因在 HTTP 请求体的传输方式上——vLLM 对分块传输编码chunked transfer encoding的请求解析有问题。分块传输编码是什么白话概念正常发 HTTP POST 时你相当于寄一个封好的整箱货物箱子上贴着总件数Content-Length 头收件方开箱即知全貌。而分块传输则是先说一声我要分几批寄具体几批不告诉你边写边发、发完为止。vLLM 这边只认整箱货见到分批发来的请求就当成没带货处理于是回你一个缺少必填字段的报错。这个坑最折磨人的地方在于同样的服务用 curl 直接打是通的只有走 Spring AI 才会挂。下面按我实际的排查顺序走一遍。场景还原curl 能通OpenAiChatModel 就报 400当时我手上有三样东西一台跑着 vLLM 的机器上面加载了 DeepSeek 系列模型、一份能正常对话的 curl 命令、以及一个标准的 Spring AI 工程。vLLM 服务健康、模型能推理这些我都先用 curl 验证过/v1/chat/completions端点返回完全正常。然后切到 Java 侧ChatModel chatModel OpenAiChatModel.builder() .openAiApi(openAiApi) // baseUrl 指向 vLLM 的 /v1apiKey 任意占位 .defaultOptions(OpenAiChatOptions.builder().model(deepseek).build()) .build();一调用直接炸出这段报错400 - {object:error,message:[{type: missing, loc: (body,), msg: Field required, input: None}],type:BadRequestError,param:null,code:400}注意看报错里的input: NonevLLM 认为它收到的 body 是空的。可我们明明把 prompt 塞进去了。这就是整个案子唯一的矛盾点——货物明明发了对方却说没收到。排查路线从外到内三层排除法第一层排除配置问题。逐项核对spring.ai.openai.base-url、api-key、model三个配置并且用 Postman 复现了一遍 Spring AI 发出去的请求header、body 完全一致——Postman 通了。这说明配置错了和vLLM 挂了两个嫌疑人当场出局。第二层排除框架序列化问题。我一度怀疑是 Jackson 把 body 序列化空了于是把请求体打到日志里检查JSON 完整、字段齐全。这条线也断了。第三层抓包看传输层。既然内容没问题问题只能出在内容的发送方式上。用mitmproxy抓了 Spring AI 发出的请求头关键差异出现了Transfer-Encoding: chunkedcurl 和 Postman 发出的请求是带Content-Length的整包传输而 Spring 默认 HTTP 栈在某些场景尤其是响应式链路或 body 由流式构造时会退化为分块传输。对比抓包结果的那一刻元凶就锁定了vLLM 无法正确解析 chunked 请求体。解决操作把默认 HTTP 客户端换成 Jetty确认是发货方式的问题之后解法就不多了要么让服务端学会收分块vLLM 侧的事要么换一家只发整箱货的快递公司。我们可控的是前者不可行、后者可行于是把 Spring AI 底层用的 HTTP 客户端换成 Jetty 实现。分两步走。第 1 步补上 Jetty 的两个依赖dependency groupIdorg.eclipse.jetty/groupId artifactIdjetty-client/artifactId /dependency dependency groupIdorg.eclipse.jetty/groupId artifactIdjetty-reactive-httpclient/artifactId /dependency一个是 Jetty 的阻塞式客户端对应RestClient另一个提供响应式连接器对应WebClient流式对话走的就是这条线。两个都要缺一个就会出现非流式好了、流式还是坏的半残状态。第 2 步构造 OpenAiApi 时显式指定这两套客户端OpenAiApi openAiApi OpenAiApi.builder() .baseUrl(chatConfig.getBaseUrl()) .apiKey(chatConfig.getApiKey()) .restClientBuilder(RestClient.builder() .requestFactory(new JettyClientHttpRequestFactory())) .webClientBuilder(WebClient.builder() .clientConnector(new JettyClientHttpConnector())) .build();这里restClientBuilder管的是同步调用chatModel.call()webClientBuilder管的是流式调用chatModel.stream()。改完再跑一遍Transfer-Encoding: chunked消失了400 报错随之消失DeepSeek 正常出词。避坑备忘一张表对齐检查点检查项预期踩坑表现curl 直连 vLLM/v1/chat/completions200通了却报 400说明问题在客户端侧报错 body 里input字段非 null为None说明服务端根本没收到 body抓包看请求头有Content-Length出现Transfer-Encoding: chunked即命中本坑Jetty 依赖两个都加只加jetty-client时流式调用仍会失败Spring AI 版本1.x 可用OpenAiApi.builder()2.0 已重构见下方提示最后这条值得展开一句当前仓库主分支2.0 线的 OpenAI 模块已经换了底座models/spring-ai-openai/ 下的OpenAiChatModel直接构建在 OpenAI 官方 Java SDK 之上HTTP 层是自带 OkHttp 实现的SpringAiOpenAiHttpClient见 models/spring-ai-openai/src/main/java/org/springframework/ai/openai/http/okhttp/不再有OpenAiApi.builder()这个入口。也就是说本文的 Jetty 换法适用于 1.x 版本如果你在 2.0 上复现类似症状思路仍然成立先抓包确认传输编码只是替换客户端的代码路径不同。给同坑的三句话400 加 body missing 不等于你没传 body先抓包看Transfer-Encoding再看服务端日志顺序反了会浪费一整天。curl 通、框架不通优先怀疑发送方式而非发送内容——序列化、参数绑定这些内容问题用日志就能查掉传输层问题只能靠抓包。临时方案换 Jetty救急没问题但记得盯一下 vLLM 侧对分块传输的支持进展上游修复后这套绕行的客户端替换就可以摘掉了。【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表