在使用SpringAI时,会有调用第三方模型的需求,然而框架本身不支持开发者传入自定义请求参数,模型响应也就做不到高度自定义化。
在使用SpringAI做DouBao的模型接入时就遇到了该问题,DouBao虽然支持OpenAI格式,但是要控制模型是否思考时却不能定义相关参数,针对此问题,提出了一个临时的解决办法。
在SpringAI向 API 发送请求时进行拦截,拦截后将自定义的请求参数加上
该文档飞书文档地址:gx6ax5529hd.feishu.cn/wiki/ZaOAwG...
SpringAI version : 1.0.1
一、目标请求参数:
vbnet
curl --location "https://ark.cn-beijing.volces.com/api/v3/chat/completions" \
--header "Authorization: Bearer $ARK_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"model": "doubao-seed-1.6-250615",
"messages": [
{
"role": "user",
"content": [
{
"type":"text",
"text":"我要研究深度思考模型与非深度思考模型区别的课题,体现出我的专业性"
}
]
}
],
"thinking":{
"type":"disabled"
}
}'
由于SpringAI-OpenAI的限制,无法在请求中加上"thinking"字段,所以在调用时模型会默认为思考模式这也就导致我们等待的响应时间要比其他模型长。
二、拦截请求:
具体拦截请求代码如下:
java
/**
* 当 RestClient 写出 OpenAiApi.ChatCompletionRequest 时拦截并注入额外字段(例如 "thinking")。
*/
public class ChatCompletionRequestHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private static final Logger log = LoggerFactory.getLogger(ChatCompletionRequestHttpMessageConverter.class);
private final ObjectMapper mapper;
private final Function<OpenAiApi.ChatCompletionRequest, Map<String, Object>> extraParameter;
public ChatCompletionRequestHttpMessageConverter(ObjectMapper mapper,
Function<OpenAiApi.ChatCompletionRequest, Map<String, Object>> extraParameter) {
super(MediaType.APPLICATION_JSON);
this.mapper = mapper;
this.extraParameter = extraParameter;
}
@Override
protected boolean supports(Class<?> clazz) {
// 只对 OpenAiApi.ChatCompletionRequest 生效(也可以根据需要扩展)
return OpenAiApi.ChatCompletionRequest.class.isAssignableFrom(clazz);
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException {
// 反序列化交给 Jackson 来做(默认行为)
return mapper.readValue(inputMessage.getBody(), clazz);
}
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException {
if (object instanceof OpenAiApi.ChatCompletionRequest request) {
// 把请求对象转为 JSON 树
ObjectNode node = mapper.valueToTree(request);
// 计算要注入的额外参数
Map<String, Object> extras = extraParameter.apply(request);
if (extras != null && !extras.isEmpty()) {
node.set("thinking", mapper.valueToTree(extras));
}
// DEBUG: 打印最终请求体(仅用于调试,生产可改为 log.debug)
String finalJson = mapper.writeValueAsString(node);
log.info("[ChatCompletionRequestHttpMessageConverter] final request JSON: {}", finalJson);
// 写出最终 JSON 到输出流
mapper.writeValue(outputMessage.getBody(), node);
return;
}
// 不太可能走到这里(因为 supports 限制),但以防万一 fallback:
mapper.writeValue(outputMessage.getBody(), object);
}
}
三、注入RestClient.Builder Bean
同时需要将ChatCompletionRequestHttpMessageConverter类注入到RestClient.BuilderBean中,同时设置请求超时时间为3分钟
java
@Bean
@Primary
public RestClient.Builder customRestClientBuilder(ObjectMapper objectMapper) {
ClientHttpRequestFactorySettings settings = ClientHttpRequestFactorySettings.defaults()
.withConnectTimeout(Duration.ofMinutes(3))
.withReadTimeout(Duration.ofMinutes(5));
// 自定义 converter:注入 thinking: { type: "disabled" }
ChatCompletionRequestHttpMessageConverter customConverter =
new ChatCompletionRequestHttpMessageConverter(objectMapper,
request -> Map.of("type", "disabled"));
return RestClient.builder()
.requestFactory(ClientHttpRequestFactoryBuilder.reactor().build(settings))
.messageConverters(converters -> {
// converters 是默认 converters 的 List,往最前面插入,确保优先匹配
converters.add(0, customConverter);
// (可选)你也可以打印当前 converters 顺序帮助调试
System.out.println("Converters: " + converters);
});
}
四、模型调用
在OpenAiApi中Builder进一个.restClientBuilder参数,传入(restClientBuilder类)
java
private ChatClient getChatClient() {
OpenAiChatModel chatModel = openAiChatModel.mutate()
.openAiApi(OpenAiApi.builder()
.restClientBuilder(restClientBuilder) // 在此注入
.baseUrl(apiHost)
.completionsPath(completionPath)
.apiKey(apiKey)
.build())
.defaultOptions(OpenAiChatOptions.builder()
.model(modelName)
.build())
.build();
this.promptText = promptTemplateService.queryByDeptId(1L);
return ChatClient.builder(chatModel)
.build();
}
总结
上述只是临时的解决办法,需要完全解决还需要等到官方进行适配支持
同时SpringAI的Github上也有类似的issue,地址如下,大家可以去看看