2 Commits

Author SHA1 Message Date
e59e89a5ad feat(ai-web): 尝试设计流程引擎 2025-06-30 23:31:01 +08:00
b7626180c1 refactor(ai-web): 统一bean工具类 2025-06-30 22:54:40 +08:00
15 changed files with 132 additions and 79 deletions

View File

@@ -2,15 +2,12 @@ package com.lanyuanxiaoyao.service.ai.web;
import com.blinkfox.fenix.EnableFenix; import com.blinkfox.fenix.EnableFenix;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@@ -27,27 +24,13 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling @EnableScheduling
@EnableFenix @EnableFenix
@EnableJpaAuditing @EnableJpaAuditing
public class WebApplication implements ApplicationRunner, ApplicationContextAware { public class WebApplication implements ApplicationRunner {
private static ApplicationContext context;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args); SpringApplication.run(WebApplication.class, args);
} }
public static <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return context.getBean(name, clazz);
}
@Override @Override
public void run(ApplicationArguments args) { public void run(ApplicationArguments args) {
} }
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
WebApplication.context = context;
}
} }

View File

@@ -0,0 +1,24 @@
package com.lanyuanxiaoyao.service.ai.web.configuration;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringBeanGetter implements ApplicationContextAware {
private static ApplicationContext context;
public static <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return context.getBean(name, clazz);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringBeanGetter.context = context;
}
}

View File

@@ -1,19 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 流程图中的边
*
* @author lanyuanxiaoyao
* @version 20250630
*/
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class FlowEdge {
@EqualsAndHashCode.Include
private String id;
private String source;
private String target;
}

View File

@@ -1,5 +1,15 @@
package com.lanyuanxiaoyao.service.ai.web.engine; package com.lanyuanxiaoyao.service.ai.web.engine;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowContext;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowEdge;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowGraph;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNode;
import com.lanyuanxiaoyao.service.ai.web.engine.entity.FlowNodeRunner;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.Queue;
import org.eclipse.collections.api.map.ImmutableMap;
/** /**
* 流程执行器 * 流程执行器
* *
@@ -7,4 +17,25 @@ package com.lanyuanxiaoyao.service.ai.web.engine;
* @version 20250630 * @version 20250630
*/ */
public class FlowExecutor { public class FlowExecutor {
private final ImmutableMap<String, Class<? extends FlowNodeRunner>> runnerMap;
public FlowExecutor(ImmutableMap<String, Class<? extends FlowNodeRunner>> runnerMap) {
this.runnerMap = runnerMap;
}
public void execute(FlowGraph graph) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
var nodeInputMap = graph.edges().groupBy(FlowEdge::source);
var nodeOutputMap = graph.edges().groupBy(FlowEdge::target);
var startNodes = graph.nodes().select(node -> !nodeInputMap.containsKey(node.id()));
Queue<FlowNode> queue = new LinkedList<>();
for (FlowNode node : startNodes) {
queue.offer(node);
}
while (!queue.isEmpty()) {
var node = queue.poll();
var clazz = runnerMap.get(node.type());
var runner = clazz.getDeclaredConstructor(FlowContext.class).newInstance();
runner.run();
}
}
} }

View File

@@ -1,20 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.engine;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.eclipse.collections.api.set.ImmutableSet;
/**
* 流程图中的节点
*
* @author lanyuanxiaoyao
* @version 20250630
*/
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class FlowNode {
@EqualsAndHashCode.Include
private String id;
private ImmutableSet<String> sourceHandlers;
private ImmutableSet<String> targetHandlers;
}

View File

@@ -0,0 +1,10 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import lombok.Data;
import org.eclipse.collections.api.factory.Maps;
import org.eclipse.collections.api.map.MutableMap;
@Data
public class FlowContext {
private MutableMap<String, Object> data = Maps.mutable.<String, Object>empty().asSynchronized();
}

View File

@@ -0,0 +1,16 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
/**
* 流程图中的边
*
* @author lanyuanxiaoyao
* @version 20250630
*/
public record FlowEdge(
String id,
String source,
String target,
String sourcePoint,
String targetPoint
) {
}

