Compare commits
10 Commits
8f6878a07c
...
edc250847b
| Author | SHA1 | Date | |
|---|---|---|---|
| edc250847b | |||
| 785134bd68 | |||
| 041a908180 | |||
| 077166e1a6 | |||
| bef6849e45 | |||
| 7a72358fc7 | |||
| f783658227 | |||
| e72dfab9fe | |||
| 61047720ab | |||
| ddb5f4e36c |
54
.drone.yml
54
.drone.yml
@@ -45,34 +45,32 @@ steps:
|
||||
|
||||
|
||||
- name: notify
|
||||
kind: template
|
||||
load: notify_template.yaml
|
||||
# image: 10.7.127.190:38080/plugins/webhook:latest
|
||||
# environment:
|
||||
# NOTIFY_WX_URL:
|
||||
# from_secret: notify_wx_url
|
||||
# when:
|
||||
# status: [ success,failure ]
|
||||
# settings:
|
||||
# urls:
|
||||
# from_secret: notify_wx_url
|
||||
# content_type: application/json
|
||||
# template: |
|
||||
# {
|
||||
# "msgtype": "markdown",
|
||||
# "markdown": {
|
||||
# "content": "{{#success build.status}}<font color=\"green\">✅ 构建成功</font>{{else}}<font color=\"red\">❌ 构建失败</font>{{/success}}
|
||||
# >**项目名称**: #{{ repo.name }}
|
||||
# >**构建编号**: #{{build.number}}
|
||||
# >**构建状态**: {{build.status}}
|
||||
# >**代码分支**: {{build.branch}}
|
||||
# >**提交哈希**: {{build.commit}}
|
||||
# >**提交作者**: {{build.author}}
|
||||
# >**提交信息**: {{build.message}}
|
||||
# >[查看构建详情]({{build.link}})
|
||||
# >{{^success build.status}}[查看失败日志]({{build.link}}/logs){{/success}}"
|
||||
# }
|
||||
# }
|
||||
image: 10.7.127.190:38080/plugins/webhook:latest
|
||||
environment:
|
||||
NOTIFY_WX_URL:
|
||||
from_secret: notify_wx_url
|
||||
when:
|
||||
status: [ success,failure ]
|
||||
settings:
|
||||
urls:
|
||||
from_secret: notify_wx_url
|
||||
content_type: application/json
|
||||
template: |
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": "{{#success build.status}}<font color=\"green\">✅ 构建成功</font>{{else}}<font color=\"red\">❌ 构建失败</font>{{/success}}
|
||||
>**项目名称**: #{{ repo.name }}
|
||||
>**构建编号**: #{{build.number}}
|
||||
>**构建状态**: {{build.status}}
|
||||
>**代码分支**: {{build.branch}}
|
||||
>**提交哈希**: {{build.commit}}
|
||||
>**提交作者**: {{build.author}}
|
||||
>**提交信息**: {{build.message}}
|
||||
>[查看构建详情]({{build.link}})
|
||||
>{{^success build.status}}[查看失败日志]({{build.link}}/logs){{/success}}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
volumes:
|
||||
|
||||
@@ -31,14 +31,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
@@ -77,6 +69,24 @@
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</dependency>
|
||||
<!-- velocity 模板渲染 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity</artifactId>
|
||||
</dependency>
|
||||
<!-- langchain4j -->
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-ollama</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.metis.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DateFormatterEnum {
|
||||
|
||||
/**
|
||||
* 时间格式
|
||||
*/
|
||||
COMMON_DATE_TIME(0, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault())),
|
||||
COMMON_SHORT_DATE(1, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault())),
|
||||
COMMON_MONTH_DAY(2, DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault())),
|
||||
COMMON_MONTH(3, DateTimeFormatter.ofPattern("yyyy-MM").withZone(ZoneId.systemDefault())),
|
||||
YEAR(4, DateTimeFormatter.ofPattern("yyyy").withZone(ZoneId.systemDefault())),
|
||||
COLON_DELIMITED_TIME(5, DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault())),
|
||||
COLON_DELIMITED_SHORT_TIME(6, DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_DATE_TIME(7, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_SHORT_DATE(8, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_DATE(9, DateTimeFormatter.ofPattern("yyyy年MM月dd日").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_MONTH(10, DateTimeFormatter.ofPattern("yyyy年MM月").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_YEAR(11, DateTimeFormatter.ofPattern("yyyy年").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_TIME(12, DateTimeFormatter.ofPattern("HH时mm分ss秒").withZone(ZoneId.systemDefault())),
|
||||
CHINESE_SHORT_TIME(13, DateTimeFormatter.ofPattern("HH时mm分").withZone(ZoneId.systemDefault()));
|
||||
|
||||
private final Integer code;
|
||||
private final DateTimeFormatter dateTimeFormatter;
|
||||
|
||||
|
||||
public static DateFormatterEnum findByCode(int code){
|
||||
return Stream.of(values())
|
||||
.filter(e -> e.getCode().equals(code))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.metis.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* 杰克逊自动配置
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
@Order(Integer.MIN_VALUE)
|
||||
@AutoConfiguration
|
||||
@AllArgsConstructor
|
||||
@ConditionalOnClass(ObjectMapper.class)
|
||||
@AutoConfigureBefore(org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class)
|
||||
public class JacksonAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Jackson2ObjectMapperBuilder
|
||||
*
|
||||
* @return {@link Jackson2ObjectMapperBuilder}
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
|
||||
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
|
||||
builder.locale(Locale.CHINA);
|
||||
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
|
||||
|
||||
//时间类型支持
|
||||
JavaTimeModule javaTimeModule = buildJavaTimeModule();
|
||||
//其他类型支持
|
||||
SimpleModule simpleModule = buildSimpleModule();
|
||||
|
||||
builder.modules(javaTimeModule,simpleModule);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* ObjectMapper
|
||||
*
|
||||
* @param builder 建设者
|
||||
* @return {@link ObjectMapper}
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
|
||||
objectMapper.findAndRegisterModules();
|
||||
|
||||
// 其他配置
|
||||
// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
|
||||
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
objectMapper.configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER.mappedFeature(), true);
|
||||
objectMapper.configure(JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION, true);
|
||||
//开启整数反序列化时使用长整型(long)的选项。确保处理的数据不会因类型限制丢失信息
|
||||
objectMapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true);
|
||||
//开启输出缩进功能。这通常用于美化JSON格式输出,使结构更清晰易读
|
||||
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||
//禁用报错
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建简单模块
|
||||
*
|
||||
* @return {@link SimpleModule}
|
||||
*/
|
||||
private SimpleModule buildSimpleModule() {
|
||||
SimpleModule simpleModule = new SimpleModule();
|
||||
|
||||
//BigInteger
|
||||
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
|
||||
simpleModule.addDeserializer(BigInteger.class, new NumberDeserializers.BigIntegerDeserializer());
|
||||
|
||||
//Long
|
||||
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
|
||||
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
|
||||
simpleModule.addDeserializer(Long.class, new NumberDeserializers.LongDeserializer(Long.class, null));
|
||||
|
||||
//BigDecimal
|
||||
simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
|
||||
simpleModule.addDeserializer(BigDecimal.class, new NumberDeserializers.BigDecimalDeserializer());
|
||||
|
||||
return simpleModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建Java时间模块
|
||||
*
|
||||
* @return {@link JavaTimeModule}
|
||||
*/
|
||||
private JavaTimeModule buildJavaTimeModule() {
|
||||
// 添加自定义序列化和反序列化器
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateFormatterEnum.COMMON_DATE_TIME.getDateTimeFormatter()));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateFormatterEnum.COMMON_DATE_TIME.getDateTimeFormatter()));
|
||||
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateFormatterEnum.COMMON_MONTH_DAY.getDateTimeFormatter()));
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateFormatterEnum.COLON_DELIMITED_TIME.getDateTimeFormatter()));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateFormatterEnum.COMMON_MONTH_DAY.getDateTimeFormatter()));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateFormatterEnum.COLON_DELIMITED_TIME.getDateTimeFormatter()));
|
||||
|
||||
//不提供 java.util.Date 的序列化处理,全项目禁用 java.util.Date 类
|
||||
return javaTimeModule;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,4 +3,12 @@ package com.metis.constant;
|
||||
public interface BaseConstant {
|
||||
|
||||
Integer DEFAULT_VERSION = 1;
|
||||
|
||||
String TEXT = "text";
|
||||
|
||||
String FINISH_REASON = "finishReason";
|
||||
|
||||
String USAGE = "usage";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ public class ProcessDefinitionController {
|
||||
|
||||
|
||||
@Operation(summary = "根据部署ID获取流程定义")
|
||||
@GetMapping("/{deploymentId}")
|
||||
public Result<AppVo> getByDeploymentId(@PathVariable Long deploymentId) {
|
||||
AppVo app = processDefinitionFacade.getByDeploymentId(deploymentId);
|
||||
@GetMapping("/{workflowId}")
|
||||
public Result<AppVo> getByWorkflowId(@PathVariable Long workflowId) {
|
||||
AppVo app = processDefinitionFacade.getByWorkflowId(workflowId);
|
||||
return Result.ok(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.metis.convert;
|
||||
|
||||
import com.metis.domain.entity.ModelPlatform;
|
||||
import com.metis.domain.entity.ModelPlatformInfo;
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface ModelPlatformConvert {
|
||||
|
||||
ModelPlatformConvert INSTANCE = Mappers.getMapper(ModelPlatformConvert.class);
|
||||
|
||||
ModelPlatformInfo toInfo(ModelPlatform modelPlatform);
|
||||
|
||||
|
||||
Model toModel(ModelPlatform modelPlatform);
|
||||
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import com.metis.enums.YesOrNoEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "应用")
|
||||
public class AppBO {
|
||||
|
||||
@Schema(description = "应用id")
|
||||
private Long appId;
|
||||
|
||||
@Schema(description = "应用名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "应用描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "画布")
|
||||
private GraphBO graph;
|
||||
|
||||
@Schema(description = "是否默认使用")
|
||||
private YesOrNoEnum defaultUse;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import com.metis.domain.entity.base.Graph;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -11,24 +12,32 @@ import lombok.Data;
|
||||
@Builder
|
||||
public final class BuildApp {
|
||||
|
||||
/**
|
||||
* 应用id
|
||||
*/
|
||||
@Schema(description = "应用id")
|
||||
private Long appId;
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
@NotBlank(message = "流程名称不能为空")
|
||||
@Schema(description = "流程名称")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "流程模型不能为空")
|
||||
@Valid
|
||||
@NotNull(message = "流程模型不能为空")
|
||||
@Schema(description = "流程模型")
|
||||
private Graph graph;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "流程描述")
|
||||
private String description;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import com.metis.domain.entity.base.Graph;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -13,20 +14,24 @@ public class CreateApp {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
@NotBlank(message = "流程名称不能为空")
|
||||
@Schema(description = "流程名称")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "流程模型不能为空")
|
||||
@Valid
|
||||
@Schema(description = "流程模型")
|
||||
private Graph graph;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "流程描述")
|
||||
private String description;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import com.metis.enums.EdgeType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -10,7 +12,7 @@ public class EdgeBO {
|
||||
/**
|
||||
* 唯一标识符
|
||||
*/
|
||||
@NotNull(message = "唯一标识符不能为空")
|
||||
@NotBlank(message = "唯一标识符不能为空")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
@@ -28,39 +30,78 @@ public class EdgeBO {
|
||||
* 源节点ID,对应节点id
|
||||
*/
|
||||
@NotNull(message = "源节点ID不能为空")
|
||||
private Long source;
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 目标节点ID,对应节点id
|
||||
*/
|
||||
@NotNull(message = "目标节点ID不能为空")
|
||||
private Long target;
|
||||
private String target;
|
||||
/**
|
||||
* 源句柄id
|
||||
*/
|
||||
@NotNull(message = "源句柄ID不能为空")
|
||||
private Long sourceHandle;
|
||||
private String sourceHandle;
|
||||
|
||||
/**
|
||||
* 目标句柄id
|
||||
*/
|
||||
@NotNull(message = "目标句柄ID不能为空")
|
||||
private Long targetHandle;
|
||||
private String targetHandle;
|
||||
|
||||
/**
|
||||
* 边是否动画true/false
|
||||
*/
|
||||
@Schema(description = "边是否动画true/false")
|
||||
private Boolean animated;
|
||||
|
||||
/**
|
||||
* 开始标志
|
||||
*/
|
||||
@Schema(description = "开始标志")
|
||||
private String markerStart;
|
||||
|
||||
/**
|
||||
* 结束标记
|
||||
*/
|
||||
@Schema(description = "结束标记")
|
||||
private String markerEnd;
|
||||
/**
|
||||
* 边是否可选中true,false
|
||||
*/
|
||||
@Schema(description = "边是否可选中true,false")
|
||||
private Boolean selectable;
|
||||
|
||||
/**
|
||||
* 边是否可更新true,false(更改它们的源/目标位置。)
|
||||
*/
|
||||
@Schema(description = "边是否可更新true,false(更改它们的源/目标位置。)")
|
||||
private Boolean updatable;
|
||||
|
||||
/**
|
||||
* 边是否可以删除true,false
|
||||
*/
|
||||
@Schema(description = "边是否可以删除true,false")
|
||||
private Boolean deletable;
|
||||
/**
|
||||
* 源位置x坐标
|
||||
*/
|
||||
@Schema(description = "源位置x坐标")
|
||||
private Float sourceX;
|
||||
/**
|
||||
* 源位置y坐标
|
||||
*/
|
||||
@Schema(description = "源位置y坐标")
|
||||
private Float sourceY;
|
||||
/**
|
||||
* 目标位置x坐标
|
||||
*/
|
||||
@Schema(description = "目标位置x坐标")
|
||||
private Float targetX;
|
||||
/**
|
||||
* 目标位置y坐标
|
||||
*/
|
||||
@Schema(description = "目标位置y坐标")
|
||||
private Float targetY;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
@@ -7,6 +8,7 @@ import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Schema(description = "画布")
|
||||
public class GraphBO {
|
||||
|
||||
/**
|
||||
@@ -14,6 +16,7 @@ public class GraphBO {
|
||||
*/
|
||||
@Valid
|
||||
@NotEmpty(message = "连线不能为空")
|
||||
@Schema(description = "连线")
|
||||
private List<EdgeBO> edges;
|
||||
|
||||
/**
|
||||
@@ -21,22 +24,26 @@ public class GraphBO {
|
||||
*/
|
||||
@Valid
|
||||
@NotEmpty(message = "节点不能为空")
|
||||
@Schema(description = "节点")
|
||||
private List<NodeBO> nodes;
|
||||
|
||||
|
||||
/**
|
||||
* 位置
|
||||
*/
|
||||
@Schema(description = "位置")
|
||||
private List<Double> position;
|
||||
|
||||
/**
|
||||
* 变焦
|
||||
*/
|
||||
@Schema(description = "变焦")
|
||||
private Double zoom;
|
||||
|
||||
/**
|
||||
* 视窗
|
||||
*/
|
||||
@Schema(description = "视窗")
|
||||
private ViewportBo viewport;
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.metis.domain.bo;
|
||||
|
||||
import com.metis.enums.HandleType;
|
||||
import com.metis.enums.PositionType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -13,24 +15,28 @@ public class HandleBO {
|
||||
/**
|
||||
* 句柄id
|
||||
*/
|
||||
@NotNull(message = "句柄id不能为空")
|
||||
private Long id;
|
||||
@NotBlank(message = "句柄id不能为空")
|
||||
@Schema(description = "句柄id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 句柄类型
|
||||
*/
|
||||
@NotNull(message = "句柄类型不能为空")
|
||||
@Schema(description = "句柄类型")
|
||||
private HandleType type;
|
||||
|
||||
/**
|
||||
* 句柄位置
|
||||
*/
|
||||
@NotNull(message = "句柄位置不能为空")
|
||||
@Schema(description = "句柄位置")
|
||||
private PositionType position;
|
||||
|
||||
/**
|
||||
* 是否可以连接
|
||||
*/
|
||||
@NotNull(message = "是否可以连接不能为空")
|
||||
@Schema(description = "是否可以连接")
|
||||
private Boolean connectable;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import com.metis.enums.NodeType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -11,18 +13,21 @@ public class NodeBO {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@NotNull(message = "节点id不能为空")
|
||||
private Long id;
|
||||
@NotBlank(message = "节点id不能为空")
|
||||
@Schema(description = "节点id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
@NotNull(message = "节点类型不能为空")
|
||||
@Schema(description = "节点类型")
|
||||
private NodeType type;
|
||||
|
||||
/**
|
||||
* 自定义类型
|
||||
*/
|
||||
@Schema(description = "自定义类型")
|
||||
private String customType;
|
||||
|
||||
/**
|
||||
@@ -30,6 +35,7 @@ public class NodeBO {
|
||||
*/
|
||||
@Valid
|
||||
@NotNull(message = "节点位置不能为空")
|
||||
@Schema(description = "节点位置")
|
||||
private PositionBO position;
|
||||
|
||||
/**
|
||||
@@ -37,23 +43,25 @@ public class NodeBO {
|
||||
*/
|
||||
@Valid
|
||||
@NotNull(message = "节点业务数据不能为空")
|
||||
@Schema(description = "节点业务数据")
|
||||
private NodeDataBO data;
|
||||
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
// @NotNull(message = "节点宽度不能为空")
|
||||
@Schema(description = "节点宽度")
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
// @NotNull(message = "节点高度不能为空")
|
||||
@Schema(description = "节点高度")
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
* 节点是否选中
|
||||
*/
|
||||
@Schema(description = "节点是否选中")
|
||||
private Boolean selected;
|
||||
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ public class NodeDataBO {
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 工具栏位置
|
||||
*/
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "节点位置")
|
||||
public class PositionBO {
|
||||
/**
|
||||
* x坐标
|
||||
*/
|
||||
@NotNull(message = "x坐标不能为空")
|
||||
@Schema(description = "x坐标")
|
||||
private Double x;
|
||||
|
||||
/**
|
||||
* y坐标
|
||||
*/
|
||||
@Schema(description = "y坐标")
|
||||
@NotNull(message = "y坐标不能为空")
|
||||
private Double y;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.metis.domain.bo;
|
||||
|
||||
import com.metis.enums.YesOrNoEnum;
|
||||
import com.metis.domain.entity.base.Graph;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
@@ -15,22 +16,30 @@ public class UpdateApp {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@Schema(description = "id")
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
@NotBlank(message = "流程名称不能为空")
|
||||
@Schema(description = "名字")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "流程模型不能为空")
|
||||
@Valid
|
||||
@Schema(description = "流程模型")
|
||||
private Graph graph;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否默认使用
|
||||
*/
|
||||
@Schema(description = "是否默认使用")
|
||||
private YesOrNoEnum defaultUse;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package com.metis.domain.bo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "视窗")
|
||||
public class ViewportBo {
|
||||
|
||||
@Schema(description = "x坐标")
|
||||
private Double x;
|
||||
|
||||
@Schema(description = "y坐标")
|
||||
private Double y;
|
||||
|
||||
@Schema(description = "变焦")
|
||||
private Double zoom;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
package com.metis.domain.context;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.runner.FlowRunningContext;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Builder
|
||||
public class RunningContext {
|
||||
|
||||
private final static String SYS_PREFIX = "sys.";
|
||||
|
||||
/**
|
||||
* 系统数据
|
||||
*/
|
||||
@@ -26,12 +36,12 @@ public class RunningContext {
|
||||
/**
|
||||
* 节点运行上下文, 需要数据进行传递
|
||||
*/
|
||||
private Map<Long, JSONObject> nodeRunningContext;
|
||||
private Map<String, JSONObject> nodeRunningContext;
|
||||
|
||||
/**
|
||||
* 下一个运行节点id集合, 可能是多个, 执行器每一次清空该节点
|
||||
*/
|
||||
private Set<Long> nextRunNodeId;
|
||||
private Set<String> nextRunNodeId;
|
||||
|
||||
|
||||
/**
|
||||
@@ -40,15 +50,79 @@ public class RunningContext {
|
||||
* @param nodeId 节点id
|
||||
* @param nodeRunningContext 节点运行背景信息
|
||||
*/
|
||||
public void addNodeRunningContext(Long nodeId, JSONObject nodeRunningContext) {
|
||||
public void addNodeRunningContext(String nodeId, JSONObject nodeRunningContext) {
|
||||
this.nodeRunningContext.put(nodeId, nodeRunningContext);
|
||||
}
|
||||
|
||||
public JSONObject getRunningContext(Long nodeId) {
|
||||
/**
|
||||
* 获取运行上下文
|
||||
*
|
||||
* @param nodeId 节点id
|
||||
* @return {@link JSONObject }
|
||||
*/
|
||||
public JSONObject getRunningContext(String nodeId) {
|
||||
return this.nodeRunningContext.get(nodeId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获得值
|
||||
*
|
||||
* @param key 关键
|
||||
* @return {@link Object }
|
||||
*/
|
||||
public Object getValue(String key) {
|
||||
Assert.isTrue(StrUtil.isNotBlank(key), "key is blank");
|
||||
if (key.startsWith(SYS_PREFIX)) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(this);
|
||||
return parser.parseExpression(key).getValue(context);
|
||||
}
|
||||
|
||||
|
||||
// try {
|
||||
// // 解析 key 中的数字部分并转换为 Long 类型
|
||||
// String[] parts = key.split("\\.");
|
||||
// if (parts.length == 2 && StrUtil.isNumeric(parts[0])) {
|
||||
// String nodeId = Long.valueOf(parts[0]);
|
||||
// JSONObject runningContext = getRunningContext(nodeId);
|
||||
// if (runningContext != null) {
|
||||
// return runningContext.get(parts[1]);
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// log.error("数字类型获取动态参数失败: {}", key);
|
||||
// }
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
Map<String, Object> variables = new HashMap<>(this.nodeRunningContext);
|
||||
context.setVariables(variables);
|
||||
return parser.parseExpression(convertDotToSquareBrackets(key)).getValue(context);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获得价值 不满足条件, 则返回null, 需要业务自行判断
|
||||
*
|
||||
* @param nodeId 节点id
|
||||
* @param key 关键
|
||||
* @return {@link Object }
|
||||
*/
|
||||
public Object getValue(String nodeId, String key) {
|
||||
if (ObjectUtil.isNull(nodeId)) {
|
||||
return null;
|
||||
}
|
||||
if (StrUtil.isBlank(key)) {
|
||||
return null;
|
||||
}
|
||||
JSONObject runningContext = getRunningContext(nodeId);
|
||||
if (ObjectUtil.isNull(runningContext)) {
|
||||
return null;
|
||||
}
|
||||
return runningContext.get(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建上下文
|
||||
*
|
||||
@@ -63,6 +137,8 @@ public class RunningContext {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public String convertDotToSquareBrackets(String key) {
|
||||
return key.replaceAll("(\\w+)\\.(\\w+)", "#$1['$2']");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RunningResult {
|
||||
/**
|
||||
* 下一个运行节点id, 一些特殊节点需要, 必须条件节点满足后, 才会运行下一个节点
|
||||
*/
|
||||
private Set<Long> nextRunNodeId;
|
||||
private Set<String> nextRunNodeId;
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ public class RunningResult {
|
||||
* @param nextRunNodeId 下一个运行节点id
|
||||
* @return {@link RunningResult }
|
||||
*/
|
||||
public static RunningResult buildResult(JSONObject nodeContext, Set<Long> nextRunNodeId) {
|
||||
public static RunningResult buildResult(JSONObject nodeContext, Set<String> nextRunNodeId) {
|
||||
return RunningResult.builder()
|
||||
.nodeContext(nodeContext)
|
||||
.nextRunNodeId(nextRunNodeId)
|
||||
@@ -41,7 +41,7 @@ public class RunningResult {
|
||||
* @param nextRunNodeId 下一个运行节点id
|
||||
* @return {@link RunningResult }
|
||||
*/
|
||||
public static RunningResult buildResult(Set<Long> nextRunNodeId) {
|
||||
public static RunningResult buildResult(Set<String> nextRunNodeId) {
|
||||
return RunningResult.builder()
|
||||
.nextRunNodeId(nextRunNodeId)
|
||||
.build();
|
||||
|
||||
@@ -48,4 +48,5 @@ public class SysContext {
|
||||
* 实例id
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
}
|
||||
|
||||
@@ -12,13 +12,13 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class GraphDto {
|
||||
|
||||
private final Map<Long, Node> nodeMap;
|
||||
private final Map<String, Node> nodeMap;
|
||||
|
||||
private final Map<Long, Boolean> nodeReadyMap;
|
||||
private final Map<String, Boolean> nodeReadyMap;
|
||||
|
||||
private final Map<Long, List<Edge>> edgeMap;
|
||||
private final Map<String, List<Edge>> edgeMap;
|
||||
|
||||
private final Map<Long, List<Long>> adjacencyList = new HashMap<>();
|
||||
private final Map<String, List<String>> adjacencyList = new HashMap<>();
|
||||
|
||||
private final List<Node> sortedNodes = new ArrayList<>();
|
||||
|
||||
@@ -27,7 +27,7 @@ public class GraphDto {
|
||||
return new ArrayList<>(sortedNodes);
|
||||
}
|
||||
|
||||
public List<Edge> getEdgeNodeId(Long nodeId) {
|
||||
public List<Edge> getEdgeNodeId(String nodeId) {
|
||||
return edgeMap.getOrDefault(nodeId, new ArrayList<>());
|
||||
}
|
||||
|
||||
@@ -38,15 +38,15 @@ public class GraphDto {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Node getNode(Long nodeId) {
|
||||
public Node getNode(String nodeId) {
|
||||
return nodeMap.get(nodeId);
|
||||
}
|
||||
|
||||
public void updateNodeReadyMap(Long nodeId, Boolean ready) {
|
||||
public void updateNodeReadyMap(String nodeId, Boolean ready) {
|
||||
nodeReadyMap.put(nodeId, ready);
|
||||
}
|
||||
|
||||
public Boolean isNodeReady(Long nodeId) {
|
||||
public Boolean isNodeReady(String nodeId) {
|
||||
return nodeReadyMap.get(nodeId);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class GraphDto {
|
||||
|
||||
private void initAdjacencyList(List<Edge> edges) {
|
||||
for (Edge edge : edges) {
|
||||
List<Long> targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>());
|
||||
List<String> targetList = adjacencyList.getOrDefault(edge.getSource(), new ArrayList<>());
|
||||
targetList.add(edge.getTarget());
|
||||
adjacencyList.put(edge.getSource(), targetList);
|
||||
}
|
||||
@@ -83,9 +83,9 @@ public class GraphDto {
|
||||
*/
|
||||
private List<Node> topologicalSort() {
|
||||
List<Node> sortedNodes = new ArrayList<>();
|
||||
Set<Long> visited = new HashSet<>();
|
||||
Set<Long> visiting = new HashSet<>();
|
||||
for (Long nodeId : nodeMap.keySet()) {
|
||||
Set<String> visited = new HashSet<>();
|
||||
Set<String> visiting = new HashSet<>();
|
||||
for (String nodeId : nodeMap.keySet()) {
|
||||
if (!visited.contains(nodeId)) {
|
||||
dfs(nodeId, visited, visiting, sortedNodes);
|
||||
}
|
||||
@@ -102,13 +102,13 @@ public class GraphDto {
|
||||
* @param visiting 参观
|
||||
* @param sortedNodes 排序节点
|
||||
*/
|
||||
private void dfs(Long nodeId, Set<Long> visited, Set<Long> visiting, List<Node> sortedNodes) {
|
||||
private void dfs(String nodeId, Set<String> visited, Set<String> visiting, List<Node> sortedNodes) {
|
||||
if (visiting.contains(nodeId)) {
|
||||
throw new IllegalStateException("Cycle detected in the graph");
|
||||
}
|
||||
if (!visited.contains(nodeId)) {
|
||||
visiting.add(nodeId);
|
||||
for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
|
||||
for (String neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
|
||||
dfs(neighbor, visited, visiting, sortedNodes);
|
||||
}
|
||||
visiting.remove(nodeId);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.metis.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.metis.domain.SimpleBaseEntity;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.enums.YesOrNoEnum;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* llm model平台
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("model_platform")
|
||||
public class ModelPlatform extends SimpleBaseEntity {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private ModelTypeEnum type;
|
||||
|
||||
/**
|
||||
* 自定义类型
|
||||
*/
|
||||
private String customType;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* api密匙
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
private String configJson;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private YesOrNoEnum state;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.metis.domain.entity;
|
||||
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ModelPlatformInfo {
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private ModelTypeEnum type;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* api密匙
|
||||
*/
|
||||
private String apiKey;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.metis.domain.entity.base;
|
||||
|
||||
import com.metis.enums.EdgeType;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -10,42 +11,42 @@ public class Edge {
|
||||
/**
|
||||
* 唯一标识符
|
||||
*/
|
||||
@NotNull(message = "唯一标识符不能为空")
|
||||
@NotBlank(message = "唯一标识符不能为空")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
*/
|
||||
@NotNull(message = "线类型不能为空")
|
||||
private EdgeType type;
|
||||
|
||||
/**
|
||||
* 标签
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 源节点ID,对应节点id
|
||||
*/
|
||||
@NotNull(message = "源节点ID不能为空")
|
||||
private Long source;
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 目标节点ID,对应节点id
|
||||
*/
|
||||
@NotNull(message = "目标节点ID不能为空")
|
||||
private Long target;
|
||||
private String target;
|
||||
/**
|
||||
* 源句柄id
|
||||
*/
|
||||
@NotNull(message = "源句柄ID不能为空")
|
||||
private Long sourceHandle;
|
||||
private String sourceHandle;
|
||||
|
||||
/**
|
||||
* 目标句柄id
|
||||
*/
|
||||
@NotNull(message = "目标句柄ID不能为空")
|
||||
private Long targetHandle;
|
||||
private String targetHandle;
|
||||
|
||||
/**
|
||||
* 边是否动画true/false
|
||||
@@ -62,5 +63,36 @@ public class Edge {
|
||||
*/
|
||||
private String markerEnd;
|
||||
|
||||
/**
|
||||
* 边是否可选中true,false
|
||||
*/
|
||||
private Boolean selectable;
|
||||
|
||||
/**
|
||||
* 边是否可更新true,false(更改它们的源/目标位置。)
|
||||
*/
|
||||
private Boolean updatable;
|
||||
|
||||
/**
|
||||
* 边是否可以删除true,false
|
||||
*/
|
||||
private Boolean deletable;
|
||||
/**
|
||||
* 源位置x坐标
|
||||
*/
|
||||
private Float sourceX;
|
||||
/**
|
||||
* 源位置y坐标
|
||||
*/
|
||||
private Float sourceY;
|
||||
/**
|
||||
* 目标位置x坐标
|
||||
*/
|
||||
private Float targetX;
|
||||
/**
|
||||
* 目标位置y坐标
|
||||
*/
|
||||
private Float targetY;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.metis.domain.entity.base;
|
||||
|
||||
import com.metis.enums.HandleType;
|
||||
import com.metis.enums.PositionType;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -13,8 +14,8 @@ public class Handle {
|
||||
/**
|
||||
* 句柄id
|
||||
*/
|
||||
@NotNull(message = "句柄id不能为空")
|
||||
private Long id;
|
||||
@NotBlank(message = "句柄id不能为空")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 句柄类型
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.metis.domain.entity.base;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.enums.YesOrNoEnum;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Model {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private ModelTypeEnum type;
|
||||
|
||||
/**
|
||||
* 自定义类型
|
||||
*/
|
||||
private String customType;
|
||||
|
||||
/**
|
||||
* 请求地址
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* api密匙
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 配置
|
||||
*/
|
||||
private JSONObject config;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private YesOrNoEnum state;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private transient Class<?> configClass;
|
||||
|
||||
/**
|
||||
* 获取配置
|
||||
*
|
||||
* @return {@link T }
|
||||
*/
|
||||
public <T> T getConfig() {
|
||||
if (ObjectUtil.isNull(config)) {
|
||||
return null;
|
||||
}
|
||||
return (T) config.to(configClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置类
|
||||
*
|
||||
* @param configClass 配置类
|
||||
*/
|
||||
public <T> void setConfigClass(Class<T> configClass) {
|
||||
if (ObjectUtil.isNotNull(this.configClass)) {
|
||||
return;
|
||||
}
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class Node {
|
||||
* id
|
||||
*/
|
||||
@NotNull(message = "节点id不能为空")
|
||||
private Long id;
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
@@ -40,13 +40,11 @@ public class Node {
|
||||
/**
|
||||
* 宽度
|
||||
*/
|
||||
// @NotNull(message = "节点宽度不能为空")
|
||||
private Integer width;
|
||||
|
||||
/**
|
||||
* 高度
|
||||
*/
|
||||
// @NotNull(message = "节点高度不能为空")
|
||||
private Integer height;
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,11 @@ public class NodeData {
|
||||
*/
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 工具栏位置
|
||||
*/
|
||||
|
||||
@@ -36,7 +36,7 @@ public class NodeVariable {
|
||||
* 类型
|
||||
*/
|
||||
@NotNull(message = "类型不能为空")
|
||||
private String type;
|
||||
private NodeVariableType type;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
@@ -74,7 +74,7 @@ public class NodeVariable {
|
||||
}
|
||||
|
||||
private Object getSerializable(JSONObject custom) {
|
||||
switch (getVariableType()) {
|
||||
switch (this.type) {
|
||||
case TEXT_INPUT, PARAGRAPH, SELECT, FILE -> {
|
||||
return custom.getString(variable);
|
||||
}
|
||||
@@ -89,10 +89,5 @@ public class NodeVariable {
|
||||
}
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
public NodeVariableType getVariableType() {
|
||||
return NodeVariableType.get(type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.metis.domain.entity.base;
|
||||
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@@ -15,7 +15,7 @@ public class VariableOption {
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
@NotNull(message = "值不能为空")
|
||||
@NotBlank(message = "值不能为空")
|
||||
private String value;
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
package com.metis.domain.entity.config.node;
|
||||
|
||||
import com.metis.domain.entity.base.NodeConfig;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EndNodeConfig extends NodeConfig {
|
||||
|
||||
|
||||
@Valid
|
||||
private List<Variable> variables;
|
||||
|
||||
|
||||
@Data
|
||||
public static class Variable {
|
||||
@NotBlank(message = "参数字段不能为空")
|
||||
private String variable;
|
||||
@NotBlank(message = "参数key不能为空")
|
||||
private String variableKey;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.metis.domain.entity.config.node;
|
||||
|
||||
import com.metis.domain.entity.base.NodeConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LLMNodeConfig extends NodeConfig {
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.metis.domain.entity.config.node.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 重试配置
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/05/04
|
||||
*/
|
||||
@Data
|
||||
public class RetryConfig {
|
||||
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
private Boolean enable;
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private Integer maxRetries;
|
||||
|
||||
/**
|
||||
* 重试时间间隔
|
||||
*/
|
||||
private Integer retryInterval;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.metis.domain.entity.config.node.llm;
|
||||
|
||||
import com.metis.domain.entity.base.NodeConfig;
|
||||
import com.metis.domain.entity.config.node.common.RetryConfig;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LLMNodeConfig extends NodeConfig {
|
||||
|
||||
private String context;
|
||||
|
||||
/**
|
||||
* 重试配置
|
||||
*/
|
||||
private RetryConfig retryConfig;
|
||||
|
||||
/**
|
||||
* 提示模板
|
||||
*/
|
||||
private List<PromptTemplate> promptTemplate;
|
||||
|
||||
/**
|
||||
* 模型
|
||||
*/
|
||||
private LLMChatModeConfig model;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.metis.domain.entity.config.node.llm;
|
||||
|
||||
import com.metis.enums.ChatRoleType;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PromptTemplate {
|
||||
|
||||
private ChatRoleType role;
|
||||
|
||||
private String text;
|
||||
|
||||
private String id;
|
||||
|
||||
}
|
||||
@@ -67,4 +67,41 @@ public class EdgeVo {
|
||||
@Schema(description = "结束标记")
|
||||
private String markerEnd;
|
||||
|
||||
/**
|
||||
* 边是否可选中true,false
|
||||
*/
|
||||
@Schema(description = "边是否可选中true,false")
|
||||
private Boolean selectable;
|
||||
|
||||
/**
|
||||
* 边是否可更新true,false(更改它们的源/目标位置。)
|
||||
*/
|
||||
@Schema(description = "边是否可更新true,false(更改它们的源/目标位置。)")
|
||||
private Boolean updatable;
|
||||
|
||||
/**
|
||||
* 边是否可以删除true,false
|
||||
*/
|
||||
@Schema(description = "边是否可以删除true,false")
|
||||
private Boolean deletable;
|
||||
/**
|
||||
* 源位置x坐标
|
||||
*/
|
||||
@Schema(description = "源位置x坐标")
|
||||
private Float sourceX;
|
||||
/**
|
||||
* 源位置y坐标
|
||||
*/
|
||||
@Schema(description = "源位置y坐标")
|
||||
private Float sourceY;
|
||||
/**
|
||||
* 目标位置x坐标
|
||||
*/
|
||||
@Schema(description = "目标位置x坐标")
|
||||
private Float targetX;
|
||||
/**
|
||||
* 目标位置y坐标
|
||||
*/
|
||||
@Schema(description = "目标位置y坐标")
|
||||
private Float targetY;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
|
||||
|
||||
@Data
|
||||
@Schema(description = "画布")
|
||||
public class GraphVo {
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,12 @@ public class NodeDataVo {
|
||||
@Schema(description = "图标")
|
||||
private String icon;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 工具栏位置
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "节点")
|
||||
public class NodeVo {
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,7 +59,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic
|
||||
// 开始节点为空,则表示数据存在异常
|
||||
Assert.isTrue(ObjectUtil.isNotNull(readyRunningNode), "流程图不存在开始节点");
|
||||
for (Node node : graph.getSortedNodes()) {
|
||||
Long nodeId = node.getId();
|
||||
String nodeId = node.getId();
|
||||
if (!graph.isNodeReady(nodeId)) {
|
||||
continue;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic
|
||||
}
|
||||
// 下一个需要运行的节点id加入到可以运行的节点中
|
||||
if (CollUtil.isNotEmpty(result.getNextRunNodeId())) {
|
||||
for (Long nextNodeId : result.getNextRunNodeId()) {
|
||||
for (String nextNodeId : result.getNextRunNodeId()) {
|
||||
graph.updateNodeReadyMap(nextNodeId, true);
|
||||
}
|
||||
} else {
|
||||
@@ -95,7 +95,7 @@ public class AppFlowEngineRunnerServiceImpl implements AppFlowEngineRunnerServic
|
||||
|
||||
return RunnerResult.builder()
|
||||
.result(endRunningContext)
|
||||
.context(sysContext)
|
||||
// .context(sysContext)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.metis.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ChatRoleType {
|
||||
SYSTEM("system", "系统角色"),
|
||||
USER("user", "用户角色"),
|
||||
AI("ai", "ai返回"),
|
||||
TOOL_EXECUTION_RESULT("toolExecutionResult", "工具(函数调用)返回");
|
||||
|
||||
|
||||
|
||||
@JsonValue
|
||||
private final String value;
|
||||
|
||||
private final String desc;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.metis.enums;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* llm 类型 枚举
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ModelTypeEnum implements BaseEnum<ModelTypeEnum> {
|
||||
CUSTOM(0, "自定义"),
|
||||
OPEN_AI(1, "OpenAI"),
|
||||
OLLAMA(2, "Ollama"),
|
||||
|
||||
;
|
||||
|
||||
|
||||
@JsonValue
|
||||
private final Integer code;
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 根据 code 转换枚举
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 枚举
|
||||
*/
|
||||
public static Optional<ModelTypeEnum> of(Integer code) {
|
||||
return Optional.ofNullable(BaseEnum.parseByCode(ModelTypeEnum.class, code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举序列化器(前端传code时自动转换为对应枚举)
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 枚举
|
||||
*/
|
||||
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
|
||||
public static ModelTypeEnum get(Integer code) {
|
||||
return BaseEnum.parseByCode(ModelTypeEnum.class, code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,11 +28,11 @@ public class ProcessDefinitionFacade {
|
||||
/**
|
||||
* 通过部署id获取
|
||||
*
|
||||
* @param deploymentId 部署id
|
||||
* @param workflowId 部署id
|
||||
* @return {@link App }
|
||||
*/
|
||||
public AppVo getByDeploymentId(Long deploymentId) {
|
||||
App app = appEngineService.getByWorkflowId(deploymentId);
|
||||
public AppVo getByWorkflowId(Long workflowId) {
|
||||
App app = appEngineService.getByWorkflowId(workflowId);
|
||||
return AppConvert.INSTANCE.toVo(app);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.metis.llm.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CompletionParams {
|
||||
|
||||
/**
|
||||
* 温度
|
||||
*/
|
||||
private Double temperature;
|
||||
|
||||
/**
|
||||
* 最大token数
|
||||
*/
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* Top P
|
||||
*/
|
||||
private Double topP;
|
||||
|
||||
/**
|
||||
* Top K
|
||||
*/
|
||||
private Double topK;
|
||||
|
||||
/**
|
||||
* 随机种子
|
||||
*/
|
||||
private Integer seed;
|
||||
|
||||
/**
|
||||
* 重复处罚
|
||||
*/
|
||||
private Double presencePenalty;
|
||||
|
||||
/**
|
||||
* 响应格式
|
||||
*/
|
||||
private String responseFormat;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.metis.llm.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LLMChatModeConfig {
|
||||
|
||||
/**
|
||||
* 模型id
|
||||
*/
|
||||
private Long modelId;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
|
||||
/**
|
||||
* 完成参数
|
||||
*/
|
||||
private CompletionParams completionParams;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.metis.llm.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LLMEmbeddingModelConfig {
|
||||
|
||||
/**
|
||||
* 模型id
|
||||
*/
|
||||
private Long modelId;
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private String modelName;
|
||||
}
|
||||
49
metis-starter/src/main/java/com/metis/llm/domain/Usage.java
Normal file
49
metis-starter/src/main/java/com/metis/llm/domain/Usage.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.metis.llm.domain;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import dev.langchain4j.model.openai.OpenAiTokenUsage;
|
||||
import dev.langchain4j.model.output.TokenUsage;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Usage {
|
||||
private Integer inputTokenCount;
|
||||
private Integer outputTokenCount;
|
||||
private Integer totalTokenCount;
|
||||
private InputTokensDetails inputTokensDetails;
|
||||
private OutputTokensDetails outputTokensDetails;
|
||||
|
||||
@Data
|
||||
private static class InputTokensDetails {
|
||||
private Integer cachedTokens;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class OutputTokensDetails {
|
||||
private Integer reasoningTokens;
|
||||
}
|
||||
|
||||
|
||||
public static Usage buildTokenUsage(TokenUsage tokenUsage) {
|
||||
Usage usage = new Usage();
|
||||
usage.setInputTokenCount(tokenUsage.inputTokenCount());
|
||||
usage.setOutputTokenCount(tokenUsage.outputTokenCount());
|
||||
usage.setTotalTokenCount(tokenUsage.totalTokenCount());
|
||||
if (tokenUsage instanceof OpenAiTokenUsage openAiTokenUsage) {
|
||||
if (ObjectUtil.isNotNull(openAiTokenUsage.inputTokensDetails())) {
|
||||
InputTokensDetails inputTokensDetails = new InputTokensDetails();
|
||||
Integer cachedTokens = openAiTokenUsage.inputTokensDetails().cachedTokens();
|
||||
inputTokensDetails.setCachedTokens(cachedTokens);
|
||||
usage.setInputTokensDetails(inputTokensDetails);
|
||||
}
|
||||
if (ObjectUtil.isNotNull(openAiTokenUsage.outputTokensDetails())) {
|
||||
OutputTokensDetails outputTokensDetails = new OutputTokensDetails();
|
||||
Integer reasoningTokens = openAiTokenUsage.outputTokensDetails().reasoningTokens();
|
||||
outputTokensDetails.setReasoningTokens(reasoningTokens);
|
||||
usage.setOutputTokensDetails(outputTokensDetails);
|
||||
}
|
||||
}
|
||||
|
||||
return usage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.metis.llm.domain.config;
|
||||
|
||||
public abstract class BaseModelConfig {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.metis.llm.domain.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Ollama模型配置
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/05/04
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OllamaModelConfig extends BaseModelConfig{
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.metis.llm.domain.config;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OpenApiConfig extends BaseModelConfig {
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.metis.llm.engine;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.domain.CompletionParams;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import com.metis.llm.domain.LLMEmbeddingModelConfig;
|
||||
import com.metis.llm.domain.config.OllamaModelConfig;
|
||||
import com.metis.llm.service.ModelEngine;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.request.ResponseFormat;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.model.ollama.OllamaChatModel;
|
||||
import dev.langchain4j.model.ollama.OllamaEmbeddingModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OllamaModelEngine implements ModelEngine<OllamaModelConfig> {
|
||||
|
||||
|
||||
@Override
|
||||
public ModelTypeEnum getType() {
|
||||
return ModelTypeEnum.OLLAMA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel getChatLanguageModel(Model model, LLMChatModeConfig modelConfig) {
|
||||
CompletionParams completionParams = modelConfig.getCompletionParams();
|
||||
ResponseFormat responseFormat = ResponseFormat.JSON;
|
||||
if (StrUtil.isNotBlank(completionParams.getResponseFormat()) && "text".equals(completionParams.getResponseFormat())) {
|
||||
responseFormat = ResponseFormat.TEXT;
|
||||
}
|
||||
return OllamaChatModel.builder()
|
||||
.baseUrl(model.getUrl())
|
||||
.modelName(modelConfig.getModelName())
|
||||
.modelName(modelConfig.getModelName())
|
||||
.temperature(completionParams.getTemperature())
|
||||
.maxRetries(completionParams.getMaxTokens())
|
||||
.topP(completionParams.getTopP())
|
||||
.seed(completionParams.getSeed())
|
||||
.responseFormat(responseFormat)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmbeddingModel getEmbeddingModel(Model model, LLMEmbeddingModelConfig modeConfig) {
|
||||
return OllamaEmbeddingModel.builder()
|
||||
.baseUrl(model.getUrl())
|
||||
.modelName(modeConfig.getModelName())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.metis.llm.engine;
|
||||
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.service.ModelEngine;
|
||||
import com.metis.llm.domain.CompletionParams;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import com.metis.llm.domain.LLMEmbeddingModelConfig;
|
||||
import com.metis.llm.domain.config.OpenApiConfig;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpenApiModelEngine implements ModelEngine<OpenApiConfig> {
|
||||
|
||||
@Override
|
||||
public ModelTypeEnum getType() {
|
||||
return ModelTypeEnum.OPEN_AI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel getChatLanguageModel(Model model, LLMChatModeConfig modelConfig) {
|
||||
CompletionParams completionParams = modelConfig.getCompletionParams();
|
||||
return OpenAiChatModel.builder()
|
||||
.apiKey(model.getApiKey())
|
||||
.baseUrl(model.getUrl())
|
||||
.modelName(modelConfig.getModelName())
|
||||
.temperature(completionParams.getTemperature())
|
||||
.maxTokens(completionParams.getMaxTokens())
|
||||
.topP(completionParams.getTopP())
|
||||
.seed(completionParams.getSeed())
|
||||
.presencePenalty(completionParams.getPresencePenalty())
|
||||
.responseFormat(completionParams.getResponseFormat())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmbeddingModel getEmbeddingModel(Model model, LLMEmbeddingModelConfig modelConfig) {
|
||||
return OpenAiEmbeddingModel.builder()
|
||||
.apiKey(model.getApiKey())
|
||||
.baseUrl(model.getUrl())
|
||||
.modelName(modelConfig.getModelName())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.metis.llm.factory;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.service.CustomModelEngine;
|
||||
import com.metis.llm.service.ModelEngine;
|
||||
import com.metis.llm.domain.config.BaseModelConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ModelEngineFactory {
|
||||
|
||||
|
||||
/**
|
||||
* 模型图
|
||||
*/
|
||||
private static final Map<ModelTypeEnum, ModelEngine<? extends BaseModelConfig>> MODEL_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 自定义模型图
|
||||
*/
|
||||
private static final Map<String, ModelEngine<? extends BaseModelConfig>> CUSTOM_MODEL_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 型号表
|
||||
*
|
||||
* @return {@link Set }<{@link ModelTypeEnum }>
|
||||
*/
|
||||
public static Set<ModelTypeEnum> modelTypeList() {
|
||||
return MODEL_MAP.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义模型类型列表
|
||||
*
|
||||
* @return {@link Set }<{@link String }>
|
||||
*/
|
||||
public static Set<String> customModelTypeList() {
|
||||
return CUSTOM_MODEL_MAP.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发动机型号清单
|
||||
*
|
||||
* @return {@link List }<{@link ModelEngine }<{@link ? } {@link extends } {@link BaseModelConfig }>>
|
||||
*/
|
||||
public static List<ModelEngine<? extends BaseModelConfig>> modelEngineList() {
|
||||
return new ArrayList<>(MODEL_MAP.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义型号引擎列表
|
||||
*
|
||||
* @return {@link List }<{@link ModelEngine }<{@link ? } {@link extends } {@link BaseModelConfig }>>
|
||||
*/
|
||||
public static List<ModelEngine<? extends BaseModelConfig>> customModelEngineList() {
|
||||
return new ArrayList<>(CUSTOM_MODEL_MAP.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @param modelEngine 模型引擎
|
||||
*/
|
||||
static void register(ModelEngine<? extends BaseModelConfig> modelEngine) {
|
||||
MODEL_MAP.put(modelEngine.getType(), modelEngine);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 得到
|
||||
*
|
||||
* @param type 类型
|
||||
* @return {@link ModelEngine }<{@link ? } {@link extends } {@link BaseModelConfig }>
|
||||
*/
|
||||
public static ModelEngine<? extends BaseModelConfig> get(ModelTypeEnum type) {
|
||||
return MODEL_MAP.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义
|
||||
*
|
||||
* @param modelEngine 模型引擎
|
||||
*/
|
||||
static void registerCustom(CustomModelEngine<? extends BaseModelConfig> modelEngine) {
|
||||
Assert.isTrue(!CUSTOM_MODEL_MAP.containsKey(modelEngine.getCustomType()), "已存在类型:{}, class:{}的模型", modelEngine.getCustomType(), modelEngine.getClass());
|
||||
CUSTOM_MODEL_MAP.put(modelEngine.getCustomType(), modelEngine);
|
||||
}
|
||||
|
||||
/**
|
||||
* 得到自定义
|
||||
*
|
||||
* @param type 类型
|
||||
* @return {@link ModelEngine }<{@link ? } {@link extends } {@link BaseModelConfig }>
|
||||
*/
|
||||
public static ModelEngine<? extends BaseModelConfig> getCustom(String type) {
|
||||
return CUSTOM_MODEL_MAP.get(type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.metis.llm.factory;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.service.CustomModelEngine;
|
||||
import com.metis.llm.service.ModelEngine;
|
||||
import com.metis.llm.domain.config.BaseModelConfig;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ModelEngineInitiate implements ApplicationContextAware {
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
Map<String, ModelEngine> modelMap = applicationContext.getBeansOfType(ModelEngine.class);
|
||||
modelMap.forEach((modelBeanName, model) -> {
|
||||
if (ObjectUtil.isNull(model.getType())) {
|
||||
return;
|
||||
}
|
||||
if (ModelTypeEnum.CUSTOM.equals(model.getType())) {
|
||||
Assert.isTrue(model instanceof CustomModelEngine<? extends BaseModelConfig>, "自定义模型必须实现CustomModelEngine接口");
|
||||
ModelEngineFactory.registerCustom((CustomModelEngine<? extends BaseModelConfig>) model);
|
||||
}
|
||||
ModelEngineFactory.register((ModelEngine<? extends BaseModelConfig>) model);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.service.Result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ChatBoot {
|
||||
|
||||
Result<String> chat(List<ChatMessage> chatMessageList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.domain.config.BaseModelConfig;
|
||||
|
||||
public interface CustomModelEngine<T extends BaseModelConfig> extends ModelEngine<T> {
|
||||
|
||||
|
||||
String getCustomType();
|
||||
|
||||
|
||||
default ModelTypeEnum getType() {
|
||||
return ModelTypeEnum.CUSTOM;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
import dev.langchain4j.Internal;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.memory.ChatMemory;
|
||||
import dev.langchain4j.model.chat.request.ChatRequest;
|
||||
import dev.langchain4j.model.chat.request.ChatRequestParameters;
|
||||
import dev.langchain4j.model.chat.request.ResponseFormat;
|
||||
import dev.langchain4j.model.chat.request.json.JsonSchema;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.input.Prompt;
|
||||
import dev.langchain4j.model.input.PromptTemplate;
|
||||
import dev.langchain4j.model.input.structured.StructuredPrompt;
|
||||
import dev.langchain4j.model.input.structured.StructuredPromptProcessor;
|
||||
import dev.langchain4j.model.moderation.Moderation;
|
||||
import dev.langchain4j.rag.AugmentationRequest;
|
||||
import dev.langchain4j.rag.AugmentationResult;
|
||||
import dev.langchain4j.rag.query.Metadata;
|
||||
import dev.langchain4j.service.*;
|
||||
import dev.langchain4j.service.memory.ChatMemoryAccess;
|
||||
import dev.langchain4j.service.memory.ChatMemoryService;
|
||||
import dev.langchain4j.service.output.ServiceOutputParser;
|
||||
import dev.langchain4j.service.tool.ToolServiceContext;
|
||||
import dev.langchain4j.service.tool.ToolServiceResult;
|
||||
import dev.langchain4j.spi.services.AiServicesFactory;
|
||||
import dev.langchain4j.spi.services.TokenStreamAdapter;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static dev.langchain4j.internal.Exceptions.illegalArgument;
|
||||
import static dev.langchain4j.internal.Utils.isNotNullOrBlank;
|
||||
import static dev.langchain4j.model.chat.Capability.RESPONSE_FORMAT_JSON_SCHEMA;
|
||||
import static dev.langchain4j.model.chat.request.ResponseFormatType.JSON;
|
||||
import static dev.langchain4j.service.IllegalConfigurationException.illegalConfiguration;
|
||||
import static dev.langchain4j.service.TypeUtils.typeHasRawClass;
|
||||
import static dev.langchain4j.spi.ServiceHelper.loadFactories;
|
||||
|
||||
@Internal
|
||||
public class FlowAiServices<T> extends AiServices<T> {
|
||||
|
||||
|
||||
private final ServiceOutputParser serviceOutputParser = new ServiceOutputParser();
|
||||
private final Collection<TokenStreamAdapter> tokenStreamAdapters = loadFactories(TokenStreamAdapter.class);
|
||||
|
||||
FlowAiServices(AiServiceContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins the construction of an AI Service.
|
||||
*
|
||||
* @param aiService The class of the interface to be implemented.
|
||||
* @return builder
|
||||
*/
|
||||
public static <T> AiServices<T> builder(Class<T> aiService) {
|
||||
AiServiceContext context = new AiServiceContext(aiService);
|
||||
for (AiServicesFactory factory : loadFactories(AiServicesFactory.class)) {
|
||||
return factory.create(context);
|
||||
}
|
||||
return new FlowAiServices<>(context);
|
||||
}
|
||||
|
||||
|
||||
static void validateParameters(Method method) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
if (parameters == null || parameters.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Parameter parameter : parameters) {
|
||||
V v = parameter.getAnnotation(V.class);
|
||||
dev.langchain4j.service.UserMessage userMessage =
|
||||
parameter.getAnnotation(dev.langchain4j.service.UserMessage.class);
|
||||
MemoryId memoryId = parameter.getAnnotation(MemoryId.class);
|
||||
UserName userName = parameter.getAnnotation(UserName.class);
|
||||
if (v == null && userMessage == null && memoryId == null && userName == null) {
|
||||
throw illegalConfiguration(
|
||||
"Parameter '%s' of method '%s' should be annotated with @V or @UserMessage "
|
||||
+ "or @UserName or @MemoryId",
|
||||
parameter.getName(), method.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public T build() {
|
||||
|
||||
performBasicValidation();
|
||||
|
||||
if (!context.hasChatMemory() && ChatMemoryAccess.class.isAssignableFrom(context.aiServiceClass)) {
|
||||
throw illegalConfiguration(
|
||||
"In order to have a service implementing ChatMemoryAccess, please configure the ChatMemoryProvider on the '%s'.",
|
||||
context.aiServiceClass.getName());
|
||||
}
|
||||
|
||||
for (Method method : context.aiServiceClass.getMethods()) {
|
||||
if (method.isAnnotationPresent(Moderate.class) && context.moderationModel == null) {
|
||||
throw illegalConfiguration(
|
||||
"The @Moderate annotation is present, but the moderationModel is not set up. "
|
||||
+ "Please ensure a valid moderationModel is configured before using the @Moderate annotation.");
|
||||
}
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (returnType == void.class) {
|
||||
throw illegalConfiguration("'%s' is not a supported return type of an AI Service method", returnType.getName());
|
||||
}
|
||||
if (returnType == Result.class || returnType == List.class || returnType == Set.class) {
|
||||
TypeUtils.validateReturnTypesAreProperlyParametrized(method.getName(), method.getGenericReturnType());
|
||||
}
|
||||
|
||||
if (!context.hasChatMemory()) {
|
||||
for (Parameter parameter : method.getParameters()) {
|
||||
if (parameter.isAnnotationPresent(MemoryId.class)) {
|
||||
throw illegalConfiguration(
|
||||
"In order to use @MemoryId, please configure the ChatMemoryProvider on the '%s'.",
|
||||
context.aiServiceClass.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object proxyInstance = Proxy.newProxyInstance(
|
||||
context.aiServiceClass.getClassLoader(),
|
||||
new Class<?>[]{context.aiServiceClass},
|
||||
new InvocationHandler() {
|
||||
|
||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
|
||||
|
||||
if (method.getDeclaringClass() == Object.class) {
|
||||
// methods like equals(), hashCode() and toString() should not be handled by this proxy
|
||||
return method.invoke(this, args);
|
||||
}
|
||||
|
||||
if (method.getDeclaringClass() == ChatMemoryAccess.class) {
|
||||
return switch (method.getName()) {
|
||||
case "getChatMemory" -> context.chatMemoryService.getChatMemory(args[0]);
|
||||
case "evictChatMemory" -> context.chatMemoryService.evictChatMemory(args[0]) != null;
|
||||
default -> throw new UnsupportedOperationException(
|
||||
"Unknown method on ChatMemoryAccess class : " + method.getName());
|
||||
};
|
||||
}
|
||||
|
||||
validateParameters(method);
|
||||
|
||||
final Object memoryId = findMemoryId(method, args).orElse(ChatMemoryService.DEFAULT);
|
||||
final ChatMemory chatMemory = context.hasChatMemory()
|
||||
? context.chatMemoryService.getOrCreateChatMemory(memoryId)
|
||||
: null;
|
||||
|
||||
Optional<SystemMessage> systemMessage = prepareSystemMessage(memoryId, method, args);
|
||||
UserMessage userMessage = prepareUserMessage(method, args);
|
||||
AugmentationResult augmentationResult = null;
|
||||
if (context.retrievalAugmentor != null) {
|
||||
List<ChatMessage> chatMemoryMessages = chatMemory != null ? chatMemory.messages() : null;
|
||||
Metadata metadata = Metadata.from(userMessage, memoryId, chatMemoryMessages);
|
||||
AugmentationRequest augmentationRequest = new AugmentationRequest(userMessage, metadata);
|
||||
augmentationResult = context.retrievalAugmentor.augment(augmentationRequest);
|
||||
userMessage = (UserMessage) augmentationResult.chatMessage();
|
||||
}
|
||||
|
||||
Type returnType = method.getGenericReturnType();
|
||||
boolean streaming = returnType == TokenStream.class || canAdaptTokenStreamTo(returnType);
|
||||
boolean supportsJsonSchema = supportsJsonSchema();
|
||||
Optional<JsonSchema> jsonSchema = Optional.empty();
|
||||
if (supportsJsonSchema && !streaming) {
|
||||
jsonSchema = serviceOutputParser.jsonSchema(returnType);
|
||||
}
|
||||
if ((!supportsJsonSchema || jsonSchema.isEmpty()) && !streaming) {
|
||||
userMessage = appendOutputFormatInstructions(returnType, userMessage);
|
||||
}
|
||||
|
||||
List<ChatMessage> messages;
|
||||
if (chatMemory != null) {
|
||||
systemMessage.ifPresent(chatMemory::add);
|
||||
chatMemory.add(userMessage);
|
||||
messages = chatMemory.messages();
|
||||
} else {
|
||||
messages = new ArrayList<>();
|
||||
systemMessage.ifPresent(messages::add);
|
||||
messages.add(userMessage);
|
||||
}
|
||||
|
||||
Future<Moderation> moderationFuture = triggerModerationIfNeeded(method, messages);
|
||||
|
||||
ToolServiceContext toolServiceContext =
|
||||
context.toolService.createContext(memoryId, userMessage);
|
||||
|
||||
if (streaming) {
|
||||
TokenStream tokenStream = new AiServiceTokenStream(AiServiceTokenStreamParameters.builder()
|
||||
.messages(messages)
|
||||
.toolSpecifications(toolServiceContext.toolSpecifications())
|
||||
.toolExecutors(toolServiceContext.toolExecutors())
|
||||
.retrievedContents(
|
||||
augmentationResult != null ? augmentationResult.contents() : null)
|
||||
.context(context)
|
||||
.memoryId(memoryId)
|
||||
.build());
|
||||
// TODO moderation
|
||||
if (returnType == TokenStream.class) {
|
||||
return tokenStream;
|
||||
} else {
|
||||
return adapt(tokenStream, returnType);
|
||||
}
|
||||
}
|
||||
|
||||
ResponseFormat responseFormat = null;
|
||||
if (supportsJsonSchema && jsonSchema.isPresent()) {
|
||||
responseFormat = ResponseFormat.builder()
|
||||
.type(JSON)
|
||||
.jsonSchema(jsonSchema.get())
|
||||
.build();
|
||||
}
|
||||
|
||||
ChatRequestParameters parameters = ChatRequestParameters.builder()
|
||||
.toolSpecifications(toolServiceContext.toolSpecifications())
|
||||
.responseFormat(responseFormat)
|
||||
.build();
|
||||
|
||||
ChatRequest chatRequest = ChatRequest.builder()
|
||||
.messages(messages)
|
||||
.parameters(parameters)
|
||||
.build();
|
||||
|
||||
ChatResponse chatResponse = context.chatModel.chat(chatRequest);
|
||||
|
||||
verifyModerationIfNeeded(moderationFuture);
|
||||
|
||||
ToolServiceResult toolServiceResult = context.toolService.executeInferenceAndToolsLoop(
|
||||
chatResponse,
|
||||
parameters,
|
||||
messages,
|
||||
context.chatModel,
|
||||
chatMemory,
|
||||
memoryId,
|
||||
toolServiceContext.toolExecutors());
|
||||
|
||||
chatResponse = toolServiceResult.chatResponse();
|
||||
|
||||
Object parsedResponse = serviceOutputParser.parse(chatResponse, returnType);
|
||||
if (typeHasRawClass(returnType, Result.class)) {
|
||||
return Result.builder()
|
||||
.content(parsedResponse)
|
||||
.tokenUsage(chatResponse.tokenUsage())
|
||||
.sources(augmentationResult == null ? null : augmentationResult.contents())
|
||||
.finishReason(chatResponse.finishReason())
|
||||
.toolExecutions(toolServiceResult.toolExecutions())
|
||||
.build();
|
||||
} else {
|
||||
return parsedResponse;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canAdaptTokenStreamTo(Type returnType) {
|
||||
for (TokenStreamAdapter tokenStreamAdapter : tokenStreamAdapters) {
|
||||
if (tokenStreamAdapter.canAdaptTokenStreamTo(returnType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Object adapt(TokenStream tokenStream, Type returnType) {
|
||||
for (TokenStreamAdapter tokenStreamAdapter : tokenStreamAdapters) {
|
||||
if (tokenStreamAdapter.canAdaptTokenStreamTo(returnType)) {
|
||||
return tokenStreamAdapter.adapt(tokenStream);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Can't find suitable TokenStreamAdapter");
|
||||
}
|
||||
|
||||
private boolean supportsJsonSchema() {
|
||||
return context.chatModel != null
|
||||
&& context.chatModel.supportedCapabilities().contains(RESPONSE_FORMAT_JSON_SCHEMA);
|
||||
}
|
||||
|
||||
private UserMessage appendOutputFormatInstructions(Type returnType, UserMessage userMessage) {
|
||||
String outputFormatInstructions = serviceOutputParser.outputFormatInstructions(returnType);
|
||||
String text = userMessage.singleText() + outputFormatInstructions;
|
||||
if (isNotNullOrBlank(userMessage.name())) {
|
||||
userMessage = UserMessage.from(userMessage.name(), text);
|
||||
} else {
|
||||
userMessage = UserMessage.from(text);
|
||||
}
|
||||
return userMessage;
|
||||
}
|
||||
|
||||
private Future<Moderation> triggerModerationIfNeeded(Method method, List<ChatMessage> messages) {
|
||||
if (method.isAnnotationPresent(Moderate.class)) {
|
||||
return executor.submit(() -> {
|
||||
List<ChatMessage> messagesToModerate = removeToolMessages(messages);
|
||||
return context.moderationModel
|
||||
.moderate(messagesToModerate)
|
||||
.content();
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return (T) proxyInstance;
|
||||
}
|
||||
|
||||
private Optional<SystemMessage> prepareSystemMessage(Object memoryId, Method method, Object[] args) {
|
||||
return findSystemMessageTemplate(memoryId, method)
|
||||
.map(systemMessageTemplate -> PromptTemplate.from(systemMessageTemplate)
|
||||
.apply(findTemplateVariables(systemMessageTemplate, method, args))
|
||||
.toSystemMessage());
|
||||
}
|
||||
|
||||
private Optional<String> findSystemMessageTemplate(Object memoryId, Method method) {
|
||||
dev.langchain4j.service.SystemMessage annotation =
|
||||
method.getAnnotation(dev.langchain4j.service.SystemMessage.class);
|
||||
if (annotation != null) {
|
||||
return Optional.of(getTemplate(
|
||||
method, "System", annotation.fromResource(), annotation.value(), annotation.delimiter()));
|
||||
}
|
||||
|
||||
return context.systemMessageProvider.apply(memoryId);
|
||||
}
|
||||
|
||||
private static Map<String, Object> findTemplateVariables(String template, Method method, Object[] args) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
String variableName = getVariableName(parameters[i]);
|
||||
Object variableValue = args[i];
|
||||
variables.put(variableName, variableValue);
|
||||
}
|
||||
|
||||
if (template.contains("{{it}}") && !variables.containsKey("it")) {
|
||||
String itValue = getValueOfVariableIt(parameters, args);
|
||||
variables.put("it", itValue);
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static String getVariableName(Parameter parameter) {
|
||||
V annotation = parameter.getAnnotation(V.class);
|
||||
if (annotation != null) {
|
||||
return annotation.value();
|
||||
} else {
|
||||
return parameter.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValueOfVariableIt(Parameter[] parameters, Object[] args) {
|
||||
if (parameters.length == 1) {
|
||||
Parameter parameter = parameters[0];
|
||||
if (!parameter.isAnnotationPresent(MemoryId.class)
|
||||
&& !parameter.isAnnotationPresent(dev.langchain4j.service.UserMessage.class)
|
||||
&& !parameter.isAnnotationPresent(UserName.class)
|
||||
&& (!parameter.isAnnotationPresent(V.class) || isAnnotatedWithIt(parameter))) {
|
||||
return toString(args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (isAnnotatedWithIt(parameters[i])) {
|
||||
return toString(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
throw illegalConfiguration("Error: cannot find the value of the prompt template variable \"{{it}}\".");
|
||||
}
|
||||
|
||||
private static boolean isAnnotatedWithIt(Parameter parameter) {
|
||||
V annotation = parameter.getAnnotation(V.class);
|
||||
return annotation != null && "it".equals(annotation.value());
|
||||
}
|
||||
|
||||
private static UserMessage prepareUserMessage(Method method, Object[] args) {
|
||||
|
||||
String template = getUserMessageTemplate(method, args);
|
||||
Map<String, Object> variables = findTemplateVariables(template, method, args);
|
||||
|
||||
Prompt prompt = PromptTemplate.from(template).apply(variables);
|
||||
|
||||
Optional<String> maybeUserName = findUserName(method.getParameters(), args);
|
||||
return maybeUserName
|
||||
.map(userName -> UserMessage.from(userName, prompt.text()))
|
||||
.orElseGet(prompt::toUserMessage);
|
||||
}
|
||||
|
||||
private static String getUserMessageTemplate(Method method, Object[] args) {
|
||||
|
||||
Optional<String> templateFromMethodAnnotation = findUserMessageTemplateFromMethodAnnotation(method);
|
||||
Optional<String> templateFromParameterAnnotation =
|
||||
findUserMessageTemplateFromAnnotatedParameter(method.getParameters(), args);
|
||||
|
||||
if (templateFromMethodAnnotation.isPresent() && templateFromParameterAnnotation.isPresent()) {
|
||||
throw illegalConfiguration(
|
||||
"Error: The method '%s' has multiple @UserMessage annotations. Please use only one.",
|
||||
method.getName());
|
||||
}
|
||||
|
||||
if (templateFromMethodAnnotation.isPresent()) {
|
||||
return templateFromMethodAnnotation.get();
|
||||
}
|
||||
if (templateFromParameterAnnotation.isPresent()) {
|
||||
return templateFromParameterAnnotation.get();
|
||||
}
|
||||
|
||||
Optional<String> templateFromTheOnlyArgument =
|
||||
findUserMessageTemplateFromTheOnlyArgument(method.getParameters(), args);
|
||||
if (templateFromTheOnlyArgument.isPresent()) {
|
||||
return templateFromTheOnlyArgument.get();
|
||||
}
|
||||
|
||||
throw illegalConfiguration("Error: The method '%s' does not have a user message defined.", method.getName());
|
||||
}
|
||||
|
||||
private static Optional<String> findUserMessageTemplateFromMethodAnnotation(Method method) {
|
||||
return Optional.ofNullable(method.getAnnotation(dev.langchain4j.service.UserMessage.class))
|
||||
.map(a -> getTemplate(method, "User", a.fromResource(), a.value(), a.delimiter()));
|
||||
}
|
||||
|
||||
private static Optional<String> findUserMessageTemplateFromAnnotatedParameter(
|
||||
Parameter[] parameters, Object[] args) {
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (parameters[i].isAnnotationPresent(dev.langchain4j.service.UserMessage.class)) {
|
||||
return Optional.of(toString(args[i]));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> findUserMessageTemplateFromTheOnlyArgument(Parameter[] parameters, Object[] args) {
|
||||
if (parameters != null && parameters.length == 1 && parameters[0].getAnnotations().length == 0) {
|
||||
return Optional.of(toString(args[0]));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> findUserName(Parameter[] parameters, Object[] args) {
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (parameters[i].isAnnotationPresent(UserName.class)) {
|
||||
return Optional.of(args[i].toString());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static String getTemplate(Method method, String type, String resource, String[] value, String delimiter) {
|
||||
String messageTemplate;
|
||||
if (!resource.trim().isEmpty()) {
|
||||
messageTemplate = getResourceText(method.getDeclaringClass(), resource);
|
||||
if (messageTemplate == null) {
|
||||
throw illegalConfiguration("@%sMessage's resource '%s' not found", type, resource);
|
||||
}
|
||||
} else {
|
||||
messageTemplate = String.join(delimiter, value);
|
||||
}
|
||||
if (messageTemplate.trim().isEmpty()) {
|
||||
throw illegalConfiguration("@%sMessage's template cannot be empty", type);
|
||||
}
|
||||
return messageTemplate;
|
||||
}
|
||||
|
||||
private static String getResourceText(Class<?> clazz, String resource) {
|
||||
InputStream inputStream = clazz.getResourceAsStream(resource);
|
||||
if (inputStream == null) {
|
||||
inputStream = clazz.getResourceAsStream("/" + resource);
|
||||
}
|
||||
return getText(inputStream);
|
||||
}
|
||||
|
||||
private static String getText(InputStream inputStream) {
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
try (Scanner scanner = new Scanner(inputStream);
|
||||
Scanner s = scanner.useDelimiter("\\A")) {
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<Object> findMemoryId(Method method, Object[] args) {
|
||||
Parameter[] parameters = method.getParameters();
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
if (parameters[i].isAnnotationPresent(MemoryId.class)) {
|
||||
Object memoryId = args[i];
|
||||
if (memoryId == null) {
|
||||
throw illegalArgument(
|
||||
"The value of parameter '%s' annotated with @MemoryId in method '%s' must not be null",
|
||||
parameters[i].getName(), method.getName());
|
||||
}
|
||||
return Optional.of(memoryId);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static String toString(Object arg) {
|
||||
if (arg.getClass().isArray()) {
|
||||
return arrayToString(arg);
|
||||
} else if (arg.getClass().isAnnotationPresent(StructuredPrompt.class)) {
|
||||
return StructuredPromptProcessor.toPrompt(arg).text();
|
||||
} else {
|
||||
return arg.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static String arrayToString(Object arg) {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
int length = Array.getLength(arg);
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(toString(Array.get(arg, i)));
|
||||
if (i < length - 1) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import com.metis.llm.domain.LLMEmbeddingModelConfig;
|
||||
import com.metis.llm.domain.config.BaseModelConfig;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
|
||||
public interface ModelEngine<T extends BaseModelConfig> {
|
||||
|
||||
|
||||
/**
|
||||
* 获取模型类型
|
||||
*
|
||||
* @return {@link ModelTypeEnum }
|
||||
*/
|
||||
ModelTypeEnum getType();
|
||||
|
||||
/**
|
||||
* 获取聊天语言模型
|
||||
*
|
||||
* @param model 模型
|
||||
* @param modelConfig
|
||||
* @return {@link ChatModel }
|
||||
*/
|
||||
ChatModel getChatLanguageModel(Model model, LLMChatModeConfig modelConfig);
|
||||
|
||||
/**
|
||||
* 获取嵌入模型
|
||||
*
|
||||
* @param model 模型
|
||||
* @param modeConfig
|
||||
* @return {@link EmbeddingModel }
|
||||
*/
|
||||
EmbeddingModel getEmbeddingModel(Model model, LLMEmbeddingModelConfig modeConfig);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
|
||||
public interface ModelEngineService {
|
||||
|
||||
|
||||
/**
|
||||
* 获取聊天语言模型
|
||||
*
|
||||
* @param modeConfig 模型id
|
||||
* @return {@link ChatModel }
|
||||
*/
|
||||
ChatModel getChatLanguageModel(LLMChatModeConfig modeConfig);
|
||||
|
||||
|
||||
/**
|
||||
* 获取嵌入模型
|
||||
*
|
||||
* @param modeConfig@return {@link EmbeddingModel }
|
||||
*/
|
||||
EmbeddingModel getEmbeddingModel(LLMChatModeConfig modeConfig);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.metis.llm.service;
|
||||
|
||||
import com.metis.domain.entity.base.Model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ModelService {
|
||||
|
||||
|
||||
/**
|
||||
* 列表
|
||||
*
|
||||
* @param modelId llm id
|
||||
* @return {@link List }<{@link Model }>
|
||||
*/
|
||||
Model getByModelId(Long modelId);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.metis.llm.service.impl;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import com.metis.enums.ModelTypeEnum;
|
||||
import com.metis.llm.service.ModelEngine;
|
||||
import com.metis.llm.service.ModelEngineService;
|
||||
import com.metis.llm.service.ModelService;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import com.metis.llm.domain.LLMEmbeddingModelConfig;
|
||||
import com.metis.llm.factory.ModelEngineFactory;
|
||||
import com.metis.utils.GenericInterfacesUtils;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
import lombok.Builder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ModelEngineServiceImpl implements ModelEngineService {
|
||||
|
||||
private final ModelService modelService;
|
||||
|
||||
@Override
|
||||
public ChatModel getChatLanguageModel(LLMChatModeConfig modeConfig) {
|
||||
ModelWrapper modelWrapper = getModelWrapper(modeConfig);
|
||||
return modelWrapper.getChatLanguageModel();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public EmbeddingModel getEmbeddingModel(LLMChatModeConfig modeConfig) {
|
||||
ModelWrapper modelWrapper = getModelWrapper(modeConfig);
|
||||
return modelWrapper.getEmbeddingModel();
|
||||
}
|
||||
|
||||
private ModelWrapper getModelWrapper(LLMChatModeConfig modeConfig) {
|
||||
Model model = getModel(modeConfig.getModelId());
|
||||
ModelEngine modelEngine = null;
|
||||
if (model.getType().equals(ModelTypeEnum.CUSTOM)) {
|
||||
modelEngine = ModelEngineFactory.getCustom(model.getCustomType());
|
||||
} else {
|
||||
modelEngine = ModelEngineFactory.get(model.getType());
|
||||
}
|
||||
model.setConfigClass(GenericInterfacesUtils.getClass(modelEngine));
|
||||
return ModelWrapper.builder()
|
||||
.modelEngine(modelEngine)
|
||||
.modeConfig(modeConfig)
|
||||
.model(model)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private Model getModel(Long modelId) {
|
||||
Assert.isTrue(ObjectUtil.isNotNull(modelId), "模型ID不能为空");
|
||||
Model model = modelService.getByModelId(modelId);
|
||||
Assert.isTrue(ObjectUtil.isNotNull(model), "模型不存在");
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
@Builder
|
||||
private record ModelWrapper(Model model,
|
||||
ModelEngine modelEngine,
|
||||
LLMChatModeConfig modeConfig,
|
||||
LLMEmbeddingModelConfig embeddingModelConfig) {
|
||||
public ChatModel getChatLanguageModel() {
|
||||
return modelEngine.getChatLanguageModel(model, modeConfig);
|
||||
}
|
||||
|
||||
public EmbeddingModel getEmbeddingModel() {
|
||||
return modelEngine.getEmbeddingModel(model, embeddingModelConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.metis.llm.service.impl;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.metis.convert.ModelPlatformConvert;
|
||||
import com.metis.domain.entity.ModelPlatform;
|
||||
import com.metis.domain.entity.base.Model;
|
||||
import com.metis.llm.service.ModelService;
|
||||
import com.metis.service.ModelPlatformService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ModelServiceImpl implements ModelService {
|
||||
|
||||
private final ModelPlatformService modelPlatformService;
|
||||
|
||||
|
||||
@Override
|
||||
public Model getByModelId(Long modelId) {
|
||||
Assert.isTrue(ObjectUtil.isNotNull(modelId), "模型平台ID不能为空");
|
||||
ModelPlatform modelPlatform = modelPlatformService.getById(modelId);
|
||||
Assert.isTrue(ObjectUtil.isNotNull(modelPlatform), "模型平台不存在");
|
||||
return ModelPlatformConvert.INSTANCE.toModel(modelPlatform);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.metis.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.metis.domain.entity.ModelPlatform;
|
||||
|
||||
/**
|
||||
* LLM模型平台映射器
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
public interface ModelPlatformMapper extends BaseMapper<ModelPlatform> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface NodeRunner<T extends NodeConfig> {
|
||||
* @param edges 边缘
|
||||
* @return {@link Set }<{@link Long }>
|
||||
*/
|
||||
default Set<Long> getNextNodeIds(List<Edge> edges) {
|
||||
default Set<String> getNextNodeIds(List<Edge> edges) {
|
||||
if (CollUtil.isEmpty(edges)) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ package com.metis.runner;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.domain.context.SysContext;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运行结果
|
||||
*
|
||||
@@ -26,7 +23,7 @@ public class RunnerResult {
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
private SysContext context;
|
||||
// private SysContext context;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.metis.runner.factory;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.metis.domain.entity.base.NodeConfig;
|
||||
import com.metis.enums.NodeType;
|
||||
import com.metis.runner.CustomNodeRunner;
|
||||
import com.metis.runner.NodeRunner;
|
||||
@@ -13,18 +14,18 @@ public final class NodeRunnerFactory {
|
||||
/**
|
||||
* 内置节点运行器
|
||||
*/
|
||||
private static final Map<NodeType, NodeRunner> NODE_MAP = new ConcurrentHashMap<>(8);
|
||||
private static final Map<NodeType, NodeRunner<? extends NodeConfig>> NODE_MAP = new ConcurrentHashMap<>(8);
|
||||
/**
|
||||
* 自定义节点映射
|
||||
*/
|
||||
private static final Map<String, NodeRunner> CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8);
|
||||
private static final Map<String, NodeRunner<? extends NodeConfig>> CUSTOM_NODE_MAP = new ConcurrentHashMap<>(8);
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @param runner 跑步者
|
||||
*/
|
||||
static void register(NodeRunner runner) {
|
||||
static void register(NodeRunner<? extends NodeConfig> runner) {
|
||||
NODE_MAP.put(runner.getType(), runner);
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ public final class NodeRunnerFactory {
|
||||
* @param type 类型
|
||||
* @return {@link NodeRunner }
|
||||
*/
|
||||
public static NodeRunner get(NodeType type) {
|
||||
public static NodeRunner<? extends NodeConfig> get(NodeType type) {
|
||||
return NODE_MAP.get(type);
|
||||
}
|
||||
|
||||
@@ -45,7 +46,7 @@ public final class NodeRunnerFactory {
|
||||
*
|
||||
* @param runner 跑步者
|
||||
*/
|
||||
static void registerCustom(CustomNodeRunner runner) {
|
||||
static void registerCustom(CustomNodeRunner<? extends NodeConfig> runner) {
|
||||
Assert.isTrue(!CUSTOM_NODE_MAP.containsKey(runner.getCustomNodeType()), "已存在类型:{}, class:{}的运行器", runner.getCustomNodeType(), runner.getClass());
|
||||
CUSTOM_NODE_MAP.put(runner.getCustomNodeType(), runner);
|
||||
}
|
||||
@@ -56,7 +57,7 @@ public final class NodeRunnerFactory {
|
||||
* @param type 类型
|
||||
* @return {@link NodeRunner }
|
||||
*/
|
||||
public static NodeRunner getCustom(String type) {
|
||||
public static NodeRunner<? extends NodeConfig> getCustom(String type) {
|
||||
return CUSTOM_NODE_MAP.get(type);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.metis.runner.factory;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.metis.domain.entity.base.NodeConfig;
|
||||
import com.metis.enums.NodeType;
|
||||
import com.metis.runner.CustomNodeRunner;
|
||||
import com.metis.runner.NodeRunner;
|
||||
@@ -26,11 +28,14 @@ public class RunnerInitialize implements ApplicationContextAware {
|
||||
Map<String, NodeRunner> runnerMap = applicationContext.getBeansOfType(NodeRunner.class);
|
||||
|
||||
runnerMap.forEach((runnerBeanName, runner) -> {
|
||||
if (ObjectUtil.isNull(runner.getType())) {
|
||||
return;
|
||||
}
|
||||
if (NodeType.CUSTOM.equals(runner.getType())) {
|
||||
Assert.isTrue(runner instanceof CustomNodeRunner, "自定义节点必须实现CustomNodeRunner接口");
|
||||
NodeRunnerFactory.registerCustom((CustomNodeRunner) runner);
|
||||
NodeRunnerFactory.registerCustom((CustomNodeRunner<? extends NodeConfig>) runner);
|
||||
} else {
|
||||
NodeRunnerFactory.register(runner);
|
||||
NodeRunnerFactory.register((NodeRunner<? extends NodeConfig>) runner);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.metis.runner.impl;
|
||||
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.domain.context.RunningContext;
|
||||
import com.metis.domain.context.RunningResult;
|
||||
@@ -21,7 +22,15 @@ public class EndNodeRunner implements NodeRunner<EndNodeConfig> {
|
||||
@Override
|
||||
public RunningResult run(RunningContext context, Node node, List<Edge> edges) {
|
||||
JSONObject contextNodeValue = new JSONObject();
|
||||
contextNodeValue.put("userId", context.getSys().getAppId());
|
||||
EndNodeConfig config = node.getConfig();
|
||||
List<EndNodeConfig.Variable> variables = config.getVariables();
|
||||
if (CollUtil.isEmpty(variables)){
|
||||
return RunningResult.buildResult();
|
||||
}
|
||||
for (EndNodeConfig.Variable variable : variables) {
|
||||
Object value = context.getValue(variable.getVariableKey());
|
||||
contextNodeValue.put(variable.getVariable(), value);
|
||||
}
|
||||
return RunningResult.buildResult(contextNodeValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,107 @@
|
||||
package com.metis.runner.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.constant.BaseConstant;
|
||||
import com.metis.domain.context.RunningContext;
|
||||
import com.metis.domain.context.RunningResult;
|
||||
import com.metis.domain.entity.base.Edge;
|
||||
import com.metis.domain.entity.base.Node;
|
||||
import com.metis.domain.entity.config.node.LLMNodeConfig;
|
||||
import com.metis.domain.entity.config.node.llm.LLMNodeConfig;
|
||||
import com.metis.domain.entity.config.node.llm.PromptTemplate;
|
||||
import com.metis.enums.NodeType;
|
||||
import com.metis.llm.domain.LLMChatModeConfig;
|
||||
import com.metis.llm.domain.Usage;
|
||||
import com.metis.llm.service.ChatBoot;
|
||||
import com.metis.llm.service.FlowAiServices;
|
||||
import com.metis.llm.service.ModelEngineService;
|
||||
import com.metis.runner.NodeRunner;
|
||||
import com.metis.template.domain.RenderContext;
|
||||
import com.metis.template.utils.VelocityUtil;
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.message.ChatMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.service.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LLMNodeRunner implements NodeRunner<LLMNodeConfig> {
|
||||
|
||||
private final ModelEngineService modelEngineService;
|
||||
|
||||
|
||||
@Override
|
||||
public RunningResult run(RunningContext context, Node node, List<Edge> edges) {
|
||||
return RunningResult.buildResult();
|
||||
LLMNodeConfig config = node.getConfig();
|
||||
LLMChatModeConfig model = config.getModel();
|
||||
|
||||
ChatModel chatModel = modelEngineService.getChatLanguageModel(model);
|
||||
List<ChatMessage> chatMessageList = buildChatMessage(context, config);
|
||||
|
||||
// todo 需要使用FlowAiServices单独扩展build方法中返回数据格式的操作, 可以用在二期or三期进行扩展开发
|
||||
ChatBoot chatBoot = FlowAiServices.builder(ChatBoot.class)
|
||||
.chatModel(chatModel)
|
||||
.build();
|
||||
Result<String> chatResult = chatBoot.chat(chatMessageList);
|
||||
String text = chatResult.content();
|
||||
|
||||
|
||||
log.info("LLM 输出结果: {}", text);
|
||||
JSONObject result = new JSONObject();
|
||||
result.put(BaseConstant.TEXT, text);
|
||||
result.put(BaseConstant.USAGE, Usage.buildTokenUsage(chatResult.tokenUsage()));
|
||||
result.put(BaseConstant.FINISH_REASON, chatResult.finishReason());
|
||||
return RunningResult.buildResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeType getType() {
|
||||
return NodeType.LLM;
|
||||
}
|
||||
|
||||
|
||||
private List<ChatMessage> buildChatMessage(RunningContext context,
|
||||
LLMNodeConfig config) {
|
||||
// 获取模板
|
||||
List<PromptTemplate> promptTemplate = config.getPromptTemplate();
|
||||
// 如果没有模板, 则直接返回
|
||||
if (CollUtil.isEmpty(promptTemplate)) {
|
||||
return List.of();
|
||||
}
|
||||
// 获取模板参数
|
||||
Map<String, String> templateMap = promptTemplate.stream().collect(Collectors.toMap(PromptTemplate::getId, PromptTemplate::getText));
|
||||
|
||||
// 上下文参数
|
||||
String contextValue = (String) context.getValue(config.getContext());
|
||||
RenderContext renderContext = RenderContext.builder()
|
||||
.context(contextValue)
|
||||
.sys(context.getSys())
|
||||
.templateMap(templateMap)
|
||||
.nodeRunningContext(context.getNodeRunningContext())
|
||||
.build();
|
||||
// 渲染结果
|
||||
Map<String, String> rendered = VelocityUtil.renderBatch(renderContext);
|
||||
// 构建消息返回
|
||||
return promptTemplate.stream()
|
||||
.map(template -> switch (template.getRole()) {
|
||||
case SYSTEM -> SystemMessage.from(rendered.get(template.getId()));
|
||||
case USER -> UserMessage.from(rendered.get(template.getId()));
|
||||
case AI -> AiMessage.from(rendered.get(template.getId()));
|
||||
default -> null;
|
||||
}).filter(ObjectUtil::isNotNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ public class QuestionClassifierRunner implements NodeRunner<QuestionClassifierCo
|
||||
|
||||
@Override
|
||||
public RunningResult run(RunningContext context, Node node, List<Edge> edges) {
|
||||
Set<Long> nextNodeIds = getNextNodeIds(edges);
|
||||
Set<String> nextNodeIds = getNextNodeIds(edges);
|
||||
// 生成随机索引
|
||||
Random random = new Random();
|
||||
int randomIndex = random.nextInt(nextNodeIds.size());
|
||||
List<Long> nodeIds = new ArrayList<>(nextNodeIds);
|
||||
List<String> nodeIds = new ArrayList<>(nextNodeIds);
|
||||
return RunningResult.buildResult(Set.of(nodeIds.get(randomIndex)));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.domain.context.RunningContext;
|
||||
import com.metis.domain.context.RunningResult;
|
||||
import com.metis.domain.context.SysContext;
|
||||
import com.metis.domain.entity.base.Edge;
|
||||
import com.metis.domain.entity.base.Node;
|
||||
import com.metis.domain.entity.base.NodeVariable;
|
||||
@@ -37,6 +38,10 @@ public class StartNodeRunner implements NodeRunner<StartNodeConfig> {
|
||||
// 获取用户自定义参数
|
||||
JSONObject custom = context.getCustom();
|
||||
JSONObject contextNodeValue = new JSONObject();
|
||||
// 获取到系统上下文, 并将系统上下文放入到start的运行结果中, 用于后续调用
|
||||
// JSONObject sysContext = getSysContext(context);
|
||||
// contextNodeValue.putAll(sysContext);
|
||||
|
||||
for (NodeVariable variable : variables) {
|
||||
Object value = variable.getValue(custom);
|
||||
contextNodeValue.put(variable.getVariable(), value);
|
||||
@@ -44,6 +49,27 @@ public class StartNodeRunner implements NodeRunner<StartNodeConfig> {
|
||||
return RunningResult.buildResult(contextNodeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统上下文
|
||||
*
|
||||
* @param context 上下文
|
||||
* @return {@link JSONObject }
|
||||
*/
|
||||
private JSONObject getSysContext(RunningContext context) {
|
||||
JSONObject sys = new JSONObject();
|
||||
// 系统参数全部以sys.开头
|
||||
SysContext sysContext = context.getSys();
|
||||
sys.put("sys.userId", sysContext.getUserId());
|
||||
sys.put("sys.appId", sysContext.getAppId());
|
||||
sys.put("sys.workflowId", sysContext.getWorkflowId());
|
||||
sys.put("sys.instanceId", sysContext.getInstanceId());
|
||||
sys.put("sys.conversationId", sysContext.getConversationId());
|
||||
sys.put("sys.dialogueCount", sysContext.getDialogueCount());
|
||||
sys.put("sys.files", sysContext.getFiles());
|
||||
sys.put("sys.query", sysContext.getQuery());
|
||||
return sys;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NodeType getType() {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.metis.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.metis.domain.entity.ModelPlatform;
|
||||
|
||||
/**
|
||||
* LLM模型平台服务
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
public interface ModelPlatformService extends IService<ModelPlatform> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.metis.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.metis.domain.entity.ModelPlatform;
|
||||
import com.metis.mapper.ModelPlatformMapper;
|
||||
import com.metis.service.ModelPlatformService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* LLM模型平台服务实现
|
||||
*
|
||||
* @author clay
|
||||
* @date 2025/04/26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ModelPlatformServiceImpl extends ServiceImpl<ModelPlatformMapper, ModelPlatform>
|
||||
implements ModelPlatformService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.metis.template.domain;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.metis.domain.context.SysContext;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RenderContext {
|
||||
|
||||
/**
|
||||
* 模板
|
||||
*/
|
||||
private String template;
|
||||
|
||||
/**
|
||||
* 模板映射
|
||||
*/
|
||||
private Map<String, String> templateMap;
|
||||
|
||||
/**
|
||||
* 上下文
|
||||
*/
|
||||
private String context;
|
||||
|
||||
/**
|
||||
* 系统数据
|
||||
*/
|
||||
private SysContext sys;
|
||||
|
||||
/**
|
||||
* 节点运行上下文, 需要数据进行传递
|
||||
*/
|
||||
private Map<String, JSONObject> nodeRunningContext;
|
||||
|
||||
|
||||
public Map<String, Object> getContext() {
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("context", this.context);
|
||||
context.put("sys", this.sys);
|
||||
for (Map.Entry<String, JSONObject> entry : nodeRunningContext.entrySet()) {
|
||||
context.put(String.valueOf(entry.getKey()), entry.getValue());
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package com.metis.template;
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.metis.template.utils;
|
||||
|
||||
import com.metis.template.domain.RenderContext;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.runtime.RuntimeConstants;
|
||||
import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class VelocityUtil {
|
||||
// 初始化Velocity引擎
|
||||
private static final VelocityEngine engine = new VelocityEngine();
|
||||
|
||||
static {
|
||||
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "string");
|
||||
engine.setProperty("string.resource.loader.class",
|
||||
StringResourceLoader.class.getName());
|
||||
engine.init();
|
||||
}
|
||||
|
||||
|
||||
public static String parseContent(String templateContent, Map<String, ?> context) {
|
||||
VelocityContext velocityContext = new VelocityContext(context);
|
||||
// 执行模板渲染
|
||||
StringWriter writer = new StringWriter();
|
||||
engine.evaluate(velocityContext, writer, "StringTemplate", templateContent);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
*
|
||||
* @param renderContext 渲染上下文
|
||||
* @return {@link String }
|
||||
*/
|
||||
public static String render(RenderContext renderContext) {
|
||||
// 获取上下文
|
||||
Map<String, Object> context = renderContext.getContext();
|
||||
// 渲染
|
||||
return VelocityUtil.parseContent(renderContext.getTemplate(), context);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
*
|
||||
* @param renderContext 渲染上下文
|
||||
* @return {@link Map }<{@link String:标识 }, {@link String:渲染结果 }>
|
||||
*/
|
||||
public static Map<String, String> renderBatch(RenderContext renderContext) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : renderContext.getTemplateMap().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String template = entry.getValue();
|
||||
String render = VelocityUtil.parseContent(template, renderContext.getContext());
|
||||
result.put(key, render);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -137,10 +137,10 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
* @param edges 边缘
|
||||
*/
|
||||
private void validateEdgeConnections(List<Node> nodes, List<Edge> edges) {
|
||||
Map<Long, Node> nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity()));
|
||||
Map<String, Node> nodeMap = nodes.stream().collect(Collectors.toMap(Node::getId, Function.identity()));
|
||||
for (Edge edge : edges) {
|
||||
Long source = edge.getSource();
|
||||
Long target = edge.getTarget();
|
||||
String source = edge.getSource();
|
||||
String target = edge.getTarget();
|
||||
Assert.isTrue(nodeMap.containsKey(source), "边 {} 的源节点 {} 不存在", edge.getLabel(), source);
|
||||
Assert.isTrue(nodeMap.containsKey(target), "边 {} 的目标节点 {} 不存在", edge.getLabel(), target);
|
||||
}
|
||||
@@ -153,7 +153,7 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
* @param edges 边缘
|
||||
*/
|
||||
private void validateCycle(List<Node> nodes, List<Edge> edges) {
|
||||
Map<Long, List<Long>> adjacencyList = buildAdjacencyList(edges);
|
||||
Map<String, List<String>> adjacencyList = buildAdjacencyList(edges);
|
||||
for (Node node : nodes) {
|
||||
if (hasCycle(node.getId(), adjacencyList, new HashSet<>())) {
|
||||
throw new IllegalArgumentException("图中存在环结构,起始节点: " + node.getData().getLabel());
|
||||
@@ -168,7 +168,7 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
* @param edges 边缘
|
||||
*/
|
||||
private void validateIsolatedNodes(List<Node> nodes, List<Edge> edges) {
|
||||
Set<Long> connectedNodes = new HashSet<>();
|
||||
Set<String> connectedNodes = new HashSet<>();
|
||||
for (Edge edge : edges) {
|
||||
connectedNodes.add(edge.getSource());
|
||||
connectedNodes.add(edge.getTarget());
|
||||
@@ -184,8 +184,8 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
* @param edges 边缘
|
||||
* @return {@link Map }<{@link Long }, {@link List }<{@link Long }>>
|
||||
*/
|
||||
private Map<Long, List<Long>> buildAdjacencyList(List<Edge> edges) {
|
||||
Map<Long, List<Long>> adjacencyList = new HashMap<>();
|
||||
private Map<String, List<String>> buildAdjacencyList(List<Edge> edges) {
|
||||
Map<String, List<String>> adjacencyList = new HashMap<>();
|
||||
for (Edge edge : edges) {
|
||||
adjacencyList.computeIfAbsent(edge.getSource(), k -> new ArrayList<>()).add(edge.getTarget());
|
||||
}
|
||||
@@ -200,12 +200,12 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
* @param visited 是否已经访问过了
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean hasCycle(Long nodeId, Map<Long, List<Long>> adjacencyList, Set<Long> visited) {
|
||||
private boolean hasCycle(String nodeId, Map<String, List<String>> adjacencyList, Set<String> visited) {
|
||||
if (visited.contains(nodeId)) {
|
||||
return true; // 发现环
|
||||
}
|
||||
visited.add(nodeId);
|
||||
for (Long neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
|
||||
for (String neighbor : adjacencyList.getOrDefault(nodeId, new ArrayList<>())) {
|
||||
if (hasCycle(neighbor, adjacencyList, visited)) {
|
||||
return true;
|
||||
}
|
||||
@@ -236,8 +236,8 @@ public class ValidatorServiceImpl implements ValidatorService {
|
||||
*/
|
||||
private void validateNodeRelations(List<Node> nodes, List<Edge> edges) {
|
||||
// 构建 source 和 target 的映射
|
||||
Map<Long, List<Edge>> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource));
|
||||
Map<Long, List<Edge>> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget));
|
||||
Map<String, List<Edge>> sourceMap = edges.stream().collect(Collectors.groupingBy(Edge::getSource));
|
||||
Map<String, List<Edge>> targetMap = edges.stream().collect(Collectors.groupingBy(Edge::getTarget));
|
||||
|
||||
for (Node node : nodes) {
|
||||
NodeValidator validator = getNodeValidator(node);
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.metis.validator.impl.node;
|
||||
|
||||
import com.metis.domain.entity.base.Edge;
|
||||
import com.metis.domain.entity.base.Node;
|
||||
import com.metis.domain.entity.config.node.LLMNodeConfig;
|
||||
import com.metis.domain.entity.config.node.llm.LLMNodeConfig;
|
||||
import com.metis.enums.NodeType;
|
||||
import com.metis.validator.NodeValidator;
|
||||
import com.metis.validator.ValidatorResult;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class StartNodeValidator implements NodeValidator<StartNodeConfig> {
|
||||
}
|
||||
|
||||
for (NodeVariable variable : variables) {
|
||||
switch (variable.getVariableType()) {
|
||||
switch (variable.getType()) {
|
||||
// 文本类型校验
|
||||
case TEXT_INPUT, PARAGRAPH -> textVariableValidator(variable);
|
||||
// 下拉框变量校验
|
||||
|
||||
37
metis-starter/src/test/java/aa/ModelClient.java
Normal file
37
metis-starter/src/test/java/aa/ModelClient.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package aa;
|
||||
|
||||
import dev.langchain4j.http.client.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.time.Duration.ofSeconds;
|
||||
|
||||
public class ModelClient {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String baseUrl = "https://api.siliconflow.cn";
|
||||
|
||||
HttpClientBuilder httpClientBuilder = HttpClientBuilderLoader.loadHttpClientBuilder();
|
||||
|
||||
HttpClient httpClient = httpClientBuilder
|
||||
.connectTimeout(ofSeconds(15))
|
||||
.readTimeout(ofSeconds(60))
|
||||
.build();
|
||||
|
||||
Map<String, String> defaultHeaders = new HashMap<>();
|
||||
defaultHeaders.put("Authorization", "Bearer sk-nnaoladfdjcybfelzkyhmihhnbbazycemiosghvhxfqujfjl");
|
||||
HttpRequest httpRequest = HttpRequest.builder()
|
||||
.method(HttpMethod.GET)
|
||||
.url(baseUrl, "/v1/models/'Qwen/Qwen2.5-Coder-7B-Instruct'")
|
||||
// .url(baseUrl, "/v1/models/Qwen2.5-Coder-32B-Instruct")
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeaders(defaultHeaders)
|
||||
.build();
|
||||
SuccessfulHttpResponse httpResponse = httpClient.execute(httpRequest);
|
||||
String body = httpResponse.body();
|
||||
System.out.println(body);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
253
metis-starter/src/test/resources/flow.json
Normal file
253
metis-starter/src/test/resources/flow.json
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"name": "llm运行测试",
|
||||
"description": "llm运行测试",
|
||||
"graph": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node_5",
|
||||
"type": "start",
|
||||
"initialized": false,
|
||||
"position": {
|
||||
"x": -221.9030111576117,
|
||||
"y": 59.99186709389075
|
||||
},
|
||||
"data": {
|
||||
"label": "开始",
|
||||
"icon": "SuitcaseLine",
|
||||
"toolbarPosition": "right",
|
||||
"config": {
|
||||
// 输入变量
|
||||
"variables": [
|
||||
{
|
||||
// 输入变量字段名称
|
||||
"variable": "query",
|
||||
"label": "查询条件",
|
||||
// 类型 TEXT_INPUT(1, "text-input", "文本"),
|
||||
// PARAGRAPH(2, "paragraph", "段落"),
|
||||
// SELECT(3, "select", "下拉框"),
|
||||
// NUMBER(4, "number", "数字"),
|
||||
// FILE(5, "file", "文件"),
|
||||
// FILE_LIST(6, "file-list", "文件列表")
|
||||
"type": "text-input",
|
||||
// 最大长度
|
||||
"maxLength": 60,
|
||||
// 是否必填
|
||||
"required": true,
|
||||
"options": {
|
||||
"label": "描述",
|
||||
// 描述为空的时候, label显示值
|
||||
"value": "值"
|
||||
// 值不能为空
|
||||
},
|
||||
"allowedFileUploadMethods": [
|
||||
"localFile",
|
||||
"remoteUrl"
|
||||
],
|
||||
// 允许上传方式
|
||||
"allowedFileTypes": [
|
||||
""
|
||||
],
|
||||
// 允许文件类型 自定义
|
||||
"allowedFileExtensions": [
|
||||
""
|
||||
]
|
||||
// 允许文件扩展名 可以自定义
|
||||
},
|
||||
{
|
||||
"variable": "background",
|
||||
"label": "背景",
|
||||
"type": "text-input",
|
||||
"maxLength": 60,
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"handles": [
|
||||
{
|
||||
"id": "51",
|
||||
"type": "source",
|
||||
"position": "right",
|
||||
"connectable": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"customType": null,
|
||||
"width": 200,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"id": "node_700",
|
||||
"type": "llm",
|
||||
"initialized": false,
|
||||
"position": {
|
||||
"x": 6.81248018754539,
|
||||
"y": 68.80431452712736
|
||||
},
|
||||
"data": {
|
||||
"label": "llm",
|
||||
"icon": "",
|
||||
"toolbarPosition": "right",
|
||||
"config": {
|
||||
// 上下文字段 node_5 为节点id, background为阶段运行之后的内容
|
||||
"context": "node_5.background",
|
||||
"retryConfig": {
|
||||
"enable": true,
|
||||
"maxRetries": 3,
|
||||
"retryInterval": 1000
|
||||
},
|
||||
// 提示模板
|
||||
"promptTemplate": [
|
||||
{
|
||||
// 角色
|
||||
// SYSTEM("system", "系统角色"),
|
||||
// USER("user", "用户角色"),
|
||||
// AI("ai", "ai返回"),
|
||||
// TOOL_EXECUTION_RESULT("toolExecutionResult", "工具(函数调用)返回");
|
||||
"role": "system",
|
||||
"text": "你的背景是${context}",
|
||||
"id": "1"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"text": "请你解释一下上述问题${node_5.query}",
|
||||
"id": "2"
|
||||
}
|
||||
],
|
||||
// 模型参数
|
||||
"model": {
|
||||
// 模型id, 需要在模型管理中创建, 调用接口返回
|
||||
"modelId": 1,
|
||||
// 模型名称, 接口中获取
|
||||
"modelName": "Qwen/Qwen2.5-Coder-32B-Instruct",
|
||||
// 模型参数. 与dify保持一致, 默认值也使用dify
|
||||
"completionParams": {
|
||||
"temperature": 0.7,
|
||||
"topP": 0.9,
|
||||
"topK": 1.1,
|
||||
"maxTokens": 1024,
|
||||
"seed": 1234,
|
||||
"presencePenalty": 0,
|
||||
"responseFormat": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handles": [
|
||||
{
|
||||
"id": "57",
|
||||
"type": "source",
|
||||
"position": "left",
|
||||
"connectable": true
|
||||
},
|
||||
{
|
||||
"id": "35",
|
||||
"type": "source",
|
||||
"position": "right",
|
||||
"connectable": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"customType": null,
|
||||
"width": 200,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"id": "node_802",
|
||||
"type": "end",
|
||||
"initialized": false,
|
||||
"position": {
|
||||
"x": 221.7192701843481,
|
||||
"y": 67.57111673995398
|
||||
},
|
||||
"data": {
|
||||
"label": "结束",
|
||||
"icon": "",
|
||||
"toolbarPosition": "right",
|
||||
"config": {
|
||||
// 结束节点的返回变量
|
||||
"variables": [
|
||||
{
|
||||
// 返回变量字段名称
|
||||
"variable": "query",
|
||||
// 返回变量字段值, 动态模板获取
|
||||
"variableKey": "node_5.query"
|
||||
},
|
||||
{
|
||||
"variable": "background",
|
||||
"variableKey": "node_5.background"
|
||||
},
|
||||
{
|
||||
"variable": "usage",
|
||||
"variableKey": "node_700.usage"
|
||||
},
|
||||
{
|
||||
"variable": "finishReason",
|
||||
"variableKey": "node_700.finishReason"
|
||||
},
|
||||
{
|
||||
"variable": "text",
|
||||
"variableKey": "node_700.text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"handles": [
|
||||
{
|
||||
"id": "13",
|
||||
"type": "target",
|
||||
"position": "left",
|
||||
"connectable": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"customType": null,
|
||||
"width": 200,
|
||||
"height": 40
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "vueflow__edge-551-70057",
|
||||
"type": "default",
|
||||
"source": "node_5",
|
||||
"target": "node_700",
|
||||
"sourceHandle": "51",
|
||||
"targetHandle": "57",
|
||||
"data": {},
|
||||
"label": "",
|
||||
"animated": true,
|
||||
"markerStart": "none",
|
||||
"markerEnd": "none",
|
||||
"sourceX": -15.903013850339164,
|
||||
"sourceY": 79.9918688890424,
|
||||
"targetX": 4.312479626560497,
|
||||
"targetY": 88.80432081015815
|
||||
},
|
||||
{
|
||||
"id": "802",
|
||||
"type": "default",
|
||||
"source": "node_700",
|
||||
"target": "node_802",
|
||||
"sourceHandle": "35",
|
||||
"targetHandle": "13",
|
||||
"data": {},
|
||||
"label": "",
|
||||
"animated": true,
|
||||
"markerStart": "none",
|
||||
"markerEnd": "none",
|
||||
"sourceX": 212.81248478762151,
|
||||
"sourceY": 88.80432081015815,
|
||||
"targetX": 219.2192701843481,
|
||||
"targetY": 87.5711275108639
|
||||
}
|
||||
],
|
||||
"position": [
|
||||
987.9498689166815,
|
||||
102.12938290987324
|
||||
],
|
||||
"zoom": 3.5988075528449266,
|
||||
"viewport": {
|
||||
"x": 987.9498689166815,
|
||||
"y": 102.12938290987324,
|
||||
"zoom": 3.5988075528449266
|
||||
}
|
||||
}
|
||||
}
|
||||
40
pom.xml
40
pom.xml
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.metis</groupId>
|
||||
@@ -19,7 +20,11 @@
|
||||
<lombok.version>1.18.34</lombok.version>
|
||||
<org.mapstruct.version>1.6.2</org.mapstruct.version>
|
||||
<sanitizer.version>1.2.3</sanitizer.version>
|
||||
<velocity.version>1.7</velocity.version>
|
||||
<langchain4j.version>1.0.0-beta2</langchain4j.version>
|
||||
<open.api.version>1.0.0-rc1</open.api.version>
|
||||
<ollama.version>1.0.0-beta4</ollama.version>
|
||||
<mcp.version>1.0.0-beta4</mcp.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
<versions-maven-plugin.version>2.18.0</versions-maven-plugin.version>
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
@@ -45,16 +50,6 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
<version>1.0.0-beta2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
@@ -78,7 +73,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.24</version>
|
||||
<version>5.8.37</version>
|
||||
</dependency>
|
||||
<!-- 类转换 -->
|
||||
<dependency>
|
||||
@@ -101,6 +96,27 @@
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
<version>2.2.15</version>
|
||||
</dependency>
|
||||
<!--velocity模板渲染-->
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity</artifactId>
|
||||
<version>${velocity.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>${open.api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-ollama</artifactId>
|
||||
<version>${ollama.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
<version>${mcp.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user