初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
package tech.easyflow.common.ai;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
/**
* 聊天专用SSE发射器
*/
public class ChatSseEmitter extends SseEmitter {
// 默认超时时间30分钟
public static final long DEFAULT_TIMEOUT = 5 * 60 * 1000L;
public ChatSseEmitter() {
super(DEFAULT_TIMEOUT);
// 注册基础的超时/异常回调(避免连接泄漏)
registerBasicCallbacks();
}
public ChatSseEmitter(long timeout) {
super(timeout);
registerBasicCallbacks();
}
/**
* 基础回调:仅做简单的连接清理
*/
private void registerBasicCallbacks() {
// 超时回调
onTimeout(this::complete);
// 异常回调
onError(e -> complete());
}
public static ChatSseEmitter create() {
return new ChatSseEmitter();
}
/**
* 发送普通消息(最常用的核心方法)
* @param message 消息内容
* @throws IOException 发送失败时抛出IO异常
*/
public void sendMessage(String message) throws IOException {
// 封装标准的SSE消息格式
send(SseEmitter.event().name("message").data(message));
}
/**
* 快速创建发射器
*/
public ChatSseEmitter createChatSseEmitter() {
return new ChatSseEmitter();
}
}

View File

@@ -0,0 +1,37 @@
package tech.easyflow.common.ai.inteceptor;
import com.easyagents.core.model.chat.tool.GlobalToolInterceptors;
import com.easyagents.core.model.chat.tool.ToolChain;
import com.easyagents.core.model.chat.tool.ToolContext;
import com.easyagents.core.model.chat.tool.ToolInterceptor;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Map;
@Component
public class ToolLoggingInterceptor implements ToolInterceptor {
@PostConstruct
public void init() {
GlobalToolInterceptors.addInterceptor(this);
}
@Override
public Object intercept(ToolContext context, ToolChain chain) throws Exception {
String toolName = context.getTool().getName();
Map<String, Object> args = context.getArgsMap();
System.out.println("▶ 调用工具: " + toolName + ", 参数: " + args);
long start = System.currentTimeMillis();
try {
Object result = chain.proceed(context);
System.out.println("✅ 工具返回: " + result);
return result;
} finally {
long duration = System.currentTimeMillis() - start;
System.out.println("⏱️ 耗时: " + duration + "ms");
}
}
}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.common.ai.plugin;
import java.util.*;
public class NestedParamConverter {
public static Map<String, Object> convertToNestedParamMap(List<PluginParam> pluginParams) {
Map<String, Object> result = new LinkedHashMap<>();
if (pluginParams == null || pluginParams.isEmpty()) return result;
for (PluginParam param : pluginParams) {
if (!param.isEnabled()) continue;
result.put(param.getName(), buildValue(param));
}
return result;
}
private static Object buildValue(PluginParam param) {
if ("String".equalsIgnoreCase(param.getType())) {
return param.getDefaultValue();
} else if ("Object".equalsIgnoreCase(param.getType())) {
Map<String, Object> objMap = new LinkedHashMap<>();
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
for (PluginParam child : param.getChildren()) {
objMap.put(child.getName(), buildValue(child));
}
}
return objMap;
} else if ("Array".equalsIgnoreCase(param.getType())) {
if (param.getChildren() != null && !param.getChildren().isEmpty()) {
PluginParam arrayItemTemplate = param.getChildren().get(0);
if ("Array[Object]".equalsIgnoreCase(arrayItemTemplate.getType())) {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> item = new LinkedHashMap<>();
for (PluginParam child : arrayItemTemplate.getChildren()) {
item.put(child.getName(), buildValue(child));
}
list.add(item); // 示例中只添加一个对象
return list;
}
}
return Collections.EMPTY_LIST;
}
return null;
}
}

View File

