From 0d0e2691e73e84762541185cc703525b391c80b0 Mon Sep 17 00:00:00 2001 From: q1279335527 <1279335527@qq.com> Date: Sat, 4 Oct 2025 20:41:15 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0ai=E6=BA=90=E5=BD=95?= =?UTF-8?q?=E5=85=A5=E5=8A=9F=E8=83=BD=EF=BC=8Cfunctioncall=E5=A4=84?= =?UTF-8?q?=E7=90=86=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/MCPServerAutoConfiguration.java | 2 +- ...bMvcSseMcpSyncServerAutoConfiguration.java | 152 ------ .../mcp/server/factory/McpServerFactory.java | 2 +- ...ntroller.java => AiSupportController.java} | 9 +- .../resources/static/html/pages/AiNode.json | 468 ++++++++++++++++++ commons/pom.xml | 1 + commons/viewer-common-ai-model/pom.xml | 37 ++ .../model/CommonAIModelAutoConfiguration.java | 13 + .../viewer/common/ai/model/dao/AiNodeDao.java | 9 + .../common/ai/model/dao/ModelParamDao.java | 9 + .../viewer/common/ai/model/entity/AiNode.java | 32 ++ .../common/ai/model/entity/ModelParam.java | 23 + .../common/ai/model/factory/ModelBuilder.java | 37 ++ .../common/ai/model/factory/ModelFactory.java | 48 ++ .../ai/model/factory/ModelsRegistry.java | 41 ++ .../ai/model/service/AiNodeService.java | 11 + .../ai/model/service/AiNodeServiceImpl.java | 51 ++ .../ai/model/service/ModelParamService.java | 9 + .../model/service/ModelParamServiceImpl.java | 35 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + executor/viewer-executor-blocks/pom.xml | 11 + .../executor/blocks/entity/BlockTypeEnum.java | 2 + .../blocks/entity/FunctionCallBody.java | 15 + .../executor/AbstractBlockExecutor.java | 22 +- .../FunctionCallingBlockExecutor.java | 57 +++ .../tool}/BlockToolCallbackProvider.java | 8 +- .../tool/FunctionCallBlockToolCallback.java | 51 ++ ...FunctionCallBlockToolCallbackProvider.java | 48 ++ .../tool/MCPServerBlockToolCallback.java | 12 +- .../tool}/json/ParamJsonSchemaGenerator.java | 2 +- .../executor/blocks/utlis/BlockArgsUtils.java | 42 ++ models/pom.xml | 31 ++ models/viewer-models-deepseek/pom.xml | 28 ++ .../models/deepseek/DeepSeekBuilder.java | 29 ++ .../ModelDeepseekAutoConfiguration.java | 12 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../viewer/modules/fun/dao/FunInfoDao.java | 7 +- .../modules/fun/service/FunInfoService.java | 2 +- .../fun/service/FunInfoServiceImpl.java | 4 +- .../main/resources/mybatis/FunInfoMapper.xml | 41 +- pom.xml | 13 +- 41 files changed, 1220 insertions(+), 208 deletions(-) delete mode 100644 ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/config/WebMvcSseMcpSyncServerAutoConfiguration.java rename apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/{McpServerController.java => AiSupportController.java} (75%) create mode 100644 apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json create mode 100644 commons/viewer-common-ai-model/pom.xml create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/CommonAIModelAutoConfiguration.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/AiNodeDao.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/ModelParamDao.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeService.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamService.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java create mode 100644 commons/viewer-common-ai-model/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java rename {ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server => executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool}/BlockToolCallbackProvider.java (81%) create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java rename ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallback.java => executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/MCPServerBlockToolCallback.java (74%) rename {ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server => executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool}/json/ParamJsonSchemaGenerator.java (98%) create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java create mode 100644 models/pom.xml create mode 100644 models/viewer-models-deepseek/pom.xml create mode 100644 models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java create mode 100644 models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/ModelDeepseekAutoConfiguration.java create mode 100644 models/viewer-models-deepseek/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/MCPServerAutoConfiguration.java b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/MCPServerAutoConfiguration.java index e19c760..7911288 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/MCPServerAutoConfiguration.java +++ b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/MCPServerAutoConfiguration.java @@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan @EnableConfigurationProperties -@MapperScan(basePackages = "xyz.thoughtset.viewer.ai.mcp.server") +//@MapperScan(basePackages = "xyz.thoughtset.viewer.ai.mcp.server") public class MCPServerAutoConfiguration { } diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/config/WebMvcSseMcpSyncServerAutoConfiguration.java b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/config/WebMvcSseMcpSyncServerAutoConfiguration.java deleted file mode 100644 index 2429cc3..0000000 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/config/WebMvcSseMcpSyncServerAutoConfiguration.java +++ /dev/null @@ -1,152 +0,0 @@ -package xyz.thoughtset.viewer.ai.mcp.server.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.McpServer; -import io.modelcontextprotocol.server.McpServerFeatures; -import io.modelcontextprotocol.server.McpSyncServer; -import io.modelcontextprotocol.server.McpSyncServerExchange; -import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; -import io.modelcontextprotocol.spec.McpSchema; -import lombok.extern.slf4j.Slf4j; -import org.springframework.ai.mcp.McpToolUtils; -//import org.springframework.ai.mcp.server.autoconfigure.McpServerAutoConfiguration; -import org.springframework.ai.support.ToolCallbacks; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerResponse; -import xyz.thoughtset.viewer.ai.mcp.server.BlockToolCallbackProvider; - -import java.util.List; -import java.util.function.BiConsumer; - -@Deprecated -@Slf4j -//@Configuration -public class WebMvcSseMcpSyncServerAutoConfiguration { -// @Autowired - private ObjectMapper objectMapper; -// @Autowired - private BlockToolCallbackProvider blockToolCallbackProvider; - - @Bean - @ConditionalOnMissingBean - public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() { - return new WebMvcSseServerTransportProvider(objectMapper, "/mcp/messages2"); -// return WebMvcSseServerTransportProvider.MESSAGE_EVENT_TYPE -// .objectMapper(objectMapper) -// .messageEndpoint(serverProperties.getSseMessageEndpoint()) -// .build(); -// HttpServletSseServerTransportProvider - } - -// @Bean -// public RouterFunction mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) { -// return transportProvider.getRouterFunction(); -// } - -// McpServerAutoConfiguration - @Bean - public McpSyncServer funMcpSyncServer(WebMvcSseServerTransportProvider transportProvider, - ObjectProvider> tools, - ObjectProvider> resources, - ObjectProvider> prompts, - ObjectProvider> completions, - ObjectProvider>> rootsChangeConsumers) { - -// McpSchema.ServerCapabilities.Builder capabilitiesBuilder = McpSchema.ServerCapabilities.builder(); -// McpServerProperties serverProperties = new McpServerProperties(); -// McpSchema.Implementation serverInfo = new McpSchema.Implementation("mcp-server","1.0.0"); -// -// McpServer.SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo); -// -// // Tools -// if (serverProperties.getCapabilities().isTool()) { -// capabilitiesBuilder.tools(serverProperties.isToolChangeNotification()); -// -// List toolSpecifications = new ArrayList<>( -// tools.stream().flatMap(List::stream).toList()); -// -// if (!CollectionUtils.isEmpty(toolSpecifications)) { -// serverBuilder.tools(toolSpecifications); -// log.info("Registered tools: " + toolSpecifications.size()); -// } -// } -// -// // Resources -// if (serverProperties.getCapabilities().isResource()) { -// log.info("Enable resources capabilities, notification: " -// + serverProperties.isResourceChangeNotification()); -// capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification()); -// -// List resourceSpecifications = resources.stream().flatMap(List::stream).toList(); -// if (!CollectionUtils.isEmpty(resourceSpecifications)) { -// serverBuilder.resources(resourceSpecifications); -// log.info("Registered resources: " + resourceSpecifications.size()); -// } -// } -// // Prompts -// if (serverProperties.getCapabilities().isPrompt()) { -// log.info("Enable prompts capabilities, notification: " -// + serverProperties.isPromptChangeNotification()); -// capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification()); -// -// List promptSpecifications = prompts.stream().flatMap(List::stream).toList(); -// if (!CollectionUtils.isEmpty(promptSpecifications)) { -// serverBuilder.prompts(promptSpecifications); -// log.info("Registered prompts: " + promptSpecifications.size()); -// } -// } -// -// // Completions -// if (serverProperties.getCapabilities().isCompletion()) { -// log.info("Enable completions capabilities"); -// capabilitiesBuilder.completions(); -// -// List completionSpecifications = completions.stream() -// .flatMap(List::stream) -// .toList(); -// if (!CollectionUtils.isEmpty(completionSpecifications)) { -// serverBuilder.completions(completionSpecifications); -// log.info("Registered completions: " + completionSpecifications.size()); -// } -// } -// -// rootsChangeConsumers.ifAvailable(consumer -> { -// BiConsumer> syncConsumer = (exchange, roots) -> consumer -// .accept(exchange, roots); -// serverBuilder.rootsChangeHandler(syncConsumer); -// log.info("Registered roots change consumer"); -// }); -// -// serverBuilder.capabilities(capabilitiesBuilder.build()); -// -// serverBuilder.instructions(serverProperties.getInstructions()); -// -// serverBuilder.requestTimeout(serverProperties.getRequestTimeout()); -// if (environment instanceof StandardServletEnvironment) { -// serverBuilder.immediateExecution(true); -// } - - //临时 - var capabilities = McpSchema.ServerCapabilities.builder() - .tools(true) // Tool support with list changes notifications - .logging() // Logging support - .build(); -// MethodToolCallbackProvider toolCallbackProvider = MethodToolCallbackProvider.builder() -// .toolObjects(new WeatherApiClient()) -// .build(); - // Create the server with both tool and resource capabilities - McpServer.SyncSpecification serverBuilder = McpServer.sync(transportProvider) - .serverInfo("MCP Demo Weather Server", "1.0.0") - .capabilities(capabilities) - .tools(McpToolUtils.toSyncToolSpecifications( - blockToolCallbackProvider.getToolCallbacks() - )); - return serverBuilder.build(); - } - -} diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java index deb3ff3..957df88 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java +++ b/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/factory/McpServerFactory.java @@ -11,7 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; -import xyz.thoughtset.viewer.ai.mcp.server.BlockToolCallbackProvider; +import xyz.thoughtset.viewer.executor.blocks.tool.BlockToolCallbackProvider; import xyz.thoughtset.viewer.ai.mcp.server.config.DynamicRouterRegistry; import xyz.thoughtset.viewer.ai.mcp.server.entity.McpServerInfo; import xyz.thoughtset.viewer.ai.mcp.server.exception.UrlConflictException; diff --git a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/McpServerController.java b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java similarity index 75% rename from apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/McpServerController.java rename to apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java index 3d0bd5a..68e896f 100644 --- a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/McpServerController.java +++ b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java @@ -6,12 +6,15 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import xyz.thoughtset.viewer.common.ai.model.factory.ModelsRegistry; import xyz.thoughtset.viewer.common.api.controller.BaseController; import xyz.thoughtset.viewer.modules.mcp.server.service.McpServerInfoService; +import java.util.Collection; + @RestController @RequestMapping -public class McpServerController extends BaseController { +public class AiSupportController extends BaseController { @Autowired private McpServerInfoService mcpServerInfoService; @@ -24,5 +27,9 @@ public class McpServerController extends BaseController { public void unpublishedApi(@RequestParam String apiId){ mcpServerInfoService.unpublishedServer(apiId); } + @GetMapping(value = "/supportProvider") + public Collection supportProvider(){ + return ModelsRegistry.supportProvider(); + } } diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json new file mode 100644 index 0000000..3b396ae --- /dev/null +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json @@ -0,0 +1,468 @@ +{ + "type": "page", + "title": "AiNode", + "body": [ + { + "id": "u:6e6616d6cf7c", + "type": "crud2", + "mode": "table2", + "dsType": "api", + "syncLocation": true, + "primaryField": "id", + "loadType": "pagination", + "api": { + "url": "/q/findList/aiNode", + "method": "get", + "requestAdaptor": "", + "adaptor": "", + "messages": {} + }, + "quickSaveItemApi": { + "url": "/c/save/apiInfo", + "method": "post", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "dataType": "json" + }, + "filter": { + "type": "form", + "title": "条件查询", + "mode": "inline", + "columnCount": 3, + "clearValueOnHidden": true, + "behavior": [ + "SimpleQuery" + ], + "body": [ + { + "name": "title", + "label": "标题", + "type": "input-text", + "size": "full", + "required": false, + "behavior": "SimpleQuery", + "id": "u:61520af119d3" + }, + { + "type": "select", + "label": "厂商", + "name": "provider", + "id": "u:bbfcbd00a80c", + "multiple": false, + "size": "full", + "source": "supportProvider", + "selectFirst": true, + "searchable": true + }, + { + "name": "baseUrl", + "label": "baseUrl", + "type": "input-text", + "size": "full", + "required": false, + "behavior": "SimpleQuery", + "id": "u:a99741f525f4" + } + ], + "actions": [ + { + "type": "button", + "label": "新增", + "onEvent": { + "click": { + "actions": [ + { + "ignoreError": false, + "actionType": "dialog", + "dialog": { + "$ref": "modal-ref-1" + } + } + ] + } + }, + "id": "u:e2219875dd08", + "level": "default", + "themeCss": { + "className": { + "font:hover": {} + } + } + }, + { + "type": "reset", + "label": "重置", + "id": "u:5ce9fc236061" + }, + { + "type": "submit", + "label": "查询", + "level": "primary", + "id": "u:ba7fb3975d54" + } + ], + "id": "u:d38a67c10ea9", + "feat": "Insert" + }, + "headerToolbar": [], + "footerToolbar": [ + { + "type": "flex", + "direction": "row", + "justify": "flex-start", + "alignItems": "stretch", + "style": { + "position": "static", + "flexWrap": "nowrap" + }, + "items": [ + { + "type": "container", + "align": "right", + "body": [ + { + "type": "pagination", + "behavior": "Pagination", + "layout": [ + "total", + "perPage", + "pager" + ], + "perPage": 10, + "perPageAvailable": [ + 10, + 20, + 50, + 100 + ], + "align": "right", + "id": "u:7a8aeed01b15" + } + ], + "wrapperBody": false, + "style": { + "flexGrow": 1, + "flex": "1 1 auto", + "position": "static", + "display": "flex", + "flexBasis": "auto", + "flexDirection": "row", + "flexWrap": "nowrap", + "alignItems": "stretch", + "justifyContent": "flex-end" + }, + "id": "u:e4b9e45be11e" + } + ], + "id": "u:e7bdb59c323f", + "isFixedHeight": false, + "isFixedWidth": false + } + ], + "columns": [ + { + "type": "tpl", + "title": "标题", + "name": "title", + "id": "u:f13387e84576", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "厂商", + "name": "provider", + "id": "u:ab634f92276c", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "baseUrl", + "name": "baseUrl", + "id": "u:ed411e99abe0" + }, + { + "type": "tpl", + "title": "remark", + "name": "remark", + "id": "u:8fad2e4eb74c", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "updatedAt", + "name": "updatedAt", + "id": "u:6a0ba56fe926" + }, + { + "type": "tpl", + "title": "id", + "name": "id", + "id": "u:710272271cc3", + "placeholder": "-", + "width": "1%", + "hidden": true + }, + { + "type": "operation", + "title": "操作", + "buttons": [ + { + "type": "button", + "label": "编辑", + "level": "link", + "behavior": "Edit", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "dialog", + "dialog": { + "$ref": "modal-ref-1" + } + } + ] + } + }, + "id": "u:d63d5da1f382" + }, + { + "type": "button", + "label": "删除", + "behavior": "Delete", + "className": "m-r-xs text-danger", + "level": "link", + "confirmText": "确认要删除数据", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "ajax", + "api": { + "method": "post", + "url": "/c/delete/apiInfo", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "data": { + "pkey": "${id}" + } + }, + "data": { + "&": "$$" + } + }, + { + "actionType": "search", + "groupType": "component", + "componentId": "u:6e6616d6cf7c" + } + ] + } + }, + "id": "u:9eb23a446645" + } + ], + "id": "u:3bdca48cc560", + "width": "12%" + } + ], + "editorSetting": { + "mock": { + "enable": true, + "maxDisplayRows": 5 + } + } + } + ], + "id": "u:3dc63152e7a2", + "asideResizor": false, + "pullRefresh": { + "disabled": false + }, + "regions": [ + "body" + ], + "definitions": { + "modal-ref-1": { + "type": "dialog", + "body": [ + { + "id": "u:cb5eb54d608a", + "type": "form", + "title": "编辑数据", + "mode": "horizontal", + "labelAlign": "left", + "dsType": "api", + "feat": "Edit", + "body": [ + { + "type": "grid", + "columns": [ + { + "body": [ + { + "name": "title", + "label": "标题", + "type": "input-text", + "id": "u:a77e7afac568", + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-24)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true + }, + { + "type": "textarea", + "label": "备注", + "name": "remark", + "id": "u:5d1a4d6fcac8", + "minRows": 3, + "maxRows": 20, + "labelWidth": "var(--sizes-base-24)", + "labelAlign": "inherit" + } + ], + "id": "u:ae4495c42958", + "md": 5 + }, + { + "body": [ + { + "name": "provider", + "label": "ai厂商", + "type": "select", + "id": "u:e9b0792c969c", + "required": true, + "mode": "horizontal", + "labelAlign": "right", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "multiple": false, + "size": "full", + "source": "supportProvider", + "selectFirst": true, + "searchable": true + }, + { + "name": "baseUrl", + "label": "baseUrl", + "type": "input-url", + "id": "u:9901a3a5a302", + "mode": "horizontal", + "labelAlign": "right", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true + }, + { + "name": "apiKey", + "label": "apiKey", + "type": "input-text", + "id": "u:6dc99c21b7a7", + "mode": "horizontal", + "labelAlign": "right", + "required": true + } + ], + "id": "u:cb05123662f1" + } + ], + "id": "u:37385dcadd41", + "gap": "none" + }, + { + "name": "id", + "label": "id", + "type": "input-text", + "id": "u:8f3591076b66", + "hidden": false, + "visible": false + } + ], + "api": { + "url": "/c/save/apiInfo", + "method": "post", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "dataType": "json" + }, + "resetAfterSubmit": true, + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "取消" + }, + { + "type": "button", + "actionType": "submit", + "label": "提交", + "level": "primary" + } + ], + "onEvent": { + "submitSucc": { + "actions": [ + { + "actionType": "search", + "groupType": "component", + "componentId": "u:6e6616d6cf7c" + } + ] + } + }, + "initApi": { + "url": "/q/findOne/aiNode", + "method": "get", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "data": { + "pkey": "${id}" + }, + "sendOn": "${!ISEMPTY(id)}" + } + } + ], + "title": "编辑ai数据源", + "size": "md", + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "取消", + "id": "u:925eea5c2a26" + }, + { + "type": "button", + "actionType": "submit", + "label": "提交", + "level": "primary", + "id": "u:1f6d672a9b9d" + } + ], + "actionType": "dialog", + "id": "u:19d73f9b8168", + "showCloseButton": true, + "closeOnOutside": false, + "closeOnEsc": false, + "showErrorMsg": true, + "showLoading": true, + "draggable": false + } + } +} \ No newline at end of file diff --git a/commons/pom.xml b/commons/pom.xml index ef7bcf7..40e5169 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -23,6 +23,7 @@ viewer-common-log viewer-common-annotation + viewer-common-ai-model diff --git a/commons/viewer-common-ai-model/pom.xml b/commons/viewer-common-ai-model/pom.xml new file mode 100644 index 0000000..5d3e4a2 --- /dev/null +++ b/commons/viewer-common-ai-model/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + commons + ${revision} + + + viewer-common-ai-model + + + 17 + 17 + UTF-8 + + + + + + xyz.thoughtset.viewer + viewer-common-core + + + xyz.thoughtset.viewer + viewer-common-crud-core + + + org.springframework.ai + spring-ai-client-chat + ${springai.version} + + + + \ No newline at end of file diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/CommonAIModelAutoConfiguration.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/CommonAIModelAutoConfiguration.java new file mode 100644 index 0000000..57479cb --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/CommonAIModelAutoConfiguration.java @@ -0,0 +1,13 @@ +package xyz.thoughtset.viewer.common.ai.model; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +@MapperScan(basePackages = "xyz.thoughtset.viewer.common.ai.model.dao") +public class CommonAIModelAutoConfiguration { +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/AiNodeDao.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/AiNodeDao.java new file mode 100644 index 0000000..a90463d --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/AiNodeDao.java @@ -0,0 +1,9 @@ +package xyz.thoughtset.viewer.common.ai.model.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; + +@Mapper +public interface AiNodeDao extends BaseMapper { +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/ModelParamDao.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/ModelParamDao.java new file mode 100644 index 0000000..6efb59e --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/dao/ModelParamDao.java @@ -0,0 +1,9 @@ +package xyz.thoughtset.viewer.common.ai.model.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; + +@Mapper +public interface ModelParamDao extends BaseMapper { +} 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 new file mode 100644 index 0000000..7c46a0f --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/AiNode.java @@ -0,0 +1,32 @@ +package xyz.thoughtset.viewer.common.ai.model.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import xyz.thoughtset.viewer.common.core.entity.BaseMeta; +import xyz.thoughtset.viewer.common.crud.core.annotation.ApiCRUDPower; + +import java.util.List; +import java.util.Map; + +@TableName +@Data +@AllArgsConstructor +@ApiCRUDPower(insert = false,save = true,update = false,list = true) +public class AiNode extends BaseMeta { + protected String provider; + protected String baseUrl; + protected String apiKey; + protected String headersStr; + protected String settingStr; + + @TableField(exist = false) + protected transient Map settingMap; + @TableField(exist = false) + protected transient Map headerMap; + @TableField(exist = false) + protected transient List defaultModelParams; + + +} 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 new file mode 100644 index 0000000..156f40c --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/ModelParam.java @@ -0,0 +1,23 @@ +package xyz.thoughtset.viewer.common.ai.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import xyz.thoughtset.viewer.common.core.entity.IdMeta; + +import java.util.Map; + +@TableName +@Data +@AllArgsConstructor +public class ModelParam extends IdMeta { + protected String model; + protected String systemPrompt; + protected Integer maxTokens; + private Double temperature; + protected String paramJson; + + + protected transient Map paramMap; + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java new file mode 100644 index 0000000..3ea98b4 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java @@ -0,0 +1,37 @@ +package xyz.thoughtset.viewer.common.ai.model.factory; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; + +@Getter +public abstract class ModelBuilder { + protected String provider; + protected String aiModel; + @Autowired + protected ObjectMapper objectMapper; + + protected ModelBuilder(@NonNull String provider) { + this(provider,null); + } + protected ModelBuilder(@NonNull String provider, String aiModel) { + this.provider = provider; + this.aiModel = aiModel; + ModelsRegistry.registerModel(this); + } + + public boolean wasDefault(){return false;} + + public boolean checkExecModel(AiNode node, ModelParam modelParam){ + return this.provider.equals(node.getProvider()) || this.aiModel.equals(modelParam.getModel()); + } + + public abstract ChatModel buildMode(AiNode node, ModelParam modelParam); + + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java new file mode 100644 index 0000000..cf7c2ee --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java @@ -0,0 +1,48 @@ +package xyz.thoughtset.viewer.common.ai.model.factory; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; +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.service.AiNodeService; + +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@Component +public class ModelFactory { + private ConcurrentHashMap CLIENT_BUILDER_CACHE = new ConcurrentHashMap<>(); + @Autowired + private ModelParamDao modelParamDao; + @Autowired + private AiNodeService aiNodeService; + + + public synchronized ChatClient.Builder clientBuilder(@NonNull String settingId){ + ModelParam modelParam = modelParamDao.selectById(settingId); + AiNode aiNode = aiNodeService.selectDetail(modelParam.getPid()); + ChatClient.Builder clientBuilder = CLIENT_BUILDER_CACHE.get(settingId); + if(clientBuilder == null){ + clientBuilder = clientBuilder(aiNode,modelParam); + CLIENT_BUILDER_CACHE.put(settingId, clientBuilder); + } + return clientBuilder; + } + + public ChatModel buildModel(AiNode aiNode, ModelParam modelParam){ + ModelBuilder builder = ModelsRegistry.loadBuilder(aiNode, modelParam); + return builder.buildMode(aiNode, modelParam); + } + + public ChatClient.Builder clientBuilder(AiNode aiNode, ModelParam modelParam){ + ChatModel chatModel = buildModel(aiNode, modelParam); + return ChatClient.builder(chatModel).defaultSystem(modelParam.getSystemPrompt()); + } + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java new file mode 100644 index 0000000..3026161 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java @@ -0,0 +1,41 @@ +package xyz.thoughtset.viewer.common.ai.model.factory; + + +import org.springframework.util.MultiValueMap; +import org.springframework.util.MultiValueMapAdapter; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; + + +public class ModelsRegistry { + + private static final MultiValueMap MODEL_BUILDER_MAP = new MultiValueMapAdapter<>(new HashMap<>()); + + public static void registerModel(ModelBuilder modelBuilder){ + String modelProvider = modelBuilder.getProvider(); + MODEL_BUILDER_MAP.add(modelProvider,modelBuilder); + } + + public static ModelBuilder loadBuilder(AiNode aiNode, ModelParam params){ + List list = MODEL_BUILDER_MAP.get(aiNode.getProvider()); + ModelBuilder targetBuilder = null; + for (ModelBuilder ele : list){ + if (ele.wasDefault()) targetBuilder = ele; + if (ele.checkExecModel(aiNode,params)){ + targetBuilder = ele; + break; + } + } + return targetBuilder; + } + + public static Collection supportProvider(){ + return MODEL_BUILDER_MAP.keySet(); + } + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeService.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeService.java new file mode 100644 index 0000000..144acfc --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeService.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.common.ai.model.service; + + +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.crud.core.service.BaseService; + +import java.util.List; + +public interface AiNodeService extends BaseService { + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java new file mode 100644 index 0000000..b995f04 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java @@ -0,0 +1,51 @@ +package xyz.thoughtset.viewer.common.ai.model.service; + +import cn.zhxu.bs.BeanSearcher; +import cn.zhxu.bs.util.MapUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.databind.JavaType; +import jakarta.annotation.PostConstruct; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.dao.AiNodeDao; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.crud.core.service.BaseServiceImpl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class AiNodeServiceImpl extends BaseServiceImpl implements AiNodeService { + @SneakyThrows + @Override + public AiNode saveData(AiNode data) { + if (!ObjectUtils.isEmpty(data.getSettingMap())){ + data.setSettingStr(mapper.writeValueAsString(data.getSettingMap())); + } + if (!ObjectUtils.isEmpty(data.getHeaderMap())){ + data.setHeadersStr(mapper.writeValueAsString(data.getHeaderMap())); + } + return super.saveData(data); + } + + @Override + @SneakyThrows + public AiNode selectDetail(String pkey) { + AiNode data = super.selectDetail(pkey); + if (!StringUtils.hasText(data.getSettingStr())){ + data.setSettingMap(mapper.readValue(data.getSettingStr(), Map.class)); + } + if (!StringUtils.hasText(data.getHeadersStr())){ + data.setHeaderMap(mapper.readValue(data.getHeadersStr(), Map.class)); + } + return data; + } +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamService.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamService.java new file mode 100644 index 0000000..5e46aa4 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamService.java @@ -0,0 +1,9 @@ +package xyz.thoughtset.viewer.common.ai.model.service; + + +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.crud.core.service.BaseService; + +public interface ModelParamService extends BaseService { + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java new file mode 100644 index 0000000..894cf88 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java @@ -0,0 +1,35 @@ +package xyz.thoughtset.viewer.common.ai.model.service; + +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.crud.core.service.BaseServiceImpl; + +import java.util.Map; + +@Service +public class ModelParamServiceImpl extends BaseServiceImpl implements ModelParamService { + @SneakyThrows + @Override + public ModelParam saveData(ModelParam data) { + if (!ObjectUtils.isEmpty(data.getParamMap())){ + data.setParamJson(mapper.writeValueAsString(data.getParamMap())); + } + return super.saveData(data); + } + + @Override + @SneakyThrows + public ModelParam selectDetail(String pkey) { + ModelParam data = super.selectDetail(pkey); + if (!StringUtils.hasText(data.getParamJson())){ + data.setParamMap(mapper.readValue(data.getParamJson(), Map.class)); + } + return data; + } +} diff --git a/commons/viewer-common-ai-model/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/commons/viewer-common-ai-model/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..2ca0678 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.common.ai.model.CommonAIModelAutoConfiguration \ No newline at end of file diff --git a/executor/viewer-executor-blocks/pom.xml b/executor/viewer-executor-blocks/pom.xml index b02a35e..2d7cd84 100644 --- a/executor/viewer-executor-blocks/pom.xml +++ b/executor/viewer-executor-blocks/pom.xml @@ -26,6 +26,17 @@ xyz.thoughtset.viewer viewer-modules-fun + + org.springframework.ai + spring-ai-model + ${springai.version} + true + + + + xyz.thoughtset.viewer + viewer-models-deepseek + \ No newline at end of file diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java index 36b12f1..7bc5061 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java @@ -16,6 +16,8 @@ public enum BlockTypeEnum { // PARALLEL("parallel", ParallelBody.class), // MCP MCP("mcp", MCPBody.class), + // MCP + FUNCTION_CALL("FunctionCall", FunctionCallBody.class), VALUE("value", ValueBody.class), // 单体块 DEFAULT("default", SingleBody.class), diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java new file mode 100644 index 0000000..622fd89 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java @@ -0,0 +1,15 @@ +package xyz.thoughtset.viewer.executor.blocks.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class FunctionCallBody extends BaseBlockBody { + private String bodyType = BlockTypeEnum.FUNCTION_CALL.getType(); + private String modelId; + private String userMsg; + +} 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 b7d9786..33ddb4f 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 @@ -12,6 +12,7 @@ import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; import xyz.thoughtset.viewer.executor.blocks.entity.BaseBlockBody; import xyz.thoughtset.viewer.executor.blocks.entity.BlockTypeEnum; import xyz.thoughtset.viewer.executor.blocks.entity.BodyEle; +import xyz.thoughtset.viewer.executor.blocks.utlis.BlockArgsUtils; import xyz.thoughtset.viewer.modules.ds.core.factory.ExecutorManager; import xyz.thoughtset.viewer.executor.blocks.constants.NodeConstant; import xyz.thoughtset.viewer.modules.step.entity.BlockParam; @@ -76,26 +77,9 @@ public abstract class AbstractBlockExecutor implements } abstract Object doQuery(BlockInfo block, T body, Map params, ExpressionParser parser,StandardEvaluationContext context) throws ExecException; - protected Map filterParams(BlockBodyEle bodyEle,Map contentMap, ExpressionParser parser,StandardEvaluationContext context){ - Map tmpParamsMap = new HashMap(); - List params = bodyEle.getParams(); - if (Objects.nonNull(params)){ - for (EleParam blockParam : params){ - if(!StringUtils.hasText(blockParam.getDataExp())){ - continue; - } - String exp =blockParam.getDataExp(); - exp = blockParam.getDataExp().startsWith("#")?exp:"#"+exp; - Object value = parser.parseExpression(exp).getValue(context); - context.setVariable(blockParam.getParamId(), value); - tmpParamsMap.put(blockParam.getParamId(), value); - } - } - return tmpParamsMap; - } protected Object execNode(BlockBodyEle ele, Map params, ExpressionParser parser, StandardEvaluationContext context){ - Map tmpMap = filterParams(ele, params, parser, context); + Map tmpMap = BlockArgsUtils.filterParams(ele, parser, context); Object val = ele.eleWasFun()? blockExecutorManager.execBlocks(ele.getEleId(),tmpMap,parser): executorManager.execute(ele.getEleId(),tmpMap); if (ObjectUtils.isEmpty(val) || !StringUtils.hasText(ele.getValNum())) { @@ -136,6 +120,8 @@ public abstract class AbstractBlockExecutor implements return val; } + + protected void putItVal(int index, Map params,StandardEvaluationContext context,Object val){ String key = NodeConstant.getDataNodeWithNum(index); params.put(key, 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 new file mode 100644 index 0000000..c3897d7 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/FunctionCallingBlockExecutor.java @@ -0,0 +1,57 @@ +package xyz.thoughtset.viewer.executor.blocks.executor; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; +import xyz.thoughtset.viewer.common.ai.model.factory.ModelFactory; +import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; +import xyz.thoughtset.viewer.executor.blocks.entity.FunctionCallBody; +import xyz.thoughtset.viewer.executor.blocks.tool.BlockToolCallbackProvider; +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.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Function; +import java.util.stream.Collectors; + + +@Component +public class FunctionCallingBlockExecutor extends AbstractBlockExecutor { + @Autowired + private ModelFactory modelFactory; + @Autowired + private FunctionCallBlockToolCallbackProvider provider; +//先选择函数,再加载参数执行函数 + @Override + Object doQuery(BlockInfo block, FunctionCallBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + ChatClient.Builder builder = modelFactory.clientBuilder(body.getModelId()); + ChatClient client = builder.build(); +// ListOutputConverter + List funs = body.getBodyEles(); + ChatClient.ChatClientRequestSpec spec = client.prompt() + .system("当我给你一个自然语言查询时,请只返回数组类型的JSON,不要任何额外说明") + .user(body.getUserMsg()); + if (!ObjectUtils.isEmpty(funs)){ + Map funParamMap = funs.parallelStream().collect(Collectors.toMap( + BlockBodyEle::getEleId, Function.identity() + )); + spec.toolContext(params) + .toolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); + } + List results = spec.call() +// .entity(Map.class) + .entity(new ParameterizedTypeReference>() {}); + return results; + } + + +} diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallbackProvider.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/BlockToolCallbackProvider.java similarity index 81% rename from ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallbackProvider.java rename to executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/BlockToolCallbackProvider.java index dcf5117..ada0b80 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallbackProvider.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/BlockToolCallbackProvider.java @@ -1,4 +1,4 @@ -package xyz.thoughtset.viewer.ai.mcp.server; +package xyz.thoughtset.viewer.executor.blocks.tool; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.tool.ToolCallback; @@ -7,8 +7,8 @@ import org.springframework.ai.tool.definition.DefaultToolDefinition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import xyz.thoughtset.viewer.ai.mcp.server.json.ParamJsonSchemaGenerator; import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; +import xyz.thoughtset.viewer.executor.blocks.tool.json.ParamJsonSchemaGenerator; import xyz.thoughtset.viewer.modules.fun.service.FunInfoService; @Component @@ -26,7 +26,7 @@ public class BlockToolCallbackProvider implements ToolCallbackProvider { } public ToolCallback[] getToolCallbacks(String serverId) { - ToolCallback[] toolCallbacks = this.funInfoService.findMcpToolsWithInParam(serverId).stream() + ToolCallback[] toolCallbacks = this.funInfoService.findAIToolsWithInParam(serverId).stream() .map((funInfo) -> { String funTitle = funInfo.getTitle(); String funRemark = funInfo.getRemark(); @@ -35,7 +35,7 @@ public class BlockToolCallbackProvider implements ToolCallbackProvider { .description(StringUtils.hasText(funRemark) ? funRemark : funTitle) .inputSchema(ParamJsonSchemaGenerator.generateForFunInput(funInfo)) .build(); - return new BlockToolCallback(toolDefinition,blockExecutorManager,objectMapper,funInfo.getId()); + return new MCPServerBlockToolCallback(toolDefinition,blockExecutorManager,objectMapper,funInfo.getId()); }) .toArray(ToolCallback[]::new); return toolCallbacks; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java new file mode 100644 index 0000000..22d65f8 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java @@ -0,0 +1,51 @@ +package xyz.thoughtset.viewer.executor.blocks.tool; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.ai.tool.metadata.ToolMetadata; +import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; +import xyz.thoughtset.viewer.executor.blocks.utlis.BlockArgsUtils; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; + +import java.util.Map; + +@Slf4j +public record FunctionCallBlockToolCallback(ToolDefinition toolDefinition, + BlockBodyEle bodyEle, + BlockExecutorManager blockExecutorManager, + ObjectMapper objectMapper, String funId) implements ToolCallback { + + + @Override + public ToolDefinition getToolDefinition() { + return toolDefinition; + } + + @Override + public ToolMetadata getToolMetadata() { + return ToolCallback.super.getToolMetadata(); + } + + @Override + public String call(String toolInput) { + return call(toolInput,null); + } + + @SneakyThrows + @Override + public String call(String toolInput, ToolContext toolContext) { + Map paramMap = objectMapper.readValue(toolInput,Map.class); + if (toolContext != null && toolContext.getContext() != null){ + Map tmpMap = BlockArgsUtils.filterParams(bodyEle, toolContext.getContext()); + paramMap.putAll(tmpMap); + } +// Map inputMap = BlockArgsUtils.filterParams(bodyEle, paramMap); + Object result = blockExecutorManager.execBlocks(funId,paramMap); + log.info(objectMapper.writeValueAsString(result)); + return objectMapper.writeValueAsString(result); + } +} diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java new file mode 100644 index 0000000..10144a1 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java @@ -0,0 +1,48 @@ +package xyz.thoughtset.viewer.executor.blocks.tool; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.ToolCallbackProvider; +import org.springframework.ai.tool.definition.DefaultToolDefinition; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; +import xyz.thoughtset.viewer.executor.blocks.tool.json.ParamJsonSchemaGenerator; +import xyz.thoughtset.viewer.modules.fun.dao.FunInfoDao; +import xyz.thoughtset.viewer.modules.fun.service.FunInfoService; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class FunctionCallBlockToolCallbackProvider { + + @Autowired + private FunInfoDao funInfoDao; + @Autowired + private BlockExecutorManager blockExecutorManager; + @Autowired + private ObjectMapper objectMapper; + + + public ToolCallback[] getToolCallbacks(String blockId, Map funParamMap) { + + ToolCallback[] toolCallbacks = this.funInfoDao.findBlockFunsWithInParam(blockId).stream() + .map((funInfo) -> { + String funTitle = funInfo.getTitle(); + String funRemark = funInfo.getRemark(); + DefaultToolDefinition toolDefinition = (DefaultToolDefinition) DefaultToolDefinition.builder() + .name(funTitle) + .description(StringUtils.hasText(funRemark) ? funRemark : funTitle) + .inputSchema(ParamJsonSchemaGenerator.generateForFunInput(funInfo)) + .build(); + String funId = funInfo.getId(); + return new FunctionCallBlockToolCallback(toolDefinition,funParamMap.get(funId),blockExecutorManager,objectMapper,funId); + }) + .toArray(ToolCallback[]::new); + return toolCallbacks; + } + +} diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallback.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/MCPServerBlockToolCallback.java similarity index 74% rename from ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallback.java rename to executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/MCPServerBlockToolCallback.java index 91194f9..fc576ad 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/BlockToolCallback.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/MCPServerBlockToolCallback.java @@ -1,7 +1,6 @@ -package xyz.thoughtset.viewer.ai.mcp.server; +package xyz.thoughtset.viewer.executor.blocks.tool; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.model.ToolContext; @@ -13,9 +12,9 @@ import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; import java.util.Map; @Slf4j -public record BlockToolCallback(ToolDefinition toolDefinition, - BlockExecutorManager blockExecutorManager, - ObjectMapper objectMapper,String funId) implements ToolCallback { +public record MCPServerBlockToolCallback(ToolDefinition toolDefinition, + BlockExecutorManager blockExecutorManager, + ObjectMapper objectMapper, String funId) implements ToolCallback { // private final ToolDefinition toolDefinition; // private final BlockExecutorManager blockExecutorManager; // private final ObjectMapper objectMapper; @@ -39,6 +38,9 @@ public record BlockToolCallback(ToolDefinition toolDefinition, @Override public String call(String toolInput, ToolContext toolContext) { Map paramMap = objectMapper.readValue(toolInput,Map.class); + if (toolContext != null && toolContext.getContext() != null){ + paramMap.putAll(toolContext.getContext()); + } Object result = blockExecutorManager.execBlocks(funId,paramMap); log.info(objectMapper.writeValueAsString(result)); return objectMapper.writeValueAsString(result); diff --git a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/json/ParamJsonSchemaGenerator.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java similarity index 98% rename from ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/json/ParamJsonSchemaGenerator.java rename to executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java index a38e2e8..a545bfa 100644 --- a/ai/viewer-ai-mcp-server/src/main/java/xyz/thoughtset/viewer/ai/mcp/server/json/ParamJsonSchemaGenerator.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/json/ParamJsonSchemaGenerator.java @@ -1,4 +1,4 @@ -package xyz.thoughtset.viewer.ai.mcp.server.json; +package xyz.thoughtset.viewer.executor.blocks.tool.json; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.victools.jsonschema.generator.*; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java new file mode 100644 index 0000000..a5f6131 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java @@ -0,0 +1,42 @@ +package xyz.thoughtset.viewer.executor.blocks.utlis; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; +import xyz.thoughtset.viewer.modules.step.entity.block.EleParam; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class BlockArgsUtils { +// new SpelExpressionParser(), new StandardEvaluationContext() + public static Map filterParams(BlockBodyEle bodyEle, Map contentMap){ + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setVariables(contentMap); + return filterParams(bodyEle, parser, context); + } + + public static Map filterParams(BlockBodyEle bodyEle, ExpressionParser parser, StandardEvaluationContext context){ + Map tmpParamsMap = new HashMap(); + List params = bodyEle.getParams(); + if (Objects.nonNull(params)){ + for (EleParam blockParam : params){ + if(!StringUtils.hasText(blockParam.getDataExp())){ + continue; + } + String exp =blockParam.getDataExp(); + exp = blockParam.getDataExp().startsWith("#")?exp:"#"+exp; + Object value = parser.parseExpression(exp).getValue(context); + context.setVariable(blockParam.getParamId(), value); + tmpParamsMap.put(blockParam.getParamId(), value); + } + } + return tmpParamsMap; + } + +} diff --git a/models/pom.xml b/models/pom.xml new file mode 100644 index 0000000..bc4d23f --- /dev/null +++ b/models/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + Viewer + ${revision} + + + models + pom + + viewer-models-deepseek + + + + 17 + 17 + UTF-8 + + + + + xyz.thoughtset.viewer + viewer-common-ai-model + + + + \ No newline at end of file diff --git a/models/viewer-models-deepseek/pom.xml b/models/viewer-models-deepseek/pom.xml new file mode 100644 index 0000000..a6e2225 --- /dev/null +++ b/models/viewer-models-deepseek/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + models + ${revision} + + + viewer-models-deepseek + + + 17 + 17 + UTF-8 + + + + + org.springframework.ai + spring-ai-deepseek + ${springai.version} + + + + \ No newline at end of file diff --git a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java new file mode 100644 index 0000000..efe19bf --- /dev/null +++ b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java @@ -0,0 +1,29 @@ +package xyz.thoughtset.viewer.models.deepseek; + +import lombok.NonNull; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.deepseek.DeepSeekChatModel; +import org.springframework.ai.deepseek.DeepSeekChatOptions; +import org.springframework.ai.deepseek.api.DeepSeekApi; +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.factory.ModelBuilder; + +@Component +public class DeepSeekBuilder extends ModelBuilder { + public DeepSeekBuilder() { + super("DeepSeek"); + } + + @Override + public ChatModel buildMode(AiNode node, ModelParam modelParam) { + DeepSeekChatOptions options = DeepSeekChatOptions.builder() + .model(modelParam.getModel()).temperature(modelParam.getTemperature()).build(); + DeepSeekApi api = DeepSeekApi.builder() + .baseUrl(node.getBaseUrl()) + .apiKey(node.getApiKey()).build(); + ChatModel chatModel = DeepSeekChatModel.builder().defaultOptions(options).deepSeekApi(api).build(); + return chatModel; + } +} diff --git a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/ModelDeepseekAutoConfiguration.java b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/ModelDeepseekAutoConfiguration.java new file mode 100644 index 0000000..918b114 --- /dev/null +++ b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/ModelDeepseekAutoConfiguration.java @@ -0,0 +1,12 @@ +package xyz.thoughtset.viewer.models.deepseek; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class ModelDeepseekAutoConfiguration { +} diff --git a/models/viewer-models-deepseek/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/models/viewer-models-deepseek/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..ed2a06c --- /dev/null +++ b/models/viewer-models-deepseek/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.models.deepseek.ModelDeepseekAutoConfiguration \ No newline at end of file diff --git a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/dao/FunInfoDao.java b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/dao/FunInfoDao.java index 6143f0f..151f565 100644 --- a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/dao/FunInfoDao.java +++ b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/dao/FunInfoDao.java @@ -9,6 +9,9 @@ import java.util.List; @Mapper public interface FunInfoDao extends BaseMapper { - List findFunsWithInParam(String funId); - List findMcpToolsWithInParam(String serverId); + List findBlockFunsWithInParam(String blockId); + default List findAIToolsWithInParam(String serverId){ + return findAIToolsWithInParam(serverId,null); + } + List findAIToolsWithInParam(String serverId,String topic); } diff --git a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoService.java b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoService.java index f598b8e..fa7f75b 100644 --- a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoService.java +++ b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoService.java @@ -10,6 +10,6 @@ import java.util.List; public interface FunInfoService extends BaseService { List findFunInputParams(String funId); - List findMcpToolsWithInParam(String serverId); + List findAIToolsWithInParam(String serverId); } diff --git a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoServiceImpl.java b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoServiceImpl.java index 477b52d..7207e4f 100644 --- a/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoServiceImpl.java +++ b/modules/viewer-modules-fun/src/main/java/xyz/thoughtset/viewer/modules/fun/service/FunInfoServiceImpl.java @@ -93,8 +93,8 @@ public class FunInfoServiceImpl extends BaseServiceImpl imp } @Override - public List findMcpToolsWithInParam(String serverId) { - List list = baseMapper.findMcpToolsWithInParam(serverId); + public List findAIToolsWithInParam(String serverId) { + List list = baseMapper.findAIToolsWithInParam(serverId); return list; } } diff --git a/modules/viewer-modules-fun/src/main/resources/mybatis/FunInfoMapper.xml b/modules/viewer-modules-fun/src/main/resources/mybatis/FunInfoMapper.xml index c5b924b..81f5cd2 100644 --- a/modules/viewer-modules-fun/src/main/resources/mybatis/FunInfoMapper.xml +++ b/modules/viewer-modules-fun/src/main/resources/mybatis/FunInfoMapper.xml @@ -14,26 +14,8 @@ - - - + SELECT + + FROM + blockbodyele bbe + inner join funinfo i on bbe.eleId = i.id + left join funparam p on i.id = p.pid + WHERE p.paramType = '0' and i.statesCode = ]]> 200 + + AND bbe.pid = #{blockId} + + + + diff --git a/pom.xml b/pom.xml index 1537bfb..07cae71 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ modules executor ai + models @@ -52,7 +53,6 @@ 4.11.0 0.2.25 3.2.0 - 1.24 1.0.1 0.12.0 @@ -291,6 +291,7 @@ viewer-modules-ds-http-simple ${revision} + xyz.thoughtset.viewer viewer-ai-mcp-server @@ -301,6 +302,16 @@ viewer-modules-mcp-server ${revision} + + xyz.thoughtset.viewer + viewer-common-ai-model + ${revision} + + + xyz.thoughtset.viewer + viewer-models-deepseek + ${revision} + -- Gitee From f8a739237ef8f13812528b10d7023be4c65880ec Mon Sep 17 00:00:00 2001 From: q1279335527 <1279335527@qq.com> Date: Wed, 8 Oct 2025 23:36:22 +0800 Subject: [PATCH 2/4] =?UTF-8?q?1=E6=B7=BB=E5=8A=A0=E5=AF=B9=E4=BA=8EOpenAi?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=A8=A1=E5=9E=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AiSupportController.java | 2 +- .../resources/static/html/pages/AiNode.json | 232 +++++---- .../static/html/pages/ModelParam.json | 493 ++++++++++++++++++ .../resources/static/html/pages/funInfo.json | 185 +++++-- .../resources/static/html/pages/index.json | 12 +- .../viewer/common/ai/model/entity/AiNode.java | 5 +- .../common/ai/model/entity/ModelParam.java | 10 +- .../ai/model/factory/DefaultModelBuilder.java | 29 ++ .../common/ai/model/factory/ModelBuilder.java | 7 +- .../common/ai/model/factory/ModelFactory.java | 17 +- .../ai/model/service/AiNodeServiceImpl.java | 4 +- .../model/service/ModelParamServiceImpl.java | 2 +- .../ai/model/utils/ModelBuilderHelper.java | 25 + .../api/advice/SuccessResponseAdvice.java | 4 +- executor/viewer-executor-blocks/pom.xml | 4 + .../blocks/entity/FunctionCallBody.java | 1 + .../executor/AbstractBlockExecutor.java | 7 +- .../blocks/executor/BlockExecutorManager.java | 3 + .../FunctionCallingBlockExecutor.java | 118 ++++- .../tool/FunctionCallBlockToolCallback.java | 8 +- ...FunctionCallBlockToolCallbackProvider.java | 2 + .../executor/blocks/utlis/BlockArgsUtils.java | 11 +- models/pom.xml | 1 + .../models/deepseek/DeepSeekBuilder.java | 21 +- models/viewer-models-openai/pom.xml | 28 + .../openai/ModelOpenAIAutoConfiguration.java | 11 + .../models/openai/OpenAIChatBuilder.java | 40 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + pom.xml | 5 + 29 files changed, 1095 insertions(+), 193 deletions(-) create mode 100644 apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/DefaultModelBuilder.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/utils/ModelBuilderHelper.java create mode 100644 models/viewer-models-openai/pom.xml create mode 100644 models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelOpenAIAutoConfiguration.java create mode 100644 models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java create mode 100644 models/viewer-models-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java index 68e896f..3734eef 100644 --- a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java +++ b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java @@ -28,7 +28,7 @@ public class AiSupportController extends BaseController { mcpServerInfoService.unpublishedServer(apiId); } @GetMapping(value = "/supportProvider") - public Collection supportProvider(){ + public Object supportProvider(){ return ModelsRegistry.supportProvider(); } diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json index 3b396ae..18fa4b4 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/AiNode.json @@ -7,7 +7,7 @@ "type": "crud2", "mode": "table2", "dsType": "api", - "syncLocation": true, + "syncLocation": false, "primaryField": "id", "loadType": "pagination", "api": { @@ -18,7 +18,7 @@ "messages": {} }, "quickSaveItemApi": { - "url": "/c/save/apiInfo", + "url": "/c/save/aiNode", "method": "post", "requestAdaptor": "", "adaptor": "", @@ -42,7 +42,8 @@ "size": "full", "required": false, "behavior": "SimpleQuery", - "id": "u:61520af119d3" + "id": "u:61520af119d3", + "clearable": true }, { "type": "select", @@ -52,8 +53,9 @@ "multiple": false, "size": "full", "source": "supportProvider", - "selectFirst": true, - "searchable": true + "selectFirst": false, + "searchable": true, + "clearable": true }, { "name": "baseUrl", @@ -62,7 +64,8 @@ "size": "full", "required": false, "behavior": "SimpleQuery", - "id": "u:a99741f525f4" + "id": "u:a99741f525f4", + "clearable": true } ], "actions": [ @@ -74,8 +77,8 @@ "actions": [ { "ignoreError": false, - "actionType": "dialog", - "dialog": { + "actionType": "drawer", + "drawer": { "$ref": "modal-ref-1" } } @@ -137,7 +140,9 @@ 100 ], "align": "right", - "id": "u:7a8aeed01b15" + "id": "u:7a8aeed01b15", + "size": "", + "mode": "normal" } ], "wrapperBody": false, @@ -166,7 +171,8 @@ "title": "标题", "name": "title", "id": "u:f13387e84576", - "placeholder": "-" + "placeholder": "-", + "width": "16%" }, { "type": "tpl", @@ -183,7 +189,7 @@ }, { "type": "tpl", - "title": "remark", + "title": "备注", "name": "remark", "id": "u:8fad2e4eb74c", "placeholder": "-" @@ -192,7 +198,9 @@ "type": "tpl", "title": "updatedAt", "name": "updatedAt", - "id": "u:6a0ba56fe926" + "id": "u:6a0ba56fe926", + "placeholder": "-", + "width": "15%" }, { "type": "tpl", @@ -216,8 +224,8 @@ "click": { "actions": [ { - "actionType": "dialog", - "dialog": { + "actionType": "drawer", + "drawer": { "$ref": "modal-ref-1" } } @@ -240,7 +248,7 @@ "actionType": "ajax", "api": { "method": "post", - "url": "/c/delete/apiInfo", + "url": "/c/delete/aiNode", "requestAdaptor": "", "adaptor": "", "messages": {}, @@ -264,7 +272,7 @@ } ], "id": "u:3bdca48cc560", - "width": "12%" + "width": "11%" } ], "editorSetting": { @@ -272,7 +280,10 @@ "enable": true, "maxDisplayRows": 5 } - } + }, + "showHeader": true, + "resizable": true, + "keepItemSelectionOnPageChange": true } ], "id": "u:3dc63152e7a2", @@ -285,10 +296,10 @@ ], "definitions": { "modal-ref-1": { - "type": "dialog", + "type": "drawer", "body": [ { - "id": "u:cb5eb54d608a", + "id": "u:ba7bc36478fd", "type": "form", "title": "编辑数据", "mode": "horizontal", @@ -296,104 +307,101 @@ "dsType": "api", "feat": "Edit", "body": [ - { - "type": "grid", - "columns": [ - { - "body": [ - { - "name": "title", - "label": "标题", - "type": "input-text", - "id": "u:a77e7afac568", - "labelAlign": "inherit", - "labelWidth": "var(--sizes-base-24)", - "wrapperCustomStyle": { - "root": { - "margin-bottom": "6px", - "margin-top": "6px" - } - }, - "required": true - }, - { - "type": "textarea", - "label": "备注", - "name": "remark", - "id": "u:5d1a4d6fcac8", - "minRows": 3, - "maxRows": 20, - "labelWidth": "var(--sizes-base-24)", - "labelAlign": "inherit" - } - ], - "id": "u:ae4495c42958", - "md": 5 - }, - { - "body": [ - { - "name": "provider", - "label": "ai厂商", - "type": "select", - "id": "u:e9b0792c969c", - "required": true, - "mode": "horizontal", - "labelAlign": "right", - "wrapperCustomStyle": { - "root": { - "margin-bottom": "6px", - "margin-top": "6px" - } - }, - "multiple": false, - "size": "full", - "source": "supportProvider", - "selectFirst": true, - "searchable": true - }, - { - "name": "baseUrl", - "label": "baseUrl", - "type": "input-url", - "id": "u:9901a3a5a302", - "mode": "horizontal", - "labelAlign": "right", - "wrapperCustomStyle": { - "root": { - "margin-bottom": "6px", - "margin-top": "6px" - } - }, - "required": true - }, - { - "name": "apiKey", - "label": "apiKey", - "type": "input-text", - "id": "u:6dc99c21b7a7", - "mode": "horizontal", - "labelAlign": "right", - "required": true - } - ], - "id": "u:cb05123662f1" - } - ], - "id": "u:37385dcadd41", - "gap": "none" - }, { "name": "id", "label": "id", "type": "input-text", - "id": "u:8f3591076b66", + "id": "u:b25f600700d8", "hidden": false, "visible": false + }, + { + "name": "title", + "label": "标题", + "type": "input-text", + "id": "u:79f990bed5ed", + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-26)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true + }, + { + "name": "provider", + "label": "ai厂商", + "type": "select", + "id": "u:c7f74bb48a68", + "required": true, + "mode": "horizontal", + "labelAlign": "left", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "multiple": false, + "size": "full", + "source": "supportProvider", + "selectFirst": true, + "searchable": true, + "labelWidth": "var(--sizes-base-26)" + }, + { + "name": "baseUrl", + "label": "baseUrl", + "type": "input-url", + "id": "u:24e37782eb11", + "mode": "horizontal", + "labelAlign": "left", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true, + "labelWidth": "var(--sizes-base-26)" + }, + { + "name": "apiKey", + "label": "apiKey", + "type": "input-text", + "id": "u:4d066fa32996", + "mode": "horizontal", + "labelAlign": "left", + "required": true, + "labelWidth": "var(--sizes-base-26)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + } + }, + { + "type": "textarea", + "label": "备注", + "name": "remark", + "id": "u:24421ea7f7a6", + "minRows": 3, + "maxRows": 20, + "labelWidth": "var(--sizes-base-26)", + "labelAlign": "inherit", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + } } ], "api": { - "url": "/c/save/apiInfo", + "url": "/c/save/aiNode", "method": "post", "requestAdaptor": "", "adaptor": "", @@ -445,18 +453,18 @@ "type": "button", "actionType": "cancel", "label": "取消", - "id": "u:925eea5c2a26" + "id": "u:973cf6b3eec4" }, { "type": "button", "actionType": "submit", "label": "提交", "level": "primary", - "id": "u:1f6d672a9b9d" + "id": "u:acba9083c54f" } ], - "actionType": "dialog", - "id": "u:19d73f9b8168", + "actionType": "drawer", + "id": "u:059bc573ccbb", "showCloseButton": true, "closeOnOutside": false, "closeOnEsc": false, 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 new file mode 100644 index 0000000..19cfc10 --- /dev/null +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/ModelParam.json @@ -0,0 +1,493 @@ +{ + "type": "page", + "title": "ModelParam", + "body": [ + { + "id": "u:d6a80c06043f", + "type": "crud2", + "mode": "table2", + "dsType": "api", + "syncLocation": true, + "primaryField": "id", + "loadType": "", + "headerToolbar": [], + "footerToolbar": [ + { + "type": "flex", + "direction": "row", + "justify": "flex-start", + "alignItems": "stretch", + "style": { + "position": "static", + "flexWrap": "nowrap" + }, + "items": [], + "id": "u:3a44f63469d8", + "isFixedHeight": false, + "isFixedWidth": false + } + ], + "columns": [ + { + "type": "tpl", + "title": "标题", + "name": "title", + "id": "u:3a5dd004af07", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "厂商", + "name": "provider", + "id": "u:b6de197aa95a", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "模型", + "name": "model", + "id": "u:659469efc312", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "备注", + "name": "remark", + "id": "u:5ecf6804ea5e", + "placeholder": "-" + }, + { + "type": "tpl", + "title": "updatedAt", + "name": "updatedAt", + "id": "u:8718d95a2242", + "placeholder": "-", + "width": "12%" + }, + { + "type": "tpl", + "title": "id", + "name": "id", + "id": "u:8fbc0ee7091c", + "placeholder": "-", + "width": "1%", + "hidden": true + }, + { + "type": "operation", + "title": "操作", + "buttons": [ + { + "type": "button", + "label": "编辑", + "level": "link", + "behavior": "Edit", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "drawer", + "drawer": { + "$ref": "modal-ref-1" + } + } + ] + } + }, + "id": "u:64fd04718c71" + }, + { + "type": "button", + "label": "删除", + "behavior": "Delete", + "className": "m-r-xs text-danger", + "level": "link", + "confirmText": "确认要删除数据", + "onEvent": { + "click": { + "actions": [ + { + "actionType": "ajax", + "api": { + "method": "post", + "url": "/c/delete/modelParam", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "data": { + "pkey": "${id}" + } + }, + "outputVar": "responseResult", + "options": {} + }, + { + "actionType": "search", + "groupType": "component", + "componentId": "u:d6a80c06043f" + } + ] + } + }, + "id": "u:cf1f9b6d88ed" + } + ], + "id": "u:0aef628393af", + "width": "11%" + } + ], + "editorSetting": { + "mock": { + "enable": true, + "maxDisplayRows": 5 + } + }, + "api": { + "url": "/q/findList/modelParam", + "method": "get", + "requestAdaptor": "", + "adaptor": "", + "messages": {} + }, + "quickSaveItemApi": { + "url": "/c/save/modelParam", + "method": "post", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "dataType": "json" + }, + "filter": { + "type": "form", + "title": "条件查询", + "mode": "inline", + "columnCount": 3, + "clearValueOnHidden": true, + "behavior": [ + "SimpleQuery" + ], + "body": [ + { + "name": "title", + "label": "标题", + "type": "input-text", + "size": "full", + "required": false, + "behavior": "SimpleQuery", + "id": "u:011c6b545370" + }, + { + "type": "select", + "label": "厂商", + "name": "provider", + "id": "u:18ea50831ac3", + "multiple": false, + "size": "full", + "source": "supportProvider", + "selectFirst": false, + "searchable": true + }, + { + "name": "model", + "label": "模型", + "type": "input-text", + "size": "full", + "required": false, + "behavior": "SimpleQuery", + "id": "u:990e49421eb3" + } + ], + "actions": [ + { + "type": "button", + "label": "新增", + "onEvent": { + "click": { + "actions": [ + { + "ignoreError": false, + "actionType": "drawer", + "drawer": { + "$ref": "modal-ref-1" + } + } + ] + } + }, + "id": "u:e0bfc68369e4", + "level": "default", + "themeCss": { + "className": { + "font:hover": {} + } + } + }, + { + "type": "reset", + "label": "重置", + "id": "u:0ef665da7461" + }, + { + "type": "submit", + "label": "查询", + "level": "primary", + "id": "u:afac63e6ac19" + } + ], + "id": "u:73ce245a7067", + "feat": "Insert" + }, + "showHeader": true, + "resizable": true, + "keepItemSelectionOnPageChange": true, + "autoFillHeight": true + } + ], + "id": "u:b3c253bb3402", + "asideResizor": false, + "pullRefresh": { + "disabled": true + }, + "regions": [ + "body" + ], + "definitions": { + "modal-ref-1": { + "type": "drawer", + "body": [ + { + "type": "form", + "id": "u:d0c5625761aa", + "title": "编辑数据", + "mode": "horizontal", + "labelAlign": "left", + "dsType": "api", + "feat": "Edit", + "body": [ + { + "name": "id", + "label": "id", + "type": "input-text", + "id": "u:bd9da970a208", + "hidden": false, + "visible": false + }, + { + "name": "title", + "label": "标题", + "type": "input-text", + "id": "u:b29d12d6ab23", + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-24)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true + }, + { + "type": "select", + "label": "目标ai", + "name": "pid", + "id": "u:fb5b29c4ed9f", + "multiple": false, + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-24)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "selectFirst": true, + "source": { + "method": "get", + "url": "/q/findList/aiNode", + "requestAdaptor": "", + "adaptor": "", + "messages": {} + }, + "labelField": "title", + "valueField": "id", + "required": true + }, + { + "name": "model", + "label": "模型", + "type": "input-text", + "id": "u:a1ad0ab131ad", + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-24)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "required": true + }, + { + "type": "textarea", + "label": "系统提示词", + "name": "systemPrompt", + "id": "u:f69a7e0c79b5", + "minRows": 5, + "maxRows": 20, + "labelWidth": "var(--sizes-base-24)", + "labelAlign": "top", + "mode": "normal" + }, + { + "name": "temperature", + "label": "随机性", + "type": "input-number", + "id": "u:a62c3df783e5", + "labelAlign": "inherit", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "keyboard": true, + "step": "", + "min": 0, + "value": "", + "precision": 1, + "kilobitSeparator": false, + "max": 1 + }, + { + "name": "maxTokens", + "label": "单次最大Tokens", + "type": "input-number", + "id": "u:68e43de6ec4f", + "labelAlign": "inherit", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "keyboard": true, + "step": 1, + "min": 0, + "value": "", + "precision": "", + "kilobitSeparator": false + }, + { + "draggable": true, + "type": "input-kv", + "label": "其他参数", + "name": "paramMap", + "id": "u:25ab98e04283", + "multiple": true, + "items": [ + { + "placeholder": "Key", + "type": "input-text", + "unique": true, + "name": "key", + "required": true, + "validateOnChange": true + }, + { + "placeholder": "Value", + "type": "input-text", + "name": "value" + } + ] + }, + { + "type": "textarea", + "label": "备注", + "name": "remark", + "id": "u:faf4e33fcdf6", + "minRows": 3, + "maxRows": 20, + "labelWidth": "var(--sizes-base-24)", + "labelAlign": "inherit" + } + ], + "api": { + "url": "/c/save/modelParam", + "method": "post", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "dataType": "json" + }, + "resetAfterSubmit": true, + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "取消" + }, + { + "type": "button", + "actionType": "submit", + "label": "提交", + "level": "primary" + } + ], + "onEvent": { + "submitSucc": { + "actions": [ + { + "actionType": "reload", + "groupType": "component", + "componentId": "u:d6a80c06043f", + "args": {} + } + ] + } + }, + "initApi": { + "url": "/q/findOne/modelParam", + "method": "get", + "requestAdaptor": "", + "adaptor": "", + "messages": {}, + "data": { + "pkey": "${id}" + }, + "sendOn": "${!ISEMPTY(id)}" + } + } + ], + "title": "编辑模型", + "id": "u:84c1d59acdf3", + "actions": [ + { + "type": "button", + "actionType": "cancel", + "label": "取消", + "id": "u:02f3cadcd474" + }, + { + "type": "button", + "actionType": "submit", + "label": "提交", + "id": "u:8fb3e48926ed", + "level": "primary" + } + ], + "showCloseButton": true, + "closeOnOutside": false, + "closeOnEsc": false, + "showErrorMsg": true, + "showLoading": true, + "draggable": false, + "size": "md", + "actionType": "drawer", + "editorSetting": { + "displayName": "" + }, + "resizable": false + } + } +} \ No newline at end of file diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json index 655996c..be7830f 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json @@ -103,14 +103,15 @@ "name": "title", "id": "u:7b5904fba5cc", "placeholder": "-", - "width": "30%" + "width": "25%" }, { "type": "tpl", - "title": "remark", + "title": "说明(备注)", "name": "remark", "id": "u:968da5f1938e", - "placeholder": "-" + "placeholder": "-", + "popOver": false }, { "type": "tpl", @@ -118,7 +119,7 @@ "name": "updatedAt", "id": "u:0c07bb68b2d3", "placeholder": "-", - "width": "16%" + "width": "15%" }, { "type": "operation", @@ -264,38 +265,48 @@ } } }, + { + "type": "textarea", + "label": "说明(备注)", + "name": "remark", + "id": "u:d6669f75c1ce", + "row": 2, + "minRows": 3, + "maxRows": 20 + }, { "type": "input-table", - "name": "params", - "label": "参数设定", "id": "u:904bffd1aa6c", "columns": [ { "label": "id", "name": "id", - "quickEdit": false, "id": "u:0f712b46e01b", "placeholder": "-", + "width": 1, + "quickEdit": false, "inline": true, "visible": false, - "hidden": true, - "width": 1 + "hidden": true }, { "label": "title", "name": "title", + "type": "text", + "id": "u:600e240e8211", + "placeholder": "-", "quickEdit": { "type": "input-text", "name": "title", "mode": "popOver" - }, - "type": "text", - "id": "u:600e240e8211", - "placeholder": "-" + } }, { "label": "dataType", "name": "dataType", + "type": "text", + "id": "u:488b2d50e1d1", + "placeholder": "-", "quickEdit": { "type": "wrapper", "mode": "popOver", @@ -324,22 +335,19 @@ }, "isFixedHeight": false, "isFixedWidth": false - }, - "type": "text", - "id": "u:488b2d50e1d1", - "placeholder": "-" + } }, { "label": "defaultVal", - "name": "defaultVal", "type": "text", + "id": "u:4f6793cda9ec", + "placeholder": "-", + "name": "defaultVal", "quickEdit": { "type": "input-text", "name": "defaultVal", "mode": "popOver" - }, - "id": "u:4f6793cda9ec", - "placeholder": "-" + } }, { "label": "paramType", @@ -390,6 +398,9 @@ } } ], + "draggable": true, + "name": "params", + "label": "参数设定", "addable": true, "footerAddBtn": { "label": "新增", @@ -400,8 +411,7 @@ "copyable": true, "editable": true, "removable": true, - "columnsTogglable": false, - "draggable": true + "columnsTogglable": false }, { "type": "crud", @@ -780,14 +790,10 @@ "id": "u:e3820994e2e4", "themeCss": { "baseControlClassName": { - "padding-and-margin:default": { - "marginTop": "var(--sizes-size-5)", - "marginRight": "var(--sizes-size-5)", - "marginBottom": "var(--sizes-size-5)", - "marginLeft": "var(--sizes-size-5)" - } + "padding-and-margin:default": {} } - } + }, + "gap": "" }, { "type": "grid", @@ -827,10 +833,13 @@ "label": "single", "value": "single" }, + { + "label": "FunctionCall", + "value": "FunctionCall" + }, { "label": "mcp", - "value": "mcp", - "hiddenOn": "true" + "value": "mcp" }, { "label": "parallel", @@ -840,7 +849,8 @@ ], "id": "u:fd07835da4f1", "multiple": false, - "value": "task" + "value": "task", + "labelAlign": "inherit" } ], "md": 12, @@ -895,7 +905,19 @@ "title": "spel表达式", "content": "" } - }, + } + ], + "md": 12, + "id": "u:a5bb6d1cfbaf" + } + ], + "id": "u:7dcbec6179e3" + }, + { + "type": "grid", + "columns": [ + { + "body": [ { "type": "input-text", "label": "转置对象", @@ -908,10 +930,11 @@ } ], "md": 12, - "id": "u:a5bb6d1cfbaf" + "id": "u:b0d7d10d715a" } ], - "id": "u:7dcbec6179e3" + "id": "u:c14295a3a82f", + "gap": "" }, { "type": "grid", @@ -970,6 +993,96 @@ "align": "left", "valign": "middle" }, + { + "type": "grid", + "columns": [ + { + "body": [ + { + "type": "select", + "label": "目标模型", + "name": "data.modelId", + "id": "u:8d2a2efc675c", + "value": "", + "visible": true, + "hiddenOn": "${bodyType!=\"FunctionCall\"}", + "clearValueOnHidden": true, + "multiple": false, + "source": { + "method": "get", + "url": "/q/findList/modelParam", + "requestAdaptor": "", + "adaptor": "", + "messages": {} + }, + "labelField": "title", + "valueField": "id", + "required": true + } + ], + "id": "u:7a776752f367", + "md": 8 + }, + { + "body": [ + { + "type": "switch", + "label": "json格式返回", + "option": "", + "name": "data.jsonType", + "falseValue": false, + "trueValue": true, + "id": "u:474142764e7d", + "optionAtLeft": true, + "value": true, + "visible": true, + "hiddenOn": "${bodyType!=\"FunctionCall\" }", + "clearValueOnHidden": true + } + ], + "id": "u:db45bc616979", + "themeCss": { + "baseControlClassName": { + "padding-and-margin:default": {} + } + }, + "md": "auto" + } + ], + "id": "u:7bf7db822a6d", + "gap": "base", + "themeCss": { + "baseControlClassName": { + "padding-and-margin:default": { + "marginBottom": "var(--sizes-size-4)" + } + } + }, + "align": "left", + "valign": "middle" + }, + { + "type": "grid", + "columns": [ + { + "body": [ + { + "type": "textarea", + "label": "提示词", + "name": "data.userMsg", + "id": "u:018b6276b71b", + "minRows": 3, + "maxRows": 20, + "hiddenOn": "${bodyType!=\"FunctionCall\"}", + "clearValueOnHidden": true + } + ], + "md": 12, + "id": "u:95f4ee1686d9" + } + ], + "id": "u:639bc959f389" + }, { "type": "input-table", "name": "bodys", diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/index.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/index.json index 9ee5d6f..94ae109 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/index.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/index.json @@ -5,12 +5,22 @@ "url": "/", "schemaApi": "/html/pages/queryBody.json" },{ - "label": "Mcp", + "label": "AI 支持", "children": [ { "label": "McpServer", "url": "McpServer", "schemaApi": "/html/pages/McpServer.json" + }, + { + "label": "定义AI源", + "url": "AINode", + "schemaApi": "/html/pages/AINode.json" + }, + { + "label": "模型预设", + "url": "ModelParam", + "schemaApi": "/html/pages/ModelParam.json" } ] }, 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 7c46a0f..d078ce6 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 @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import xyz.thoughtset.viewer.common.core.entity.BaseMeta; import xyz.thoughtset.viewer.common.crud.core.annotation.ApiCRUDPower; @@ -12,10 +13,10 @@ import java.util.Map; @TableName @Data -@AllArgsConstructor +@NoArgsConstructor @ApiCRUDPower(insert = false,save = true,update = false,list = true) public class AiNode extends BaseMeta { - protected String provider; + protected String provider = null; protected String baseUrl; protected String apiKey; protected String headersStr; 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 156f40c..21f07f7 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 @@ -3,17 +3,21 @@ package xyz.thoughtset.viewer.common.ai.model.entity; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import xyz.thoughtset.viewer.common.core.entity.BaseMeta; import xyz.thoughtset.viewer.common.core.entity.IdMeta; +import xyz.thoughtset.viewer.common.crud.core.annotation.ApiCRUDPower; import java.util.Map; @TableName @Data -@AllArgsConstructor -public class ModelParam extends IdMeta { +@NoArgsConstructor +@ApiCRUDPower(insert = false,save = true,update = false,list = true) +public class ModelParam extends BaseMeta { protected String model; protected String systemPrompt; - protected Integer maxTokens; + protected Long maxTokens; private Double temperature; protected String paramJson; diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/DefaultModelBuilder.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/DefaultModelBuilder.java new file mode 100644 index 0000000..be6ed16 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/DefaultModelBuilder.java @@ -0,0 +1,29 @@ +package xyz.thoughtset.viewer.common.ai.model.factory; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.NonNull; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; + +@Getter +public abstract class DefaultModelBuilder extends ModelBuilder{ + + + protected DefaultModelBuilder(@NonNull String provider) { + super(provider); + } + + protected DefaultModelBuilder(@NonNull String provider, String aiModel) { + super(provider, aiModel); + } + + @Override + public boolean wasDefault() { + return true; + } +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java index 3ea98b4..c12565f 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.NonNull; import org.springframework.ai.chat.model.ChatModel; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; @@ -28,7 +29,11 @@ public abstract class ModelBuilder { public boolean wasDefault(){return false;} public boolean checkExecModel(AiNode node, ModelParam modelParam){ - return this.provider.equals(node.getProvider()) || this.aiModel.equals(modelParam.getModel()); + boolean checkFlag = StringUtils.hasText(this.aiModel); + if (checkFlag){ + checkFlag = this.aiModel.equals(modelParam.getModel()); + } + return this.provider.equals(node.getProvider()) && checkFlag; } public abstract ChatModel buildMode(AiNode node, ModelParam modelParam); diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java index cf7c2ee..dee5f32 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java @@ -12,26 +12,22 @@ import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; import xyz.thoughtset.viewer.common.ai.model.service.AiNodeService; import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Slf4j @Component public class ModelFactory { - private ConcurrentHashMap CLIENT_BUILDER_CACHE = new ConcurrentHashMap<>(); @Autowired private ModelParamDao modelParamDao; @Autowired private AiNodeService aiNodeService; - public synchronized ChatClient.Builder clientBuilder(@NonNull String settingId){ - ModelParam modelParam = modelParamDao.selectById(settingId); + public ChatClient.Builder clientBuilder(@NonNull ModelParam modelParam){ + AiNode aiNode = aiNodeService.selectDetail(modelParam.getPid()); - ChatClient.Builder clientBuilder = CLIENT_BUILDER_CACHE.get(settingId); - if(clientBuilder == null){ - clientBuilder = clientBuilder(aiNode,modelParam); - CLIENT_BUILDER_CACHE.put(settingId, clientBuilder); - } + ChatClient.Builder clientBuilder = clientBuilder(aiNode,modelParam); return clientBuilder; } @@ -42,7 +38,10 @@ public class ModelFactory { public ChatClient.Builder clientBuilder(AiNode aiNode, ModelParam modelParam){ ChatModel chatModel = buildModel(aiNode, modelParam); - return ChatClient.builder(chatModel).defaultSystem(modelParam.getSystemPrompt()); + + return ChatClient.builder(chatModel); } + + } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java index b995f04..9d4906b 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/AiNodeServiceImpl.java @@ -40,10 +40,10 @@ public class AiNodeServiceImpl extends BaseServiceImpl implem @SneakyThrows public AiNode selectDetail(String pkey) { AiNode data = super.selectDetail(pkey); - if (!StringUtils.hasText(data.getSettingStr())){ + if (StringUtils.hasText(data.getSettingStr())){ data.setSettingMap(mapper.readValue(data.getSettingStr(), Map.class)); } - if (!StringUtils.hasText(data.getHeadersStr())){ + if (StringUtils.hasText(data.getHeadersStr())){ data.setHeaderMap(mapper.readValue(data.getHeadersStr(), Map.class)); } return data; diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java index 894cf88..d48f503 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java @@ -27,7 +27,7 @@ public class ModelParamServiceImpl extends BaseServiceImpl void setIfNotNull(ModelParam modelParam, String key, Consumer setter) { + Map map = modelParam.getParamMap(); + setIfNotNull(map, key, setter); + } + public static void setIfNotNull(@NonNull Map map, String key, Consumer setter) { + T value = (T) map.get(key); + if (value != null) { + setter.accept(value); + } + } +} diff --git a/commons/viewer-common-api/src/main/java/xyz/thoughtset/viewer/common/api/advice/SuccessResponseAdvice.java b/commons/viewer-common-api/src/main/java/xyz/thoughtset/viewer/common/api/advice/SuccessResponseAdvice.java index 1d83402..6718a47 100644 --- a/commons/viewer-common-api/src/main/java/xyz/thoughtset/viewer/common/api/advice/SuccessResponseAdvice.java +++ b/commons/viewer-common-api/src/main/java/xyz/thoughtset/viewer/common/api/advice/SuccessResponseAdvice.java @@ -20,7 +20,9 @@ public class SuccessResponseAdvice { @Around("execution(* xyz.thoughtset.viewer.common.api.controller.BaseController+.*(..))") public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable { Object body; - try{ body = pjp.proceed();} + try{ + body = pjp.proceed(); + } catch (Exception e) {throw e;} if (body instanceof Result) { return body; diff --git a/executor/viewer-executor-blocks/pom.xml b/executor/viewer-executor-blocks/pom.xml index 2d7cd84..cddff70 100644 --- a/executor/viewer-executor-blocks/pom.xml +++ b/executor/viewer-executor-blocks/pom.xml @@ -37,6 +37,10 @@ xyz.thoughtset.viewer viewer-models-deepseek + + xyz.thoughtset.viewer + viewer-models-openai + \ No newline at end of file diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java index 622fd89..e6f28ea 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java @@ -11,5 +11,6 @@ public class FunctionCallBody extends BaseBlockBody { private String bodyType = BlockTypeEnum.FUNCTION_CALL.getType(); private String modelId; private String userMsg; + private Boolean jsonType; } 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 33ddb4f..48912f9 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 @@ -1,5 +1,6 @@ package xyz.thoughtset.viewer.executor.blocks.executor; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import org.springframework.beans.factory.InitializingBean; @@ -36,6 +37,7 @@ public abstract class AbstractBlockExecutor implements protected ObjectMapper objectMapper; @Autowired protected BlockExecutorManager blockExecutorManager; + protected TypeReference DATA_TYPE = new TypeReference>>() {}; @Override public void afterPropertiesSet() throws Exception { @@ -73,7 +75,10 @@ public abstract class AbstractBlockExecutor implements && !BlockTypeEnum.TRANSPOSE.equals(supportType)) { return null; } - return doQuery(block, body, new HashMap<>(),parser, context); + if (ObjectUtils.isEmpty(params)) { + params = new HashMap<>(); + } + return doQuery(block, body, params,parser, context); } abstract Object doQuery(BlockInfo block, T body, Map params, ExpressionParser parser,StandardEvaluationContext context) throws ExecException; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java index be99779..9ae9a17 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java @@ -100,6 +100,9 @@ public class BlockExecutorManager { for(FunParam param : inputParams) { String key = param.getTitle(); Object paramValue = searchMap.get(key); + if (paramValue == null) { + paramValue = searchMap.get(param.getId()); + } if (paramValue == null) { paramValue = param.getDefaultVal(); } 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 index c3897d7..2d5cfcb 100644 --- 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 @@ -1,13 +1,31 @@ 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.ChatClientResponse; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.DefaultChatOptions; +import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.ai.deepseek.DeepSeekChatOptions; +import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.tool.ToolCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; import xyz.thoughtset.viewer.common.ai.model.factory.ModelFactory; import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; import xyz.thoughtset.viewer.executor.blocks.entity.FunctionCallBody; @@ -16,42 +34,112 @@ import xyz.thoughtset.viewer.executor.blocks.tool.FunctionCallBlockToolCallbackP import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; import xyz.thoughtset.viewer.modules.step.entity.block.BlockInfo; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.LongAdder; import java.util.function.Function; import java.util.stream.Collectors; +@Slf4j @Component public class FunctionCallingBlockExecutor extends AbstractBlockExecutor { + @Autowired + private ModelParamDao modelParamDao; @Autowired private ModelFactory modelFactory; @Autowired private FunctionCallBlockToolCallbackProvider provider; //先选择函数,再加载参数执行函数 + @SneakyThrows @Override Object doQuery(BlockInfo block, FunctionCallBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { - ChatClient.Builder builder = modelFactory.clientBuilder(body.getModelId()); - ChatClient client = builder.build(); -// ListOutputConverter + if (!StringUtils.hasText(body.getModelId())){ + throw new Exception("模型参数未配置"); + } + ModelParam modelParam = modelParamDao.selectById(body.getModelId()); + ChatClient.Builder builder = modelFactory.clientBuilder(modelParam); + MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder() + .maxMessages(10) + .build(); + String systemText = modelParam.getSystemPrompt(); + if (body.getJsonType()!=null && body.getJsonType().booleanValue()){ +// systemText = """ +// 请只返回数组类型的JSON,不要任何额外说明,当后续要求与当前冲突时,以当前要求为准! +// 你是一个函数调用助手,请严格按照要求的格式返回结果,不要任何额外说明. +// 1.返回值必须是一个数组,数组的每一项是一个对象 +// 2.对象的key是函数参数名,value是对应的值 +// 3.如果没有值,请返回null,不要返回空字符串,不要省略该key +// 4.如果值是字符串,请确保值是合法的JSON字符串,如果值本身是一个JSON对象或数组,请直接返回该对象或数组,不要转义为字符串 +// 5.如果没有数据,请返回一个空数组,不要返回null,不要返回空对象,不要返回字符串 +// 6.请严格按照要求的格式返回,不要有任何多余的内容,不要有任何拼写错误,不要有任何语法错误""" + systemText = """ + 必须按照JSON数组格式返回,不要有其余任何内容,不要有任何拼写错误,不要有任何语法错误,当后续要求与当前冲突时,以当前要求为准! + 选择适当的方法执行,由于部分参数存储在本地,对于非required参数,可以填入建议值或空值。预期值与方法执行结果不一致时,以方法结果为准。 + """ + +systemText; + } + String finalSystemText = systemText; + builder.defaultSystem(s->s.text(finalSystemText).params(params)); List funs = body.getBodyEles(); - ChatClient.ChatClientRequestSpec spec = client.prompt() - .system("当我给你一个自然语言查询时,请只返回数组类型的JSON,不要任何额外说明") - .user(body.getUserMsg()); + ToolCallingChatOptions.Builder toolCallingBuilder = ToolCallingChatOptions.builder() + .internalToolExecutionEnabled(true); if (!ObjectUtils.isEmpty(funs)){ Map funParamMap = funs.parallelStream().collect(Collectors.toMap( BlockBodyEle::getEleId, Function.identity() )); - spec.toolContext(params) - .toolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); + builder.defaultToolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); + + toolCallingBuilder = toolCallingBuilder.toolCallbacks(provider.getToolCallbacks(block.getId(),funParamMap)); + } + ChatClient client = builder.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(); + String val = chatResponse.getResult().getOutput().getText(); +// log.info("FunctionCallingBlockExecutor response: {}", val); + Object result = val; + + try { +// List results = callResponseSpec.entity(new ParameterizedTypeReference>() {}); + result = objectMapper.readValue(val, DATA_TYPE); + }catch (Exception e){ + e.printStackTrace(); + result = val; } - List results = spec.call() -// .entity(Map.class) - .entity(new ParameterizedTypeReference>() {}); - return results; + + return result; } + private void saveMegToChatMemory(String chatId, List messages, ChatMemory chatMemory) { + chatMemory.add(chatId, messages); + + List memoryMessage = chatMemory.get(chatId); + + long assistantCount = memoryMessage.stream().filter(m -> m.getMessageType() == MessageType.ASSISTANT).count(); + long toolCount = memoryMessage.stream().filter(m -> m.getMessageType() == MessageType.TOOL).count(); + + if (assistantCount != toolCount) { + log.error("tool 工具消息不成对,重置 memory list"); + chatMemory.clear(chatId); + chatMemory.add(chatId, memoryMessage.subList(1, memoryMessage.size())); + } + } } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java index 22d65f8..461a559 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallback.java @@ -7,6 +7,9 @@ import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.ai.tool.metadata.ToolMetadata; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.ObjectUtils; import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; import xyz.thoughtset.viewer.executor.blocks.utlis.BlockArgsUtils; import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; @@ -39,8 +42,9 @@ public record FunctionCallBlockToolCallback(ToolDefinition toolDefinition, @Override public String call(String toolInput, ToolContext toolContext) { Map paramMap = objectMapper.readValue(toolInput,Map.class); - if (toolContext != null && toolContext.getContext() != null){ - Map tmpMap = BlockArgsUtils.filterParams(bodyEle, toolContext.getContext()); + Map contextMap = toolContext != null && toolContext.getContext() != null ? toolContext.getContext(): Map.of(); + if (!ObjectUtils.isEmpty(contextMap)){ + Map tmpMap = BlockArgsUtils.filterParams(bodyEle, contextMap); paramMap.putAll(tmpMap); } // Map inputMap = BlockArgsUtils.filterParams(bodyEle, paramMap); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java index 10144a1..e4a90f9 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/tool/FunctionCallBlockToolCallbackProvider.java @@ -5,6 +5,8 @@ import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.definition.DefaultToolDefinition; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.executor.blocks.executor.BlockExecutorManager; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java index a5f6131..76da6b8 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/utlis/BlockArgsUtils.java @@ -18,10 +18,14 @@ public class BlockArgsUtils { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariables(contentMap); - return filterParams(bodyEle, parser, context); + return filterParams(false,bodyEle, parser, context); } public static Map filterParams(BlockBodyEle bodyEle, ExpressionParser parser, StandardEvaluationContext context){ + return filterParams(true, bodyEle, parser, context); + } + + public static Map filterParams(boolean keyUseId,BlockBodyEle bodyEle, ExpressionParser parser, StandardEvaluationContext context){ Map tmpParamsMap = new HashMap(); List params = bodyEle.getParams(); if (Objects.nonNull(params)){ @@ -32,8 +36,9 @@ public class BlockArgsUtils { String exp =blockParam.getDataExp(); exp = blockParam.getDataExp().startsWith("#")?exp:"#"+exp; Object value = parser.parseExpression(exp).getValue(context); - context.setVariable(blockParam.getParamId(), value); - tmpParamsMap.put(blockParam.getParamId(), value); + String key = keyUseId?blockParam.getParamId():blockParam.getTitle(); + context.setVariable(key, value); + tmpParamsMap.put(key, value); } } return tmpParamsMap; diff --git a/models/pom.xml b/models/pom.xml index bc4d23f..fe307bb 100644 --- a/models/pom.xml +++ b/models/pom.xml @@ -13,6 +13,7 @@ pom viewer-models-deepseek + viewer-models-openai diff --git a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java index efe19bf..20e3f1a 100644 --- a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java +++ b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java @@ -16,14 +16,29 @@ public class DeepSeekBuilder extends ModelBuilder { super("DeepSeek"); } + @Override + public boolean wasDefault() { + return true; + } + @Override public ChatModel buildMode(AiNode node, ModelParam modelParam) { - DeepSeekChatOptions options = DeepSeekChatOptions.builder() - .model(modelParam.getModel()).temperature(modelParam.getTemperature()).build(); + DeepSeekChatOptions.Builder builder = DeepSeekChatOptions.builder() + .model(modelParam.getModel()); + if (modelParam.getMaxTokens() != null) { + builder.maxTokens(modelParam.getMaxTokens().intValue()); + } + if (modelParam.getTemperature() != null) { + builder.temperature(modelParam.getTemperature()); + } + DeepSeekChatOptions options = builder.build(); DeepSeekApi api = DeepSeekApi.builder() .baseUrl(node.getBaseUrl()) .apiKey(node.getApiKey()).build(); - ChatModel chatModel = DeepSeekChatModel.builder().defaultOptions(options).deepSeekApi(api).build(); + ChatModel chatModel = DeepSeekChatModel.builder() + .defaultOptions(options) + .deepSeekApi(api) + .build(); return chatModel; } } diff --git a/models/viewer-models-openai/pom.xml b/models/viewer-models-openai/pom.xml new file mode 100644 index 0000000..2ece91a --- /dev/null +++ b/models/viewer-models-openai/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + models + ${revision} + + + viewer-models-openai + + + 17 + 17 + UTF-8 + + + + + org.springframework.ai + spring-ai-openai + ${springai.version} + + + + \ No newline at end of file diff --git a/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelOpenAIAutoConfiguration.java b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelOpenAIAutoConfiguration.java new file mode 100644 index 0000000..554512f --- /dev/null +++ b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelOpenAIAutoConfiguration.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.models.openai; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class ModelOpenAIAutoConfiguration { +} diff --git a/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java new file mode 100644 index 0000000..d1570ee --- /dev/null +++ b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java @@ -0,0 +1,40 @@ +package xyz.thoughtset.viewer.models.openai; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.api.OpenAiApi; +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.factory.DefaultModelBuilder; +import xyz.thoughtset.viewer.common.ai.model.factory.ModelBuilder; + +@Component +public class OpenAIChatBuilder extends DefaultModelBuilder { + public OpenAIChatBuilder() { + super("OpenAI"); + } + + + @Override + public ChatModel buildMode(AiNode node, ModelParam modelParam) { + OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder() + .model(modelParam.getModel()); + if (modelParam.getMaxTokens() != null) { + builder.maxTokens(modelParam.getMaxTokens().intValue()); + } + if (modelParam.getTemperature() != null) { + builder.temperature(modelParam.getTemperature()); + } + OpenAiChatOptions options = builder.build(); + OpenAiApi api = OpenAiApi.builder() + .baseUrl(node.getBaseUrl()) + .apiKey(node.getApiKey()).build(); + ChatModel chatModel = OpenAiChatModel.builder() + .defaultOptions(options) + .openAiApi(api) + .build(); + return chatModel; + } +} diff --git a/models/viewer-models-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/models/viewer-models-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f15a472 --- /dev/null +++ b/models/viewer-models-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.models.openai.ModelOpenAIAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index 07cae71..5552927 100644 --- a/pom.xml +++ b/pom.xml @@ -312,6 +312,11 @@ viewer-models-deepseek ${revision} + + xyz.thoughtset.viewer + viewer-models-openai + ${revision} + -- Gitee From 7ee909da2c55cb48c890051c279241760f56dc1b Mon Sep 17 00:00:00 2001 From: q1279335527 <1279335527@qq.com> Date: Sun, 12 Oct 2025 13:09:27 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E4=BA=8E?= =?UTF-8?q?=E8=B4=A8=E8=B0=B1AI=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 +++- .../controller/AiSupportController.java | 5 + .../static/html/pages/ModelParam.json | 91 ++++++++----- .../resources/static/html/pages/funInfo.json | 36 +++--- .../common/ai/model/entity/ModelParam.java | 18 ++- .../entity/purpose/BaseModelSetting.java | 11 ++ .../entity/purpose/ChatModelSetting.java | 16 +++ .../entity/purpose/ImageModelSetting.java | 12 ++ .../entity/purpose/ModelPurposeEnum.java | 32 +++++ .../ai/model/factory/ChatModelBuilder.java | 20 +++ .../common/ai/model/factory/ModelBuilder.java | 37 +++++- .../common/ai/model/factory/ModelFactory.java | 22 ++-- .../ai/model/factory/ModelsRegistry.java | 14 +- .../model/service/ModelParamServiceImpl.java | 7 + .../viewer/common/core/enums/EnumValue.java | 11 ++ .../core/util/ExtractConstantFieldUtil.java | 23 ++++ executor/viewer-executor-blocks/pom.xml | 4 + .../executor/blocks/entity/AISupportBody.java | 16 +++ .../executor/blocks/entity/BaseBlockBody.java | 13 +- .../executor/blocks/entity/BlockTypeEnum.java | 4 +- .../executor/blocks/entity/ChooseBody.java | 7 +- .../executor/blocks/entity/ExecAIBody.java | 19 +++ .../blocks/entity/FunctionCallBody.java | 17 ++- .../executor/blocks/entity/IteratorBody.java | 8 +- .../executor/blocks/entity/LoopBody.java | 8 +- .../executor/blocks/entity/MCPBody.java | 12 +- .../executor/blocks/entity/SingleBody.java | 13 +- .../executor/blocks/entity/TaskBody.java | 13 +- .../executor/blocks/entity/TransposeBody.java | 9 +- .../executor/blocks/entity/ValueBody.java | 12 +- .../executor/AbstractBlockExecutor.java | 4 +- .../blocks/executor/BlockExecutorManager.java | 3 +- .../blocks/executor/ExecAIBlockExecutor.java | 63 +++++++++ .../FunctionCallingBlockExecutor.java | 121 ++++++++++++------ .../executor/IteratorBlockExecutor.java | 2 +- .../blocks/executor/LoopBlockExecutor.java | 2 +- .../blocks/executor/MCPBlockExecutor.java | 8 +- .../blocks/executor/SingleBlockExecutor.java | 2 +- .../blocks/executor/TaskBlockExecutor.java | 2 +- .../executor/TransposeBlockExecutor.java | 2 +- .../blocks/executor/ValueBlockExecutor.java | 2 +- .../ai/AbstractAISupportBlockExecutor.java | 75 +++++++++++ images/AiNode.png | Bin 0 -> 62172 bytes images/FunctionCallBlock.png | Bin 0 -> 75372 bytes images/FunctionCallResult.png | Bin 0 -> 37724 bytes images/ModelParam.png | Bin 0 -> 66607 bytes models/pom.xml | 1 + .../models/deepseek/DeepSeekBuilder.java | 22 +++- .../models/openai/OpenAIChatBuilder.java | 20 ++- models/viewer-models-zhipuai/pom.xml | 28 ++++ .../openai/ModelZhipuAIAutoConfiguration.java | 11 ++ .../models/openai/ZhipuChatBuilder.java | 36 ++++++ .../models/openai/ZhipuImageBuilder.java | 41 ++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../api/controller/EasyQueryController.java | 9 -- .../step/service/BlockInfoServiceImpl.java | 6 +- pom.xml | 8 +- 57 files changed, 829 insertions(+), 172 deletions(-) create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ImageModelSetting.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ModelPurposeEnum.java create mode 100644 commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ChatModelBuilder.java create mode 100644 commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/enums/EnumValue.java create mode 100644 commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/util/ExtractConstantFieldUtil.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/AISupportBody.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ExecAIBody.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ExecAIBlockExecutor.java create mode 100644 executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ai/AbstractAISupportBlockExecutor.java create mode 100644 images/AiNode.png create mode 100644 images/FunctionCallBlock.png create mode 100644 images/FunctionCallResult.png create mode 100644 images/ModelParam.png create mode 100644 models/viewer-models-zhipuai/pom.xml create mode 100644 models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelZhipuAIAutoConfiguration.java create mode 100644 models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuChatBuilder.java create mode 100644 models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuImageBuilder.java create mode 100644 models/viewer-models-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/README.md b/README.md index a1050f1..b4e1062 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Viewer 是一款基于SpringBoot构建的API敏捷开发平台,突破传统编 - **任意数据库**:默认支持MySQL可通过自定义驱动,通过界面自行扩展,支持所有JDBC连接的数据库 - **多种方式查询**:通过自定义http请求和自定义连接器,实现对第三方系统的集成 - **多数据源**:通过多数据源,支持在一次请求中可跨数据源,跨系统查询 +- **AI辅助**:通过FunctionCall,轻松实现AI与业务数据的结合 - **高性能**:基于SpringBoot的轻量级架构 - **实时生效**:支持动态创建、修改API;动态创建、修改数据源。热部署全程无感。 @@ -105,6 +106,16 @@ kill 进程号 ![mcp server端点截图](images/mcpserver.png) #### mcp server验证 ![mcp server验证截图](images/mcpservershow.png) +### AI 相关 +#### AI端点 +![AI端点截图](images/AiNode.png) +#### 模型设置 +![模型设置截图](images/ModelParam.png) +#### Function Call 逻辑块 +![FunctionCallBlock截图](images/FunctionCallBlock.png) +#### 运行结果 +![FunctionCallResult截图](images/FunctionCallResult.png) + ## ⚙️ 生产环境配置 ### 创建 `application.yml` 文件: @@ -129,10 +140,10 @@ java -jar viewer-apis-client.jar --spring.config.location=application.yml ## 功能规划 1. ✔️MCP SERVER (v1.2.0) -2. ⏳Function Call (目标版本 V1.3.0) -3. ⏳加入AI数据源(deepseek,openai) (目标版本 V1.3.0) -4. MCP CLIENT (计划版本 V1.4.0>> -5. 补充支持mybatis 注解 +2. ✔️Function Call (目标版本 V1.3.0) +3. ✔️加入AI数据源(deepseek,openai,zhipu) (目标版本 V1.3.0) +4. ⏳MCP CLIENT (计划版本 V1.4.0) +5. 补充支持mybatis \ 注解 6. 加入查询缓存 7. 执行算子(lua,js脚本) 8. 支持MCP协议全部功能 @@ -140,8 +151,9 @@ java -jar viewer-apis-client.jar --spring.config.location=application.yml 10. 定时任务 11. 接口认证 12. 优化导出 -13. 结合jdk25+springboot4优化 <> +13. 结合jdk25+springboot4优化 \ 14. spring native +15. AI Agent能力 ## 🤝 参与贡献 diff --git a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java index 3734eef..462e6c3 100644 --- a/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java +++ b/apis/viewer-apis-client/src/main/java/xyz/thoughtset/viewer/apis/client/controller/AiSupportController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import xyz.thoughtset.viewer.common.ai.model.factory.ModelsRegistry; import xyz.thoughtset.viewer.common.api.controller.BaseController; +import xyz.thoughtset.viewer.common.core.util.ExtractConstantFieldUtil; import xyz.thoughtset.viewer.modules.mcp.server.service.McpServerInfoService; import java.util.Collection; @@ -31,5 +32,9 @@ public class AiSupportController extends BaseController { public Object supportProvider(){ return ModelsRegistry.supportProvider(); } + @GetMapping(value = "/modelPurposes") + public Object modelPurposes(){ + return ExtractConstantFieldUtil.extractEnumToList(xyz.thoughtset.viewer.common.ai.model.entity.purpose.ModelPurposeEnum.class); + } } 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 19cfc10..f0f28c1 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 @@ -37,16 +37,16 @@ }, { "type": "tpl", - "title": "厂商", - "name": "provider", - "id": "u:b6de197aa95a", + "title": "用途", + "name": "purpose", + "id": "u:659469efc312", "placeholder": "-" }, { "type": "tpl", "title": "模型", "name": "model", - "id": "u:659469efc312", + "id": "u:db4c11e09de8", "placeholder": "-" }, { @@ -178,14 +178,15 @@ }, { "type": "select", - "label": "厂商", - "name": "provider", + "label": "用途", + "name": "purpose", "id": "u:18ea50831ac3", "multiple": false, "size": "full", - "source": "supportProvider", + "source": "modelPurposes", "selectFirst": false, - "searchable": true + "searchable": false, + "clearable": true }, { "name": "model", @@ -313,6 +314,30 @@ "valueField": "id", "required": true }, + { + "type": "select", + "label": "用途", + "name": "purpose", + "id": "u:f58dec9a6fbd", + "multiple": false, + "labelAlign": "inherit", + "labelWidth": "var(--sizes-base-24)", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "selectFirst": true, + "source": { + "method": "get", + "url": "modelPurposes", + "requestAdaptor": "", + "adaptor": "", + "messages": {} + }, + "required": true + }, { "name": "model", "label": "模型", @@ -331,7 +356,7 @@ { "type": "textarea", "label": "系统提示词", - "name": "systemPrompt", + "name": "modelArgs.systemPrompt", "id": "u:f69a7e0c79b5", "minRows": 5, "maxRows": 20, @@ -340,7 +365,7 @@ "mode": "normal" }, { - "name": "temperature", + "name": "modelArgs.temperature", "label": "随机性", "type": "input-number", "id": "u:a62c3df783e5", @@ -360,23 +385,30 @@ "max": 1 }, { - "name": "maxTokens", - "label": "单次最大Tokens", - "type": "input-number", - "id": "u:68e43de6ec4f", - "labelAlign": "inherit", - "wrapperCustomStyle": { - "root": { - "margin-bottom": "6px", - "margin-top": "6px" + "type": "service", + "body": [ + { + "name": "modelArgs.maxTokens", + "label": "单次最大Tokens", + "type": "input-number", + "id": "u:68e43de6ec4f", + "labelAlign": "inherit", + "wrapperCustomStyle": { + "root": { + "margin-bottom": "6px", + "margin-top": "6px" + } + }, + "keyboard": true, + "step": 1, + "min": 0, + "value": "", + "precision": "", + "kilobitSeparator": false } - }, - "keyboard": true, - "step": 1, - "min": 0, - "value": "", - "precision": "", - "kilobitSeparator": false + ], + "id": "u:7004050cb373", + "dsType": "api" }, { "draggable": true, @@ -408,7 +440,6 @@ "id": "u:faf4e33fcdf6", "minRows": 3, "maxRows": 20, - "labelWidth": "var(--sizes-base-24)", "labelAlign": "inherit" } ], @@ -456,7 +487,8 @@ "pkey": "${id}" }, "sendOn": "${!ISEMPTY(id)}" - } + }, + "debug": false } ], "title": "编辑模型", @@ -487,7 +519,8 @@ "editorSetting": { "displayName": "" }, - "resizable": false + "resizable": false, + "hideActions": false } } } \ No newline at end of file diff --git a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json index be7830f..6879e7c 100644 --- a/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json +++ b/apis/viewer-apis-client/src/main/resources/static/html/pages/funInfo.json @@ -463,7 +463,7 @@ "outputVar": "responseResult", "options": {}, "api": { - "url": "/c/delete/queryBlock", + "url": "/c/delete/blockInfo", "method": "post", "requestAdaptor": "", "adaptor": "", @@ -810,27 +810,27 @@ "value": "task" }, { - "label": "iterator", + "label": "循环", "value": "iterator" }, { - "label": "choose", + "label": "选择", "value": "choose" }, { - "label": "loop", + "label": "循环", "value": "loop" }, { - "label": "value", + "label": "赋值", "value": "value" }, { - "label": "transpose", + "label": "数据转置", "value": "transpose" }, { - "label": "single", + "label": "单一值", "value": "single" }, { @@ -839,17 +839,23 @@ }, { "label": "mcp", - "value": "mcp" + "value": "mcp", + "hiddenOn": "${true}" }, { "label": "parallel", "value": "parallel", "hiddenOn": "${true}" + }, + { + "label": "AI创作", + "value": "execAI", + "hiddenOn": "${true}" } ], "id": "u:fd07835da4f1", "multiple": false, - "value": "task", + "value": "single", "labelAlign": "inherit" } ], @@ -1005,7 +1011,7 @@ "id": "u:8d2a2efc675c", "value": "", "visible": true, - "hiddenOn": "${bodyType!=\"FunctionCall\"}", + "hiddenOn": "${!(bodyType==\"FunctionCall\" || bodyType==\"execAI\" || bodyType==\"mcp\")}", "clearValueOnHidden": true, "multiple": false, "source": { @@ -1036,7 +1042,7 @@ "optionAtLeft": true, "value": true, "visible": true, - "hiddenOn": "${bodyType!=\"FunctionCall\" }", + "hiddenOn": "${!(bodyType==\"FunctionCall\" || bodyType==\"execAI\" || bodyType==\"mcp\")}", "clearValueOnHidden": true } ], @@ -1073,7 +1079,7 @@ "id": "u:018b6276b71b", "minRows": 3, "maxRows": 20, - "hiddenOn": "${bodyType!=\"FunctionCall\"}", + "hiddenOn": "${!(bodyType==\"FunctionCall\" || bodyType==\"execAI\" || bodyType==\"mcp\")}", "clearValueOnHidden": true } ], @@ -1432,12 +1438,12 @@ "editBtnIcon": "", "editBtnLabel": "编辑", "clearValueOnHidden": true, - "hiddenOn": "${bodyType==\"transpose\" || bodyType==\"value\"}" + "hiddenOn": "${bodyType==\"transpose\" || bodyType==\"value\" || bodyType==\"execAI\"}" }, { "type": "input-table", "name": "bodys[0].params", - "label": "表格表单", + "label": "参数设置", "columns": [ { "label": "属性名", @@ -1473,7 +1479,7 @@ "removable": true, "showIndex": true, "clearValueOnHidden": true, - "hiddenOn": "${ bodyType!=\"value\"}" + "hiddenOn": "${!(bodyType==\"value\" || bodyType==\"execAI\")}" } ], "id": "u:1bf35f729bae", 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 21f07f7..2f90fd8 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 @@ -4,6 +4,9 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.BaseModelSetting; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.ChatModelSetting; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.ModelPurposeEnum; import xyz.thoughtset.viewer.common.core.entity.BaseMeta; import xyz.thoughtset.viewer.common.core.entity.IdMeta; import xyz.thoughtset.viewer.common.crud.core.annotation.ApiCRUDPower; @@ -16,12 +19,19 @@ import java.util.Map; @ApiCRUDPower(insert = false,save = true,update = false,list = true) public class ModelParam extends BaseMeta { protected String model; - protected String systemPrompt; - protected Long maxTokens; - private Double temperature; + protected ModelPurposeEnum purpose; + protected String setting; protected String paramJson; - + protected Integer maxMemory = 8; protected transient Map paramMap; + protected transient BaseModelSetting modelArgs; + + public String chatSystemPrompt(){ + if(modelArgs!=null && modelArgs instanceof ChatModelSetting){ + return ((ChatModelSetting) modelArgs).getSystemPrompt(); + } + return ""; + } } 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 new file mode 100644 index 0000000..62ad41b --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/BaseModelSetting.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.common.ai.model.entity.purpose; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +//@Data +//@NoArgsConstructor +//@AllArgsConstructor +public class BaseModelSetting { +} 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 new file mode 100644 index 0000000..ce0d5eb --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ChatModelSetting.java @@ -0,0 +1,16 @@ +package xyz.thoughtset.viewer.common.ai.model.entity.purpose; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ChatModelSetting extends BaseModelSetting { + protected String systemPrompt; + protected Long maxTokens; + protected Double temperature; + + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ImageModelSetting.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ImageModelSetting.java new file mode 100644 index 0000000..18e0bfb --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ImageModelSetting.java @@ -0,0 +1,12 @@ +package xyz.thoughtset.viewer.common.ai.model.entity.purpose; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ImageModelSetting extends BaseModelSetting { + private String size; +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ModelPurposeEnum.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ModelPurposeEnum.java new file mode 100644 index 0000000..3ad8fcf --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/entity/purpose/ModelPurposeEnum.java @@ -0,0 +1,32 @@ +package xyz.thoughtset.viewer.common.ai.model.entity.purpose; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; +import xyz.thoughtset.viewer.common.core.enums.EnumValue; + +@Getter +@AllArgsConstructor +public enum ModelPurposeEnum implements IEnum { + CHAT("CHAT", ChatModelSetting.class) + ,IMAGE("IMAGE", ImageModelSetting.class) +// ,EMBEDDING("EMBEDDING") +// ,EMBEDDING("EMBEDDING") + ; + + @JsonValue + private final String title; + private final Class settingClass; + +// private ModelPurposeEnum(String title,String value) { +// this.value = value; +// this.title = title; +// } + + + @Override + public String getValue() { + return title; + } +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ChatModelBuilder.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ChatModelBuilder.java new file mode 100644 index 0000000..e729e62 --- /dev/null +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ChatModelBuilder.java @@ -0,0 +1,20 @@ +package xyz.thoughtset.viewer.common.ai.model.factory; + + +import lombok.Getter; +import lombok.NonNull; + +@Getter +public abstract class ChatModelBuilder extends ModelBuilder{ + + + protected ChatModelBuilder(@NonNull String provider) { + super(provider); + } + + protected ChatModelBuilder(@NonNull String provider, String aiModel) { + super(provider, aiModel); + } + + +} diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java index c12565f..a8e2bac 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelBuilder.java @@ -5,38 +5,61 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.Getter; import lombok.NonNull; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.model.Model; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ObjectUtils; 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.purpose.ModelPurposeEnum; + +import java.util.List; +import java.util.Optional; @Getter public abstract class ModelBuilder { protected String provider; - protected String aiModel; + protected List supportModels; +// +// protected ModelPurposeEnum purposeEnum; + @Autowired protected ObjectMapper objectMapper; protected ModelBuilder(@NonNull String provider) { - this(provider,null); + this(provider,""); } protected ModelBuilder(@NonNull String provider, String aiModel) { + this(provider, List.of(aiModel)); + } + + protected ModelBuilder(String provider, List supportModels) { this.provider = provider; - this.aiModel = aiModel; + this.supportModels = supportModels; ModelsRegistry.registerModel(this); } public boolean wasDefault(){return false;} public boolean checkExecModel(AiNode node, ModelParam modelParam){ - boolean checkFlag = StringUtils.hasText(this.aiModel); - if (checkFlag){ - checkFlag = this.aiModel.equals(modelParam.getModel()); + boolean checkFlag = ObjectUtils.isEmpty(this.supportModels); + if (!checkFlag){ + for (String model : this.supportModels) { + //todo: 模型模糊匹配 + if (model.equals(modelParam.getModel())){ + checkFlag = true; + break; + } + } } return this.provider.equals(node.getProvider()) && checkFlag; } - public abstract ChatModel buildMode(AiNode node, ModelParam modelParam); + public abstract Model buildMode(AiNode node, ModelParam modelParam); + + public void setOptionalValue(T value, java.util.function.Consumer setter) { + Optional.ofNullable(value).ifPresent(setter); + } } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java index dee5f32..966a82e 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelFactory.java @@ -1,14 +1,19 @@ package xyz.thoughtset.viewer.common.ai.model.factory; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.model.Model; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; 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.purpose.BaseModelSetting; import xyz.thoughtset.viewer.common.ai.model.service.AiNodeService; import java.util.HashMap; @@ -19,25 +24,24 @@ import java.util.concurrent.ConcurrentHashMap; @Component public class ModelFactory { @Autowired - private ModelParamDao modelParamDao; + private ObjectMapper objectMapper; @Autowired private AiNodeService aiNodeService; + @Bean + public ToolCallingManager toolCallingManager() { + return ToolCallingManager.builder().build(); + } - public ChatClient.Builder clientBuilder(@NonNull ModelParam modelParam){ + public Model buildModel(ModelParam modelParam){ AiNode aiNode = aiNodeService.selectDetail(modelParam.getPid()); - ChatClient.Builder clientBuilder = clientBuilder(aiNode,modelParam); - return clientBuilder; - } - - public ChatModel buildModel(AiNode aiNode, ModelParam modelParam){ ModelBuilder builder = ModelsRegistry.loadBuilder(aiNode, modelParam); return builder.buildMode(aiNode, modelParam); } - public ChatClient.Builder clientBuilder(AiNode aiNode, ModelParam modelParam){ - ChatModel chatModel = buildModel(aiNode, modelParam); + public ChatClient.Builder clientBuilder(@NonNull ModelParam modelParam){ + ChatModel chatModel = (ChatModel) buildModel(modelParam); return ChatClient.builder(chatModel); } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java index 3026161..ae2b5bc 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/factory/ModelsRegistry.java @@ -6,18 +6,18 @@ import org.springframework.util.MultiValueMapAdapter; import xyz.thoughtset.viewer.common.ai.model.entity.AiNode; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; +import java.util.*; public class ModelsRegistry { - + private static final Map DEFAULT_MODEL_BUILDER_MAP = new HashMap(); private static final MultiValueMap MODEL_BUILDER_MAP = new MultiValueMapAdapter<>(new HashMap<>()); public static void registerModel(ModelBuilder modelBuilder){ String modelProvider = modelBuilder.getProvider(); + if (modelBuilder.wasDefault()){ + DEFAULT_MODEL_BUILDER_MAP.put(modelProvider,modelBuilder); + } MODEL_BUILDER_MAP.add(modelProvider,modelBuilder); } @@ -25,12 +25,14 @@ public class ModelsRegistry { List list = MODEL_BUILDER_MAP.get(aiNode.getProvider()); ModelBuilder targetBuilder = null; for (ModelBuilder ele : list){ - if (ele.wasDefault()) targetBuilder = ele; if (ele.checkExecModel(aiNode,params)){ targetBuilder = ele; break; } } + if (targetBuilder == null){ + targetBuilder = DEFAULT_MODEL_BUILDER_MAP.get(aiNode.getProvider()); + } return targetBuilder; } diff --git a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java index d48f503..f54fce1 100644 --- a/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java +++ b/commons/viewer-common-ai-model/src/main/java/xyz/thoughtset/viewer/common/ai/model/service/ModelParamServiceImpl.java @@ -8,6 +8,7 @@ import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.BaseModelSetting; import xyz.thoughtset.viewer.common.crud.core.service.BaseServiceImpl; import java.util.Map; @@ -17,6 +18,9 @@ public class ModelParamServiceImpl extends BaseServiceImpl { + default public String getTitle() { + return null; + } + public T getValue(); + +} diff --git a/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/util/ExtractConstantFieldUtil.java b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/util/ExtractConstantFieldUtil.java new file mode 100644 index 0000000..6c92c29 --- /dev/null +++ b/commons/viewer-common-core/src/main/java/xyz/thoughtset/viewer/common/core/util/ExtractConstantFieldUtil.java @@ -0,0 +1,23 @@ +package xyz.thoughtset.viewer.common.core.util; + +import com.baomidou.mybatisplus.annotation.IEnum; +import org.springframework.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ExtractConstantFieldUtil { + + public static List extractEnumToList(Class clazz) { + IEnum[] enumConstants = clazz.getEnumConstants(); + if (ObjectUtils.isEmpty(enumConstants)) return Collections.EMPTY_LIST; + List list = new ArrayList(enumConstants.length); + for (IEnum enumConstant : enumConstants) { + list.add(enumConstant.getValue()); + } + return list; + } + + +} diff --git a/executor/viewer-executor-blocks/pom.xml b/executor/viewer-executor-blocks/pom.xml index cddff70..add7290 100644 --- a/executor/viewer-executor-blocks/pom.xml +++ b/executor/viewer-executor-blocks/pom.xml @@ -41,6 +41,10 @@ xyz.thoughtset.viewer viewer-models-openai + + xyz.thoughtset.viewer + viewer-models-zhipuai + \ No newline at end of file diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/AISupportBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/AISupportBody.java new file mode 100644 index 0000000..cb9e70f --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/AISupportBody.java @@ -0,0 +1,16 @@ +package xyz.thoughtset.viewer.executor.blocks.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public abstract class AISupportBody extends BaseBlockBody { + + protected String modelId; + protected String userMsg; + protected Boolean jsonType; + +} diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BaseBlockBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BaseBlockBody.java index fd6ce83..6b9454d 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BaseBlockBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BaseBlockBody.java @@ -11,11 +11,22 @@ import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor -public class BaseBlockBody { +public abstract class BaseBlockBody { protected List bodyEles; + protected String bodyType = supportType().getType(); public int listSize() { return !ObjectUtils.isEmpty(bodyEles) ? bodyEles.size()+2 : 0; } + protected abstract BlockTypeEnum supportType(); + + public String getBodyType() { + return supportType().getType(); + } + + public void setBodyType(String bodyType) { + this.bodyType = supportType().getType(); + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java index 7bc5061..5f30f84 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/BlockTypeEnum.java @@ -16,8 +16,10 @@ public enum BlockTypeEnum { // PARALLEL("parallel", ParallelBody.class), // MCP MCP("mcp", MCPBody.class), - // MCP + // FunctionCall FUNCTION_CALL("FunctionCall", FunctionCallBody.class), + // ExecAI + EXECAI("ExecAI", ExecAIBody.class), VALUE("value", ValueBody.class), // 单体块 DEFAULT("default", SingleBody.class), diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ChooseBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ChooseBody.java index 31bf3ca..6666606 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ChooseBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ChooseBody.java @@ -9,6 +9,11 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class ChooseBody extends BaseBlockBody { private String caseRepx; - private String bodyType = BlockTypeEnum.CHOOSE.getType(); + // private String bodyType = BlockTypeEnum.CHOOSE.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.CHOOSE; + } } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ExecAIBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ExecAIBody.java new file mode 100644 index 0000000..4941356 --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ExecAIBody.java @@ -0,0 +1,19 @@ +package xyz.thoughtset.viewer.executor.blocks.entity; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +//@NoArgsConstructor +//@AllArgsConstructor +public class ExecAIBody extends AISupportBody { + // private String bodyType = BlockTypeEnum.EXECAI.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.EXECAI; + } + + +} diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java index e6f28ea..eb51b90 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/FunctionCallBody.java @@ -5,12 +5,15 @@ import lombok.Data; import lombok.NoArgsConstructor; @Data -@NoArgsConstructor -@AllArgsConstructor -public class FunctionCallBody extends BaseBlockBody { - private String bodyType = BlockTypeEnum.FUNCTION_CALL.getType(); - private String modelId; - private String userMsg; - private Boolean jsonType; +//@NoArgsConstructor +//@AllArgsConstructor +public class FunctionCallBody extends AISupportBody { + // private String bodyType = BlockTypeEnum.FUNCTION_CALL.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.FUNCTION_CALL; + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/IteratorBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/IteratorBody.java index a80f9b0..6db6378 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/IteratorBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/IteratorBody.java @@ -9,6 +9,12 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class IteratorBody extends BaseBlockBody { private String iteratorRepx; - private String bodyType = BlockTypeEnum.ITERATOR.getType(); + // private String bodyType = BlockTypeEnum.ITERATOR.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.ITERATOR; + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/LoopBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/LoopBody.java index e113460..2a04ad7 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/LoopBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/LoopBody.java @@ -10,6 +10,12 @@ import lombok.NoArgsConstructor; public class LoopBody extends BaseBlockBody { private String loopRepx; private boolean doFirst = false; - private String bodyType = BlockTypeEnum.LOOP.getType(); +// private String bodyType = BlockTypeEnum.LOOP.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.LOOP; + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/MCPBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/MCPBody.java index c24fb98..4c9eaab 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/MCPBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/MCPBody.java @@ -5,9 +5,15 @@ import lombok.Data; import lombok.NoArgsConstructor; @Data -@NoArgsConstructor -@AllArgsConstructor +//@NoArgsConstructor +//@AllArgsConstructor public class MCPBody extends BaseBlockBody { - private String bodyType = BlockTypeEnum.MCP.getType(); + +// private String bodyType = BlockTypeEnum.MCP.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.MCP; + } } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/SingleBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/SingleBody.java index 22ce9b4..518343e 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/SingleBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/SingleBody.java @@ -6,13 +6,20 @@ import lombok.NoArgsConstructor; import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; @Data -@NoArgsConstructor -@AllArgsConstructor +//@NoArgsConstructor +//@AllArgsConstructor public class SingleBody extends BaseBlockBody { - private String bodyType = BlockTypeEnum.SINGLE.getType(); + +// private String bodyType = BlockTypeEnum.SINGLE.getType(); public BlockBodyEle getNode(){ return this.getBodyEles().get(0); } + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.SINGLE; + } + + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TaskBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TaskBody.java index 0f3183b..fc0664c 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TaskBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TaskBody.java @@ -5,9 +5,16 @@ import lombok.Data; import lombok.NoArgsConstructor; @Data -@NoArgsConstructor -@AllArgsConstructor +//@NoArgsConstructor +//@AllArgsConstructor public class TaskBody extends BaseBlockBody { - private String bodyType = BlockTypeEnum.TASK.getType(); + +// private String bodyType = BlockTypeEnum.TASK.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.TASK; + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TransposeBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TransposeBody.java index 492d861..68fc38a 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TransposeBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/TransposeBody.java @@ -9,6 +9,13 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class TransposeBody extends BaseBlockBody { private String targetRepx; - private String bodyType = BlockTypeEnum.TRANSPOSE.getType(); + +// private String bodyType = BlockTypeEnum.TRANSPOSE.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.TRANSPOSE; + } + } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ValueBody.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ValueBody.java index 97b75ae..90fe9bd 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ValueBody.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/entity/ValueBody.java @@ -5,9 +5,15 @@ import lombok.Data; import lombok.NoArgsConstructor; @Data -@NoArgsConstructor -@AllArgsConstructor +//@NoArgsConstructor +//@AllArgsConstructor public class ValueBody extends BaseBlockBody { - private String bodyType = BlockTypeEnum.VALUE.getType(); +// private String bodyType = BlockTypeEnum.VALUE.getType(); + + @Override + protected BlockTypeEnum supportType() { + return BlockTypeEnum.VALUE; + } + } 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 48912f9..c88ec12 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 @@ -55,7 +55,6 @@ public abstract class AbstractBlockExecutor implements if (superClass instanceof ParameterizedType) { // 获取实际类型参数 Type[] typeArgs = ((ParameterizedType) superClass).getActualTypeArguments(); -// System.out.println("T's type: " + typeArgs[0]); supportType = BlockTypeEnum.fromClass((Class) typeArgs[0]); } } @@ -72,6 +71,7 @@ public abstract class AbstractBlockExecutor implements } body.setBodyEles((List) block.getBodys()); if (ObjectUtils.isEmpty(body.getBodyEles()) + && !BlockTypeEnum.EXECAI.equals(supportType) && !BlockTypeEnum.TRANSPOSE.equals(supportType)) { return null; } @@ -80,7 +80,7 @@ public abstract class AbstractBlockExecutor implements } return doQuery(block, body, params,parser, context); } - abstract Object doQuery(BlockInfo block, T body, Map params, ExpressionParser parser,StandardEvaluationContext context) throws ExecException; + protected abstract Object doQuery(BlockInfo block, T body, Map params, ExpressionParser parser,StandardEvaluationContext context) throws ExecException; protected Object execNode(BlockBodyEle ele, Map params, ExpressionParser parser, StandardEvaluationContext context){ diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java index 9ae9a17..6d6ff9d 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/BlockExecutorManager.java @@ -45,14 +45,13 @@ public class BlockExecutorManager { for (BlockInfo block : blocks) { Object queryResult; try{ - //todo: block执行 blockInfoService.loadBlockEles(block); queryResult = ExecutorRegistry.findExecutor(block.getBodyType()) .executeQuery(block, parser, contentMap,context); -// queryResult = blockExecutorManager.(block.getId(), params); }catch (ExecException e){ throw e.addApiId(funId); }catch (Exception e){ + e.printStackTrace(); ExecException exc = ExcInfo.buildAndThrowExc(e,params); exc.addBlockId(block.getId()); exc.addApiId(funId); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ExecAIBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ExecAIBlockExecutor.java new file mode 100644 index 0000000..a9a450f --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ExecAIBlockExecutor.java @@ -0,0 +1,63 @@ +package xyz.thoughtset.viewer.executor.blocks.executor; + +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.model.Model; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; +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.executor.blocks.entity.BlockTypeEnum; +import xyz.thoughtset.viewer.executor.blocks.entity.ExecAIBody; +import xyz.thoughtset.viewer.executor.blocks.entity.ValueBody; +import xyz.thoughtset.viewer.executor.blocks.executor.ai.AbstractAISupportBlockExecutor; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockInfo; +import xyz.thoughtset.viewer.modules.step.entity.block.EleParam; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Component +public class ExecAIBlockExecutor extends AbstractAISupportBlockExecutor { + + + @Override + protected Object doQuery(BlockInfo block, ExecAIBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + HashMap resultMaps = new HashMap<>(params); +// BlockBodyEle bodyEle = block.firstBody(); +// List eleParams = bodyEle.getParams(); +// if (Objects.nonNull(eleParams)){ +// int size = eleParams.size(); +// for (EleParam blockParam : eleParams){ +// Object value = null; +// if(StringUtils.hasLength(blockParam.getDataExp())){ +// String exp =blockParam.getDataExp(); +// exp = blockParam.getDataExp().startsWith("#")?exp:"#"+exp; +// value = parser.parseExpression(exp).getValue(context); +// } +// //qbid作为值对应名称进行存入 +// String key = blockParam.getParamId(); +// if (StringUtils.hasText(key)){ +// context.setVariable(key, value); +// params.put(key, value); +// resultMaps.put(key, value); +// } else if (size == 1) { +// return value; +// } +// } +// } +// ModelParam modelParam = loadModelParam(body); +// String userPrompt = fillPrompt(body.getUserMsg(), parser, context); +// String systemPrompt = fillPrompt(modelParam.getSystemPrompt(), parser, context); +// Model aiModel = modelFactory.buildModel(modelParam); +// if (StringUtils.hasText(systemPrompt)){ +// builder.defaultSystem(systemPrompt); +// } +// ChatClient client = builder.build(); + return null; + } +} 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 index 2d5cfcb..45c6fbc 100644 --- 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 @@ -4,11 +4,10 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClientResponse; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.MessageWindowChatMemory; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.messages.UserMessage; +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.DefaultChatOptions; @@ -16,19 +15,25 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.model.tool.ToolCallingChatOptions; +import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.ToolExecutionResult; import org.springframework.ai.tool.ToolCallback; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.entity.purpose.ChatModelSetting; import xyz.thoughtset.viewer.common.ai.model.factory.ModelFactory; import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; +import xyz.thoughtset.viewer.executor.blocks.entity.BlockTypeEnum; import xyz.thoughtset.viewer.executor.blocks.entity.FunctionCallBody; +import xyz.thoughtset.viewer.executor.blocks.executor.ai.AbstractAISupportBlockExecutor; import xyz.thoughtset.viewer.executor.blocks.tool.BlockToolCallbackProvider; import xyz.thoughtset.viewer.executor.blocks.tool.FunctionCallBlockToolCallbackProvider; import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; @@ -42,26 +47,30 @@ import java.util.stream.Collectors; @Slf4j @Component -public class FunctionCallingBlockExecutor extends AbstractBlockExecutor { +public class FunctionCallingBlockExecutor extends AbstractAISupportBlockExecutor { +// @Autowired +// protected ModelParamDao modelParamDao; +// @Autowired +// protected ModelFactory modelFactory; @Autowired - private ModelParamDao modelParamDao; - @Autowired - private ModelFactory modelFactory; + private ToolCallingManager toolCallingManager; @Autowired private FunctionCallBlockToolCallbackProvider provider; + + //先选择函数,再加载参数执行函数 @SneakyThrows @Override - Object doQuery(BlockInfo block, FunctionCallBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { - if (!StringUtils.hasText(body.getModelId())){ - throw new Exception("模型参数未配置"); - } - ModelParam modelParam = modelParamDao.selectById(body.getModelId()); + 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); MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder() - .maxMessages(10) + .maxMessages(modelParam.getMaxMemory()) .build(); - String systemText = modelParam.getSystemPrompt(); +// ChatModelSetting settingObj = (ChatModelSetting) modelParam.getModelArgs(); + String limitSystemPrompt = + "选择适当的方法执行,由于部分参数存储在本地,对于非required参数,可以填入建议值或空值。预期值与方法执行结果不一致时,以方法结果为准。"; + String systemText = modelParam.chatSystemPrompt(); if (body.getJsonType()!=null && body.getJsonType().booleanValue()){ // systemText = """ // 请只返回数组类型的JSON,不要任何额外说明,当后续要求与当前冲突时,以当前要求为准! @@ -72,17 +81,20 @@ public class FunctionCallingBlockExecutor extends AbstractBlockExecutors.text(finalSystemText).params(params)); List funs = body.getBodyEles(); ToolCallingChatOptions.Builder toolCallingBuilder = ToolCallingChatOptions.builder() - .internalToolExecutionEnabled(true); + .internalToolExecutionEnabled(false); if (!ObjectUtils.isEmpty(funs)){ Map funParamMap = funs.parallelStream().collect(Collectors.toMap( BlockBodyEle::getEleId, Function.identity() @@ -91,7 +103,9 @@ public class FunctionCallingBlockExecutor extends AbstractBlockExecutor 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.info("FunctionCallingBlockExecutor response: {}", val); - Object result = val; + log.debug("===结果:{}", val); + Object result; try { // List results = callResponseSpec.entity(new ParameterizedTypeReference>() {}); result = objectMapper.readValue(val, DATA_TYPE); }catch (Exception e){ - e.printStackTrace(); + if (!e.getClass().getName().equals("com.fasterxml.jackson.core.JsonParseException")){ + log.error("错误结果:{}", val); + e.printStackTrace(); + } result = val; } return result; } - private void saveMegToChatMemory(String chatId, List messages, ChatMemory chatMemory) { - chatMemory.add(chatId, messages); - List memoryMessage = chatMemory.get(chatId); - - long assistantCount = memoryMessage.stream().filter(m -> m.getMessageType() == MessageType.ASSISTANT).count(); - long toolCount = memoryMessage.stream().filter(m -> m.getMessageType() == MessageType.TOOL).count(); - - if (assistantCount != toolCount) { - log.error("tool 工具消息不成对,重置 memory list"); - chatMemory.clear(chatId); - chatMemory.add(chatId, memoryMessage.subList(1, memoryMessage.size())); - } - } } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/IteratorBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/IteratorBlockExecutor.java index a17f46b..c1c5b9a 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/IteratorBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/IteratorBlockExecutor.java @@ -23,7 +23,7 @@ public class IteratorBlockExecutor extends AbstractBlockExecutor @Override - Object doQuery(BlockInfo block, IteratorBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, IteratorBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { List results = new ArrayList<>(); LongAdder index = new LongAdder(); context.setVariable(NodeConstant.DATA_NODE, results); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/LoopBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/LoopBlockExecutor.java index 5a12588..17518f5 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/LoopBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/LoopBlockExecutor.java @@ -21,7 +21,7 @@ public class LoopBlockExecutor extends AbstractBlockExecutor { @Override - Object doQuery(BlockInfo block, LoopBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, LoopBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { List results = new ArrayList<>(); context.setVariable(NodeConstant.DATA_NODE, results); params.put(NodeConstant.DATA_NODE, results); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/MCPBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/MCPBlockExecutor.java index bfbe8c0..527a86b 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/MCPBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/MCPBlockExecutor.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; import xyz.thoughtset.viewer.executor.blocks.constants.NodeConstant; +import xyz.thoughtset.viewer.executor.blocks.entity.BlockTypeEnum; import xyz.thoughtset.viewer.executor.blocks.entity.MCPBody; import xyz.thoughtset.viewer.executor.blocks.entity.MCPBody; import xyz.thoughtset.viewer.modules.step.entity.block.BlockBodyEle; @@ -20,10 +21,13 @@ import java.util.concurrent.atomic.LongAdder; //todo //@Component public class MCPBlockExecutor extends AbstractBlockExecutor { - + @Override + BlockTypeEnum getSupportType() { + return BlockTypeEnum.MCP; + } @Override - Object doQuery(BlockInfo block, MCPBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, MCPBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { List results = new ArrayList<>(); StringBuilder sb = new StringBuilder("你是方法调度助手,任务是从用户问题中选择最合适的方法,并给出参数。\n可用方法:\n"); // registry.getAll().forEach((name, regMethod) -> diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/SingleBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/SingleBlockExecutor.java index cdb3d98..ae4e58a 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/SingleBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/SingleBlockExecutor.java @@ -24,7 +24,7 @@ public class SingleBlockExecutor extends AbstractBlockExecutor { } @Override - Object doQuery(BlockInfo block, SingleBody baseBlockBody, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, SingleBody baseBlockBody, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { return execNode(baseBlockBody.getNode(), params, parser, context); } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TaskBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TaskBlockExecutor.java index 901a7e8..2732eb1 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TaskBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TaskBlockExecutor.java @@ -19,7 +19,7 @@ public class TaskBlockExecutor extends AbstractBlockExecutor { @Override - Object doQuery(BlockInfo block, TaskBody baseBlockBody, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, TaskBody baseBlockBody, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { // Implement the logic for executing a loop block here // This is a placeholder implementation List results = new ArrayList<>(baseBlockBody.listSize()); diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TransposeBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TransposeBlockExecutor.java index f3f3791..9c2f37e 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TransposeBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/TransposeBlockExecutor.java @@ -26,7 +26,7 @@ public class TransposeBlockExecutor extends AbstractBlockExecutor @Override - Object doQuery(BlockInfo block, TransposeBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, TransposeBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { //行列转换 String exp = body.getTargetRepx(); exp = exp.startsWith("#")?exp:"#"+exp; diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ValueBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ValueBlockExecutor.java index fa4a4a1..cc9053d 100644 --- a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ValueBlockExecutor.java +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ValueBlockExecutor.java @@ -55,7 +55,7 @@ public class ValueBlockExecutor extends AbstractBlockExecutor { } @Override - Object doQuery(BlockInfo block, ValueBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { + protected Object doQuery(BlockInfo block, ValueBody body, Map params, ExpressionParser parser, StandardEvaluationContext context) throws ExecException { return null; } } diff --git a/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ai/AbstractAISupportBlockExecutor.java b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ai/AbstractAISupportBlockExecutor.java new file mode 100644 index 0000000..4f06b0f --- /dev/null +++ b/executor/viewer-executor-blocks/src/main/java/xyz/thoughtset/viewer/executor/blocks/executor/ai/AbstractAISupportBlockExecutor.java @@ -0,0 +1,75 @@ +package xyz.thoughtset.viewer.executor.blocks.executor.ai; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.memory.ChatMemory; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.lang.NonNull; +import org.springframework.util.StringUtils; +import xyz.thoughtset.viewer.common.ai.model.dao.ModelParamDao; +import xyz.thoughtset.viewer.common.ai.model.entity.ModelParam; +import xyz.thoughtset.viewer.common.ai.model.factory.ModelFactory; +import xyz.thoughtset.viewer.common.ai.model.service.ModelParamService; +import xyz.thoughtset.viewer.common.exc.exceptions.ExecException; +import xyz.thoughtset.viewer.executor.blocks.entity.AISupportBody; +import xyz.thoughtset.viewer.executor.blocks.entity.BaseBlockBody; +import xyz.thoughtset.viewer.executor.blocks.entity.FunctionCallBody; +import xyz.thoughtset.viewer.executor.blocks.executor.AbstractBlockExecutor; +import xyz.thoughtset.viewer.modules.step.entity.block.BlockInfo; + +import java.util.List; +import java.util.Map; + +@Slf4j +@SuppressWarnings({"unchecked","SpringJavaInjectionPointsAutowiringInspection"}) +public abstract class AbstractAISupportBlockExecutor extends AbstractBlockExecutor { + @Autowired + protected ModelParamService modelParamService; + @Autowired + protected ModelFactory modelFactory; + + protected void saveMegToChatMemory(String chatId, List messages, ChatMemory chatMemory) { + chatMemory.add(chatId, messages); + + List memoryMessage = chatMemory.get(chatId); + + long assistantCount = memoryMessage.stream() + .filter(m -> m.getMessageType() == MessageType.ASSISTANT) + .count(); + long toolCount = memoryMessage.stream() + .filter(m -> m.getMessageType() == MessageType.TOOL) + .count(); + + if (assistantCount != toolCount) { + log.error("消息不成对{},重置",memoryMessage); + chatMemory.clear(chatId); + chatMemory.add(chatId, memoryMessage.subList(1, memoryMessage.size())); + } + } + @SneakyThrows + protected ModelParam loadModelParam(@NonNull AISupportBody aiBody) throws ExecException { + String modelId = aiBody.getModelId(); + if (!StringUtils.hasText(modelId)){ + throw new Exception("模型参数未配置"); + } + ModelParam modelParam = modelParamService.selectDetail(modelId); + if (modelParam == null){ + throw new Exception("模型参数不存在"); + } + return modelParam; + } + + @SneakyThrows + protected static String fillPrompt(String template, ExpressionParser parser,StandardEvaluationContext context) { + if (!StringUtils.hasText(template)){ + return template; + } + // 使用 SpEL 表达式解析模板并返回填充后的结果 + return parser.parseExpression(template).getValue(context, String.class); + } + +} diff --git a/images/AiNode.png b/images/AiNode.png new file mode 100644 index 0000000000000000000000000000000000000000..36a864d76bc71525458032d082b9caa99e7683bf GIT binary patch literal 62172 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≦8fe_k(V~wDj~e$)5GqB#A{GoCKYmcr9xaM&`+r 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

