From b5432682b9ee3bb141b8af0eb815cce3af9eda63 Mon Sep 17 00:00:00 2001 From: q1279335527 <1279335527@qq.com> Date: Sat, 20 Dec 2025 21:10:11 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E7=95=8C=E9=9D=A2=EF=BC=8C?= =?UTF-8?q?=E5=87=8F=E5=B0=91=E5=BD=95=E5=85=A5=E6=97=B6=E5=8F=91=E7=94=9F?= =?UTF-8?q?=E7=9A=84=E6=AD=A7=E4=B9=89=202.=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E4=BA=8E=E8=B1=86=E5=8C=85=E6=A8=A1=E5=9E=8B=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=EF=BC=8C=E7=BB=98=E5=9B=BE=EF=BC=8C=E8=A7=86=E9=A2=91=E7=94=9F?= =?UTF-8?q?=E6=88=90=E8=83=BD=E5=8A=9B=E7=9A=84=E6=94=AF=E6=8C=81=203.?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E4=BA=8EGemini=E5=A4=A7=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E8=81=8A=E5=A4=A9=E8=83=BD=E5=8A=9B=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81=204.=E6=B7=BB=E5=8A=A0=E5=AF=B9=E4=BA=8Epostgresql?= =?UTF-8?q?=E7=9A=84=E5=BF=AB=E9=80=9F=E8=BF=9E=E6=8E=A5=205.=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- apis/pom.xml | 4 + .../src/main/resources/db/schema.sql | 4 - .../static/html/pages/ModelParam.json | 15 +- .../main/resources/static/html/pages/env.json | 2 +- .../viewer/common/ai/model/entity/AiNode.java | 9 + .../common/ai/model/entity/ModelParam.java | 3 +- .../entity/purpose/BaseModelSetting.java | 1 + .../entity/purpose/ChatModelSetting.java | 1 + .../common/crud/core/service/BaseService.java | 2 + .../crud/core/service/BaseServiceImpl.java | 1 + executor/viewer-executor-blocks/pom.xml | 8 + .../blocks/constants/TypeConstants.java | 29 -- .../blocks/executor/AIChatExecutor.java | 6 +- .../executor/AbstractBlockExecutor.java | 12 +- .../FunctionCallingBlockExecutor.java | 178 --------- .../core/constants/TypeConstants.java | 2 + models/pom.xml | 2 + models/viewer-models-doubao/pom.xml | 28 ++ .../models/doubao/DoubaoChatBuilder.java | 55 +++ .../models/doubao/DoubaoImageBuilder.java | 40 ++ .../models/doubao/DoubaoVideoBuilder.java | 42 ++ .../doubao/ModelDoubaoAutoConfiguration.java | 11 + .../models/doubao/api/audio/AudioApi.java | 373 ++++++++++++++++++ .../models/doubao/api/audio/AudioModel.java | 105 +++++ .../models/doubao/api/audio/AudioOptions.java | 187 +++++++++ .../doubao/api/image/DoubaoImageApi.java | 176 +++++++++ .../doubao/api/image/DoubaoImageModel.java | 144 +++++++ .../doubao/api/image/DoubaoImageOptions.java | 92 +++++ .../models/doubao/api/video/VideoApi.java | 235 +++++++++++ .../models/doubao/api/video/VideoModel.java | 97 +++++ .../models/doubao/api/video/VideoOptions.java | 38 ++ .../doubao/api/video/VideoResponse.java | 77 ++++ .../models/doubao/constants/AIConstants.java | 13 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + models/viewer-models-gemini/pom.xml | 28 ++ .../viewer/models/gemini/AIConstants.java | 7 + .../models/gemini/GeminiChatBuilder.java | 56 +++ .../gemini/ModelGeminiAutoConfiguration.java | 11 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../viewer-modules-ds-jdbc/pom.xml | 2 + .../viewer-modules-ds-jdbc-drill/pom.xml | 32 ++ .../viewer-modules-ds-jdbc-pgsql/pom.xml | 33 ++ .../pgsql/DsJdbcPgsqlAutoConfiguration.java | 11 + .../ds/jdbc/pgsql/config/PgsqlDsConfig.java | 22 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../step/entity/block/BlockBodyEle.java | 6 + pom.xml | 16 +- sql/mysql/dv.sql | 4 - 49 files changed, 2000 insertions(+), 225 deletions(-) delete mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/constants/TypeConstants.java delete mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java create mode 100644 models/viewer-models-doubao/pom.xml create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoChatBuilder.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoImageBuilder.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoVideoBuilder.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/ModelDoubaoAutoConfiguration.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioApi.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioModel.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioOptions.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageApi.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageModel.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageOptions.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoApi.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoModel.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoOptions.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoResponse.java create mode 100644 models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/constants/AIConstants.java create mode 100644 models/viewer-models-doubao/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 models/viewer-models-gemini/pom.xml create mode 100644 models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/AIConstants.java create mode 100644 models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/GeminiChatBuilder.java create mode 100644 models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/ModelGeminiAutoConfiguration.java create mode 100644 models/viewer-models-gemini/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-drill/pom.xml create mode 100644 modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/pom.xml create mode 100644 modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/DsJdbcPgsqlAutoConfiguration.java create mode 100644 modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/config/PgsqlDsConfig.java create mode 100644 modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/README.md b/README.md index 0d7de93..97ddef2 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ kill 进程号 ### 创建MCP服务 1. 访问 `http://localhost:11791` 登录管理界面 2. 在"McpServer"中创建新McpServer端点 -3. 选择需要对外提供的的方法,同时设置服务名,SSE路径(如`/sse`),消息端点(如`//mcp/messages`)等信息 +3. 选择需要对外提供的的方法,同时设置服务名,SSE路径(如`/sse`),消息端点(如`/mcp/messages`)等信息 4. 保存后返回查询界面,点击"启动"按钮即可被mcp服务访问 ![mcp server验证截图](images/mcpservershow.png) diff --git a/apis/pom.xml b/apis/pom.xml index 95446f0..978d698 100644 --- a/apis/pom.xml +++ b/apis/pom.xml @@ -39,6 +39,10 @@ xyz.thoughtset.viewer viewer-modules-ds-jdbc-mysql + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc-pgsql + xyz.thoughtset.viewer viewer-modules-ds-jdbc-self diff --git a/apis/viewer-apis-client/src/main/resources/db/schema.sql b/apis/viewer-apis-client/src/main/resources/db/schema.sql index c5bdf30..865de75 100644 --- a/apis/viewer-apis-client/src/main/resources/db/schema.sql +++ b/apis/viewer-apis-client/src/main/resources/db/schema.sql @@ -356,10 +356,6 @@ CREATE TABLE IF NOT EXISTS modelparam ( updatedAt datetime NOT NULL, statesCode int(11) DEFAULT '200', model varchar(256) NOT NULL, - systemPrompt text, - maxMemory int(11) DEFAULT NULL, - maxTokens bigint(20) DEFAULT NULL, - temperature double DEFAULT NULL, paramJson text, purpose varchar(16) NOT NULL, setting text, diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json index f0f28c1..da04143 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json @@ -362,7 +362,10 @@ "maxRows": 20, "labelWidth": "var(--sizes-base-24)", "labelAlign": "top", - "mode": "normal" + "mode": "normal", + "hiddenOn": "${purpose != 'CHAT'}", + "clearValueOnHidden": true, + "visibleOn": "${purpose == 'CHAT'}" }, { "name": "modelArgs.temperature", @@ -382,7 +385,10 @@ "value": "", "precision": 1, "kilobitSeparator": false, - "max": 1 + "max": 1, + "hiddenOn": "${purpose != 'CHAT'}", + "clearValueOnHidden": true, + "visibleOn": "${purpose == 'CHAT'}" }, { "type": "service", @@ -404,7 +410,10 @@ "min": 0, "value": "", "precision": "", - "kilobitSeparator": false + "kilobitSeparator": false, + "hiddenOn": "${purpose != 'CHAT'}", + "clearValueOnHidden": true, + "visibleOn": "${purpose == 'CHAT'}" } ], "id": "u:7004050cb373", diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/env.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/env.json index 95f5057..4ed775a 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/env.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/env.json @@ -833,7 +833,7 @@ }, "sendOn": "${!ISEMPTY(id)}" }, - "debug": true + "debug": false } ], "actions": [ diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java index d078ce6..7322e80 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.common.core.entity.BaseMeta; import xyz.thoughtset.viewer.common.crud.core.annotation.ApiCRUDPower; @@ -30,4 +31,12 @@ public class AiNode extends BaseMeta { protected transient List defaultModelParams; + public String linkBaseUrlWithDefault(String defaultBaseUrl) { + if (StringUtils.hasText(this.baseUrl)) { + return this.baseUrl; + } + return defaultBaseUrl; + } + + } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java index ff3a2c6..21bbb1e 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java @@ -26,7 +26,8 @@ public class ModelParam extends BaseMeta { protected ModelPurposeEnum purpose; protected String setting; protected String paramJson; - protected Integer maxMemory = 8; +// @Deprecated +// protected Integer maxMemory = 8; //Options Value Map protected transient Map paramMap; protected transient BaseModelSetting modelArgs; diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java index a7cd2f8..959bb58 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java @@ -12,5 +12,6 @@ import lombok.NoArgsConstructor; * select specific model setting class according to different option classes. */ public class BaseModelSetting { + protected String reqUrl; protected String workWay;//select ModelOptions class } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java index ce0d5eb..23b5ea6 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java @@ -11,6 +11,7 @@ public class ChatModelSetting extends BaseModelSetting { protected String systemPrompt; protected Long maxTokens; protected Double temperature; + protected Integer maxMemory = 8; } diff --git a/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseService.java b/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseService.java index b4f8c25..f3a8622 100644 --- a/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseService.java +++ b/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseService.java @@ -24,4 +24,6 @@ public interface BaseService extends IService{ public Page selectPage(Page page, LinkedHashMap baseMap); List selectList(LinkedHashMap baseMap); + + } diff --git a/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseServiceImpl.java b/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseServiceImpl.java index 1592d75..f8cf782 100644 --- a/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseServiceImpl.java +++ b/commons/viewer-common-crud/viewer-common-crud-core/src/main/java/xyz/thoughtset/viewer/common/crud/core/service/BaseServiceImpl.java @@ -87,6 +87,7 @@ public class BaseServiceImpl , T extends IdMeta> extends return list(MPWrapperUtil.buildWrapper(convertValue(baseMap))); } + @SneakyThrows public T convertValue(LinkedHashMap baseMap) { return objectMapper.convertValue(baseMap,this.getEntityClass()); diff --git a/executor/viewer-executor-blocks/pom.xml b/executor/viewer-executor-blocks/pom.xml index 9b20e24..6e0f7e1 100644 --- a/executor/viewer-executor-blocks/pom.xml +++ b/executor/viewer-executor-blocks/pom.xml @@ -50,6 +50,14 @@ xyz.thoughtset.viewer viewer-models-zhipuai + + xyz.thoughtset.viewer + viewer-models-gemini + + + xyz.thoughtset.viewer + viewer-models-doubao + \ No newline at end of file diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/constants/TypeConstants.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/constants/TypeConstants.java deleted file mode 100644 index a843f78..0000000 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/constants/TypeConstants.java +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.thoughtset.viewer.executor.blocks.constants; - -import com.google.common.collect.ImmutableMap; - -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class TypeConstants { - public static final Map TYPE_MAP = new ImmutableMap.Builder() - .put("String", String.class) - .put("Boolean", Boolean.class) - .put("byte", Byte.class) - .put("Double", Double.class) - .put("Integer", Integer.class) - .put("Long", Long.class) - .put("Date", Date.class) - .build(); - - public static final List BASE_TYPE_LIST = TYPE_MAP.entrySet().stream() - .map( e -> { - HashMap map = new HashMap(); - map.put("title",e.getKey()); - map.put("value",e.getKey()); - return map; - }).collect(Collectors.toList()); -} diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java index 4dd7b21..2a93f70 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AIChatExecutor.java @@ -18,6 +18,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; import xyz.thoughtset.viewer.common.ai.model.entity.option.ExtraParamChatOptions; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.ChatModelSetting; import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; import xyz.thoughtset.viewer.modules.step.entity.AISupportBody; import xyz.thoughtset.viewer.modules.step.entity.BlockTypeEnum; @@ -47,8 +48,10 @@ public abstract class AIChatExecutor extends AbstractAI HashMap filterMap = filterDataAsMapForPrompt(body, params, parser, context); ModelParam modelParam = loadModelParam(body); + ChatModelSetting chatModelSetting = (ChatModelSetting) modelParam.getModelArgs(); + Integer maxTokens = chatModelSetting !=null ? chatModelSetting.getMaxMemory() : 8; MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder() - .maxMessages(modelParam.getMaxMemory()) + .maxMessages(maxTokens) .build(); ExtraParamChatOptions extraParam = new ExtraParamChatOptions(); ToolCallingChatOptions.Builder toolCallingBuilder = ToolCallingChatOptions.builder() @@ -111,6 +114,7 @@ public abstract class AIChatExecutor extends AbstractAI .build(); ChatOptions chatOptions = toolCallingBuilder.build(); UserMessage message = UserMessage.builder() + .media() .text(fillAndRenderPromptToStr(body, body.getUserMsg(), parser, context, filterMap)) .build(); // new UserMessage(body.getUserMsg(),params); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AbstractBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AbstractBlockExecutor.java index 01fc442..fa2a26c 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AbstractBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/AbstractBlockExecutor.java @@ -82,8 +82,16 @@ public abstract class AbstractBlockExecutor implements protected Object execNode(BlockBodyEle ele, Map params, ExpressionParser parser, StandardEvaluationContext context){ Map tmpMap = BlockArgsUtils.filterParams(ele, parser, context); - Object val = ele.eleWasFun()? blockExecutorManager.execBlocks(ele.getEleId(),tmpMap,parser): - executorManager.execute(ele.getEleId(),tmpMap); + Object val = null; + if (ele.eleWasFun()){ + val = blockExecutorManager.execBlocks(ele.getEleId(),tmpMap,parser); + } else if (ele.eleWasTool()) { +// val = blockExecutorManager.execToolOrScript(ele.getEleId(), tmpMap, parser); + } else if (ele.eleWasScript()) { +// val = blockExecutorManager.execToolOrScript(ele.getEleId(), tmpMap, parser); + } else { + val = executorManager.execute(ele.getEleId(),tmpMap); + } if (ObjectUtils.isEmpty(val) || !StringUtils.hasText(ele.getValNum())) { return val; } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java deleted file mode 100644 index 2857254..0000000 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java +++ /dev/null @@ -1,178 +0,0 @@ -package xyz.thoughtset.viewer.executor.blocks.executor; - -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; -import org.springframework.ai.chat.memory.MessageWindowChatMemory; -import org.springframework.ai.chat.messages.*; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.model.tool.ToolExecutionResult; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; -import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; -import xyz.thoughtset.viewer.modules.step.entity.FunctionCallBody; -import xyz.thoughtset.viewer.executor.blocks.tool.FunctionCallBlockToolCallbackProvider; -import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; -import xyz.thoughtset.viewer.modules.step.entity.block.BlockInfo; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; - - -@Slf4j -@Deprecated -public class FunctionCallingBlockExecutor extends AbstractAISupportBlockExecutor { -// @Autowired -// protected ModelParamDao modelParamDao; -// @Autowired -// protected ModelFactory modelFactory; - @Autowired - private ToolCallingManager toolCallingManager; - @Autowired - private FunctionCallBlockToolCallbackProvider provider; - - -//先选择函数,再加载参数执行函数 - @SneakyThrows - @Override - protected Object doQuery(BlockInfo block, FunctionCallBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { - ModelParam modelParam = loadModelParam(body); - ChatClient.Builder builder = modelFactory.clientBuilder(modelParam,null); - MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder() - .maxMessages(modelParam.getMaxMemory()) - .build(); -// ChatModelSetting settingObj = (ChatModelSetting) modelParam.getModelArgs(); - String limitSystemPrompt = - "选择适当的方法执行,由于部分参数存储在本地,对于非required参数,可以填入建议值或空值。预期值与方法执行结果不一致时,以方法结果为准。"; - String systemText = modelParam.chatSystemPrompt(); - if (body.getJsonType()!=null && body.getJsonType().booleanValue()){ -// systemText = """ -// 请只返回数组类型的JSON,不要任何额外说明,当后续要求与当前冲突时,以当前要求为准! -// 你是一个函数调用助手,请严格按照要求的格式返回结果,不要任何额外说明. -// 1.返回值必须是一个数组,数组的每一项是一个对象 -// 2.对象的key是函数参数名,value是对应的值 -// 3.如果没有值,请返回null,不要返回空字符串,不要省略该key -// 4.如果值是字符串,请确保值是合法的JSON字符串,如果值本身是一个JSON对象或数组,请直接返回该对象或数组,不要转义为字符串 -// 5.如果没有数据,请返回一个空数组,不要返回null,不要返回空对象,不要返回字符串 -// 6.请严格按照要求的格式返回,不要有任何多余的内容,不要有任何拼写错误,不要有任何语法错误""" - // 备用prompt2 -// """ -// 选择适当的方法执行。对于非必需参数,可使用建议值或空值(部分参数已本地存储)。若方法执行结果与预期值不一致,以方法执行结果为准。响应必须严格以JSON数组格式返回,不得包含任何其他内容。确保无拼写错误或语法错误。当后续要求与当前要求冲突时,以当前要求为准。 -// """ - systemText = limitSystemPrompt+""" - 最终结果必须按照JSON数组格式返回,不要有其余任何内容,不要有任何拼写错误,不要有任何语法错误,当后续要求与当前冲突时,以当前要求为准! - """ - + (StringUtils.hasText(systemText) ? systemText : ""); - } - String finalSystemText = systemText; - builder.defaultSystem(s->s.text(finalSystemText).params(params)); - List funs = body.getBodyEles(); - ToolCallingChatOptions.Builder toolCallingBuilder = ToolCallingChatOptions.builder() - .internalToolExecutionEnabled(false); - if (!ObjectUtils.isEmpty(funs)){ - Map funParamMap = funs.parallelStream().collect(Collectors.toMap( - BlockBodyEle::getEleId, Function.identity() - )); - builder.defaultToolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); - -// toolCallingBuilder = toolCallingBuilder.toolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); - } - ChatClient client = builder - .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) - .build(); - ChatOptions chatOptions = toolCallingBuilder.build(); -// ChatClient.ChatClientRequestSpec spec = client -// .prompt() -// .options(chatOptions) -// .toolContext(params) -// .user(body.getUserMsg()); - UserMessage message = UserMessage.builder() - .text(body.getUserMsg()) - .build(); -// new UserMessage(body.getUserMsg(),params); - String chatId = block.getId()+ UUID.randomUUID(); - saveMegToChatMemory(chatId, Collections.singletonList(message), chatMemory); - Prompt chatPrompt = new Prompt(chatMemory.get(chatId),chatOptions); -// ChatClient.CallResponseSpec callResponseSpec = spec.call(); -// String val = callResponseSpec.content(); - //ToolCallingChatClientAdvisor - // ListOutputConverter - ChatResponse chatResponse = client - .prompt(chatPrompt) - .toolContext(params).call().chatResponse(); - // 处理可能的多轮工具调用 - List messageList = new ArrayList<>(); - while (chatResponse.hasToolCalls()) { - // 获取AI助手的响应消息 - AssistantMessage assistantMessage = chatResponse.getResult().getOutput(); - log.debug("***" + assistantMessage.getText()); - - // 获取AI要求调用的工具列表 - List toolCalls = assistantMessage.getToolCalls(); - toolCalls.forEach(toolCall -> { - log.debug("*** 准备调用工具{}:{},参数:({})", toolCall.type(), toolCall.name(), toolCall.arguments()); - }); - - // 执行工具调用 - ToolExecutionResult toolExecutionResult = null; - try { - toolExecutionResult = toolCallingManager.executeToolCalls(chatPrompt, chatResponse); - } catch (Exception e) { - log.error("调用工具失败", e); - messageList.add(new AssistantMessage("调用工具失败:" + e.getMessage())); - break; - } - - List toolResultMessages = toolExecutionResult.conversationHistory(); - log.debug("*** 执行结果" + toolResultMessages); - - Message lastMessage = toolResultMessages.get(toolResultMessages.size() - 1); - if (lastMessage.getMessageType() == MessageType.TOOL) { - ToolResponseMessage toolMessage = (ToolResponseMessage) lastMessage; - toolMessage.getMetadata().put("toolArguments", toolCalls); - messageList.add(toolMessage); - - // 记录每个工具调用的详细信息 - for (ToolResponseMessage.ToolResponse resp : toolMessage.getResponses()) { - log.debug("*** {}#{}#{}",resp.id(),resp.name(),resp.responseData()); - } - } - saveMegToChatMemory(chatId, toolResultMessages, chatMemory); - chatPrompt = new Prompt(chatMemory.get(chatId), chatOptions); - chatResponse = client - .prompt(chatPrompt) - .call() - .chatResponse(); - } - - String val = chatResponse.getResult().getOutput().getText(); - log.debug("===结果:{}", val); - Object result; - - try { -// List results = callResponseSpec.entity(new ParameterizedTypeReference>() {}); - result = objectMapper.readValue(val, DATA_TYPE); - }catch (Exception e){ - if (!e.getClass().getName().equals("com.fasterxml.jackson.core.JsonParseException")){ - log.error("错误结果:{}", val); - e.printStackTrace(); - } - result = val; - } - - return result; - } - - - -} diff --git a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/constants/TypeConstants.java b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/constants/TypeConstants.java index 135d8b3..bd7f661 100644 --- a/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/constants/TypeConstants.java +++ b/executor/viewer-executor-core/src/main/java/xyz/thoughtset/viewer/executor/core/constants/TypeConstants.java @@ -2,6 +2,7 @@ package xyz.thoughtset.viewer.executor.core.constants; import com.google.common.collect.ImmutableMap; +import java.io.File; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -17,6 +18,7 @@ public class TypeConstants { .put("Integer", Integer.class) .put("Long", Long.class) .put("Date", Date.class) +// .put("File", File.class) .build(); public static final List BASE_TYPE_LIST = TYPE_MAP.entrySet().stream() diff --git a/models/pom.xml b/models/pom.xml index a90a505..0de0752 100644 --- a/models/pom.xml +++ b/models/pom.xml @@ -15,6 +15,8 @@ viewer-models-deepseek viewer-models-openai viewer-models-zhipuai + viewer-models-doubao + viewer-models-gemini diff --git a/models/viewer-models-doubao/pom.xml b/models/viewer-models-doubao/pom.xml new file mode 100644 index 0000000..fda7749 --- /dev/null +++ b/models/viewer-models-doubao/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + models + ${revision} + + + viewer-models-doubao + + + 17 + 17 + UTF-8 + + + + + org.springframework.ai + spring-ai-openai + ${springai.version} + + + + \ No newline at end of file diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoChatBuilder.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoChatBuilder.java new file mode 100644 index 0000000..5228754 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoChatBuilder.java @@ -0,0 +1,55 @@ +package xyz.thoughtset.viewer.models.doubao; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.model.ModelOptions; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.openai.api.ResponseFormat; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.entity.option.ExtraParamChatOptions; +import xyz.thoughtset.viewer.common.ai.model.factory.ChatModelBuilder; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +@Component +public class DoubaoChatBuilder extends ChatModelBuilder { + public DoubaoChatBuilder() { + super(AIConstants.MODEL_NAME); + } + + + @Override + public ChatModel buildMode(AiNode node, ModelParam modelParam, ModelOptions modelOptions) { + OpenAiChatOptions options = loadAndMergeModelOptions(modelParam, OpenAiChatOptions.class, modelOptions); + options.setModel(modelParam.getModel()); + String baseUrl = node.getBaseUrl(); + if (!StringUtils.hasText(baseUrl)) { + baseUrl = AIConstants.DEFAULT_BASE_URL; + } + OpenAiApi api = OpenAiApi.builder() + .baseUrl(baseUrl) + .completionsPath(AIConstants.CHAT_URL) + .apiKey(node.getApiKey()).build(); + ChatModel chatModel = OpenAiChatModel.builder() + .defaultOptions(options) + .openAiApi(api) + .build(); + return chatModel; + } + + @Override + public void mergeOptions(ModelOptions source, ModelOptions extraOptions) { + if (!(extraOptions instanceof ExtraParamChatOptions)) return ; + OpenAiChatOptions options = (OpenAiChatOptions) source; + ExtraParamChatOptions extraParamChatOptions = (ExtraParamChatOptions) extraOptions; + if (extraParamChatOptions.responseUsedJsonObject()){ + options.setResponseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT).build()); + } + } + + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoImageBuilder.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoImageBuilder.java new file mode 100644 index 0000000..3ef6646 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoImageBuilder.java @@ -0,0 +1,40 @@ +package xyz.thoughtset.viewer.models.doubao; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.model.Model; +import org.springframework.ai.model.ModelOptions; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.factory.ImageModelBuilder; +import xyz.thoughtset.viewer.models.doubao.api.image.*; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +@Slf4j +@Component +public class DoubaoImageBuilder extends ImageModelBuilder { + public DoubaoImageBuilder() { + super(AIConstants.MODEL_NAME); + } + + + @Override + public Model buildMode(AiNode node, ModelParam modelParam, ModelOptions modelOptions) { + DoubaoImageOptions options = loadAndMergeModelOptions(modelParam, DoubaoImageOptions.class, modelOptions); + options.setModel(modelParam.getModel()); + String baseUrl = node.getBaseUrl(); + if (!StringUtils.hasText(baseUrl)) { + baseUrl = AIConstants.DEFAULT_BASE_URL; + } + DoubaoImageApi api = DoubaoImageApi.builder() + .baseUrl(baseUrl) + .apiKey(node.getApiKey()) + .build(); + DoubaoImageModel model = new DoubaoImageModel(api, options,buildDefaultLongRetryTemplate()); + return model; + } + + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoVideoBuilder.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoVideoBuilder.java new file mode 100644 index 0000000..d6cfed9 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/DoubaoVideoBuilder.java @@ -0,0 +1,42 @@ +package xyz.thoughtset.viewer.models.doubao; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.model.Model; +import org.springframework.ai.model.ModelOptions; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.factory.VideoModelBuilder; +import xyz.thoughtset.viewer.models.doubao.api.video.VideoApi; +import xyz.thoughtset.viewer.models.doubao.api.video.VideoModel; +import xyz.thoughtset.viewer.models.doubao.api.video.VideoOptions; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +@Slf4j +@Component +public class DoubaoVideoBuilder extends VideoModelBuilder { + public DoubaoVideoBuilder() {super(AIConstants.MODEL_NAME);} + + + @Override + public Model buildMode(AiNode node, ModelParam modelParam, ModelOptions modelOptions) { + VideoOptions options = loadAndMergeModelOptions(modelParam, VideoOptions.class, modelOptions); + options.setModel(modelParam.getModel()); + String baseUrl = node.getBaseUrl(); + if (!StringUtils.hasText(baseUrl)) { + baseUrl = AIConstants.DEFAULT_BASE_URL; + } + VideoApi api = VideoApi.builder() + .baseUrl(baseUrl) + .apiKey(node.getApiKey()) + .restClientBuilder(createLoggingRestClient()) + .build(); + VideoModel model = new VideoModel(api, options,buildDefaultLongRetryTemplate(),objectMapper); + return model; + } + + + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/ModelDoubaoAutoConfiguration.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/ModelDoubaoAutoConfiguration.java new file mode 100644 index 0000000..9cbcf7a --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/ModelDoubaoAutoConfiguration.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.models.doubao; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class ModelDoubaoAutoConfiguration { +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioApi.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioApi.java new file mode 100644 index 0000000..4c1f8bd --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioApi.java @@ -0,0 +1,373 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.audio; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.ai.model.ApiKey; +import org.springframework.ai.model.NoopApiKey; +import org.springframework.ai.model.SimpleApiKey; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +import java.util.List; +import java.util.function.Consumer; + +public class AudioApi { + + private final RestClient restClient; + + /** + * Create a new audio api. + * @param baseUrl api base URL. + * @param apiKey OpenAI apiKey. + * @param headers the http headers to use. + * @param restClientBuilder RestClient builder. + * @param responseErrorHandler Response error handler. + */ + public AudioApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { + if (!StringUtils.hasText(baseUrl)) { + baseUrl = AIConstants.DEFAULT_BASE_URL; + } + Consumer authHeaders = h -> h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); + + // @formatter:off + this.restClient = restClientBuilder.clone() + .baseUrl(baseUrl) + .defaultHeaders(authHeaders) + .defaultStatusHandler(responseErrorHandler) + .defaultRequest(requestHeadersSpec -> { + if (!(apiKey instanceof NoopApiKey)) { + requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue()); + } + }) + .build(); + + } + + + public static Builder builder() { + return new Builder(); + } + + /** + * Request to generates audio from the input text. + * @param requestBody The request body. + * @return Response entity containing the audio binary. + */ + public ResponseEntity createSpeech(SpeechRequest requestBody) { + return this.restClient.post().uri(AIConstants.TTS_URL).body(requestBody).retrieve().toEntity(byte[].class); + } + + + + public ResponseEntity createTranscription(TranscriptionRequest requestBody) { + + MultiValueMap multipartBody = new LinkedMultiValueMap<>(); + multipartBody.add("file", new ByteArrayResource(requestBody.file()) { + + @Override + public String getFilename() { + return requestBody.fileName(); + } + }); + multipartBody.add("model", requestBody.model()); + multipartBody.add("temperature", requestBody.temperature()); + + return this.restClient.post() + .uri(AIConstants.ASR_URL) + .body(multipartBody) + .retrieve() + .toEntity(StructuredResponse.class); + } + + + /** + * Request to generates audio from the input text. Reference: + * Create + * Speech + * + * @param model The model to use for generating the audio. One of the available Audio + * models: audio-1, audio-1-hd, or gpt-4o-mini-audio. + * @param input The input text to synthesize. Must be at most 4096 tokens long. + * @param voice The voice to use for synthesis. One of the available voices for the + * chosen model: 'alloy', 'ash', 'ballad', 'coral', 'echo', 'fable', 'onyx', 'nova', + * 'sage', 'shimmer', and 'verse'. + * @param responseFormat The format to audio in. Supported formats are mp3, opus, aac, + * flac, wav, and pcm. Defaults to mp3. + * @param speed The speed of the voice synthesis. The acceptable range is from 0.25 + * (slowest) to 4.0 (fastest). Does not work with gpt-4o-mini-audio. + */ + @JsonInclude(Include.NON_NULL) + public record SpeechRequest( + @JsonProperty("model") String model, + @JsonProperty("input") String input, + @JsonProperty("voice") String voice, + @JsonProperty("response_format") String responseFormat, + @JsonProperty("speed") Double speed) { + // @formatter:on + + public static Builder builder() { + return new Builder(); + } + + + + /** + * Builder for the SpeechRequest. + */ + public static final class Builder { + + private String model = "cogtts"; + + private String input; + + private String voice; + + private String responseFormat = "pcm"; + + private Double speed; + + public Builder model(String model) { + this.model = model; + return this; + } + + public Builder input(String input) { + this.input = input; + return this; + } + + public Builder voice(String voice) { + this.voice = voice; + return this; + } + + public Builder responseFormat(String responseFormat) { + this.responseFormat = responseFormat; + return this; + } + + public Builder speed(Double speed) { + this.speed = speed; + return this; + } + + public SpeechRequest build() { + + return new SpeechRequest(this.model, this.input, this.voice, this.responseFormat, this.speed); + } + + } + + } + + @JsonInclude(Include.NON_NULL) + public record TranscriptionRequest( + // @formatter:off + @JsonProperty("file") byte[] file, + String fileName, + @JsonProperty("model") String model, + @JsonProperty("temperature") Float temperature + ) { + public static Builder builder() { + return new Builder(); + } + + + public static final class Builder { + + private byte[] file; + + private String fileName; + + private String model; + + private Float temperature; + + public Builder file(byte[] file) { + this.file = file; + return this; + } + + public Builder fileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder model(String model) { + this.model = model; + return this; + } + + + public Builder temperature(Float temperature) { + this.temperature = temperature; + return this; + } + + public TranscriptionRequest build() { + Assert.notNull(this.file, "file must not be null"); + Assert.hasText(this.model, "model must not be empty"); + + return new TranscriptionRequest(this.file, this.fileName, this.model, this.temperature); + } + + } + + } + + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record StructuredResponse( + // @formatter:off + @JsonProperty("language") String language, + @JsonProperty("duration") Float duration, + @JsonProperty("text") String text, + @JsonProperty("words") List words, + @JsonProperty("segments") List segments) { + // @formatter:on + + /** + * Extracted word and it corresponding timestamps. + * + * @param word The text content of the word. + * @param start The start time of the word in seconds. + * @param end The end time of the word in seconds. + */ + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record Word( + // @formatter:off + @JsonProperty("word") String word, + @JsonProperty("start") Float start, + @JsonProperty("end") Float end) { + // @formatter:on + } + + /** + * Segment of the transcribed text and its corresponding details. + * + * @param id Unique identifier of the segment. + * @param seek Seek offset of the segment. + * @param start Start time of the segment in seconds. + * @param end End time of the segment in seconds. + * @param text The text content of the segment. + * @param tokens Array of token IDs for the text content. + * @param temperature Temperature parameter used for generating the segment. + * @param avgLogprob Average logprob of the segment. If the value is lower than + * -1, consider the logprobs failed. + * @param compressionRatio Compression ratio of the segment. If the value is + * greater than 2.4, consider the compression failed. + * @param noSpeechProb Probability of no speech in the segment. If the value is + * higher than 1.0 and the avg_logprob is below -1, consider this segment silent. + */ + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record Segment( + // @formatter:off + @JsonProperty("id") Integer id, + @JsonProperty("seek") Integer seek, + @JsonProperty("start") Float start, + @JsonProperty("end") Float end, + @JsonProperty("text") String text, + @JsonProperty("tokens") List tokens, + @JsonProperty("temperature") Float temperature, + @JsonProperty("avg_logprob") Float avgLogprob, + @JsonProperty("compression_ratio") Float compressionRatio, + @JsonProperty("no_speech_prob") Float noSpeechProb) { + // @formatter:on + } + + } + + + + public static final class Builder { + + private String baseUrl = AIConstants.DEFAULT_BASE_URL; + + private ApiKey apiKey; + + private HttpHeaders headers = new HttpHeaders(); + + private RestClient.Builder restClientBuilder = RestClient.builder(); + + + private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; + + public Builder baseUrl(String baseUrl) { + Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); + this.baseUrl = baseUrl; + return this; + } + + public Builder apiKey(ApiKey apiKey) { + Assert.notNull(apiKey, "apiKey cannot be null"); + this.apiKey = apiKey; + return this; + } + + public Builder apiKey(String simpleApiKey) { + Assert.notNull(simpleApiKey, "simpleApiKey cannot be null"); + this.apiKey = new SimpleApiKey(simpleApiKey); + return this; + } + + public Builder headers(HttpHeaders headers) { + Assert.notNull(headers, "headers cannot be null"); + this.headers = headers; + return this; + } + + public Builder restClientBuilder(RestClient.Builder restClientBuilder) { + Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); + this.restClientBuilder = restClientBuilder; + return this; + } + + + public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { + Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); + this.responseErrorHandler = responseErrorHandler; + return this; + } + + public AudioApi build() { + Assert.notNull(this.apiKey, "apiKey must be set"); + return new AudioApi(this.baseUrl, this.apiKey, this.headers, this.restClientBuilder, + this.responseErrorHandler); + } + + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioModel.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioModel.java new file mode 100644 index 0000000..37e0ce5 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioModel.java @@ -0,0 +1,105 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.audio; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.audio.tts.*; +import org.springframework.ai.model.Model; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Flux; + +import java.util.List; + +@Slf4j +public class AudioModel implements Model { + + /** + * The default options used for the audio completion requests. + */ + private final AudioOptions defaultOptions; + + /** + * The retry template used to retry the OpenAI Audio API calls. + */ + private final RetryTemplate retryTemplate; + + /** + * Low-level access to the OpenAI Audio API. + */ + private final AudioApi audioApi; + + + /** + * Initializes a new instance of the AudioModel class with the provided + * OpenAiAudioApi and options. + * @param audioApi The OpenAiAudioApi to use for speech synthesis. + * @param options The AudioOptions containing the speech synthesis + * options. + * @param retryTemplate The retry template. + */ + public AudioModel(AudioApi audioApi, AudioOptions options, + RetryTemplate retryTemplate) { + Assert.notNull(audioApi, "OpenAiAudioApi must not be null"); + Assert.notNull(options, "OpenAiSpeechOptions must not be null"); + Assert.notNull(options, "RetryTemplate must not be null"); + this.audioApi = audioApi; + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + } + + + public byte[] call(String text) { + TextToSpeechPrompt prompt = new TextToSpeechPrompt(text); + return call(prompt).getResult().getOutput(); + } + + @Override + public TextToSpeechResponse call(TextToSpeechPrompt prompt) { + + AudioApi.SpeechRequest speechRequest = createRequest(prompt); + + ResponseEntity speechEntity = this.retryTemplate.execute( + (ctx) -> this.audioApi.createSpeech(speechRequest)); + + var speech = speechEntity.getBody(); + + return new TextToSpeechResponse(List.of(new Speech(speech))); + } + + + + private AudioApi.SpeechRequest createRequest(TextToSpeechPrompt prompt) { + AudioOptions options = this.defaultOptions; + + String input = StringUtils.hasText(options.getInput()) ? options.getInput() + : prompt.getInstructions().getText(); + + AudioApi.SpeechRequest.Builder requestBuilder = AudioApi.SpeechRequest.builder() + .model(options.getModel()) + .input(input) + .voice(options.getVoice()) + .responseFormat(options.getResponseFormat()) + .speed(options.getSpeed()); + + return requestBuilder.build(); + } + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioOptions.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioOptions.java new file mode 100644 index 0000000..b10b083 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/audio/AudioOptions.java @@ -0,0 +1,187 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.audio; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springframework.ai.audio.tts.TextToSpeechOptions; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AudioOptions implements TextToSpeechOptions { + + @JsonProperty("model") + private String model; + + @JsonProperty("input") + private String input; + + @JsonProperty("voice") + private String voice; + + /** + * The format of the audio output. Supported formats are mp3, opus, aac, and flac. + * Defaults to mp3. + */ + @JsonProperty("response_format") + private String responseFormat; + + /** + * The speed of the voice synthesis. The acceptable range is from 0.25 (slowest) to + * 4.0 (fastest). Defaults to 1 (normal) + */ + @JsonProperty("speed") + private Double speed; + @JsonProperty("watermark_enabled") + private boolean watermark; + + public static Builder builder() { + return new Builder(); + } + + @Override + public String getFormat() { + return null; + } + + + @Override + @SuppressWarnings("unchecked") + public AudioOptions copy() { + return AudioOptions.builder() + .model(this.model) + .input(this.input) + .voice(this.voice) + .responseFormat(this.responseFormat) + .speed(this.speed) + .build(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.model == null) ? 0 : this.model.hashCode()); + result = prime * result + ((this.input == null) ? 0 : this.input.hashCode()); + result = prime * result + ((this.voice == null) ? 0 : this.voice.hashCode()); + result = prime * result + ((this.responseFormat == null) ? 0 : this.responseFormat.hashCode()); + result = prime * result + ((this.speed == null) ? 0 : this.speed.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AudioOptions other = (AudioOptions) obj; + if (this.model == null) { + if (other.model != null) { + return false; + } + } + else if (!this.model.equals(other.model)) { + return false; + } + if (this.input == null) { + if (other.input != null) { + return false; + } + } + else if (!this.input.equals(other.input)) { + return false; + } + if (this.voice == null) { + if (other.voice != null) { + return false; + } + } + else if (!this.voice.equals(other.voice)) { + return false; + } + if (this.responseFormat == null) { + if (other.responseFormat != null) { + return false; + } + } + else if (!this.responseFormat.equals(other.responseFormat)) { + return false; + } + if (this.speed == null) { + return other.speed == null; + } + else { + return this.speed.equals(other.speed); + } + } + + @Override + public String toString() { + return "AudioOptions{" + "model='" + this.model + '\'' + ", input='" + this.input + '\'' + + ", voice='" + this.voice + '\'' + ", responseFormat='" + this.responseFormat + '\'' + ", speed=" + + this.speed + '}'; + } + + public static final class Builder { + + private final AudioOptions options = new AudioOptions(); + + public Builder model(String model) { + this.options.model = model; + return this; + } + + public Builder input(String input) { + this.options.input = input; + return this; + } + + + public Builder voice(String voice) { + this.options.voice = voice; + return this; + } + + public Builder responseFormat(String responseFormat) { + this.options.responseFormat = responseFormat; + return this; + } + + public Builder speed(Double speed) { + this.options.speed = speed; + return this; + } + + public Builder watermark(boolean watermark) { + this.options.watermark = watermark; + return this; + } + + public AudioOptions build() { + return this.options; + } + + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageApi.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageApi.java new file mode 100644 index 0000000..5a26734 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageApi.java @@ -0,0 +1,176 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.image; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.ai.model.ApiKey; +import org.springframework.ai.model.NoopApiKey; +import org.springframework.ai.model.SimpleApiKey; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +import java.util.List; + + +public class DoubaoImageApi { + + + private final RestClient restClient; + + private final String imagesPath; + + public DoubaoImageApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String imagesPath, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + + // @formatter:off + this.restClient = restClientBuilder.clone() + .baseUrl(baseUrl) + .defaultHeaders(h -> { + h.setContentType(MediaType.APPLICATION_JSON); + h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); + }) + .defaultStatusHandler(responseErrorHandler) + .defaultRequest(requestHeadersSpec -> { + if (!(apiKey instanceof NoopApiKey)) { + requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue()); + } + }) + .build(); + // @formatter:on + + this.imagesPath = imagesPath; + } + + + public DoubaoImageApi(RestClient restClient, String imagesPath) { + this.restClient = restClient; + this.imagesPath = imagesPath; + } + + public ResponseEntity createImage(DoubaoImageRequest doubaoImageRequest) { + Assert.notNull(doubaoImageRequest, "Image request cannot be null."); + Assert.hasLength(doubaoImageRequest.prompt(), "Prompt cannot be empty."); + + return this.restClient.post() + .uri(this.imagesPath) + .body(doubaoImageRequest) + .retrieve() + .toEntity(ImageResponse.class); + } + + public static Builder builder() { + return new Builder(); + } + + + + // @formatter:off + @JsonInclude(JsonInclude.Include.NON_NULL) + public record DoubaoImageRequest( + @JsonProperty("prompt") String prompt, + @JsonProperty("model") String model, + @JsonProperty("image") String image, + @JsonProperty("size") String size, + @JsonProperty("guidance_scale") Float guidanceScale, + @JsonProperty("response_format") String responseFormat, + @JsonProperty("watermark") Boolean watermark) { + + public DoubaoImageRequest(String prompt) { + this(prompt, null, null, null, null, null, null); + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record ImageResponse( + @JsonProperty("created") Long created, + @JsonProperty("data") List data) { + } + // @formatter:on + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record Data(@JsonProperty("url") String url, @JsonProperty("b64_json") String b64Json, + @JsonProperty("revised_prompt") String revisedPrompt) { + + } + + /** + * Builder to construct {@link DoubaoImageApi} instance. + */ + public static final class Builder { + + private String baseUrl = AIConstants.DEFAULT_BASE_URL; + + private ApiKey apiKey; + + private HttpHeaders headers = new HttpHeaders(); + + private RestClient.Builder restClientBuilder = RestClient.builder(); + + private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; + + private String imagesPath = AIConstants.IMAGE_URL; + + public Builder baseUrl(String baseUrl) { + Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); + this.baseUrl = baseUrl; + return this; + } + + + public Builder apiKey(String simpleApiKey) { + Assert.notNull(simpleApiKey, "simpleApiKey cannot be null"); + this.apiKey = new SimpleApiKey(simpleApiKey); + return this; + } + + public Builder headers(HttpHeaders headers) { + Assert.notNull(headers, "headers cannot be null"); + this.headers = headers; + return this; + } + + public Builder restClientBuilder(RestClient.Builder restClientBuilder) { + Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); + this.restClientBuilder = restClientBuilder; + return this; + } + + public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { + Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); + this.responseErrorHandler = responseErrorHandler; + return this; + } + + public DoubaoImageApi build() { + Assert.notNull(this.apiKey, "apiKey must be set"); + return new DoubaoImageApi(this.baseUrl, this.apiKey, this.headers, this.imagesPath, this.restClientBuilder, + this.responseErrorHandler); + } + + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageModel.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageModel.java new file mode 100644 index 0000000..1e6a9a3 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageModel.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.image; + +import io.micrometer.observation.ObservationRegistry; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.image.*; +import org.springframework.ai.image.observation.DefaultImageModelObservationConvention; +import org.springframework.ai.image.observation.ImageModelObservationContext; +import org.springframework.ai.image.observation.ImageModelObservationConvention; +import org.springframework.ai.image.observation.ImageModelObservationDocumentation; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.openai.OpenAiImageOptions; +import org.springframework.ai.openai.api.OpenAiImageApi; +import org.springframework.ai.openai.api.common.OpenAiApiConstants; +import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; + +import java.util.List; + +@Slf4j +public class DoubaoImageModel implements ImageModel { + + + private static final ImageModelObservationConvention DEFAULT_OBSERVATION_CONVENTION = new DefaultImageModelObservationConvention(); + + /** + * The default options used for the image completion requests. + */ + private final DoubaoImageOptions defaultOptions; + + /** + * The retry template used to retry the OpenAI Image API calls. + */ + private final RetryTemplate retryTemplate; + + /** + * Low-level access to the OpenAI Image API. + */ + private final DoubaoImageApi doubaoImageApi; + + /** + * Observation registry used for instrumentation. + */ + private final ObservationRegistry observationRegistry; + + /** + * Conventions to use for generating observations. + */ + private ImageModelObservationConvention observationConvention = DEFAULT_OBSERVATION_CONVENTION; + + + public DoubaoImageModel(DoubaoImageApi doubaoImageApi) { + this(doubaoImageApi, DoubaoImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + + public DoubaoImageModel(DoubaoImageApi doubaoImageApi, DoubaoImageOptions options, RetryTemplate retryTemplate) { + this(doubaoImageApi, options, retryTemplate, ObservationRegistry.NOOP); + } + + + public DoubaoImageModel(DoubaoImageApi doubaoImageApi, DoubaoImageOptions options, RetryTemplate retryTemplate, + ObservationRegistry observationRegistry) { + Assert.notNull(doubaoImageApi, "DoubaoImageApi must not be null"); + Assert.notNull(options, "options must not be null"); + Assert.notNull(retryTemplate, "retryTemplate must not be null"); + Assert.notNull(observationRegistry, "observationRegistry must not be null"); + this.doubaoImageApi = doubaoImageApi; + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + this.observationRegistry = observationRegistry; + } + + @Override + public ImageResponse call(ImagePrompt imagePrompt) { + DoubaoImageApi.DoubaoImageRequest imageRequest = this.createRequest(imagePrompt); + ImageModelObservationContext observationContext = ImageModelObservationContext.builder().imagePrompt(imagePrompt).provider(OpenAiApiConstants.PROVIDER_NAME).build(); + return (ImageResponse)ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> { + return observationContext; + }, this.observationRegistry).observe(() -> { + ResponseEntity imageResponseEntity = (ResponseEntity)this.retryTemplate.execute((ctx) -> + this.doubaoImageApi.createImage(imageRequest) + ); + ImageResponse imageResponse = this.convertResponse(imageResponseEntity, imageRequest); + observationContext.setResponse(imageResponse); + return imageResponse; + }); + } + + private DoubaoImageApi.DoubaoImageRequest createRequest(ImagePrompt imagePrompt) { + String instructions = (imagePrompt.getInstructions().get(0)).getText(); + DoubaoImageOptions imageOptions = this.defaultOptions; + DoubaoImageApi.DoubaoImageRequest imageRequest = new DoubaoImageApi.DoubaoImageRequest(instructions); + return ModelOptionsUtils.merge(imageOptions, imageRequest, DoubaoImageApi.DoubaoImageRequest.class); + } + + private ImageResponse convertResponse(ResponseEntity imageResponseEntity, + DoubaoImageApi.DoubaoImageRequest openAiImageRequest) { + OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody(); + if (imageApiResponse == null) { + log.warn("No image response returned for request: {}", openAiImageRequest); + return new ImageResponse(List.of()); + } + + List imageGenerationList = imageApiResponse.data() + .stream() + .map(entry -> new ImageGeneration(new Image(entry.url(), entry.b64Json()))) + .toList(); + + ImageResponseMetadata openAiImageResponseMetadata = new ImageResponseMetadata(imageApiResponse.created()); + return new ImageResponse(imageGenerationList, openAiImageResponseMetadata); + } + + + /** + * Use the provided convention for reporting observation data + * @param observationConvention The provided convention + */ + public void setObservationConvention(ImageModelObservationConvention observationConvention) { + Assert.notNull(observationConvention, "observationConvention cannot be null"); + this.observationConvention = observationConvention; + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageOptions.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageOptions.java new file mode 100644 index 0000000..08c5105 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/image/DoubaoImageOptions.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.image; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import org.springframework.ai.image.ImageOptions; + +import java.util.Objects; + +@Builder +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DoubaoImageOptions implements ImageOptions { + + + @JsonProperty("n") + private Integer n; + + /** + * The model to use for image generation. + */ + @JsonProperty("model") + private String model; + + /** + * The width of the generated images. Must be one of 256, 512, or 1024 for dall-e-2. + * This property is interconnected with the 'size' property - setting both width and + * height will automatically compute and set the size in "widthxheight" format. + * Conversely, setting a valid size string will parse and set the individual width and + * height values. + */ + @JsonProperty("size_width") + private Integer width; + + /** + * The height of the generated images. Must be one of 256, 512, or 1024 for dall-e-2. + * This property is interconnected with the 'size' property - setting both width and + * height will automatically compute and set the size in "widthxheight" format. + * Conversely, setting a valid size string will parse and set the individual width and + * height values. + */ + @JsonProperty("size_height") + private Integer height; + + /** + * The quality of the image that will be generated. hd creates images with finer + * details and greater consistency across the image. This param is only supported for + * dall-e-3. + */ + @JsonProperty("style") + private String style; + + /** + * The format in which the generated images are returned. Must be one of url or + * b64_json. + */ + @JsonProperty("response_format") + private String responseFormat; + + + @JsonProperty("size") + private String size; + + @JsonProperty("seed") + private Integer seed; + + @JsonProperty("guidance_scale") + private Float guidanceScale; + @JsonProperty("watermark") + private Boolean watermark; + @JsonProperty("image") + private String image; + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoApi.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoApi.java new file mode 100644 index 0000000..a590cc7 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoApi.java @@ -0,0 +1,235 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.video; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import org.springframework.ai.model.ApiKey; +import org.springframework.ai.model.NoopApiKey; +import org.springframework.ai.model.SimpleApiKey; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; +import xyz.thoughtset.viewer.common.ai.model.video.ReqInfo; +import xyz.thoughtset.viewer.models.doubao.constants.AIConstants; + +import java.util.Map; +import java.util.function.Consumer; + +public class VideoApi { + + private final RestClient restClient; + + + /** + * Create a new audio api. + * @param baseUrl api base URL. + * @param apiKey OpenAI apiKey. + * @param headers the http headers to use. + * @param restClientBuilder RestClient builder. + * @param responseErrorHandler Response error handler. + */ + public VideoApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { + if (!StringUtils.hasText(baseUrl)) { + baseUrl = AIConstants.DEFAULT_BASE_URL; + } + Consumer authHeaders = h -> h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); + + // @formatter:off + this.restClient = restClientBuilder.clone() + .baseUrl(baseUrl) + .defaultHeaders(authHeaders) + .defaultStatusHandler(responseErrorHandler) + .defaultRequest(requestHeadersSpec -> { + if (!(apiKey instanceof NoopApiKey)) { + requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey.getValue()); + } + }) + .build(); + } + + + public static Builder builder() { + return new Builder(); + } + + /** + * Request to generates audio from the input text. + * @param requestBody The request body. + * @return Response entity containing the audio binary. + */ + public ResponseEntity createReq(VideoRequest requestBody) { + return this.restClient.post().uri(requestBody.reqUrl()).body(requestBody).retrieve().toEntity(VideoResp.class); + } + + + @JsonInclude(Include.NON_NULL) + public record VideoRequest( + @JsonProperty("model") String model, + @JsonProperty("content") ReqTextContent[] content) implements ReqInfo { + + @Override + public String reqUrl() { + return AIConstants.VIDEO_URL; + } + + public static Builder builder() { + return new Builder(); + } + + + public static final class Builder { + + private String model; + + private ReqTextContent[] content; + + public Builder model(String model) { + this.model = model; + return this; + } + + public Builder prompt(ReqTextContent content) { + this.content = new ReqTextContent[]{content}; + return this; + } + + public VideoRequest build() { + return new VideoRequest(this.model, this.content); + } + } + + } + + + @JsonInclude(Include.NON_NULL) + public record ReqTextContent( + @JsonProperty("type") String type, + @JsonProperty("text") String text + ){ + + public static Builder builder() { + return new Builder(); + } + public static final class Builder { + + private String type = "text"; + + private String text; + + public Builder type(String type) { + this.type = type; + return this; + } + public Builder text(String text, Map optionsMap) { + StringBuilder stringBuilder =new StringBuilder(); + stringBuilder.append(text); + if(!ObjectUtils.isEmpty(optionsMap)){ + optionsMap.remove("model"); + optionsMap.remove("prompt"); + optionsMap.forEach((k,v)->{ + stringBuilder.append(" --").append(k).append(" ").append(v); + }); + } + this.text= stringBuilder.toString(); + return this; + } + public ReqTextContent build() { + return new ReqTextContent(this.type, this.text); + } + + + } + + } + + + @JsonInclude(Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public record VideoResp( + @JsonProperty("id") String id) { + + } + + + + public static final class Builder { + + private String baseUrl = AIConstants.DEFAULT_BASE_URL; + + private ApiKey apiKey; + + private HttpHeaders headers = new HttpHeaders(); + + private RestClient.Builder restClientBuilder = RestClient.builder(); + + private ResponseErrorHandler responseErrorHandler = RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER; + + public Builder baseUrl(String baseUrl) { + Assert.hasText(baseUrl, "baseUrl cannot be null or empty"); + this.baseUrl = baseUrl; + return this; + } + + public Builder apiKey(ApiKey apiKey) { + Assert.notNull(apiKey, "apiKey cannot be null"); + this.apiKey = apiKey; + return this; + } + + public Builder apiKey(String simpleApiKey) { + Assert.notNull(simpleApiKey, "simpleApiKey cannot be null"); + this.apiKey = new SimpleApiKey(simpleApiKey); + return this; + } + + public Builder headers(HttpHeaders headers) { + Assert.notNull(headers, "headers cannot be null"); + this.headers = headers; + return this; + } + + public Builder restClientBuilder(RestClient.Builder restClientBuilder) { + Assert.notNull(restClientBuilder, "restClientBuilder cannot be null"); + this.restClientBuilder = restClientBuilder; + return this; + } + + public Builder responseErrorHandler(ResponseErrorHandler responseErrorHandler) { + Assert.notNull(responseErrorHandler, "responseErrorHandler cannot be null"); + this.responseErrorHandler = responseErrorHandler; + return this; + } + + public VideoApi build() { + Assert.notNull(this.apiKey, "apiKey must be set"); + return new VideoApi(this.baseUrl, this.apiKey, this.headers, this.restClientBuilder, + this.responseErrorHandler); + } + + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoModel.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoModel.java new file mode 100644 index 0000000..aa6324d --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoModel.java @@ -0,0 +1,97 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.video; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.model.Model; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.video.AsyncVideo; +import xyz.thoughtset.viewer.common.ai.model.video.VideoPrompt; + +import java.util.Map; + +@Slf4j +public class VideoModel implements Model { + + /** + * The default options used for the audio completion requests. + */ + private final VideoOptions defaultOptions; + + /** + * The retry template used to retry the OpenAI Audio API calls. + */ + private final RetryTemplate retryTemplate; + + /** + * Low-level access to the OpenAI Audio API. + */ + private final VideoApi videoApi; + private final ObjectMapper objectMapper; + + + + public VideoModel(VideoApi videoApi, VideoOptions options, + RetryTemplate retryTemplate, ObjectMapper objectMapper) { + Assert.notNull(videoApi, "OpenAiAudioApi must not be null"); + Assert.notNull(options, "OpenAiSpeechOptions must not be null"); + Assert.notNull(options, "RetryTemplate must not be null"); + this.videoApi = videoApi; + this.defaultOptions = options; + this.retryTemplate = retryTemplate; + this.objectMapper = objectMapper; + } + + public String call(String text) { + VideoPrompt prompt = new VideoPrompt(text); + return call(prompt).getResult().getOutput(); + } + + + @Override + public VideoResponse call(VideoPrompt prompt) { + + VideoApi.VideoRequest speechRequest = createRequest(prompt); + + ResponseEntity respEntity = this.retryTemplate.execute( + (ctx) -> this.videoApi.createReq(speechRequest)); + + return new VideoResponse(new AsyncVideo(respEntity.getBody().id())); + } + + + + private VideoApi.VideoRequest createRequest(VideoPrompt prompt) { + VideoOptions options = this.defaultOptions; + + String input = StringUtils.hasText(options.getPrompt()) ? options.getPrompt() + : prompt.getInstructions(); + VideoApi.ReqTextContent content = VideoApi.ReqTextContent.builder() + .text(input,objectMapper.convertValue(options, Map.class)) + .build(); + return VideoApi.VideoRequest.builder() + .model(options.getModel()) + .prompt(content) + .build(); + } + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoOptions.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoOptions.java new file mode 100644 index 0000000..53b1deb --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoOptions.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License; Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing; software + * distributed under the License is distributed on an "AS IS" BASIS; + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND; either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.video; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import xyz.thoughtset.viewer.common.ai.model.video._BaseVideoOptions; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VideoOptions implements _BaseVideoOptions { + + protected String model; + protected String prompt; + protected String resolution; + protected String ratio; + protected Integer duration; + protected Integer frames; + protected Integer seed; +// protected Boolean withAudio; + protected Boolean watermark; + + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoResponse.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoResponse.java new file mode 100644 index 0000000..55b8cc4 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/api/video/VideoResponse.java @@ -0,0 +1,77 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xyz.thoughtset.viewer.models.doubao.api.video; + +import org.springframework.ai.model.ModelResponse; +import xyz.thoughtset.viewer.common.ai.model.video.AsyncVideo; +import xyz.thoughtset.viewer.common.ai.model.video.VideoResponseMetadata; + +import java.util.List; +import java.util.Objects; + + +public class VideoResponse implements ModelResponse { + + private final AsyncVideo result; + + private final VideoResponseMetadata videoResponseMetadata; + + public VideoResponse(AsyncVideo result) { + this(result, null); + } + + public VideoResponse(AsyncVideo result, VideoResponseMetadata videoResponseMetadata) { + this.result = result; + this.videoResponseMetadata = videoResponseMetadata; + } + + @Override + public List getResults() { + return List.of(this.result); + } + + public AsyncVideo getResult() { + return this.result; + } + + @Override + public VideoResponseMetadata getMetadata() { + return this.videoResponseMetadata; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VideoResponse that)) { + return false; + } + return Objects.equals(this.result, that.result); + } + + @Override + public int hashCode() { + return Objects.hash(this.result); + } + + @Override + public String toString() { + return "VideoResponseMetadata{" + "results=" + this.result + '}'; + } + +} diff --git a/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/constants/AIConstants.java b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/constants/AIConstants.java new file mode 100644 index 0000000..2ea0619 --- /dev/null +++ b/models/viewer-models-doubao/src/main/java/xyz/thoughtset/viewer/models/doubao/constants/AIConstants.java @@ -0,0 +1,13 @@ +package xyz.thoughtset.viewer.models.doubao.constants; + +public class AIConstants { + public static final String MODEL_NAME = "Doubao"; + public static final String DEFAULT_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"; + public static final String CHAT_URL = "/v4/audio/speech"; + public static final String TTS_URL = "/v4/audio/speech"; + public static final String IMAGE_URL = "/images/generations"; + public static final String ASR_URL = "/v4/audio/transcriptions"; + public static final String CLONE_URL = "/v4/voice/clone"; + public static final String VIDEO_URL = "/contents/generations/tasks"; + +} diff --git a/models/viewer-models-doubao/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/models/viewer-models-doubao/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..dcc8b6f --- /dev/null +++ b/models/viewer-models-doubao/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.models.doubao.ModelDoubaoAutoConfiguration \ No newline at end of file diff --git a/models/viewer-models-gemini/pom.xml b/models/viewer-models-gemini/pom.xml new file mode 100644 index 0000000..ab5a599 --- /dev/null +++ b/models/viewer-models-gemini/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + models + ${revision} + + + viewer-models-gemini + + + 17 + 17 + UTF-8 + + + + + org.springframework.ai + spring-ai-google-genai + ${springai.version} + + + + \ No newline at end of file diff --git a/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/AIConstants.java b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/AIConstants.java new file mode 100644 index 0000000..b042c1e --- /dev/null +++ b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/AIConstants.java @@ -0,0 +1,7 @@ +package xyz.thoughtset.viewer.models.gemini; + +public class AIConstants { + public static final String PROVIDER_NAME = "Gemini"; + public static final String DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com"; + +} diff --git a/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/GeminiChatBuilder.java b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/GeminiChatBuilder.java new file mode 100644 index 0000000..2fcb95f --- /dev/null +++ b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/GeminiChatBuilder.java @@ -0,0 +1,56 @@ +package xyz.thoughtset.viewer.models.gemini; + +import com.google.genai.Client; +import com.google.genai.HttpApiClient; +import com.google.genai.types.HttpOptions; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.google.genai.GoogleGenAiChatModel; +import org.springframework.ai.google.genai.GoogleGenAiChatOptions; +import org.springframework.ai.model.ModelOptions; +import org.springframework.stereotype.Component; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.entity.option.ExtraParamChatOptions; +import xyz.thoughtset.viewer.common.ai.model.factory.ChatModelBuilder; + +@Component +public class GeminiChatBuilder extends ChatModelBuilder { + public GeminiChatBuilder() { + super(AIConstants.PROVIDER_NAME); + } + + + @Override + public ChatModel buildMode(AiNode node, ModelParam modelParam, ModelOptions modelOptions) { + GoogleGenAiChatOptions options = loadAndMergeModelOptions(modelParam, GoogleGenAiChatOptions.class, modelOptions); + options.setModel(modelParam.getModel()); + + HttpOptions.Builder httpOptionsBuilder = HttpOptions.builder().baseUrl(node.linkBaseUrlWithDefault(AIConstants.DEFAULT_BASE_URL)); + + Client api = Client.builder() + .httpOptions(httpOptionsBuilder.build()) + .apiKey(node.getApiKey()).build(); + GoogleGenAiChatModel chatModel = GoogleGenAiChatModel.builder() + .defaultOptions(options) + .genAiClient(api) + .build(); + return chatModel; + } + + @Override + public void mergeOptions(ModelOptions source, ModelOptions extraOptions) { + if (!(extraOptions instanceof ExtraParamChatOptions)) return ; + GoogleGenAiChatOptions options = (GoogleGenAiChatOptions) source; + ExtraParamChatOptions extraParamChatOptions = (ExtraParamChatOptions) extraOptions; + if (extraParamChatOptions == null) return ; + if (extraParamChatOptions.getResponseFormatType() != null) { + options.setResponseMimeType( + ("application/json").equalsIgnoreCase(extraParamChatOptions.getResponseFormatType()) ? + "application/json" : "text/plain" + ); + } +// if (extraParamChatOptions.responseUsedJsonObject()){ +// options.setResponseMimeType(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT).build()); +// } + } +} diff --git a/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/ModelGeminiAutoConfiguration.java b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/ModelGeminiAutoConfiguration.java new file mode 100644 index 0000000..f6cce98 --- /dev/null +++ b/models/viewer-models-gemini/src/main/java/xyz/thoughtset/viewer/models/gemini/ModelGeminiAutoConfiguration.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.models.gemini; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class ModelGeminiAutoConfiguration { +} diff --git a/models/viewer-models-gemini/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/models/viewer-models-gemini/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..fe144c7 --- /dev/null +++ b/models/viewer-models-gemini/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.models.gemini.ModelGeminiAutoConfiguration \ No newline at end of file diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/pom.xml b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/pom.xml index 5b53123..a3756d6 100644 --- a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/pom.xml +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/pom.xml @@ -15,6 +15,8 @@ viewer-modules-ds-jdbc-mysql viewer-modules-ds-jdbc-self viewer-modules-ds-jdbc-core + viewer-modules-ds-jdbc-drill + viewer-modules-ds-jdbc-pgsql diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-drill/pom.xml b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-drill/pom.xml new file mode 100644 index 0000000..5098f19 --- /dev/null +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-drill/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc + ${revision} + + + viewer-modules-ds-jdbc-drill + + + 17 + 17 + UTF-8 + + + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc-core + + + + com.mysql + mysql-connector-j + provided + + + + \ No newline at end of file diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/pom.xml b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/pom.xml new file mode 100644 index 0000000..84a91f0 --- /dev/null +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc + ${revision} + + + viewer-modules-ds-jdbc-pgsql + + + 17 + 17 + UTF-8 + + + + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc-core + + + + org.postgresql + postgresql + provided + + + + \ No newline at end of file diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/DsJdbcPgsqlAutoConfiguration.java b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/DsJdbcPgsqlAutoConfiguration.java new file mode 100644 index 0000000..6097661 --- /dev/null +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/DsJdbcPgsqlAutoConfiguration.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.modules.ds.jdbc.pgsql; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class DsJdbcPgsqlAutoConfiguration { +} diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/config/PgsqlDsConfig.java b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/config/PgsqlDsConfig.java new file mode 100644 index 0000000..1431867 --- /dev/null +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/java/xyz/thoughtset/viewer/modules/ds/jdbc/pgsql/config/PgsqlDsConfig.java @@ -0,0 +1,22 @@ +package xyz.thoughtset.viewer.modules.ds.jdbc.pgsql.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import xyz.thoughtset.viewer.common.connector.entity.bo.Linker; +import xyz.thoughtset.viewer.modules.ds.jdbc.core.constant.SupportTypeConstant; +import xyz.thoughtset.viewer.modules.ds.jdbc.core.linker.JdbcConnBuilder; + +@Configuration +public class PgsqlDsConfig { + + @Bean + public JdbcConnBuilder pgsqlConnBuilder() { + Linker dataLinker = new Linker( + "postgresql", + "org.postgresql.Driver", + SupportTypeConstant.SUPPORT_TYPE + ); + return new JdbcConnBuilder(dataLinker); + } + +} diff --git a/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..a98c5e6 --- /dev/null +++ b/modules/viewer-modules-ds/viewer-modules-ds-jdbc/viewer-modules-ds-jdbc-pgsql/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.modules.ds.jdbc.pgsql.DsJdbcPgsqlAutoConfiguration \ No newline at end of file diff --git a/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/entity/block/BlockBodyEle.java b/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/entity/block/BlockBodyEle.java index a4241ad..00ea748 100644 --- a/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/entity/block/BlockBodyEle.java +++ b/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/entity/block/BlockBodyEle.java @@ -39,5 +39,11 @@ public class BlockBodyEle extends BaseMeta { public boolean eleWasFun() { return "funInfo".equalsIgnoreCase(type); } + public boolean eleWasTool() { + return "tool".equalsIgnoreCase(type); + } + public boolean eleWasScript() { + return "script".equalsIgnoreCase(type); + } } diff --git a/pom.xml b/pom.xml index 2925ac1..b27e321 100644 --- a/pom.xml +++ b/pom.xml @@ -246,6 +246,11 @@ viewer-modules-ds-jdbc-mysql ${revision} + + xyz.thoughtset.viewer + viewer-modules-ds-jdbc-pgsql + ${revision} + xyz.thoughtset.viewer viewer-modules-ds-jdbc-self @@ -323,7 +328,16 @@ viewer-models-openai ${revision} - + + xyz.thoughtset.viewer + viewer-models-gemini + ${revision} + + + xyz.thoughtset.viewer + viewer-models-doubao + ${revision} + xyz.thoughtset.viewer viewer-models-zhipuai diff --git a/sql/mysql/dv.sql b/sql/mysql/dv.sql index ff94ce4..2602dd0 100644 --- a/sql/mysql/dv.sql +++ b/sql/mysql/dv.sql @@ -367,10 +367,6 @@ CREATE TABLE `modelparam` ( `updatedAt` datetime(0) NOT NULL, `statesCode` int(11) NULL DEFAULT 200, `model` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `systemPrompt` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, - `maxMemory` int(11) NULL DEFAULT NULL, - `maxTokens` bigint(20) NULL DEFAULT NULL, - `temperature` double NULL DEFAULT NULL, `paramJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, `purpose` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `setting` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, -- Gitee