View File

@@ -0,0 +1,16 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import org.eclipse.collections.api.set.ImmutableSet;
/**
* 流程图
*
* @author lanyuanxiaoyao
* @version 20250630
*/
public record FlowGraph(
String id,
ImmutableSet<FlowNode> nodes,
ImmutableSet<FlowEdge> edges
) {
}

View File

@@ -0,0 +1,17 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
import org.eclipse.collections.api.set.ImmutableSet;
/**
* 流程图中的节点
*
* @author lanyuanxiaoyao
* @version 20250630
*/
public record FlowNode(
String id,
String type,
ImmutableSet<String> inputPoints,
ImmutableSet<String> outputPoints
) {
}

View File

@@ -0,0 +1,5 @@
package com.lanyuanxiaoyao.service.ai.web.engine.entity;
public abstract class FlowNodeRunner implements Runnable {
private final FlowContext context;
}

View File

@@ -1,10 +0,0 @@
package com.lanyuanxiaoyao.service.ai.web.engine.store;
/**
* 数据存储
*
* @author lanyuanxiaoyao
* @version 20250630
*/
public interface FlowStore {
}

View File

@@ -1,6 +1,6 @@
package com.lanyuanxiaoyao.service.ai.web.tools; package com.lanyuanxiaoyao.service.ai.web.tools;
import com.lanyuanxiaoyao.service.ai.web.WebApplication; import com.lanyuanxiaoyao.service.ai.web.configuration.SpringBeanGetter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.Tool;
@@ -44,7 +44,7 @@ public class ChartTool {
""") String request """) String request
) { ) {
log.info("Enter method: mermaid[request]. request:{}", request); log.info("Enter method: mermaid[request]. request:{}", request);
ChatClient.Builder builder = WebApplication.getBean("chat", ChatClient.Builder.class); ChatClient.Builder builder = SpringBeanGetter.getBean("chat", ChatClient.Builder.class);
ChatClient client = builder.build(); ChatClient client = builder.build();
return client.prompt() return client.prompt()
// language=TEXT // language=TEXT

View File

@@ -2,7 +2,7 @@ package com.lanyuanxiaoyao.service.ai.web.tools;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.ai.web.WebApplication; import com.lanyuanxiaoyao.service.ai.web.configuration.SpringBeanGetter;
import com.lanyuanxiaoyao.service.forest.service.KnowledgeService; import com.lanyuanxiaoyao.service.forest.service.KnowledgeService;
import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.ai.tool.annotation.ToolParam;
@@ -27,7 +27,7 @@ public class KnowledgeTool {
""") """)
String query String query
) { ) {
KnowledgeService knowledgeService = WebApplication.getBean(KnowledgeService.class); KnowledgeService knowledgeService = SpringBeanGetter.getBean(KnowledgeService.class);
var documents = knowledgeService.query(knowledgeId, query, 10, 0.5); var documents = knowledgeService.query(knowledgeId, query, 10, 0.5);
if (ObjectUtil.isNotEmpty(documents)) { if (ObjectUtil.isNotEmpty(documents)) {
return StrUtil.format(""" return StrUtil.format("""

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.ai.web.tools; package com.lanyuanxiaoyao.service.ai.web.tools;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.ai.web.WebApplication; import com.lanyuanxiaoyao.service.ai.web.configuration.SpringBeanGetter;
import com.lanyuanxiaoyao.service.forest.service.InfoService; import com.lanyuanxiaoyao.service.forest.service.InfoService;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -27,7 +27,7 @@ public class TableTool {
""") String sql """) String sql
) { ) {
log.info("Enter method: executeJdbc[sql]. sql:{}", sql); log.info("Enter method: executeJdbc[sql]. sql:{}", sql);
InfoService infoService = WebApplication.getBean(InfoService.class); InfoService infoService = SpringBeanGetter.getBean(InfoService.class);
String result = infoService.jdbc(sql) String result = infoService.jdbc(sql)
.collect(map -> map.valuesView().makeString(",")) .collect(map -> map.valuesView().makeString(","))
.makeString("\n"); .makeString("\n");
@@ -48,7 +48,7 @@ public class TableTool {
""") String type """) String type
) { ) {
log.info("Enter method: tableCount[type]. type:{}", type); log.info("Enter method: tableCount[type]. type:{}", type);
var infoService = WebApplication.getBean(InfoService.class); var infoService = SpringBeanGetter.getBean(InfoService.class);
return switch (type) { return switch (type) {
case "logic" -> StrUtil.format(""" case "logic" -> StrUtil.format("""
逻辑表共{}张,其中重点表{}张 逻辑表共{}张,其中重点表{}张
@@ -83,7 +83,7 @@ public class TableTool {
String type String type
) { ) {
log.info("Enter method: version[date, type]. date:{},type:{}", date, type); log.info("Enter method: version[date, type]. date:{},type:{}", date, type);
InfoService infoService = WebApplication.getBean(InfoService.class); InfoService infoService = SpringBeanGetter.getBean(InfoService.class);
String version = date; String version = date;
if (StrUtil.isBlank(version)) { if (StrUtil.isBlank(version)) {
version = LocalDateTime.now().minusDays(1).format(FORMATTER); version = LocalDateTime.now().minusDays(1).format(FORMATTER);

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.service.ai.web.tools; package com.lanyuanxiaoyao.service.ai.web.tools;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.lanyuanxiaoyao.service.ai.web.WebApplication; import com.lanyuanxiaoyao.service.ai.web.configuration.SpringBeanGetter;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnApplication; import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnApplication;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnQueue; import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnQueue;
import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnRootQueue; import com.lanyuanxiaoyao.service.configuration.entity.yarn.YarnRootQueue;
@@ -27,7 +27,7 @@ public class YarnTool {
""") String cluster """) String cluster
) { ) {
log.info("Enter method: yarnStatus[cluster]. cluster:{}", cluster); log.info("Enter method: yarnStatus[cluster]. cluster:{}", cluster);
YarnService yarnService = WebApplication.getBean(YarnService.class); YarnService yarnService = SpringBeanGetter.getBean(YarnService.class);
YarnRootQueue status = yarnService.cluster(cluster); YarnRootQueue status = yarnService.cluster(cluster);
return (status.getUsedCapacity() * 100.0) / status.getCapacity(); return (status.getUsedCapacity() * 100.0) / status.getCapacity();
} }
@@ -45,7 +45,7 @@ public class YarnTool {
""") String queue """) String queue
) { ) {
log.info("Enter method: yarnQueueStatus[cluster, queue]. cluster:{},queue:{}", cluster, queue); log.info("Enter method: yarnQueueStatus[cluster, queue]. cluster:{},queue:{}", cluster, queue);
YarnService yarnService = WebApplication.getBean(YarnService.class); YarnService yarnService = SpringBeanGetter.getBean(YarnService.class);
YarnQueue status = yarnService.queueDetail(cluster, queue); YarnQueue status = yarnService.queueDetail(cluster, queue);
return (status.getAbsoluteCapacity() * 100.0) / status.getAbsoluteMaxCapacity(); return (status.getAbsoluteCapacity() * 100.0) / status.getAbsoluteMaxCapacity();
} }
@@ -66,7 +66,7 @@ public class YarnTool {
""") String type """) String type
) { ) {
log.info("Enter method: yarnTaskStatus[cluster, type]. cluster:{},type:{}", cluster, type); log.info("Enter method: yarnTaskStatus[cluster, type]. cluster:{},type:{}", cluster, type);
YarnService yarnService = WebApplication.getBean(YarnService.class); YarnService yarnService = SpringBeanGetter.getBean(YarnService.class);
ImmutableList<YarnApplication> applications = yarnService.jobList(cluster).select(app -> StrUtil.isNotBlank(type) && StrUtil.contains(app.getName(), type)); ImmutableList<YarnApplication> applications = yarnService.jobList(cluster).select(app -> StrUtil.isNotBlank(type) && StrUtil.contains(app.getName(), type));
return StrUtil.format( return StrUtil.format(
""" """