diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 38ff511e..804cbc56 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -1271,6 +1271,7 @@ public void demoJobHandler() throws Exception { - 执行器代码:xxl-job-executor-sample-springboot-ai **执行器内置任务列表:** + - a、ollamaJobHandler: OllamaChat任务,支持自定义prompt、input等输入信息。示例任务入参如下: ``` { @@ -1279,6 +1280,7 @@ public void demoJobHandler() throws Exception { "model": "{模型实现,如qwen3.5:2b,可选信息}" } ``` + - b、difyWorkflowJobHandler:DifyWorkflow 任务,支持自定义inputs、user、baseUrl、apiKey 等输入信息,示例参数如下; ``` { @@ -1291,6 +1293,14 @@ public void demoJobHandler() throws Exception { } ``` +- c、openClawJobHandler: OpenClaw任务,支持自定义prompt、input等输入信息。示例任务入参如下: +``` +{ + "input": "{输入信息,必填信息}", + "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:参考 [使用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执行器” 相关配置文件说明如下: @@ -2788,15 +2798,16 @@ public void execute() { - 10、【优化】统一项目依赖管理结构,依赖版本统一到父级pom提升可维护性; ### 7.44 版本 v3.4.0 Release Notes[ING] -- 1、【新增】调度性能提升:任务触发后分批批量更新,高频调度场景可百倍降低SQL操作合并执行,提升调度性能; +- 1、【新增】AI执行器集成OpenClaw: 新增“openClawJobHandler”内置AI任务,与OpenClaw集成打通,支持快速开发AI类任务。 +- 2、【新增】调度批次写聚合提升调度性能:任务触发后分批批量更新,高频调度场景可百倍降低SQL操作合并执行,提升调度性能; (任务触发后批量更新配置“xxl.job.schedule.batchsize”) -- 2、【调整】固定频率调度策略调整,修复小概率下触发时间偏差问题; -- 3、【调整】Docker基础镜像调整为eclipse-temurin; -- 4、【优化】父POM依赖配置优化,移除容易配置;合并PR-3926; -- 5、【升级】升级多项maven依赖至较新版本; +- 3、【调整】固定频率调度策略调整,修复小概率下触发时间偏差问题; +- 4、【调整】Docker基础镜像调整为eclipse-temurin; +- 5、【优化】父POM依赖配置优化,移除容易配置;合并PR-3926; - 6、【优化】调度日志优化:支持执行器维度查看调度日志;新增调度日志索引,提升查询效率; (数据库新增索引脚本:``` create index I_jobgroup on xxl_job_log (job_group); ``` ) -- 7、【TODO】调度中心OpenAPI完善,提供任务管理能力;封装Agent Skill并推送ClawHub; +- 7、【升级】升级多项maven依赖至较新版本; +- 8、【TODO】调度中心OpenAPI完善,提供任务管理能力;封装Agent Skill并推送ClawHub; ### TODO LIST diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql index ceb5f958..4a1adb76 100644 --- a/doc/db/tables_xxl_job.sql +++ b/doc/db/tables_xxl_job.sql @@ -157,7 +157,7 @@ INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_ti VALUES (1, 1, '示例任务01', now(), now(), 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', now(), ''), - (2, 2, 'Ollama示例任务01', now(), now(), 'XXL', '', 'NONE', '', + (2, 2, 'Ollama示例任务', now(), now(), 'XXL', '', 'NONE', '', 'DO_NOTHING', 'FIRST', 'ollamaJobHandler', '{ "input": "慢SQL问题分析思路", "prompt": "你是一个研发工程师,擅长解决技术类问题。", @@ -172,6 +172,12 @@ VALUES (1, 1, '示例任务01', now(), now(), 'XXL', '', 'CRON', '0 0 0 * * ? *' "user": "xxl-job", "baseUrl": "http://localhost/v1", "apiKey": "app-OUVgNUOQRIMokfmuJvBJoUTN" +}', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', + now(), ''), + (4, 2, 'OpenClaw示例任务', now(), now(), 'XXL', '', 'NONE', '', + 'DO_NOTHING', 'FIRST', 'difyWorkflowJobHandler', '{ + "input": "查看下上海今天得天气,给出出游建议", + "prompt": "你是一个出游助手,擅长做旅游规划" }', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', now(), ''); diff --git a/pom.xml b/pom.xml index 06f0c48b..c5bfd6d3 100644 --- a/pom.xml +++ b/pom.xml @@ -161,12 +161,17 @@ ${xxl-sso.version} - + org.springframework.ai spring-ai-starter-model-ollama ${spring-ai.version} + + org.springframework.ai + spring-ai-starter-model-openai + ${spring-ai.version} + io.github.imfangs 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 e0423e5b..3f44b06f 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 @@ -37,11 +37,16 @@ xxl-job-core - + org.springframework.ai spring-ai-starter-model-ollama + + + org.springframework.ai + spring-ai-starter-model-openai + io.github.imfangs 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 1485b30b..06c39f66 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 @@ -15,6 +15,7 @@ import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.api.OllamaChatOptions; +import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.stereotype.Component; import java.util.HashMap; @@ -32,6 +33,8 @@ public class AIXxlJob { @Resource private OllamaChatModel ollamaChatModel; + @Resource + private OpenAiChatModel openAiChatModel; /** * 1、ollama Chat任务 @@ -252,4 +255,91 @@ public class AIXxlJob { } + // --------------------------------- openclaw --------------------------------- + + /** + * 3、openclaw 任务 + * + * 参数示例:格式见 OpenclawParam + *
+     *      {
+     *          "input": "{输入信息,必填信息}",
+     *          "prompt": "{模型prompt,可选信息}"
+     *      }
+     *  
+ */ + @XxlJob("openClawJobHandler") + public void openClawJobHandler() { + + // param + String param = XxlJobHelper.getJobParam(); + if (param == null || param.trim().isEmpty()) { + XxlJobHelper.log("param is empty."); + + XxlJobHelper.handleFail(); + return; + } + + // openclaw param + OpenClawParam openClawParam = null; + try { + openClawParam = GsonTool.fromJson(param, OpenClawParam.class); + if (openClawParam.getPrompt()==null || openClawParam.getPrompt().isBlank()) { + openClawParam.setPrompt("你是一个出游助手,擅长做旅游规划"); + } + if (openClawParam.getInput() == null || openClawParam.getInput().isBlank()) { + XxlJobHelper.log("input is empty."); + + XxlJobHelper.handleFail(); + return; + } + } catch (Exception e) { + XxlJobHelper.log(new RuntimeException("OpenclawParam parse error", e)); + XxlJobHelper.handleFail(); + return; + } + + // input + XxlJobHelper.log("

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

"); + + // build chat-client + ChatClient openclawChatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // call opencalw + String response = openclawChatClient + .prompt(openClawParam.getPrompt()) + .user(openClawParam.getInput()) + .call() + .content(); + + XxlJobHelper.log("

【Output】: " + response + "

"); + + } + + private static class OpenClawParam { + 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; + } + } + + } 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 2a95e692..361d7c27 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 @@ -35,6 +35,9 @@ xxl.job.executor.excludedpackage= ### ollama -spring.ai.model.chat=ollama -### ollama url spring.ai.ollama.base-url=http://localhost:11434 + +### openai (for openclaw) +spring.ai.openai.base-url=http://127.0.0.1:18789 +spring.ai.openai.api-key=xxxxxx + diff --git a/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java new file mode 100644 index 00000000..e031ba82 --- /dev/null +++ b/xxl-job-executor-samples/xxl-job-executor-sample-springboot-ai/src/test/java/com/xxl/job/executor/test/openclaw/OpenClawTest.java @@ -0,0 +1,95 @@ +package com.xxl.job.executor.test.openclaw; + +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; +import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; +import org.springframework.ai.chat.memory.MessageWindowChatMemory; +import org.springframework.ai.openai.OpenAiChatModel; +import org.springframework.boot.test.context.SpringBootTest; +import reactor.core.publisher.Flux; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@SpringBootTest +public class OpenClawTest { + private static final Logger logger = LoggerFactory.getLogger(OpenClawTest.class); + + @Resource + private OpenAiChatModel openAiChatModel; + + /*ChatModel chatModel = OpenAiChatModel + .builder() + .openAiApi(OpenAiApi + .builder() + .baseUrl(baseUrl) + .apiKey( token) + .webClientBuilder(WebClient.builder().clientConnector( + new ReactorClientHttpConnector( + HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30 * 1000) + .responseTimeout(Duration.ofMillis(30 * 1000)) + ) + )) + .build()) + .build();*/ + + @Test + public void test() throws Exception { + + String prompt = "你是一个出游助手,擅长做旅游规划"; + String input = "查看下上海今天得天气,给出出游建议"; + + // ChatClient + ChatClient chatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // Call LLM: 同步输出 + String response = chatClient + .prompt(prompt) + .user(input) + .call() + .content(); + + logger.info("Input: {}", input); + logger.info("Output: {}", response); + } + + @Test + public void test2() throws Exception { + + String prompt = "你是一个出游助手,擅长做旅游规划"; + String input = "查看下上海今天得天气,给出出游建议"; + + // ChatClient + ChatClient chatClient = ChatClient + .builder(openAiChatModel) + .defaultAdvisors(MessageChatMemoryAdvisor.builder(MessageWindowChatMemory.builder().build()).build()) + .defaultAdvisors(SimpleLoggerAdvisor.builder().build()) + .build(); + + // Call LLM: 流式输出 + Flux flux = chatClient + .prompt(prompt) + .user(input) + .user(user -> user.text(input).params(Map.of("stream", true))) + .stream() + .content(); + + flux.subscribe( + data -> System.out.println("Received: " + data), // onNext 处理 + error -> System.err.println("Error: " + error), // onError 处理 + () -> System.out.println("Completed") // onComplete 处理 + ); + + TimeUnit.SECONDS.sleep(30); + } + +}