SpringAI调用第三方模型增加自定义请求参数

在使用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,地址如下,大家可以去看看

github.com/spring-proj...

相关推荐
java1234_小锋2 小时前
RabbitMQ如何实现消息的持久化?
java·rabbitmq·java-rabbitmq
Aurora_NeAr2 小时前
对比Java学习Go——函数、集合和OOP
后端
UnnamedOrange2 小时前
有来前后端部署
前端·后端
Aurora_NeAr2 小时前
Golang并发编程及其高级特性
后端·go
tqs_123452 小时前
redis zset score的计算
java·算法
kaili2303 小时前
IDEA试用过期,无法登录,重置方法
java·intellij-idea
没有bug.的程序员3 小时前
Redis 内存优化与压缩:从原理到实战的完整指南
java·数据库·redis·内存优化·压缩内存
Yeats_Liao3 小时前
Java 软件测试(三):Mockito打桩与静态方法模拟解析
java·开发语言