001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.model.client; 017 018import com.agentsflex.core.message.Message; 019import com.agentsflex.core.message.UserMessage; 020import com.agentsflex.core.model.chat.ChatConfig; 021import com.agentsflex.core.model.chat.ChatOptions; 022import com.agentsflex.core.prompt.Prompt; 023import com.agentsflex.core.util.Maps; 024import com.agentsflex.core.util.MessageUtil; 025 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030public class OpenAIChatRequestSpecBuilder implements ChatRequestSpecBuilder { 031 032 protected ChatMessageSerializer chatMessageSerializer; 033 034 public OpenAIChatRequestSpecBuilder() { 035 this(new OpenAIChatMessageSerializer()); 036 } 037 038 public OpenAIChatRequestSpecBuilder(ChatMessageSerializer chatMessageSerializer) { 039 this.chatMessageSerializer = chatMessageSerializer; 040 } 041 042 @Override 043 public ChatRequestSpec buildRequest(Prompt prompt, ChatOptions options, ChatConfig config) { 044 045 String url = buildRequestUrl(prompt, options, config); 046 Map<String, String> headers = buildRequestHeaders(prompt, options, config); 047 String body = buildRequestBody(prompt, options, config); 048 049 boolean retryEnabled = options.getRetryEnabledOrDefault(config.isRetryEnabled()); 050 int retryCountOrDefault = options.getRetryCountOrDefault(config.getRetryCount()); 051 int retryInitialDelayMsOrDefault = options.getRetryInitialDelayMsOrDefault(config.getRetryInitialDelayMs()); 052 053 return new ChatRequestSpec(url, headers, body, retryEnabled ? retryCountOrDefault : 0, retryEnabled ? retryInitialDelayMsOrDefault : 0); 054 } 055 056 protected String buildRequestUrl(Prompt prompt, ChatOptions options, ChatConfig config) { 057 return config.getFullUrl(); 058 } 059 060 protected Map<String, String> buildRequestHeaders(Prompt prompt, ChatOptions options, ChatConfig config) { 061 Map<String, String> headers = new HashMap<>(); 062 headers.put("Content-Type", "application/json"); 063 headers.put("Authorization", "Bearer " + config.getApiKey()); 064 return headers; 065 } 066 067 068 protected String buildRequestBody(Prompt prompt, ChatOptions options, ChatConfig config) { 069 List<Message> messages = prompt.getMessages(); 070 UserMessage userMessage = MessageUtil.findLastUserMessage(messages); 071 072 Maps baseBodyJsonMap = buildBaseParamsOfRequestBody(prompt, options, config); 073 074 Maps bodyJsonMap = baseBodyJsonMap 075 .set("messages", chatMessageSerializer.serializeMessages(messages, config)) 076 .setIfNotEmpty("tools", chatMessageSerializer.serializeTools(prompt, config)) 077 .setIfContainsKey("tools", "tool_choice", prompt.getToolChoice()); 078 079 if (options.isStreaming() && options.getIncludeUsageOrDefault(true)) { 080 bodyJsonMap.set("stream_options", Maps.of("include_usage", true)); 081 } 082 083 buildThinkingBody(options, config, bodyJsonMap); 084 085 if (options.getExtraBody() != null) { 086 bodyJsonMap.putAll(options.getExtraBody()); 087 } 088 089 return bodyJsonMap.toJSON(); 090 } 091 092 protected void buildThinkingBody(ChatOptions options, ChatConfig config, Maps bodyJsonMap) { 093 if (!config.isSupportThinking()) { 094 return; 095 } 096 Boolean thinkingEnabled = options.getThinkingEnabled(); 097 if (thinkingEnabled == null) { 098 return; 099 } 100 101 String thinkingProtocol = config.getThinkingProtocol(); 102 if (thinkingProtocol == null || "none".equals(thinkingProtocol)) { 103 return; 104 } 105 106 switch (thinkingProtocol) { 107 case "qwen": 108 bodyJsonMap.set("enable_thinking", thinkingEnabled); 109 break; 110 case "deepseek": 111 bodyJsonMap.set("thinking", Maps.of("type", thinkingEnabled ? "enabled" : "disabled")); 112 break; 113 case "ollama": 114 bodyJsonMap.set("thinking", thinkingEnabled); 115 default: 116 // do nothing 117 } 118 } 119 120 121 protected Maps buildBaseParamsOfRequestBody(Prompt prompt, ChatOptions options, ChatConfig config) { 122 return Maps.of("model", options.getModelOrDefault(config.getModel())) 123 .setIf(options.isStreaming(), "stream", true) 124 .setIfNotNull("top_p", options.getTopP()) 125// .setIfNotNull("top_k", options.getTopK()) // openAI 不支持 top_k 标识 126 .setIfNotNull("temperature", options.getTemperature()) 127 .setIfNotNull("max_tokens", options.getMaxTokens()) 128 .setIfNotEmpty("stop", options.getStop()) 129 .setIfNotEmpty("response_format", options.getResponseFormat()); 130 131 } 132 133 public ChatMessageSerializer getChatMessageSerializer() { 134 return chatMessageSerializer; 135 } 136 137 public void setChatMessageSerializer(ChatMessageSerializer chatMessageSerializer) { 138 this.chatMessageSerializer = chatMessageSerializer; 139 } 140}