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.*; 019import com.agentsflex.core.model.chat.ChatConfig; 020import com.agentsflex.core.model.chat.tool.Tool; 021import com.agentsflex.core.model.chat.tool.Parameter; 022import com.agentsflex.core.util.CollectionUtil; 023import com.agentsflex.core.util.ImageUtil; 024import com.agentsflex.core.util.Maps; 025import com.agentsflex.core.util.StringUtil; 026import com.alibaba.fastjson2.JSON; 027 028import java.util.*; 029 030public class OpenAIChatMessageSerializer implements ChatMessageSerializer { 031 032 /** 033 * 将消息列表序列化为模型所需的聊天消息数组格式。 034 * 例如 OpenAI 的 [{"role": "user", "content": "..."}, ...] 035 * 036 * @param messages 消息列表,不可为 null 037 * @return 序列化后的消息数组,若输入为空则返回空列表 038 */ 039 @Override 040 public List<Map<String, Object>> serializeMessages(List<Message> messages, ChatConfig config) { 041 if (messages == null || messages.isEmpty()) { 042 return null; 043 } 044 045 return buildMessageList(messages, config); 046 } 047 048 protected List<Map<String, Object>> buildMessageList(List<Message> messages, ChatConfig config) { 049 List<Map<String, Object>> messageList = new ArrayList<>(messages.size()); 050 messages.forEach(message -> { 051 Map<String, Object> objectMap = new HashMap<>(2); 052 if (message instanceof UserMessage) { 053 buildUserMessageObject(objectMap, (UserMessage) message, config); 054 } else if (message instanceof AiMessage) { 055 buildAIMessageObject(objectMap, (AiMessage) message, config); 056 } else if (message instanceof SystemMessage) { 057 buildSystemMessageObject(objectMap, (SystemMessage) message, config); 058 } else if (message instanceof ToolMessage) { 059 buildToolMessageObject(objectMap, (ToolMessage) message, config); 060 } 061 messageList.add(objectMap); 062 }); 063 return messageList; 064 } 065 066 protected void buildToolMessageObject(Map<String, Object> objectMap, ToolMessage message, ChatConfig config) { 067 if (config.isSupportToolMessage()) { 068 objectMap.put("role", "tool"); 069 objectMap.put("content", message.getTextContent()); 070 objectMap.put("tool_call_id", message.getToolCallId()); 071 } 072 // 部分模型(如 DeepSeek V3)不支持原生 tool message 格式, 073 // 此处将 tool message 转换为 system message 格式以确保兼容性 074 else { 075 objectMap.put("role", "system"); 076 Map<String, Object> contentMap = new LinkedHashMap<>(); 077 contentMap.put("tool_call_id", message.getToolCallId()); 078 contentMap.put("content", message.getTextContent()); 079 objectMap.put("content", JSON.toJSONString(contentMap)); 080 } 081 } 082 083 protected void buildSystemMessageObject(Map<String, Object> objectMap, SystemMessage message, ChatConfig config) { 084 objectMap.put("role", "system"); 085 objectMap.put("content", message.getTextContent()); 086 } 087 088 protected void buildUserMessageObject(Map<String, Object> objectMap, UserMessage message, ChatConfig config) { 089 objectMap.put("role", "user"); 090 objectMap.put("content", buildUserMessageContent(message, config)); 091 } 092 093 protected void buildAIMessageObject(Map<String, Object> objectMap, AiMessage message, ChatConfig config) { 094 objectMap.put("role", "assistant"); 095 objectMap.put("content", message.getTextContent()); 096 097 List<ToolCall> calls = message.getToolCalls(); 098 if (calls != null && !calls.isEmpty()) { 099 if (config.isSupportToolMessage()) { 100 objectMap.put("content", ""); // 清空 content,在某模型下,会把思考的部分当做 content 的部分 101 buildAIMessageToolCalls(objectMap, calls, false); 102 103 // 兼容性处理,在 ToolMessage 中,需要将 reasoning_content 添加到 payload 中,比如 deepseek 模型 104 if (config.isNeedReasoningContentForToolMessage() && StringUtil.hasText(message.getReasoningContent())) { 105 objectMap.put("reasoning_content", message.getReasoningContent()); 106 } 107 } else { 108 objectMap.put("role", "system"); 109 buildAIMessageToolCalls(objectMap, calls, true); 110 } 111 } 112 } 113 114 protected void buildAIMessageToolCalls(Map<String, Object> objectMap, List<ToolCall> calls, boolean buildToContent) { 115 List<Map<String, Object>> toolCalls = new ArrayList<>(); 116 for (ToolCall call : calls) { 117 Maps toolCall = new Maps(); 118 toolCall.set("id", call.getId()) 119 .set("type", "function") 120 .set("function", Maps.of("name", call.getName()) 121 .set("arguments", call.getArguments()) 122 ); 123 toolCalls.add(toolCall); 124 } 125 126 if (buildToContent) { 127 objectMap.put("content", JSON.toJSONString(toolCalls)); 128 } else { 129 objectMap.put("tool_calls", toolCalls); 130 } 131 } 132 133 134 protected Object buildUserMessageContent(UserMessage userMessage, ChatConfig config) { 135 String content = userMessage.getTextContent(); 136 List<String> imageUrls = userMessage.getImageUrls(); 137 List<String> audioUrls = userMessage.getAudioUrls(); 138 List<String> videoUrls = userMessage.getVideoUrls(); 139 140 if (CollectionUtil.hasItems(imageUrls) || CollectionUtil.hasItems(audioUrls) || CollectionUtil.hasItems(videoUrls)) { 141 142 List<Map<String, Object>> messageContent = new ArrayList<>(); 143 messageContent.add(Maps.of("type", "text").set("text", content)); 144 145 if (CollectionUtil.hasItems(imageUrls)) { 146 for (String url : imageUrls) { 147 if (config.isSupportImageBase64Only() 148 && url.toLowerCase().startsWith("http")) { 149 url = ImageUtil.imageUrlToDataUri(url); 150 } 151 messageContent.add(Maps.of("type", "image_url").set("image_url", Maps.of("url", url))); 152 } 153 } 154 155 if (CollectionUtil.hasItems(audioUrls)) { 156 for (String url : audioUrls) { 157 messageContent.add(Maps.of("type", "audio_url").set("audio_url", Maps.of("url", url))); 158 } 159 } 160 161 if (CollectionUtil.hasItems(videoUrls)) { 162 for (String url : videoUrls) { 163 messageContent.add(Maps.of("type", "video_url").set("video_url", Maps.of("url", url))); 164 } 165 } 166 167 return messageContent; 168 } else { 169 return content; 170 } 171 } 172 173 174 /** 175 * 将函数定义列表序列化为模型所需的工具(tools)或函数(functions)格式。 176 * 例如 OpenAI 的 [{"type": "function", "function": {...}}, ...] 177 * 178 * @param tools 函数定义列表,可能为 null 或空 179 * @return 序列化后的函数定义数组,若输入为空则返回空列表 180 */ 181 @Override 182 public List<Map<String, Object>> serializeTools(List<Tool> tools, ChatConfig config) { 183 if (tools == null || tools.isEmpty()) { 184 return null; 185 } 186 187 // 大模型不支持 Function Calling 188 if (config != null && !config.isSupportTool()) { 189 return null; 190 } 191 192 return buildToolList(tools); 193 } 194 195 196 protected List<Map<String, Object>> buildToolList(List<Tool> tools) { 197 List<Map<String, Object>> functionList = new ArrayList<>(); 198 for (Tool tool : tools) { 199 Map<String, Object> functionRoot = new HashMap<>(); 200 functionRoot.put("type", "function"); 201 202 Map<String, Object> functionObj = new HashMap<>(); 203 functionRoot.put("function", functionObj); 204 205 functionObj.put("name", tool.getName()); 206 functionObj.put("description", tool.getDescription()); 207 208 209 Map<String, Object> parametersObj = new HashMap<>(); 210 functionObj.put("parameters", parametersObj); 211 parametersObj.put("type", "object"); 212 213 Map<String, Object> propertiesObj = new HashMap<>(); 214 parametersObj.put("properties", propertiesObj); 215 216 addParameters(tool.getParameters(), propertiesObj, parametersObj); 217 218 functionList.add(functionRoot); 219 } 220 221 return functionList; 222 } 223 224 protected void addParameters(Parameter[] parameters, Map<String, Object> propertiesObj, Map<String, Object> parametersObj) { 225 if (parameters == null || parameters.length == 0) { 226 return; 227 } 228 List<String> requiredProperties = new ArrayList<>(); 229 for (Parameter parameter : parameters) { 230 Map<String, Object> parameterObj = new HashMap<>(); 231 parameterObj.put("type", parameter.getType()); 232 parameterObj.put("description", parameter.getDescription()); 233 parameterObj.put("enum", parameter.getEnums()); 234 if (parameter.isRequired()) { 235 requiredProperties.add(parameter.getName()); 236 } 237 238 // 优先处理 properties 字段(来自 @ToolParam 注解解析) 239 Map<String, Object> properties = parameter.getProperties(); 240 if (properties != null && !properties.isEmpty()) { 241 parameterObj.put("properties", properties); 242 } 243 // 原有逻辑:处理 children(兼容旧代码) 244 else { 245 List<Parameter> children = parameter.getChildren(); 246 if (children != null && !children.isEmpty()) { 247 if ("object".equalsIgnoreCase(parameter.getType())) { 248 Map<String, Object> childrenObj = new HashMap<>(); 249 parameterObj.put("properties", childrenObj); 250 addParameters(children.toArray(new Parameter[0]), childrenObj, parameterObj); 251 } 252 if ("array".equalsIgnoreCase(parameter.getType())) { 253 Map<String, Object> itemsObj = new HashMap<>(); 254 parameterObj.put("items", itemsObj); 255 handleArrayItems(children, itemsObj); 256 } 257 } 258 } 259 260 propertiesObj.put(parameter.getName(), parameterObj); 261 } 262 263 if (!requiredProperties.isEmpty()) { 264 parametersObj.put("required", requiredProperties); 265 } 266 } 267 268 protected void handleArrayItems(List<Parameter> children, Map<String, Object> itemsObj) { 269 if (children.size() == 1 && children.get(0).getName() == null) { 270 // 单值数组,数组元素是基础类型 271 Parameter firstChild = children.get(0); 272 itemsObj.put("type", firstChild.getType()); 273 itemsObj.put("description", firstChild.getDescription()); 274 itemsObj.put("enum", firstChild.getEnums()); 275 // 如果基础类型本身也是数组,需要递归处理 276 List<Parameter> grandchildren = firstChild.getChildren(); 277 if (grandchildren != null && !grandchildren.isEmpty()) { 278 if ("array".equalsIgnoreCase(firstChild.getType())) { 279 Map<String, Object> nestedItemsObj = new HashMap<>(); 280 itemsObj.put("items", nestedItemsObj); 281 handleArrayItems(grandchildren, nestedItemsObj); 282 } else if ("object".equalsIgnoreCase(firstChild.getType())) { 283 Map<String, Object> nestedProperties = new HashMap<>(); 284 itemsObj.put("properties", nestedProperties); 285 addParameters(grandchildren.toArray(new Parameter[0]), nestedProperties, itemsObj); 286 } 287 } 288 } else { 289 // 复杂数组,数组元素是对象或其他复杂类型 290 Map<String, Object> tempProperties = new HashMap<>(); 291 addParameters(children.toArray(new Parameter[0]), tempProperties, itemsObj); 292 293 if (!tempProperties.isEmpty()) { 294 itemsObj.put("type", "object"); 295 itemsObj.put("properties", tempProperties); 296 } 297 } 298 } 299} 300