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