@@ -0,0 +1,182 @@
package tech.easyflow.common.ai.plugin;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.*;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class PluginHttpClient {
private static final int TIMEOUT = 10_000;
public static JSONObject sendRequest(String url, String method,
Map<String, Object> headers,
List<PluginParam> pluginParams) {
// 1. 处理路径参数
String processedUrl = replacePathVariables(url, pluginParams);
// 2. 初始化请求
Method httpMethod = Method.valueOf(method.toUpperCase());
HttpRequest request = HttpRequest.of(processedUrl)
.method(httpMethod)
.timeout(TIMEOUT);
// 3. 处理请求头(合并默认头和参数头)
processHeaders(request, headers, pluginParams);
// 4. 处理查询参数和请求体
processQueryAndBodyParams(request, httpMethod, pluginParams);
// 5. 执行请求
HttpResponse response = request.execute();
return JSONUtil.parseObj(response.body());
}
/**
* 处理请求头(合并默认头和参数头)
*/
private static void processHeaders(HttpRequest request,
Map<String, Object> defaultHeaders,
List<PluginParam> params) {
// 添加默认头
if (ObjectUtil.isNotEmpty(defaultHeaders)) {
defaultHeaders.forEach((k, v) -> request.header(k, v.toString()));
}
// 添加参数中指定的头
params.stream()
.filter(p -> "header".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
.forEach(p -> request.header(p.getName(), p.getDefaultValue().toString()));
}
/**
* 处理查询参数和请求体
*/
/**
* 处理查询参数和请求体(新增文件参数支持)
*/
private static void processQueryAndBodyParams(HttpRequest request,
Method httpMethod,
List<PluginParam> params) {
Map<String, Object> queryParams = new HashMap<>();
Map<String, Object> bodyParams = new HashMap<>();
// 标记是否包含文件参数
AtomicBoolean hasMultipartFile = new AtomicBoolean(false);
// 分类参数(同时检测是否有文件)
params.stream()
.filter(PluginParam::isEnabled)
.forEach(p -> {
String methodType = p.getMethod().toLowerCase();
Object paramValue = buildNestedParamValue(p);
// 检测是否为文件参数MultipartFile 类型)
if (paramValue instanceof org.springframework.web.multipart.MultipartFile) {
hasMultipartFile.set(true);
}
switch (methodType) {
case "query":
queryParams.put(p.getName(), paramValue);
break;
case "body":
bodyParams.put(p.getName(), paramValue);
break;
}
});
// 1. 设置查询参数(原有逻辑不变)
if (!queryParams.isEmpty()) {
request.form(queryParams);
}
// 2. 设置请求体(分两种情况:有文件 vs 无文件)
if (!bodyParams.isEmpty() && (httpMethod == Method.POST || httpMethod == Method.PUT)) {
if (hasMultipartFile.get()) {
// 2.1 包含文件参数 → 用 multipart/form-data 格式
processMultipartBody(request, bodyParams);
} else {
// 2.2 无文件参数 → 保持原有 JSON 格式
request.body(JSONUtil.toJsonStr(bodyParams))
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue());
}
}
}
/**
* 递归构建嵌套参数值
* @param param 当前参数
* @return 如果是 Object 类型,返回 Map否则返回 defaultValue
*/
private static Object buildNestedParamValue(PluginParam param) {
// 如果不是 Object 类型,直接返回默认值
if (!"Object".equalsIgnoreCase(param.getType())) {
return param.getDefaultValue();
}
// 如果是 Object 类型,递归处理子参数
Map<String, Object> nestedParams = new HashMap<>();
if (param.getChildren() != null) {
param.getChildren().stream()
.filter(PluginParam::isEnabled)
.forEach(child -> {
Object childValue = buildNestedParamValue(child); // 递归处理子参数
nestedParams.put(child.getName(), childValue);
});
}
return nestedParams;
}
/**
* 替换URL中的路径变量 {xxx}
*/
private static String replacePathVariables(String url, List<PluginParam> params) {
String result = url;
// 收集路径参数
Map<String, Object> pathParams = new HashMap<>();
params.stream()
.filter(p -> "path".equalsIgnoreCase(p.getMethod()) && p.isEnabled())
.forEach(p -> pathParams.put(p.getName(), p.getDefaultValue()));
// 替换变量
for (Map.Entry<String, Object> entry : pathParams.entrySet()) {
result = result.replaceAll("\\{" + entry.getKey() + "\\}",
entry.getValue().toString());
}
return result;
}
private static void processMultipartBody(HttpRequest request, Map<String, Object> bodyParams) {
// 手动设置 Content-Type 为 multipart/form-data
request.header(Header.CONTENT_TYPE, "multipart/form-data");
for (Map.Entry<String, Object> entry : bodyParams.entrySet()) {
String paramName = entry.getKey();
Object paramValue = entry.getValue();
if (paramValue instanceof MultipartFile) {
MultipartFile file = (MultipartFile) paramValue;
try {
request.form(paramName, file.getBytes(), file.getOriginalFilename());
} catch (Exception e) {
throw new RuntimeException(String.format("文件参数处理失败:参数名=%s文件名=%s",
paramName, file.getOriginalFilename()), e);
}
} else {
// 处理普通参数
String valueStr;
if (paramValue instanceof String || paramValue instanceof Number || paramValue instanceof Boolean) {
valueStr = paramValue.toString();
} else {
valueStr = JSONUtil.toJsonStr(paramValue);
}
request.form(paramName, valueStr);
}
}
}
}

View File

@@ -0,0 +1,86 @@
package tech.easyflow.common.ai.plugin;
import java.util.List;
public class PluginParam {
private String name;
private String description;
private String type;
private String method; // Query / Body / Header / PathVariable 等
private Object defaultValue;
private boolean required;
private boolean enabled;
private String key;
private List<PluginParam> children;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Object getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public List<PluginParam> getChildren() {
return children;
}
public void setChildren(List<PluginParam> children) {
this.children = children;
}
}

View File

