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...

相关推荐
武子康3 小时前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
大模型码小白3 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司3 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地4 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
IT_陈寒5 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
段一凡-华北理工大学5 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
码事漫谈5 小时前
1999年的电商前夜和2026年的AI前夜 历史押韵但从不重复
后端
Conan在掘金6 小时前
鸿蒙报错速查:arkts-strict-typing Property does not exist on type 'object',object 装函数就炸,根因 + 真解法
后端
颜酱6 小时前
01 | 骨架搭建:FastAPI + Vue 跑通第一个 SSE 流式问答
前端·人工智能·后端
程序员cxuan7 小时前
Grok Build 被众人唾骂,结果老马把它开源了
人工智能·后端·程序员