瀏覽代碼

feat:【AI 大模型】工作流增加 OLLAMA 的接入

YunaiV 6 月之前
父節點
當前提交
8b958cdc9b

+ 12 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/AiWorkflowController.http

@@ -0,0 +1,12 @@
+### 测试 AI 工作流
+POST {{baseUrl}}/ai/workflow/test
+Content-Type: application/json
+Authorization: {{token}}
+tenant-id: {{adminTenantId}}
+
+{
+  "id": 4,
+  "params": {
+    "message": "1 + 1 = ?"
+  }
+}

+ 11 - 3
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/workflow/vo/AiWorkflowTestReqVO.java

@@ -1,7 +1,8 @@
 package cn.iocoder.yudao.module.ai.controller.admin.workflow.vo;
 
+import cn.hutool.core.util.StrUtil;
 import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.AssertTrue;
 import lombok.Data;
 
 import java.util.Map;
@@ -10,11 +11,18 @@ import java.util.Map;
 @Data
 public class AiWorkflowTestReqVO {
 
-    @Schema(description = "工作流模型", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
-    @NotEmpty(message = "工作流模型不能为空")
+    @Schema(description = "工作流编号", example = "1024")
+    private Long id;
+
+    @Schema(description = "工作流模型", example = "{}")
     private String graph;
 
     @Schema(description = "参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "{}")
     private Map<String, Object> params;
 
+    @AssertTrue(message = "工作流或模型,必须传递一个")
+    public boolean isGraphValid() {
+        return id != null || StrUtil.isNotEmpty(graph);
+    }
+
 }

+ 1 - 1
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelService.java

@@ -133,7 +133,7 @@ public interface AiModelService {
     VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields);
 
     /**
-     * 获取 Tinyflow 所需 LLm Provider
+     * 获取 TinyFlow 所需 LLm Provider
      *
      * @param tinyflow tinyflow
      * @param modelId AI 模型 ID

+ 11 - 0
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/AiModelServiceImpl.java

@@ -12,6 +12,8 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReq
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO;
 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
 import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper;
+import com.agentsflex.llm.ollama.OllamaLlm;
+import com.agentsflex.llm.ollama.OllamaLlmConfig;
 import com.agentsflex.llm.qwen.QwenLlm;
 import com.agentsflex.llm.qwen.QwenLlmConfig;
 import dev.tinyflow.core.Tinyflow;
@@ -171,6 +173,7 @@ public class AiModelServiceImpl implements AiModelService {
 //         return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields);
     }
 
+    // TODO @lesan:是不是返回 Llm 对象会好点哈?
     @Override
     public void getLLmProvider4Tinyflow(Tinyflow tinyflow, Long modelId) {
         AiModelDO model = validateModel(modelId);
@@ -178,12 +181,20 @@ public class AiModelServiceImpl implements AiModelService {
         AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform());
         switch (platform) {
             // TODO @lesan 考虑到未来不需要使用agents-flex 现在仅测试通义千问
+            // TODO @lesan:【重要】是不是可以实现一个 SpringAiLlm,这样的话,内部全部用它就好了。只实现 chat 部分;这样,就把 flex 作为一个 agent 框架,内部调用,还是 spring ai 相关的。成本可能低一点?!
             case TONG_YI:
                 QwenLlmConfig qwenLlmConfig = new QwenLlmConfig();
                 qwenLlmConfig.setApiKey(apiKey.getApiKey());
                 qwenLlmConfig.setModel(model.getModel());
+                // TODO @lesan:这个有点奇怪。。。如果一个链式里,有多个模型,咋整呀。。。
                 tinyflow.setLlmProvider(id -> new QwenLlm(qwenLlmConfig));
                 break;
+            case OLLAMA:
+                OllamaLlmConfig ollamaLlmConfig = new OllamaLlmConfig();
+                ollamaLlmConfig.setEndpoint(apiKey.getUrl());
+                ollamaLlmConfig.setModel(model.getModel());
+                tinyflow.setLlmProvider(id -> new OllamaLlm(ollamaLlmConfig));
+                break;
         }
     }
 

+ 39 - 26
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java

@@ -40,7 +40,10 @@ public class AiWorkflowServiceImpl implements AiWorkflowService {
 
     @Override
     public Long createWorkflow(AiWorkflowSaveReqVO createReqVO) {
-        validateWorkflowForCreateOrUpdate(null, createReqVO.getCode());
+        // 1. 参数校验
+        validateCodeUnique(null, createReqVO.getCode());
+
+        // 2. 插入工作流配置
         AiWorkflowDO workflow = BeanUtils.toBean(createReqVO, AiWorkflowDO.class);
         workflowMapper.insert(workflow);
         return workflow.getId();
@@ -48,47 +51,33 @@ public class AiWorkflowServiceImpl implements AiWorkflowService {
 
     @Override
     public void updateWorkflow(AiWorkflowSaveReqVO updateReqVO) {
-        validateWorkflowForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getCode());
+        // 1. 参数校验
+        validateWorkflowExists(updateReqVO.getId());
+        validateCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
+
+        // 2. 更新工作流配置
         AiWorkflowDO workflow = BeanUtils.toBean(updateReqVO, AiWorkflowDO.class);
         workflowMapper.updateById(workflow);
     }
 
     @Override
     public void deleteWorkflow(Long id) {
+        // 1. 校验存在
         validateWorkflowExists(id);
-        workflowMapper.deleteById(id);
-    }
 
-    @Override
-    public AiWorkflowDO getWorkflow(Long id) {
-        return workflowMapper.selectById(id);
-    }
-
-    @Override
-    public PageResult<AiWorkflowDO> getWorkflowPage(AiWorkflowPageReqVO pageReqVO) {
-        return workflowMapper.selectPage(pageReqVO);
-    }
-
-    @Override
-    public Object testWorkflow(AiWorkflowTestReqVO testReqVO) {
-        Map<String, Object> variables = testReqVO.getParams();
-        Tinyflow tinyflow = parseFlowParam(testReqVO.getGraph());
-        return tinyflow.toChain().executeForResult(variables);
-    }
-
-    private void validateWorkflowForCreateOrUpdate(Long id, String code) {
-        validateWorkflowExists(id);
-        validateCodeUnique(id, code);
+        // 2. 删除工作流配置
+        workflowMapper.deleteById(id);
     }
 
-    private void validateWorkflowExists(Long id) {
+    private AiWorkflowDO validateWorkflowExists(Long id) {
         if (ObjUtil.isNull(id)) {
-            return;
+            throw exception(WORKFLOW_NOT_EXISTS);
         }
         AiWorkflowDO workflow = workflowMapper.selectById(id);
         if (ObjUtil.isNull(workflow)) {
             throw exception(WORKFLOW_NOT_EXISTS);
         }
+        return workflow;
     }
 
     private void validateCodeUnique(Long id, String code) {
@@ -107,6 +96,30 @@ public class AiWorkflowServiceImpl implements AiWorkflowService {
         }
     }
 
+    @Override
+    public AiWorkflowDO getWorkflow(Long id) {
+        return workflowMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<AiWorkflowDO> getWorkflowPage(AiWorkflowPageReqVO pageReqVO) {
+        return workflowMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public Object testWorkflow(AiWorkflowTestReqVO testReqVO) {
+        // 加载 graph
+        String graph = testReqVO.getGraph() != null ? testReqVO.getGraph()
+                : validateWorkflowExists(testReqVO.getId()).getGraph();
+
+        // 构建 TinyFlow 执行链
+        Tinyflow tinyflow = parseFlowParam(graph);
+
+        // 执行
+        Map<String, Object> variables = testReqVO.getParams();
+        return tinyflow.toChain().executeForResult(variables);
+    }
+
     private Tinyflow parseFlowParam(String graph) {
         // TODO @lesan:可以使用 jackson 哇?
         JSONObject json = JSONObject.parseObject(graph);