@@ -0,0 +1,79 @@
package tech.easyflow.common.ai.plugin;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PluginParamConverter {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 将JSON字符串转换为PluginParam对象列表
* @param jsonStr 数据库中的JSON字符串
* @return PluginParam对象列表
*/
public static List<PluginParam> convertFromJson(String jsonStr) {
try {
// 将JSON字符串解析为List<Map>结构
List<Map<String, Object>> paramMaps = objectMapper.readValue(
jsonStr,
new TypeReference<List<Map<String, Object>>>(){}
);
List<PluginParam> result = new ArrayList<>();
for (Map<String, Object> paramMap : paramMaps) {
result.add(convertMapToPluginParam(paramMap));
}
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to convert JSON to PluginParam", e);
}
}
/**
* 递归将Map转换为PluginParam对象
*/
private static PluginParam convertMapToPluginParam(Map<String, Object> map) {
PluginParam param = new PluginParam();
param.setKey(getStringValue(map, "key"));
param.setName(getStringValue(map, "name"));
param.setDescription(getStringValue(map, "description"));
param.setType(getStringValue(map, "type"));
param.setMethod(getStringValue(map, "method"));
param.setDefaultValue(map.get("defaultValue"));
param.setRequired(getBooleanValue(map, "required"));
param.setEnabled(getBooleanValue(map, "enabled"));
// 处理子节点
if (map.containsKey("children")) {
Object childrenObj = map.get("children");
if (childrenObj instanceof List) {
List<Map<String, Object>> childrenMaps = (List<Map<String, Object>>) childrenObj;
List<PluginParam> children = new ArrayList<>();
for (Map<String, Object> childMap : childrenMaps) {
children.add(convertMapToPluginParam(childMap));
}
param.setChildren(children);
}
}
return param;
}
private static String getStringValue(Map<String, Object> map, String key) {
Object value = map.get(key);
return value != null ? value.toString() : null;
}
private static boolean getBooleanValue(Map<String, Object> map, String key) {
Object value = map.get(key);
if (value instanceof Boolean) {
return (Boolean) value;
}
return false;
}
}

View File

@@ -0,0 +1,103 @@
package tech.easyflow.common.ai.rag;
import com.easyagents.core.document.Document;
import com.easyagents.core.document.DocumentSplitter;
import com.easyagents.core.document.id.DocumentIdGenerator;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ExcelDocumentSplitter implements DocumentSplitter {
private int rowsPerChunk;
private boolean includeHeader;
public ExcelDocumentSplitter(int rowsPerChunk) {
if (rowsPerChunk <= 0) {
throw new IllegalArgumentException("rows must be greater than 0");
}
this.rowsPerChunk = rowsPerChunk;
}
public ExcelDocumentSplitter(int rowsPerChunk, boolean includeHeader) {
if (rowsPerChunk <= 0) {
throw new IllegalArgumentException("rows must be greater than 0");
}
this.rowsPerChunk = rowsPerChunk;
this.includeHeader = includeHeader;
}
@Override
public List<Document> split(Document document, DocumentIdGenerator idGenerator) {
if (document == null || document.getContent() == null) {
return Collections.emptyList();
}
// 解析JSON数据为表格结构
List<List<String>> tableData = JSON.parseObject(document.getContent(),
new TypeReference<List<List<String>>>() {});
if (tableData == null || tableData.isEmpty()) {
return Collections.emptyList();
}
List<Document> chunks = new ArrayList<>();
List<String> headers = includeHeader ? tableData.get(0) : null;
int startRow = includeHeader ? 1 : 0;
// 按照指定行数分割数据
for (int i = startRow; i < tableData.size(); i += rowsPerChunk) {
int endRow = Math.min(i + rowsPerChunk, tableData.size());
// 构建当前分块的Markdown表格
StringBuilder sb = new StringBuilder();
// 添加表头(如果包含)
if (headers != null) {
sb.append("| ").append(String.join(" | ", headers)).append(" |\n");
sb.append("|");
for (int j = 0; j < headers.size(); j++) {
sb.append(" --- |");
}
sb.append("\n");
}
// 添加数据行
for (int j = i; j < endRow; j++) {
List<String> row = tableData.get(j);
sb.append("| ").append(String.join(" | ", row)).append(" |\n");
}
// 创建新文档
Document newDocument = new Document();
newDocument.addMetadata(document.getMetadataMap());
newDocument.setContent(sb.toString());
if (idGenerator != null) {
newDocument.setId(idGenerator.generateId(newDocument));
}
chunks.add(newDocument);
}
return chunks;
}
public int getRowsPerChunk() {
return rowsPerChunk;
}
public void setRowsPerChunk(int rowsPerChunk) {
this.rowsPerChunk = rowsPerChunk;
}
public boolean isIncludeHeader() {
return includeHeader;
}
public void setIncludeHeader(boolean includeHeader) {
this.includeHeader = includeHeader;
}
}