当前位置: 首页 > news >正文

Spring AI调用sglang模型返回HTTP 400分析处理

Spring AI调用sglang模型返回HTTP 400分析处理

一、问题描述

环境
  • java21
  • springboot: 3.5.5
  • spring-ai: 1.0.1
问题描述

Spring AI调用公司部署的sglang大模型返回错误HTTP 400 - {"object":"error","message":[{'type': 'missing', 'loc': ('body',), 'msg': 'Field required', 'input': None}]","type":"Bad Request","param":null,"code":400},但调用公网模型没问题,使用postman调用内网模型也没问题。

二、分析解决

使用wireshark捕包对比Spring AI发出的请求和postman请求差异,发现Spring AI的请求多了请求头Transfer-Encoding: chunked,postman加上此请求头后也报了同样的错误,猜测是公司部署的sglang不支持分块传输。

观察异常堆栈,有一个exchange(DefaultRestClient.java:540),看名字应该是发送请求的入口,从这里打断点调试。

  1. 定位到583行的clientRequest.execute(),继续追踪,发现底层调用的是jdk提供的HttpClientImpl
  2. 这个客户端使用了大量的异步操作,先定位到Exchange#responseAsyncImpl0,然后定位到Http1Request#headers,可见由requestPublisher#contentLength决定是否为流式请求,当值为-1时添加请求头Transfer-Encoding: chunked。而且在JdkClientHttpRequest#buildRequest方法中,自动排除了connection、content-length、expect、host、upgrade几个请求头。
  3. 向前追踪,requestPublisher构建于JdkClientHttpRequest#bodyPublisher,当请求头中存在contentLength时,才会构建包含contentLength的requestPublisher。这里推测当请求体为固定大小时,会添加contentLength请求头。
  4. 回到DefaultRestClient#createRequest,这里有两种客户端构建方式,一种是存在拦截器时通过InterceptionClientHttpRequestFactory构建,另一种是通过默认的JdkClientHttpRequestFactory
  5. JdkClientHttpRequest继承自AbstractStreamingClientHttpRequest,请求体使用流式传输。InterceptionClientHttpRequestFactory继承自AbstractBufferingClientHttpRequest,请求体会完全缓存,在executeInternal方法中会自动添加Content-Length请求头。
  6. DefaultRestClient构造方法打断点,向上一步步找到DefaultRestClientBuilderRestClientAutoConfiguration#restClientBuilderRestClientBuilderConfigurerRestClientAutoConfiguration#restClientBuilderConfigurer,发现注入参数ObjectProvider<RestClientCustomizer> customizerProvider,于是自定义Bean如下。
    import org.springframework.boot.web.client.RestClientCustomizer;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.client.RestClient;@Configuration
    public class RestClientConfig implements RestClientCustomizer {@Overridepublic void customize(RestClient.Builder restClientBuilder) {restClientBuilder.requestInterceptor((request, body, execution) -> execution.execute(request, body));}
    }
    
  7. 此时请求头中已经添加了Content-Length,但还是报错。

再次使用wireshark捕包,发现请求中多了请求头Connection: UpgradeUpgrade: h2c来协商升级到HTTP2,推测应该是sglang服务端不支持。定位到ExchangeImpl#get,这里会判断需要使用的HTTP版本,进一步定位到MultiExchange#version,发现会依次获取request.version、client.version直到取到非空值。request中的version追踪后发现是空值且无法定制,于是尝试修改client.version。

  1. client为HttpClientImpl类,打断点追踪,由JdkHttpClientBuilder#build构建,并支持通过customizer进行自定义。
  2. 继续向上追踪,找到JdkClientHttpRequestFacotryBuilder#createClientHttpRequestFactoryAbstractClientHttpRequestFactoryBuilder#build,这里有一组customizers通过LambdaSafe#callbacksJdkClientHttpReuqestFactory进行自定义。
  3. AbstractClientHttpRequestFactoryBuilder构造方法打打断点,向上追踪, 找到HttpClientAutoConfiguration#clientHttpRequestFactoryBuilder,发现注入参数ObjectProvider<ClientHttpRequestFactoryBuilzer<?>> clientHttpRequestFactoryBuilderCustomizers,于是自定义Bean如下。
    import org.springframework.boot.autoconfigure.http.client.ClientHttpRequestFactoryBuilderCustomizer;
    import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder;
    import org.springframework.context.annotation.Configuration;import java.net.http.HttpClient;@Configuration
    public class HttpClientConfig implements ClientHttpRequestFactoryBuilderCustomizer<JdkClientHttpRequestFactoryBuilder> {@Overridepublic JdkClientHttpRequestFactoryBuilder customize(JdkClientHttpRequestFactoryBuilder builder) {return builder.withHttpClientCustomizer(httpClientBuilder -> httpClientBuilder.version(HttpClient.Version.HTTP_1_1));}
    }
    

再测试已无HTTP2协商相关请求头,可以正常调用模型。


文章转载自:

http://9DCVvPlw.qwpdL.cn
http://khLcIZCk.qwpdL.cn
http://xnIpFCZ3.qwpdL.cn
http://2xM9FF7S.qwpdL.cn
http://i9S3iK1d.qwpdL.cn
http://2weaXxvJ.qwpdL.cn
http://4Y3QZcMa.qwpdL.cn
http://hKGOIGrK.qwpdL.cn
http://tO5yghSE.qwpdL.cn
http://NI4LLFMi.qwpdL.cn
http://J0vOq42H.qwpdL.cn
http://W7sNNBts.qwpdL.cn
http://lglDKDWo.qwpdL.cn
http://Nez6UgGf.qwpdL.cn
http://B8zUCi0c.qwpdL.cn
http://AWW7DGcf.qwpdL.cn
http://FWH7I7uR.qwpdL.cn
http://yRevlK2d.qwpdL.cn
http://uidI03tI.qwpdL.cn
http://hiPPVlOS.qwpdL.cn
http://zY5ZmaI4.qwpdL.cn
http://NjuAIDaD.qwpdL.cn
http://sMj1tb4R.qwpdL.cn
http://FHnoJsql.qwpdL.cn
http://mbT0bhXy.qwpdL.cn
http://E9lKKBAZ.qwpdL.cn
http://1TIU6O9M.qwpdL.cn
http://3hfA7THl.qwpdL.cn
http://qKex6nk0.qwpdL.cn
http://t9YGkjJh.qwpdL.cn
http://www.dtcms.com/a/364223.html

相关文章:

  • Unity开发保姆级教程:C#脚本+物理系统+UI交互,3大模块带你通关游戏开发
  • Oracle 10g 安装教程(详解,从exe安装到数据库配置,附安装包)​
  • 终于赶在考试券过期前把Oracle OCP证书考下来了!
  • 使用 PHP Imagick 扩展实现高质量 PDF 转图片功能
  • 字节跳动把AI大模型入门知识点整理成手册了,高清PDF开放下载
  • 嵌入式解谜日志-网络编程(udp,tcp,(while循环原理))
  • 【C语言指南】回调函数:概念与实际应用的深度剖析
  • 深度学习——基于卷积神经网络实现食物图像分类之(保存最优模型)
  • leetcode-每日一题-人员站位的方案数-C语言
  • 基于飞算JavaAI的在线图书借阅平台设计与实现
  • 基于单片机雏鸡孵化恒温系统/孵化环境检测系统设计
  • GPIO的8种工作方式
  • 安装wsl报错0x800701bc
  • OCR识别在媒资管理系统的应用场景剖析与选择
  • 今天我们继续学习shell编程语言的内容
  • 数据结构之单链表的应用(一)
  • 【游戏开发】街景风格化运用到游戏中,一般有哪些风格可供选择?
  • ThreadLocal深度解析:线程本地存储的奥秘
  • 【模型学习】LoRA的原理,及deepseek-vl2下LoRA实现
  • 【渗透测试】使用 UV 简化 Python 工具和脚本管理
  • TypeScript:unknown 类型
  • 博维智航(彭州)——面试
  • C++高频误区:vector对象到底在堆上还是栈上?
  • flume扩展实战:自定义拦截器、Source 与 Sink 全指南
  • 博主必备神器~
  • 解锁复杂工作流:Roo Code 中的「Boomerang Tasks」机制 : Orchestrator Mode 的使用
  • 用好AI,从提示词工程到上下文工程
  • ARM - GPIO 标准库开发
  • 算法模板(Java版)_非负整数的高精度运算
  • Linux之Shell编程(五)命令工具与sed编辑