a~kyv=Z8q_L`&2X*sd@?LTor rMMFcL!@ri|#SfpgMXQ*o$e{UZ3yjF$M&uiaRZiKvoGAZy;Pw9jc&5hD literal 0 HcmV?d00001 diff --git a/images/FunctionCallBlock.png b/images/FunctionCallBlock.png new file mode 100644 index 0000000000000000000000000000000000000000..a6aeb53975efdfd0a955913f9f6ca3cddcd9a7e9 GIT binary patch literal 75372 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

(}&m=x+J#lo1$E>AbT0|VbmhB)!M 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 literal 0 HcmV?d00001 diff --git a/images/FunctionCallResult.png b/images/FunctionCallResult.png new file mode 100644 index 0000000000000000000000000000000000000000..d46fef17068af6f894168653ae791c205935526b GIT binary patch literal 37724 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 O#j>-bJG#ulm;PVjGlmlY literal 0 HcmV?d00001 diff --git a/images/ModelParam.png b/images/ModelParam.png new file mode 100644 index 0000000000000000000000000000000000000000..116bb9ed62004b4492b4ba6e619bbc82c28d4b92 GIT binary patch literal 66607 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 O viewer-models-deepseek viewer-models-openai + viewer-models-zhipuai diff --git a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java index 20e3f1a..d619481 100644 --- a/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java +++ b/models/viewer-models-deepseek/src/main/java/xyz/thoughtset/viewer/models/deepseek/DeepSeekBuilder.java @@ -8,8 +8,11 @@ import org.springframework.ai.deepseek.api.DeepSeekApi; 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.purpose.ChatModelSetting; import xyz.thoughtset.viewer.common.ai.model.factory.ModelBuilder; +import java.util.Optional; + @Component public class DeepSeekBuilder extends ModelBuilder { public DeepSeekBuilder() { @@ -25,12 +28,19 @@ public class DeepSeekBuilder extends ModelBuilder { public ChatModel buildMode(AiNode node, ModelParam modelParam) { DeepSeekChatOptions.Builder builder = DeepSeekChatOptions.builder() .model(modelParam.getModel()); - if (modelParam.getMaxTokens() != null) { - builder.maxTokens(modelParam.getMaxTokens().intValue()); - } - if (modelParam.getTemperature() != null) { - builder.temperature(modelParam.getTemperature()); - } + ChatModelSetting settingObj = (ChatModelSetting) modelParam.getModelArgs(); +// if(settingObj!=null) { +// if (settingObj.getMaxTokens() != null) { +// builder.maxTokens(settingObj.getMaxTokens().intValue()); +// } +// if (settingObj.getTemperature() != null) { +// builder.temperature(settingObj.getTemperature()); +// } +// } + Optional.ofNullable(settingObj).ifPresent(setting -> { + setOptionalValue(setting.getMaxTokens(), value -> builder.maxTokens(value.intValue())); + setOptionalValue(setting.getTemperature(), builder::temperature); + }); DeepSeekChatOptions options = builder.build(); DeepSeekApi api = DeepSeekApi.builder() .baseUrl(node.getBaseUrl()) diff --git a/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java index d1570ee..6d698e9 100644 --- a/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java +++ b/models/viewer-models-openai/src/main/java/xyz/thoughtset/viewer/models/openai/OpenAIChatBuilder.java @@ -7,9 +7,12 @@ import org.springframework.ai.openai.api.OpenAiApi; 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.purpose.ChatModelSetting; import xyz.thoughtset.viewer.common.ai.model.factory.DefaultModelBuilder; import xyz.thoughtset.viewer.common.ai.model.factory.ModelBuilder; +import java.util.Optional; + @Component public class OpenAIChatBuilder extends DefaultModelBuilder { public OpenAIChatBuilder() { @@ -21,12 +24,17 @@ public class OpenAIChatBuilder extends DefaultModelBuilder { public ChatModel buildMode(AiNode node, ModelParam modelParam) { OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder() .model(modelParam.getModel()); - if (modelParam.getMaxTokens() != null) { - builder.maxTokens(modelParam.getMaxTokens().intValue()); - } - if (modelParam.getTemperature() != null) { - builder.temperature(modelParam.getTemperature()); - } + ChatModelSetting settingObj = (ChatModelSetting) modelParam.getModelArgs(); +// if (settingObj.getMaxTokens() != null) { +// builder.maxTokens(settingObj.getMaxTokens().intValue()); +// } +// if (settingObj.getTemperature() != null) { +// builder.temperature(settingObj.getTemperature()); +// } + Optional.ofNullable(settingObj).ifPresent(setting -> { + setOptionalValue(setting.getMaxTokens(), value -> builder.maxTokens(value.intValue())); + setOptionalValue(setting.getTemperature(), builder::temperature); + }); OpenAiChatOptions options = builder.build(); OpenAiApi api = OpenAiApi.builder() .baseUrl(node.getBaseUrl()) diff --git a/models/viewer-models-zhipuai/pom.xml b/models/viewer-models-zhipuai/pom.xml new file mode 100644 index 0000000..466a601 --- /dev/null +++ b/models/viewer-models-zhipuai/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + xyz.thoughtset.viewer + models + ${revision} + + + viewer-models-zhipuai + + + 17 + 17 + UTF-8 + + + + + org.springframework.ai + spring-ai-zhipuai + ${springai.version} + + + + \ No newline at end of file diff --git a/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelZhipuAIAutoConfiguration.java b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelZhipuAIAutoConfiguration.java new file mode 100644 index 0000000..47d02f6 --- /dev/null +++ b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ModelZhipuAIAutoConfiguration.java @@ -0,0 +1,11 @@ +package xyz.thoughtset.viewer.models.openai; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableConfigurationProperties +public class ModelZhipuAIAutoConfiguration { +} diff --git a/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuChatBuilder.java b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuChatBuilder.java new file mode 100644 index 0000000..ea2fd0e --- /dev/null +++ b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuChatBuilder.java @@ -0,0 +1,36 @@ +package xyz.thoughtset.viewer.models.openai; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.zhipuai.ZhiPuAiChatModel; +import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; +import org.springframework.ai.zhipuai.api.ZhiPuAiApi; +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.purpose.ChatModelSetting; +import xyz.thoughtset.viewer.common.ai.model.factory.DefaultModelBuilder; + +import java.util.Optional; + +@Component +public class ZhipuChatBuilder extends DefaultModelBuilder { + public ZhipuChatBuilder() { + super("ZhipuAI"); + } + + + @Override + public ChatModel buildMode(AiNode node, ModelParam modelParam) { + ZhiPuAiChatOptions.Builder builder = ZhiPuAiChatOptions.builder() + .model(modelParam.getModel()); + ChatModelSetting settingObj = (ChatModelSetting) modelParam.getModelArgs(); + Optional.ofNullable(settingObj).ifPresent(setting -> { + setOptionalValue(setting.getMaxTokens(), value -> builder.maxTokens(value.intValue())); + setOptionalValue(setting.getTemperature(), builder::temperature); + }); + ZhiPuAiChatOptions options = builder.build(); + ZhiPuAiApi api = new ZhiPuAiApi(node.getBaseUrl(), node.getApiKey()); + ChatModel chatModel = new ZhiPuAiChatModel(api, options); + return chatModel; + } +} diff --git a/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuImageBuilder.java b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuImageBuilder.java new file mode 100644 index 0000000..3098066 --- /dev/null +++ b/models/viewer-models-zhipuai/src/main/java/xyz/thoughtset/viewer/models/openai/ZhipuImageBuilder.java @@ -0,0 +1,41 @@ +package xyz.thoughtset.viewer.models.openai; + +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.model.Model; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.ai.zhipuai.ZhiPuAiChatModel; +import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; +import org.springframework.ai.zhipuai.ZhiPuAiImageModel; +import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; +import org.springframework.ai.zhipuai.api.ZhiPuAiApi; +import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +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.DefaultModelBuilder; +import xyz.thoughtset.viewer.common.ai.model.factory.ModelBuilder; + +import java.util.List; + +@Component +public class ZhipuImageBuilder extends ModelBuilder { + public ZhipuImageBuilder() { + super("ZhipuAI", List.of("CogView-4","Cogview-3-Flash" )); + } + + + @Override + public Model buildMode(AiNode node, ModelParam modelParam) { + ZhiPuAiImageOptions.Builder builder = ZhiPuAiImageOptions.builder() + .model(modelParam.getModel()); + ZhiPuAiImageOptions options = builder.build(); + ZhiPuAiImageApi api = new ZhiPuAiImageApi(node.getBaseUrl(), node.getApiKey(), RestClient.builder()); + ZhiPuAiImageModel model = new ZhiPuAiImageModel(api, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); +// ChatModel chatModel = ZhiPuAiChatModel.builder() +// .defaultOptions(options) +// .openAiApi(api) +// .build(); + return model; + } +} diff --git a/models/viewer-models-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/models/viewer-models-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000..f15a472 --- /dev/null +++ b/models/viewer-models-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +xyz.thoughtset.viewer.models.openai.ModelOpenAIAutoConfiguration \ No newline at end of file diff --git a/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/controller/EasyQueryController.java b/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/controller/EasyQueryController.java index b120caf..789f5c0 100644 --- a/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/controller/EasyQueryController.java +++ b/modules/viewer-modules-api/src/main/java/xyz/thoughtset/viewer/modules/api/controller/EasyQueryController.java @@ -66,15 +66,6 @@ public class EasyQueryController extends BaseApiController { Object value = entry.getValue(); writer.fill(value, writeSheet); } - // 填充列表数据,开启 forceNewRow -// FillConfig config = FillConfig.builder().forceNewRow(true).build(); -// writer.fill(data, config, writeSheet); - - // 填充普通变量 -// Map map = new HashMap<>(); -// map.put("date", "2024年11月20日"); -// map.put("total", 1000); -// writer.fill(map, writeSheet); writer.finish(); }catch (Exception e) { throw new RuntimeException(e.getMessage(), e); diff --git a/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/service/BlockInfoServiceImpl.java b/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/service/BlockInfoServiceImpl.java index 0b6b3fd..e78acec 100644 --- a/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/service/BlockInfoServiceImpl.java +++ b/modules/viewer-modules-step/src/main/java/xyz/thoughtset/viewer/modules/step/service/BlockInfoServiceImpl.java @@ -43,15 +43,17 @@ public class BlockInfoServiceImpl extends BaseServiceImpl oldEles = blockEles(entity.getId()); HashSet oldEleIds = new HashSet<>(); diff --git a/pom.xml b/pom.xml index 5552927..362576a 100644 --- a/pom.xml +++ b/pom.xml @@ -38,7 +38,7 @@ - 1.2.0 + 1.3.0 17 2025.0.0 true @@ -317,6 +317,12 @@ viewer-models-openai ${revision} + + + xyz.thoughtset.viewer + viewer-models-zhipuai + ${revision} + -- Gitee From b636b061ccfcdbabae6bbdeb25c97251384acb1c Mon Sep 17 00:00:00 2001 From: q1279335527 <1279335527@qq.com> Date: Sun, 12 Oct 2025 14:22:44 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93sql=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/schema.sql | 371 ++++++++++-------- sql/mysql/dv.sql | 48 ++- 2 files changed, 252 insertions(+), 167 deletions(-) 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 82c8a88..ad756cd 100644 --- a/apis/viewer-apis-client/src/main/resources/db/schema.sql +++ b/apis/viewer-apis-client/src/main/resources/db/schema.sql @@ -1,3 +1,20 @@ +CREATE TABLE IF NOT EXISTS ainode ( + id varchar(32) NOT NULL, + title varchar(32) DEFAULT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT NULL, + createdAt datetime NOT NULL, + updatedAt datetime NOT NULL, + statesCode int(11) DEFAULT '200', + provider varchar(256) NOT NULL, + baseUrl varchar(256) NOT NULL, + apiKey varchar(256) NOT NULL, + headersStr text, + settingStr text, + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS ApiInfo ( id varchar(32) NOT NULL, @@ -38,48 +55,48 @@ CREATE TABLE IF NOT EXISTS ApiParam( -- -- CREATE INDEX IF NOT EXISTS idx_pid ON ApiParam (pid); -- CREATE INDEX IF NOT EXISTS idx_order_up ON ApiParam (orderNum ASC, updatedAt DESC); -CREATE TABLE IF NOT EXISTS `blockbodyele` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) DEFAULT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) DEFAULT NULL, - `remark` text, - `orderNum` int(11) DEFAULT '0', - `createdAt` datetime NOT NULL, - `updatedAt` datetime NOT NULL, - `statesCode` int(11) DEFAULT '200', - `paramsPayload` text, - `type` varchar(32) DEFAULT NULL, - `eleId` varchar(32) DEFAULT NULL, - `valnum` varchar(8) DEFAULT '', - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS blockbodyele ( + id varchar(32) NOT NULL, + title varchar(32) DEFAULT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT '0', + createdAt datetime NOT NULL, + updatedAt datetime NOT NULL, + statesCode int(11) DEFAULT '200', + paramsPayload text, + type varchar(32) DEFAULT NULL, + eleId varchar(32) DEFAULT NULL, + valnum varchar(8) DEFAULT '', + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `blockinfo` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) DEFAULT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) DEFAULT NULL, - `remark` text, - `orderNum` int(11) DEFAULT '0', - `createdAt` datetime NOT NULL, - `updatedAt` datetime NOT NULL, - `statesCode` int(11) DEFAULT '200', - `dataStr` text, - `single` bit(1) DEFAULT NULL, - `cacheId` varchar(32) DEFAULT NULL, - `bodyType` varchar(32) DEFAULT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS blockinfo ( + id varchar(32) NOT NULL, + title varchar(32) DEFAULT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT '0', + createdAt datetime NOT NULL, + updatedAt datetime NOT NULL, + statesCode int(11) DEFAULT '200', + dataStr text, + single bit(1) DEFAULT NULL, + cacheId varchar(32) DEFAULT NULL, + bodyType varchar(32) DEFAULT NULL, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `blockparam` ( - `id` varchar(32) NOT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) DEFAULT NULL, - `createdAt` datetime NOT NULL, - `updatedAt` datetime NOT NULL, - `qpId` varchar(32) DEFAULT NULL, - `dataExp` text, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS blockparam ( + id varchar(32) NOT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + createdAt datetime NOT NULL, + updatedAt datetime NOT NULL, + qpId varchar(32) DEFAULT NULL, + dataExp text, + PRIMARY KEY (id) ); CREATE TABLE IF NOT EXISTS DsConfig ( @@ -101,35 +118,35 @@ CREATE TABLE IF NOT EXISTS DsConfig ( PRIMARY KEY (id) ) ; -CREATE TABLE IF NOT EXISTS `envvars` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) NULL DEFAULT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `remark` text NULL, - `orderNum` int(11) NULL DEFAULT 0, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) NULL DEFAULT 200, - `type` varchar(32) NULL DEFAULT NULL, - `topic` text NULL, - `payload` text NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS envvars ( + id varchar(32) NOT NULL, + title varchar(32) NULL DEFAULT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + remark text NULL, + orderNum int(11) NULL DEFAULT 0, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 200, + type varchar(32) NULL DEFAULT NULL, + topic text NULL, + payload text NULL, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `excinfo`( - `id` varchar(32) NOT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) NULL DEFAULT 200, - `apiId` varchar(32) NULL DEFAULT NULL, - `blockId` varchar(32) NULL DEFAULT NULL, - `errMsg` text NULL, - `paramStr` text NULL, - `errType` text NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS excinfo( + id varchar(32) NOT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 200, + apiId varchar(32) NULL DEFAULT NULL, + blockId varchar(32) NULL DEFAULT NULL, + errMsg text NULL, + paramStr text NULL, + errType text NULL, + PRIMARY KEY (id) ); CREATE TABLE IF NOT EXISTS QueryParam( @@ -190,118 +207,140 @@ CREATE TABLE IF NOT EXISTS BlockParam ( ) ; CREATE TABLE IF NOT EXISTS LinkerConfig ( - `id` varchar(32) NOT NULL, - `title` varchar(32) DEFAULT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) DEFAULT NULL, - `remark` text, - `orderNum` int(11) DEFAULT 0, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) DEFAULT 200, - `driverClassName` varchar(64) DEFAULT NULL, - `jarFilePath` varchar(256) NOT NULL, - `linkerType` varchar(32) DEFAULT NULL, - `otherSettings` text NULL, - `reactive` BIT, - PRIMARY KEY (`id`) + id varchar(32) NOT NULL, + title varchar(32) DEFAULT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT 0, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) DEFAULT 200, + driverClassName varchar(64) DEFAULT NULL, + jarFilePath varchar(256) NOT NULL, + linkerType varchar(32) DEFAULT NULL, + otherSettings text NULL, + reactive BIT, + PRIMARY KEY (id) ) ; -CREATE TABLE IF NOT EXISTS `exportdatainfo` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) NULL DEFAULT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `remark` text NULL, - `orderNum` int(11) NULL DEFAULT NULL, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) NULL DEFAULT 200, - `bodyId` varchar(32) NULL DEFAULT NULL, - `templateId` varchar(32) NULL DEFAULT NULL, - `fileName` varchar(32) NULL DEFAULT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS exportdatainfo ( + id varchar(32) NOT NULL, + title varchar(32) NULL DEFAULT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + remark text NULL, + orderNum int(11) NULL DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 200, + bodyId varchar(32) NULL DEFAULT NULL, + templateId varchar(32) NULL DEFAULT NULL, + fileName varchar(32) NULL DEFAULT NULL, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `funinfo` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) NULL DEFAULT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `remark` text NULL, - `orderNum` int(11) NULL DEFAULT NULL, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) NULL DEFAULT 200, - `cacheTime` int(11) NULL DEFAULT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS funinfo ( + id varchar(32) NOT NULL, + title varchar(32) NULL DEFAULT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + remark text NULL, + orderNum int(11) NULL DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 200, + cacheTime int(11) NULL DEFAULT NULL, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `funparam` ( - `id` varchar(32) NOT NULL, - `title` varchar(32) NULL DEFAULT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `remark` text NULL, - `orderNum` int(11) NULL DEFAULT 0, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) NULL DEFAULT 200, - `dataType` varchar(32) NULL DEFAULT NULL, - `defaultVal` varchar(32) NULL DEFAULT NULL, - `paramType` int(11) NULL DEFAULT NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS funparam ( + id varchar(32) NOT NULL, + title varchar(32) NULL DEFAULT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + remark text NULL, + orderNum int(11) NULL DEFAULT 0, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) NULL DEFAULT 200, + dataType varchar(32) NULL DEFAULT NULL, + defaultVal varchar(32) NULL DEFAULT NULL, + paramType int(11) NULL DEFAULT NULL, + PRIMARY KEY (id) ); -- ---------------------------- -- Table structure for loginfo -- ---------------------------- -CREATE TABLE IF NOT EXISTS `loginfo` ( - `id` varchar(32) NOT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `type` varchar(32), - `active` varchar(32), - `data` text NULL, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS loginfo ( + id varchar(32) NOT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + type varchar(32), + active varchar(32), + data text NULL, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `datarel` ( - `id` varchar(32) NOT NULL, - `pid` varchar(32) DEFAULT NULL, - `groupId` varchar(32) DEFAULT NULL, - `orderNum` int(11) DEFAULT '0', - `createdAt` datetime NOT NULL, - `updatedAt` datetime NOT NULL, - `topic` varchar(32), - `rel` varchar(32), - `source` varchar(32), - `target` varchar(32), - `dataExp` text, - PRIMARY KEY (`id`) +CREATE TABLE IF NOT EXISTS datarel ( + id varchar(32) NOT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + orderNum int(11) DEFAULT '0', + createdAt datetime NOT NULL, + updatedAt datetime NOT NULL, + topic varchar(32), + rel varchar(32), + source varchar(32), + target varchar(32), + dataExp text, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS mcpserverinfo ( + id varchar(32) NOT NULL, + title varchar(128) NOT NULL, + pid varchar(32) NULL DEFAULT NULL, + groupId varchar(32) NULL DEFAULT NULL, + remark text NULL, + orderNum int(11) NULL DEFAULT NULL, + createdAt datetime(0) NOT NULL, + updatedAt datetime(0) NOT NULL, + statesCode int(11) DEFAULT 0, + typeCode int(11) NOT NULL DEFAULT 11, + serverTitle varchar(128) NULL DEFAULT NULL, + version varchar(32) NULL DEFAULT NULL, + baseUrl varchar(32) NULL DEFAULT '', + sseEndpoint varchar(32) NOT NULL, + messageEndpoint varchar(32) NULL DEFAULT NULL, + instructions text NULL, + keepAliveInterval int(11) NULL DEFAULT NULL, + requestTimeout int(11) NULL DEFAULT NULL, + immediateExecution bit(1) NULL DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS modelparam ( + id varchar(32) NOT NULL, + title varchar(32) DEFAULT NULL, + pid varchar(32) DEFAULT NULL, + groupId varchar(32) DEFAULT NULL, + remark text, + orderNum int(11) DEFAULT NULL, + createdAt datetime NOT NULL, + 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, + PRIMARY KEY (id) ); -CREATE TABLE IF NOT EXISTS `mcpserverinfo` ( - `id` varchar(32) NOT NULL, - `title` varchar(128) NOT NULL, - `pid` varchar(32) NULL DEFAULT NULL, - `groupId` varchar(32) NULL DEFAULT NULL, - `remark` text NULL, - `orderNum` int(11) NULL DEFAULT NULL, - `createdAt` datetime(0) NOT NULL, - `updatedAt` datetime(0) NOT NULL, - `statesCode` int(11) DEFAULT 0, - `typeCode` int(11) NOT NULL DEFAULT 11, - `serverTitle` varchar(128) NULL DEFAULT NULL, - `version` varchar(32) NULL DEFAULT NULL, - `baseUrl` varchar(32) NULL DEFAULT '', - `sseEndpoint` varchar(32) NOT NULL, - `messageEndpoint` varchar(32) NULL DEFAULT NULL, - `instructions` text NULL, - `keepAliveInterval` int(11) NULL DEFAULT NULL, - `requestTimeout` int(11) NULL DEFAULT NULL, - `immediateExecution` bit(1) NULL DEFAULT NULL, - PRIMARY KEY (`id`) -) diff --git a/sql/mysql/dv.sql b/sql/mysql/dv.sql index 8c0dda9..0495e61 100644 --- a/sql/mysql/dv.sql +++ b/sql/mysql/dv.sql @@ -1,7 +1,28 @@ - SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for ainode +-- ---------------------------- +DROP TABLE IF EXISTS `ainode`; +CREATE TABLE `ainode` ( + `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `title` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `groupId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `orderNum` int(11) NULL DEFAULT NULL, + `createdAt` datetime(0) NOT NULL, + `updatedAt` datetime(0) NOT NULL, + `statesCode` int(11) NULL DEFAULT 200, + `provider` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `baseUrl` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `apiKey` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `headersStr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `settingStr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + -- ---------------------------- -- Table structure for apiinfo -- ---------------------------- @@ -305,6 +326,31 @@ CREATE TABLE `mcpserverinfo` ( PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; +-- ---------------------------- +-- Table structure for modelparam +-- ---------------------------- +DROP TABLE IF EXISTS `modelparam`; +CREATE TABLE `modelparam` ( + `id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `title` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `groupId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `orderNum` int(11) NULL DEFAULT NULL, + `createdAt` datetime(0) NOT NULL, + `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, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + -- ---------------------------- -- Table structure for queryblock -- ---------------------------- -- Gitee