diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 45ccd720..ec28ea6d 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -1172,7 +1172,7 @@ public void demoJobHandler() throws Exception { 为方便用户参考与快速使用,提供 “通用执行器” 并内置多个Bean模式任务Handler,可以直接配置使用,如下: **通用执行器说明:** -- 执行器:xxl-job-executor-sample +- AppName:xxl-job-executor-sample - 执行器代码: - xxl-job-executor-sample-springboot:springboot版本 - xxl-job-executor-sample-frameless:无框架版本 @@ -1194,7 +1194,7 @@ public void demoJobHandler() throws Exception { 为方便用户参考与快速使用,提供 “AI执行器” 并内置多个Bean模式任务Handler,与spring-ai集成打通,支持快速开发AI类任务,如下: **AI执行器说明:** -- 执行器:xxl-job-executor-sample-ai +- AppName:xxl-job-executor-sample-ai - 执行器代码:xxl-job-executor-sample-springboot-ai **执行器内置任务列表:** @@ -1205,8 +1205,19 @@ public void demoJobHandler() throws Exception { "prompt": "{模型prompt,可选信息}" } ``` -依赖1:参考 [Ollama本地化部署大模型](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8Ollama%E6%9C%AC%E5%9C%B0%E5%8C%96%E9%83%A8%E7%BD%B2DeepSeek.md) ,本文示例部署“qwen2.5:1.5b”模型,也可自定选择其他模型版本。 -依赖2:启动示例 “AI执行器” 相关配置文件说明如下: +- b、difyWorkflowJobHandler:DifyWorkflow 任务,支持自定义inputs、user等输入信息,示例参数如下; +``` +{ + "inputs":{ // inputs 为dify工作流任务参数;参数不固定,结合各自 workflow 自行定义。 + "input":"{用户输入信息}" // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”,可自行调整或删除。 + }, + "user": "{用户标识,选填}" +} +``` + +依赖1:参考 [Ollama本地化部署大模型](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8Ollama%E6%9C%AC%E5%9C%B0%E5%8C%96%E9%83%A8%E7%BD%B2DeepSeek.md) ,执行器示例部署“qwen2.5:1.5b”模型,也可自定选择其他模型版本。 +依赖2:参考 [使用DeepSeek与Dify搭建AI助手](https://www.xuxueli.com/blog/?blog=./notebook/13-AI/%E4%BD%BF%E7%94%A8DeepSeek%E4%B8%8EDify%E6%90%AD%E5%BB%BAAI%E5%8A%A9%E6%89%8B.md),执行器示例新建Dify DifyWork应用,并在开始节点添加“input”参数,可结合实际情况调整。 +依赖3:启动示例 “AI执行器” 相关配置文件说明如下: ``` ### ollama 配置 spring.ai.ollama.base-url=http://localhost:11434 @@ -1214,6 +1225,12 @@ spring.ai.ollama.chat.enabled=true ### Model模型配置;注意,此处配置模型版本、必须本地先通过ollama进行安装运行。 spring.ai.ollama.chat.options.model=qwen2.5:1.5b spring.ai.ollama.chat.options.temperature=0.8 + +### dify 配置;选择相关 workflow 应用,切换 “访问API” 页面获取 url 地址信息. +dify.base-url=http://localhost/v1 +### dify api-key;选择相关 workflow 应用并进入 “访问API” 页面,右上角 “API 密钥” 入口获取 api-key。 +dify.api-key={自行获取并修改} + ``` @@ -2491,24 +2508,35 @@ public void execute() { - b、版本3.x开始要求Jdk17;版本2.x及以下支持Jdk1.8。如对Jdk版本有诉求,可选择接入不同版本; ### 7.38 版本 v3.0.1 Release Notes[规划中] -- 1、【新增】新增“AI执行器”示例,并与spring-ai集成打通;内置一系列AIXxlJob,支持快速开发AI类任务(xxl-job-executor-sample-springboot-ai)。 -- 2、【新增】新增通用OllamaChat任务(ollamaJobHandler),支持自定义prompt、input等输入信息,示例参数如下; +- 1、【新增】新增“AI执行器”示例,并与spring-ai、Ollama、Dify等集成打通;内置一系列AIXxlJob,支持快速开发AI类任务(xxl-job-executor-sample-springboot-ai)。 +``` +// 说明:ollamaJobHandler 内置在“AI执行器(AppName = xxl-job-executor-sample-ai)”中,需要先新建执行器;可参考如下SQL或自行创建: +INSERT INTO `xxl_job_group`(`app_name`, `title`, `address_type`, `address_list`, `update_time`) + VALUES ('xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now()); +``` +- 2、【新增】新增通用 OllamaChat 任务(ollamaJobHandler),支持自定义prompt、input等输入信息,示例参数如下; ``` { "input": "{输入信息,必填信息}", "prompt": "{模型prompt,可选信息}" } ``` -说明:ollamaJobHandler 内置在“AI执行器(AppName = xxl-job-executor-sample-ai)”中,需要先新建执行器;可参考如下SQL或自行创建: +- 3、【新增】新增通用 DifyWorkflow 任务(difyWorkflowJobHandler),支持自定义inputs、user等输入信息,示例参数如下; ``` -INSERT INTO `xxl_job_group`(`app_name`, `title`, `address_type`, `address_list`, `update_time`) - VALUES ('xxl-job-executor-sample-ai', 'AI执行器Sample', 0, NULL, now()); +{ + "inputs":{ // inputs 为dify工作流任务参数;参数不固定,结合各自 workflow 自行定义。 + "input":"{用户输入信息}" // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”,可自行调整或删除。 + }, + "user": "{用户标识,选填}" +} ``` -- 3、【修复】任务操作逻辑优化,修复边界情况下逻辑中断问题(ISSUE-2081)。 -- 4、【修复】调度中心Cron前端组件优化,解决week配置与后端兼容性问题(ISSUE-2220)。 -- 5、【优化】Glue IDE调整,版本回溯支持查看修改时间; -- 6、【优化】任务RollingLog调整,XSS过滤支持白名单排出,提升日志易读性; -- 7、【升级】多个项目依赖升级至较新稳定版本,涉及 gson、groovy、spring/springboot 等; +- 4、【修复】任务操作逻辑优化,修复边界情况下逻辑中断问题(ISSUE-2081)。 +- 5、【修复】调度中心Cron前端组件优化,解决week配置与后端兼容性问题(ISSUE-2220)。 +- 6、【修复】合并PR-3708、PR-3704:修复固定速度模式计算,下次执行时间计算不准问题(间隔秒数超过Int时触发)。 +- 7、【优化】Glue IDE调整,版本回溯支持查看修改时间; +- 8、【优化】任务RollingLog调整,XSS过滤支持白名单排出,提升日志易读性; +- 9、【升级】多个项目依赖升级至较新稳定版本,涉及 gson、groovy、spring/springboot、mysql 等; + ### 7.39 版本 v3.0.2 Release Notes[规划中] diff --git a/pom.xml b/pom.xml index da6fd241..50e8f6f8 100644 --- a/pom.xml +++ b/pom.xml @@ -30,17 +30,17 @@ 3.2.7 2.0.17 - 5.12.1 + 5.12.2 3.0.0 4.2.0.Final - 2.12.1 + 2.13.1 - 3.4.4 - 6.2.5 + 3.4.5 + 6.2.6 3.0.4 - 9.2.0 + 9.3.0 4.0.26 diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml index c631b7e7..fcf67634 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/pom.xml @@ -17,6 +17,7 @@ 1.0.0-M6 + 1.0.7 @@ -57,6 +58,13 @@ ${spring-ai.version} + + + io.github.imfangs + dify-java-client + ${dify-java-client.version} + + diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java index 4b0b3f60..7cd5e01f 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/controller/IndexController.java @@ -1,11 +1,20 @@ package com.xxl.job.executor.controller;//package com.xxl.job.executor.mvc.controller; -import com.xxl.job.executor.config.XxlJobConfig; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.imfangs.dify.client.DifyClientFactory; +import io.github.imfangs.dify.client.DifyWorkflowClient; +import io.github.imfangs.dify.client.callback.WorkflowStreamCallback; +import io.github.imfangs.dify.client.enums.ResponseMode; +import io.github.imfangs.dify.client.event.*; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunResponse; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -13,6 +22,12 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; @Controller @EnableAutoConfiguration @@ -23,15 +38,16 @@ public class IndexController { @RequestMapping("/") @ResponseBody String index() { - return "xxl job executor running."; + return "xxl job ai executor running."; } + // --------------------------------- ollama chat --------------------------------- + @Resource private ChatClient chatClient; private static String prompt = "你好,你是一个研发工程师,擅长解决技术类问题。"; - /** * ChatClient 简单调用 */ @@ -60,4 +76,103 @@ public class IndexController { .content(); } + + // --------------------------------- dify workflow --------------------------------- + + @Value("${dify.api-key}") + private String apiKey; + @Value("${dify.base-url}") + private String baseUrl; + + @GetMapping("/dify/simple") + @ResponseBody + public String difySimple(@RequestParam(required = false, value = "input") String input) throws IOException { + + Map inputs = new HashMap<>(); + inputs.put("input", input); + + // request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(inputs) + .responseMode(ResponseMode.BLOCKING) + .user("user-123") + .build(); + + // invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + WorkflowRunResponse response = workflowClient.runWorkflow(request); + + // response + return write2Json(response.getData().getOutputs()); + } + + private String write2Json(Object obj) { + if (obj == null) { + return "null"; + } + try { + return new ObjectMapper().writeValueAsString(obj); + } catch (JsonProcessingException e) { + return obj.toString(); + } + } + + @GetMapping( "/dify/stream") + public Flux difyStream(@RequestParam(required = false, value = "input") String input) { + + Map inputs = new HashMap<>(); + inputs.put("input", input); + + // request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(inputs) + .responseMode(ResponseMode.STREAMING) + .user("user-123") + .build(); + + // invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + return Flux.create(new Consumer>() { + @Override + public void accept(FluxSink sink) { + try { + workflowClient.runWorkflowStream(request, new WorkflowStreamCallback() { + @Override + public void onWorkflowStarted(WorkflowStartedEvent event) { + sink.next("工作流开始: " + write2Json(event.getData())); + } + + @Override + public void onNodeStarted(NodeStartedEvent event) { + sink.next("节点开始: " + write2Json(event.getData())); + } + + @Override + public void onNodeFinished(NodeFinishedEvent event) { + sink.next("节点完成: " + write2Json(event.getData().getOutputs())); + } + + @Override + public void onWorkflowFinished(WorkflowFinishedEvent event) { + sink.next("工作流完成: " + write2Json(event.getData().getOutputs())); + sink.complete(); + } + + @Override + public void onError(ErrorEvent event) { + sink.error(new RuntimeException(event.getMessage())); + } + + @Override + public void onException(Throwable throwable) { + sink.error(throwable); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + } + } \ No newline at end of file diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java index 01a21957..115b2266 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/java/com/xxl/job/executor/jobhandler/AIXxlJob.java @@ -3,10 +3,17 @@ package com.xxl.job.executor.jobhandler; import com.xxl.job.core.context.XxlJobHelper; import com.xxl.job.core.handler.annotation.XxlJob; import com.xxl.job.core.util.GsonTool; +import io.github.imfangs.dify.client.DifyClientFactory; +import io.github.imfangs.dify.client.DifyWorkflowClient; +import io.github.imfangs.dify.client.enums.ResponseMode; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunRequest; +import io.github.imfangs.dify.client.model.workflow.WorkflowRunResponse; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.util.HashMap; import java.util.Map; /** @@ -23,7 +30,7 @@ public class AIXxlJob { /** * 1、ollama Chat任务 * - * 参数示例: + * 参数示例:格式见 OllamaParam *
      *      {
      *          "input": "{输入信息,必填信息}",
@@ -43,16 +50,14 @@ public class AIXxlJob {
             return;
         }
 
-        // param parse
-        String prompt = "你是一个研发工程师,擅长解决技术类问题。";
-        String input;
+        // ollama param
+        OllamaParam ollamaParam = null;
         try {
-            Map paramMap =GsonTool.fromJson(param, Map.class);
-            if (paramMap.containsKey("prompt")) {
-                prompt = paramMap.get("prompt");
+            ollamaParam = GsonTool.fromJson(param, OllamaParam.class);
+            if (ollamaParam.getPrompt() == null) {
+                ollamaParam.setPrompt("你是一个研发工程师,擅长解决技术类问题。");
             }
-            input = paramMap.get("input");
-            if (input == null || input.trim().isEmpty()) {
+            if (ollamaParam.getInput() == null || ollamaParam.getInput().trim().isEmpty()) {
                 XxlJobHelper.log("input is empty.");
 
                 XxlJobHelper.handleFail();
@@ -65,15 +70,133 @@ public class AIXxlJob {
         }
 
         // input
-        XxlJobHelper.log("

【Input】: " + input + "

"); + XxlJobHelper.log("

【Input】: " + ollamaParam.getInput()+ "

"); // invoke String result = chatClient - .prompt(prompt) - .user(input) + .prompt(ollamaParam.getPrompt()) + .user(ollamaParam.getInput()) .call() .content(); XxlJobHelper.log("

【Output】: " + result+ "

"); } + private static class OllamaParam{ + private String input; + private String prompt; + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } + } + + + + @Value("${dify.api-key}") + private String apiKey; + @Value("${dify.base-url}") + private String baseUrl; + + /** + * 2、dify Workflow任务 + * + * 参数示例:格式见 DifyParam + *
+     *      {
+     *          "inputs":{                      // inputs 为dify工作流任务参数;参数不固定,结合各自 workflow 自行定义。
+     *              "input":"{用户输入信息}"      // 该参数为示例变量,需要 workflow 的“开始”节点 自定义参数 “input”,可自行调整或删除。
+     *          },
+     *          "user": "{用户标识,选填}"
+     *      }
+     *  
+ */ + @XxlJob("difyWorkflowJobHandler") + public void difyWorkflowJobHandler() throws Exception { + + // param + String param = XxlJobHelper.getJobParam(); + if (param==null || param.trim().isEmpty()) { + XxlJobHelper.log("param is empty."); + XxlJobHelper.handleFail(); + return; + } + + // param parse + DifyParam difyParam; + try { + difyParam =GsonTool.fromJson(param, DifyParam.class); + if (difyParam.getInputs() == null) { + difyParam.setInputs(new HashMap<>()); + } + if (difyParam.getUser() == null) { + difyParam.setUser("xxl-job"); + } + } catch (Exception e) { + XxlJobHelper.log(e); + XxlJobHelper.handleFail(); + return; + } + + + // dify param + XxlJobHelper.log("

【inputs】: " + difyParam.getInputs() + "

"); + + // dify request + WorkflowRunRequest request = WorkflowRunRequest.builder() + .inputs(difyParam.getInputs()) + .responseMode(ResponseMode.BLOCKING) + .user(difyParam.getUser()) + .build(); + + // dify invoke + DifyWorkflowClient workflowClient = DifyClientFactory.createWorkflowClient(baseUrl, apiKey); + WorkflowRunResponse response = workflowClient.runWorkflow(request); + + // response + XxlJobHelper.log("

【Output】: " + response.getData().getOutputs()+ "

"); + } + + private static class DifyParam{ + + /** + * 输入参数,允许传入 App 定义的各变量值 + */ + private Map inputs; + + /** + * 用户标识 + */ + private String user; + + public Map getInputs() { + return inputs; + } + + public void setInputs(Map inputs) { + this.inputs = inputs; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + } + + } diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties index a4b8e118..33b2cf38 100644 --- a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/main/resources/application.properties @@ -3,6 +3,9 @@ server.port=8082 # no web #spring.main.web-environment=false +server.servlet.encoding.force=true +server.servlet.encoding.charset=UTF-8 + # log config logging.config=classpath:logback.xml @@ -34,3 +37,9 @@ spring.ai.ollama.chat.enabled=true ### chat model,must install it locally through ollama spring.ai.ollama.chat.options.model=qwen2.5:1.5b spring.ai.ollama.chat.options.temperature=0.8 + +### dify url +dify.base-url=http://localhost/v1 +### dify api-key +dify.api-key=app-OUVgNUOQRIMokfmuJvBJoUTN +